<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Timing · XB Field Notes</title><link>https://www.xbcatm.com/tags/timing/</link><description>Illustrated field notes on HTTP, DNS, TLS, Caddy, SSH, releases, and recovery.</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://www.xbcatm.com/tags/timing/index.xml" rel="self" type="application/rss+xml"/><item><title>Nagle and delayed ACK can stall the second part of a small request</title><link>https://www.xbcatm.com/posts/tcp-nagle-conflicts-with-delayed-ack/</link><pubDate>Sun, 31 Oct 2027 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/posts/tcp-nagle-conflicts-with-delayed-ack/</guid><description>Split a two-byte request, capture the acknowledgment that releases its second byte, and compare the same exchange with TCP_NODELAY.</description><content:encoded>&lt;p&gt;Nagle&amp;rsquo;s algorithm reduces small TCP segments by holding additional short data while earlier data remains unacknowledged, unless enough data accumulates for a full segment. Delayed acknowledgment is a separate receiver behavior that can postpone an ACK briefly. The interaction matters when an application sends a request in multiple small writes and the peer needs the later bytes before producing a response.&lt;/p&gt;
&lt;h2 id="when-it-breaks"&gt;When it breaks&lt;/h2&gt;
&lt;p&gt;An isolated small request is not automatically delayed by Nagle. If no earlier data is outstanding, the first small segment can be sent. The troublesome case has a first piece already in flight and a second short piece waiting behind it. If the receiver delays acknowledgment while its application waits for the complete request, progress can pause until the acknowledgment timer expires.&lt;/p&gt;
&lt;p&gt;This is a bounded delay, not an indefinite protocol deadlock. An ACK can release the queued data, and accumulating enough data can also change the sender&amp;rsquo;s decision. The observed delay is not universally one additional RTT or a fixed forty milliseconds. Receiver policy, existing acknowledgment state, request framing, and network conditions determine the actual packet sequence and timing.&lt;/p&gt;
&lt;p&gt;TCP_NODELAY disables Nagle on the selected socket; it does not disable every form of buffering, congestion control, receive-window flow control, or scheduler delay. TCP_QUICKACK on Linux requests a change in acknowledgment mode, but that mode is not permanent. Treating either option as a universal latency switch can hide application buffering or a different transport constraint that the option never addressed.&lt;/p&gt;
&lt;h2 id="check-it-quickly"&gt;Check it quickly&lt;/h2&gt;
&lt;p&gt;Save this as &lt;code&gt;check-nagle.py&lt;/code&gt; and run &lt;code&gt;python3 check-nagle.py&lt;/code&gt; with Docker. The disposable environment uses Python 3.11.16, iproute2 6.1.0, and tcpdump 4.99.3, tested on Linux 6.12.76. NET_ADMIN and SYS_ADMIN enable nested namespaces. Client and target communicate over a new veth link; all settings stay inside that laboratory rather than changing host networking:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;tempfile&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TemporaryDirectory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="nn"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="nn"&gt;uuid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;FROM python@sha256:528257d48c1da0dcecc2e725d1ae34498d60c965f1241e39cd6a85a8859bdf84&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;RUN apt-get update &amp;amp;&amp;amp; apt-get install -y --no-install-recommends iproute2=6.1.0-3 iputils-ping=3:20221126-1+deb12u1 nftables=1.0.6-2+deb12u2 tcpdump=4.99.3-1 traceroute=1:2.1.2-1 procps=2:4.0.2-3 &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;CMD [&amp;#34;python3&amp;#34;]&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;from contextlib import ExitStack,contextmanager
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;from pathlib import Path
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;from tempfile import TemporaryDirectory
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;import json,os,select,signal,subprocess,time
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def run(*args,check=True,input=None,timeout=8):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; result=subprocess.run(args,input=input,capture_output=True,text=True,timeout=timeout)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; if check:assert result.returncode==0,(args,result.returncode,result.stdout,result.stderr)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; return result
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def ns(name,*args,**kwargs):return run(&amp;#39;ip&amp;#39;,&amp;#39;netns&amp;#39;,&amp;#39;exec&amp;#39;,name,*args,**kwargs)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def ip(name,*args,**kwargs):return ns(name,&amp;#39;ip&amp;#39;,*args,**kwargs)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def stop(process):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; if process.poll() is None:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; process.terminate()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; try:process.wait(timeout=2)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; except subprocess.TimeoutExpired:process.kill();process.wait(timeout=2)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def line(process,timeout=3):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert select.select([process.stdout],[],[],timeout)[0],&amp;#39;child response deadline exceeded&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; value=process.stdout.readline();assert value,(&amp;#39;child exited without a response&amp;#39;,process.stderr.read())
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; return value
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def spawn(stack,command):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; process=subprocess.Popen(command,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; stack.callback(stop,process);return process
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;@contextmanager
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def lab(names):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; with TemporaryDirectory(prefix=&amp;#39;net-path-&amp;#39;) as directory,ExitStack() as stack:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; root=Path(directory);proc=root/&amp;#39;proc&amp;#39;;proc.mkdir();run(&amp;#39;mount&amp;#39;,&amp;#39;-t&amp;#39;,&amp;#39;proc&amp;#39;,&amp;#39;proc&amp;#39;,str(proc))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; stack.callback(run,&amp;#39;umount&amp;#39;,str(proc))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; for name in names:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; run(&amp;#39;ip&amp;#39;,&amp;#39;netns&amp;#39;,&amp;#39;add&amp;#39;,name);stack.callback(run,&amp;#39;ip&amp;#39;,&amp;#39;netns&amp;#39;,&amp;#39;delete&amp;#39;,name,check=False)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; ip(name,&amp;#39;link&amp;#39;,&amp;#39;set&amp;#39;,&amp;#39;lo&amp;#39;,&amp;#39;up&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; def setting(name,key,value):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert name in names
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; path=proc/&amp;#39;sys&amp;#39;/key.replace(&amp;#39;.&amp;#39;,&amp;#39;/&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; return ns(name,&amp;#39;python3&amp;#39;,&amp;#39;-c&amp;#39;,f&amp;#39;from pathlib import Path; p=Path({str(path)!r}); p.write_text({str(value)!r}); print(p.read_text().strip())&amp;#39;).stdout.strip()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; yield root,stack,setting
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def wire(left,left_if,right,right_if,left_addresses,right_addresses):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; run(&amp;#39;ip&amp;#39;,&amp;#39;link&amp;#39;,&amp;#39;add&amp;#39;,left_if,&amp;#39;type&amp;#39;,&amp;#39;veth&amp;#39;,&amp;#39;peer&amp;#39;,&amp;#39;name&amp;#39;,right_if)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; for name,interface,addresses in [(left,left_if,left_addresses),(right,right_if,right_addresses)]:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; run(&amp;#39;ip&amp;#39;,&amp;#39;link&amp;#39;,&amp;#39;set&amp;#39;,interface,&amp;#39;netns&amp;#39;,name)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; for address in addresses:ip(name,&amp;#39;address&amp;#39;,&amp;#39;add&amp;#39;,address,&amp;#39;dev&amp;#39;,interface,*([&amp;#39;nodad&amp;#39;] if &amp;#39;:&amp;#39; in address else []))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; ip(name,&amp;#39;link&amp;#39;,&amp;#39;set&amp;#39;,interface,&amp;#39;up&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def nft(name,script):return ns(name,&amp;#39;nft&amp;#39;,&amp;#39;-f&amp;#39;,&amp;#39;-&amp;#39;,input=script)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def counters(name):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; data=json.loads(ns(name,&amp;#39;nft&amp;#39;,&amp;#39;-j&amp;#39;,&amp;#39;list&amp;#39;,&amp;#39;ruleset&amp;#39;).stdout);found=&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; for entry in data[&amp;#39;nftables&amp;#39;]:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; rule=entry.get(&amp;#39;rule&amp;#39;,&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; if &amp;#39;comment&amp;#39; in rule:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; for expression in rule[&amp;#39;expr&amp;#39;]:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; if &amp;#39;counter&amp;#39; in expression:found[rule[&amp;#39;comment&amp;#39;]]=expression[&amp;#39;counter&amp;#39;][&amp;#39;packets&amp;#39;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; return found
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def capture(stack,root,name,interface,expression,label):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; path=root/(label+&amp;#39;.pcap&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; process=subprocess.Popen([&amp;#39;ip&amp;#39;,&amp;#39;netns&amp;#39;,&amp;#39;exec&amp;#39;,name,&amp;#39;tcpdump&amp;#39;,&amp;#39;--immediate-mode&amp;#39;,&amp;#39;-Z&amp;#39;,&amp;#39;root&amp;#39;,&amp;#39;-U&amp;#39;,&amp;#39;-n&amp;#39;,&amp;#39;-i&amp;#39;,interface,&amp;#39;-w&amp;#39;,str(path),expression],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; stdout=subprocess.DEVNULL,stderr=subprocess.PIPE,text=True)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; stack.callback(stop,process)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; deadline=time.monotonic()+3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; while time.monotonic()&amp;lt;deadline:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert process.poll() is None,&amp;#39;tcpdump exited before listening&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; if select.select([process.stderr],[],[],.1)[0] and &amp;#39;listening on&amp;#39; in process.stderr.readline():return process,path
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; raise AssertionError(&amp;#39;tcpdump did not become ready&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def packets(process,path):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; import struct,socket
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; process.send_signal(signal.SIGINT);_,diagnostic=process.communicate(timeout=3)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert process.returncode==0,diagnostic
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; raw=path.read_bytes();assert raw[:4]==bytes.fromhex(&amp;#39;d4c3b2a1&amp;#39;) and struct.unpack_from(&amp;#39;&amp;lt;I&amp;#39;,raw,20)[0]==1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; offset=24;result=[]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; while offset&amp;lt;len(raw):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; seconds,micros,size,original=struct.unpack_from(&amp;#39;&amp;lt;IIII&amp;#39;,raw,offset);offset+=16
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; frame=raw[offset:offset+size];offset+=size
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; if frame[12:14]!=b&amp;#39;\x08\x00&amp;#39; or frame[23]!=6:continue
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; ihl=(frame[14]&amp;amp;15)*4;length=struct.unpack_from(&amp;#39;!H&amp;#39;,frame,16)[0];tcp=14+ihl
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; sport,dport,seq,ack=struct.unpack_from(&amp;#39;!HHII&amp;#39;,frame,tcp);header=(frame[tcp+12]&amp;gt;&amp;gt;4)*4
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; opts=&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;;cursor=tcp+20
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; while cursor&amp;lt;tcp+header:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; kind=frame[cursor]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; if kind==0:break
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; if kind==1:cursor+=1;continue
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; option_length=frame[cursor+1];data=frame[cursor+2:cursor+option_length];assert option_length&amp;gt;=2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; if kind==2:opts[&amp;#39;mss&amp;#39;]=struct.unpack(&amp;#39;!H&amp;#39;,data)[0]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; elif kind==3:opts[&amp;#39;wscale&amp;#39;]=data[0]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; elif kind==4:opts[&amp;#39;sack_ok&amp;#39;]=True
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; elif kind==5:opts[&amp;#39;sack&amp;#39;]=[list(pair) for pair in struct.iter_unpack(&amp;#39;!II&amp;#39;,data)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; cursor+=option_length
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; body=frame[tcp+header:14+length]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; result.append({&amp;#39;t&amp;#39;:seconds+micros/1e6,&amp;#39;src&amp;#39;:socket.inet_ntoa(frame[26:30]),&amp;#39;dst&amp;#39;:socket.inet_ntoa(frame[30:34]),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#39;sport&amp;#39;:sport,&amp;#39;dport&amp;#39;:dport,&amp;#39;seq&amp;#39;:seq,&amp;#39;ack&amp;#39;:ack,&amp;#39;flags&amp;#39;:frame[tcp+13],&amp;#39;window&amp;#39;:struct.unpack_from(&amp;#39;!H&amp;#39;,frame,tcp+14)[0],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#39;length&amp;#39;:len(body),&amp;#39;options&amp;#39;:opts,&amp;#39;preview&amp;#39;:body[:24].hex()})
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; return result
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;with lab([&amp;#39;client&amp;#39;,&amp;#39;target&amp;#39;]) as (root,stack,setting):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; wire(&amp;#39;client&amp;#39;,&amp;#39;c0&amp;#39;,&amp;#39;target&amp;#39;,&amp;#39;t0&amp;#39;,[&amp;#39;192.0.2.1/30&amp;#39;],[&amp;#39;192.0.2.2/30&amp;#39;])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; setting(&amp;#39;client&amp;#39;,&amp;#39;net.ipv4.tcp_autocorking&amp;#39;,0)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; server_code=r&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;import socket,json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def read_exact(stream,length):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; data=b&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; while len(data)&amp;lt;length:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; chunk=stream.recv(length-len(data));assert chunk,&amp;#39;premature EOF&amp;#39;;data+=chunk
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; return data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;listener=socket.socket();listener.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;listener.bind((&amp;#39;192.0.2.2&amp;#39;,18080));listener.listen();print(&amp;#39;listening&amp;#39;,flush=True)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;for trial in range(2):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; with listener.accept()[0] as connection:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; connection.settimeout(3);connection.setsockopt(socket.IPPROTO_TCP,socket.TCP_QUICKACK,0)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; print(&amp;#39;configured&amp;#39;,flush=True)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; request=read_exact(connection,2);assert request==b&amp;#39;ab&amp;#39;,request
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; connection.sendall(b&amp;#39;OK&amp;#39;);print(json.dumps({&amp;#39;request&amp;#39;:request.decode()}),flush=True)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; service=spawn(stack,[&amp;#39;ip&amp;#39;,&amp;#39;netns&amp;#39;,&amp;#39;exec&amp;#39;,&amp;#39;target&amp;#39;,&amp;#39;python3&amp;#39;,&amp;#39;-u&amp;#39;,&amp;#39;-c&amp;#39;,server_code]);assert line(service).strip()==&amp;#39;listening&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; tap,path=capture(stack,root,&amp;#39;client&amp;#39;,&amp;#39;c0&amp;#39;,&amp;#39;tcp port 18080&amp;#39;,&amp;#39;nagle&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; client_code=r&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;import json,socket,sys,time
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;def read_exact(stream,length):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; data=b&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; while len(data)&amp;lt;length:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; chunk=stream.recv(length-len(data));assert chunk,&amp;#39;premature EOF&amp;#39;;data+=chunk
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; return data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;nodelay=int(sys.argv[1]);s=socket.socket();s.settimeout(3)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;s.setsockopt(socket.IPPROTO_TCP,socket.TCP_NODELAY,nodelay);s.bind((&amp;#39;192.0.2.1&amp;#39;,40300+nodelay));s.connect((&amp;#39;192.0.2.2&amp;#39;,18080))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;print(&amp;#39;connected&amp;#39;,flush=True);assert sys.stdin.readline().strip()==&amp;#39;go&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;start=time.monotonic();s.sendall(b&amp;#39;a&amp;#39;);time.sleep(.002);s.sendall(b&amp;#39;b&amp;#39;);reply=read_exact(s,2)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;assert reply==b&amp;#39;OK&amp;#39;;print(json.dumps({&amp;#39;nodelay&amp;#39;:nodelay,&amp;#39;reply&amp;#39;:reply.decode(),&amp;#39;elapsed_ms&amp;#39;:(time.monotonic()-start)*1000}),flush=True);s.close()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; outcomes=[]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; for nodelay in [0,1]:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; client=spawn(stack,[&amp;#39;ip&amp;#39;,&amp;#39;netns&amp;#39;,&amp;#39;exec&amp;#39;,&amp;#39;client&amp;#39;,&amp;#39;python3&amp;#39;,&amp;#39;-u&amp;#39;,&amp;#39;-c&amp;#39;,client_code,str(nodelay)])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert line(client).strip()==&amp;#39;connected&amp;#39; and line(service).strip()==&amp;#39;configured&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; client.stdin.write(&amp;#39;go\n&amp;#39;);client.stdin.flush();outcomes.append(json.loads(line(client)))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert json.loads(line(service))[&amp;#39;request&amp;#39;]==&amp;#39;ab&amp;#39;;client.wait(timeout=2);assert client.returncode==0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; observed=packets(tap,path);timelines=&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; for nodelay in [0,1]:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; port=40300+nodelay;flow=[p for p in observed if port in (p[&amp;#39;sport&amp;#39;],p[&amp;#39;dport&amp;#39;])]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; first=next(p for p in flow if p[&amp;#39;sport&amp;#39;]==port and p[&amp;#39;preview&amp;#39;]==&amp;#39;61&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; second=next(p for p in flow if p[&amp;#39;sport&amp;#39;]==port and p[&amp;#39;preview&amp;#39;]==&amp;#39;62&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert (second[&amp;#39;seq&amp;#39;]-first[&amp;#39;seq&amp;#39;])%2**32==1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; acks=[p for p in flow if p[&amp;#39;dport&amp;#39;]==port and p[&amp;#39;flags&amp;#39;]&amp;amp;0x10 and 0&amp;lt;(p[&amp;#39;ack&amp;#39;]-first[&amp;#39;seq&amp;#39;])%2**32&amp;lt;=2]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert acks
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; if not nodelay:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; intermediate=next(p for p in acks if (p[&amp;#39;ack&amp;#39;]-first[&amp;#39;seq&amp;#39;])%2**32==1)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; assert first[&amp;#39;t&amp;#39;]&amp;lt;intermediate[&amp;#39;t&amp;#39;]&amp;lt;=second[&amp;#39;t&amp;#39;] and second[&amp;#39;t&amp;#39;]-first[&amp;#39;t&amp;#39;]&amp;gt;.01
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; else:assert second[&amp;#39;t&amp;#39;]&amp;lt;=acks[0][&amp;#39;t&amp;#39;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; timelines[nodelay]={&amp;#39;first_byte&amp;#39;:first,&amp;#39;second_byte&amp;#39;:second,&amp;#39;first_ack_covering_data&amp;#39;:acks[0],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#39;data_segment_gap_ms&amp;#39;:(second[&amp;#39;t&amp;#39;]-first[&amp;#39;t&amp;#39;])*1000}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; print(json.dumps({&amp;#39;trials&amp;#39;:outcomes,&amp;#39;packet_timelines&amp;#39;:timelines,&amp;#39;receiver_quickack_requested&amp;#39;:0,&amp;#39;client_autocorking&amp;#39;:0}))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;xbcatm-tcpflow-&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;docker&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;TemporaryDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;);(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Dockerfile&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recipe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;build&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-t&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;run&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--rm&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-i&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--network&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;none&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--cap-add&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;NET_ADMIN&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--cap-add&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SYS_ADMIN&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;--memory&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;256m&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--pids-limit&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;128&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;python3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;rm&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-f&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;image&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;rm&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The server waits for the complete request &lt;code&gt;ab&lt;/code&gt; before replying &lt;code&gt;OK&lt;/code&gt;. Before each trial, it requests non-quick acknowledgment mode on the accepted connection. The client disables namespace-local automatic corking to remove that additional coalescing mechanism from the comparison. Parent-child synchronization ensures the receiver has configured its socket before the client starts sending application bytes.&lt;/p&gt;
&lt;p&gt;In the first trial, TCP_NODELAY is zero. The client sends &lt;code&gt;a&lt;/code&gt;, pauses for two milliseconds, and sends &lt;code&gt;b&lt;/code&gt;. Those writes are application actions, not a guarantee of immediate separate packets. The capture shows the first byte leaving, an ACK covering that byte arriving, and only then the second byte leaving. The program checks this ordering instead of attributing any slow response to Nagle without packet evidence.&lt;/p&gt;
&lt;p&gt;In the recorded run, the two data segments were approximately forty-one milliseconds apart, and the complete exchange took roughly the same time. The second trial enables TCP_NODELAY and repeats the same framing and deliberate pause. The second byte leaves before the first data acknowledgment, with about two milliseconds between segments. The server receives the complete request and returns the same valid reply in both cases.&lt;/p&gt;
&lt;p&gt;The timing values describe this controlled run, not a production latency guarantee. The fixture expects a visible delayed-ACK interaction under its stated settings and checks the causal packet order. A different kernel or receiver policy can require fresh investigation. Stream reads explicitly accumulate the expected number of bytes because recv, even with a requested length, does not generally promise an entire application message at once.&lt;/p&gt;
&lt;h2 id="choose-framing-and-socket-policy-together"&gt;Choose framing and socket policy together&lt;/h2&gt;
&lt;p&gt;For a small logical request already available in memory, consider writing its complete representation together. That can avoid waiting for a later fragment while preserving useful transport batching. For an interactive stream that must deliver independent small updates promptly, TCP_NODELAY may be appropriate. Choose using the workload&amp;rsquo;s framing and measured behavior rather than applying one setting to every connection.&lt;/p&gt;
&lt;p&gt;Capture both data and acknowledgments when a request periodically stalls. Determine whether the second piece is still in application buffering, queued in the sender, or already received by the peer. A timeout in application code alone cannot distinguish those stages. Correlate the capture with socket configuration and the point at which the receiver considers a request complete.&lt;/p&gt;
&lt;p&gt;Also account for packet-rate and bandwidth costs when sending many tiny updates. Reducing an unnecessary batching delay can improve interaction while increasing segments per operation. Measure the relevant response latency and resource cost together. The useful result is a justified policy for a particular exchange, supported here by two successful requests and different, directly observed transmission ordering.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc896.html"&gt;RFC 896 Congestion Control&lt;/a&gt;, coalescing small writes while earlier data remains unacknowledged, checked 2026-09-12.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc1122.html"&gt;RFC 1122 Host Requirements&lt;/a&gt;, delayed acknowledgment behavior and bounded acknowledgment delay, checked 2026-09-12.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man7/tcp.7.html"&gt;Linux tcp(7)&lt;/a&gt;, TCP_NODELAY and the temporary nature of TCP_QUICKACK, checked 2026-09-12.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>