<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Networking on Roberto Selbach</title><link>https://rselbach.com/tags/networking/</link><description>Recent content in Networking on Roberto Selbach</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Thu, 30 Oct 2014 18:50:00 +0000</lastBuildDate><atom:link href="https://rselbach.com/tags/networking/index.xml" rel="self" type="application/rss+xml"/><item><title>Where an HTTP timeout actually fires</title><link>https://rselbach.com/where-an-http-timeout-actually-fires/</link><pubDate>Thu, 30 Oct 2014 18:50:00 +0000</pubDate><guid>https://rselbach.com/where-an-http-timeout-actually-fires/</guid><description>&lt;p&gt;I added a dial timeout to an HTTP client and assumed I had bounded the entire request. Then a server accepted the connection, sent headers, and dribbled the body slowly enough to occupy a worker for minutes. The timeout had completed its duty before the interesting failure began.&lt;/p&gt;
&lt;p&gt;In Go 1.3, the first choice for a simple end-to-end limit is &lt;code&gt;http.Client.Timeout&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;client&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Client&lt;/span&gt;{&lt;span style="color:#a6e22e"&gt;Timeout&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It covers connection setup, redirects, and reading the response body. If the policy really is “this request gets five seconds,” start there. My narrower transport setting looked like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;tr&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Transport&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;Dial&lt;/span&gt;: (&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;net&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Dialer&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Timeout&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}).&lt;span style="color:#a6e22e"&gt;Dial&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;client&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Client&lt;/span&gt;{&lt;span style="color:#a6e22e"&gt;Transport&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;tr&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The dial timeout covers establishing the network connection. It does not cover waiting for response headers, reading a response body, or the time spent following redirects. DNS resolution may consume part of dialing behavior depending on the platform and resolver path, but the timeout is still not an end-to-end request deadline.&lt;/p&gt;
&lt;p&gt;To see the phases, I wrote a deliberately bad server:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;slow&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ResponseWriter&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Request&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Header&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;Set&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;text/plain&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusOK&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;f&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;ok&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Flusher&lt;/span&gt;); &lt;span style="color:#a6e22e"&gt;ok&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;f&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Flush&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Sleep&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Fprintln&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;eventually&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The client connected immediately and received headers immediately. Its &lt;code&gt;Do&lt;/code&gt; call returned a response, then reading &lt;code&gt;resp.Body&lt;/code&gt; waited. A connect timeout could not help because the connection was exemplary.&lt;/p&gt;
&lt;p&gt;The transport exposes other phase-specific controls. &lt;code&gt;ResponseHeaderTimeout&lt;/code&gt; bounds the wait for response headers after the request is written. &lt;code&gt;TLSHandshakeTimeout&lt;/code&gt; bounds the TLS handshake. Neither bounds a slowly arriving body. These knobs are useful because failures differ: a slow handshake may indicate different capacity trouble than a handler that never writes headers.&lt;/p&gt;
&lt;p&gt;For policies that &lt;code&gt;Client.Timeout&lt;/code&gt; cannot express, I can arrange cancellation through the transport&amp;rsquo;s request cancellation support and a timer. Merely returning from a selecting caller while leaving &lt;code&gt;Do&lt;/code&gt; running leaks work: timeout handling must interrupt the network operation, not only stop waiting for its answer.&lt;/p&gt;
&lt;p&gt;For direct &lt;code&gt;net.Conn&lt;/code&gt; code, deadlines are clearer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;conn&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetDeadline&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Now&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;Add&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;)); &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The deadline applies to future reads and writes and causes them to fail after the time. &lt;code&gt;SetReadDeadline&lt;/code&gt; and &lt;code&gt;SetWriteDeadline&lt;/code&gt; split those directions. A deadline is an absolute time, not an idle timeout that automatically moves after every successful byte. For an idle protocol I refresh it after each accepted unit of progress.&lt;/p&gt;
&lt;p&gt;HTTP complicates direct connection deadlines because &lt;code&gt;http.Transport&lt;/code&gt; owns and reuses pooled connections. A per-request caller should not reach underneath and set arbitrary deadlines on a connection that may later serve another request. Transport-level controls and cancellation preserve that ownership boundary.&lt;/p&gt;
&lt;p&gt;As with the earlier runtime-managed network mechanism, a goroutine can wait without occupying a thread. Cheap waiting is not a timeout policy, though. In-flight requests, response body size, queue length, and elapsed time still need deliberate limits.&lt;/p&gt;
&lt;p&gt;Servers have the same issue from the opposite side. A handler can be tied up by a client that sends or receives slowly. Depending on the Go version and server configuration, read and write timeouts establish connection-level deadlines around phases of serving a request. They are blunt instruments, especially for streaming responses, but leaving every deadline at zero means &amp;ldquo;forever.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;I tested the repaired client with three bad endpoints: one that never completed a TCP connection, one that accepted but withheld headers, and one that streamed a byte periodically. Each exercises a different phase. The output made the distinction visible:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dial stall: timeout awaiting connection
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;header stall: timeout awaiting response headers
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;body stall: request deadline reached while reading body
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Error strings vary and should not become program logic. I care that each operation terminates, closes its body or cancellation path, and leaves no steadily growing goroutine count.&lt;/p&gt;
&lt;p&gt;The temptation is to choose one five-second number and call the system safe. Real policies differ. A metadata request might have a strict total deadline. A large download may allow minutes overall but reject 30 seconds without progress. A streaming endpoint may be intentionally unbounded while still needing heartbeat and cancellation rules.&lt;/p&gt;
&lt;p&gt;Timeout errors should remain distinguishable from protocol failures. I check whether a returned network error reports &lt;code&gt;Timeout()&lt;/code&gt; rather than matching its text, then include the operation and peer in the higher-level error. Retrying blindly is still wrong: a timed-out request may have reached the server, so non-idempotent operations need application-level rules.&lt;/p&gt;
&lt;p&gt;“The HTTP timeout” is not a very useful phrase. There are limits for connecting, handshaking, receiving headers, reading bodies, remaining idle, and completing the whole operation. Naming the phase put the mechanism in the right place; before that, I had a very reliable timeout on the part that was not slow.&lt;/p&gt;</description></item><item><title>Chasing a Go latency spike</title><link>https://rselbach.com/chasing-a-go-latency-spike/</link><pubDate>Thu, 12 Jun 2014 19:05:00 +0000</pubDate><guid>https://rselbach.com/chasing-a-go-latency-spike/</guid><description>&lt;p&gt;An HTTP service of mine usually answered in a few milliseconds but occasionally took more than 100. Average latency looked healthy, which is one of the average&amp;rsquo;s less charming habits. I wanted to know whether requests were waiting on the network, the scheduler, or garbage collection.&lt;/p&gt;
&lt;p&gt;My first attempt added timers around every handler. That established that a slow request was slow, a breakthrough unlikely to threaten the scientific establishment. I needed observations below the handler.&lt;/p&gt;
&lt;p&gt;I started with &lt;code&gt;GODEBUG=gctrace=1&lt;/code&gt;. A run under load printed lines resembling:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gc1(1): 0+6+1 ms, 18 -&amp;gt; 22 MB 12033 -&amp;gt; 8411 (123004-114593) objects
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gc2(1): 0+9+1 ms, 25 -&amp;gt; 29 MB 15020 -&amp;gt; 9170 (171220-162050) objects
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The exact format is runtime-version dependent, but the useful correlation was clear: several latency spikes lined up with collections. The Go collector at this point includes stop-the-world work. During those pauses, goroutines do not make application progress. More processors do not turn a global pause into concurrent work.&lt;/p&gt;
&lt;p&gt;I then reduced allocation rate rather than immediately tuning the collector. A JSON path decoded first into &lt;code&gt;interface{}&lt;/code&gt; and then into a struct. Removing that intermediate object graph reduced collection frequency. Reusing a scratch byte slice within a request also helped, provided I kept ownership local and did not retain a huge backing array for a tiny value.&lt;/p&gt;
&lt;p&gt;Heap size and pause time are related but not identical. Setting &lt;code&gt;GOGC&lt;/code&gt; lower collects more often with a smaller heap; setting it higher collects less often while retaining more memory. Neither setting fixes accidental allocation. I leave &lt;code&gt;GOGC&lt;/code&gt; at its default until measurements show a reason to trade memory for collection frequency.&lt;/p&gt;
&lt;p&gt;The scheduler was the next suspect. Go multiplexes goroutines onto operating-system threads. Runnable goroutines wait in scheduler queues; goroutines blocked in syscalls or channel operations allow others to run. &lt;code&gt;GOMAXPROCS&lt;/code&gt; controls how many processors may execute Go code simultaneously, not how many sockets the program may hold and not a magic goroutine limit.&lt;/p&gt;
&lt;p&gt;I had one CPU-heavy goroutine parsing a large report. Function calls provide scheduling opportunities in the current runtime, but preemption is only partial. A long computation with few safe points can delay other runnable work, particularly with a low &lt;code&gt;GOMAXPROCS&lt;/code&gt;. Breaking the report into bounded jobs improved fairness and made cancellation by closing a work channel practical. Adding &lt;code&gt;runtime.Gosched&lt;/code&gt; inside the innermost loop helped one test and made the algorithm uglier, so I did not keep it.&lt;/p&gt;
&lt;p&gt;Network waiting has a different path. On supported systems, the runtime&amp;rsquo;s network poller integrates non-blocking descriptors with mechanisms such as kqueue or epoll. A goroutine waiting for socket readiness can park without consuming an operating-system thread for the duration. When the descriptor becomes ready, the poller makes the goroutine runnable again.&lt;/p&gt;
&lt;p&gt;This is why one goroutine per connection can be practical. It does not mean each connection has a dedicated thread. It also does not mean network code is immune to thread exhaustion: file operations and syscalls that the poller cannot manage may block threads, DNS behavior varies by platform and resolver path, and cgo calls have their own costs.&lt;/p&gt;
&lt;p&gt;I made the wait visible by taking a goroutine profile from &lt;code&gt;net/http/pprof&lt;/code&gt; while the service was loaded. Most goroutines were parked in network reads, which was normal. The interesting stack was a growing set waiting on one mutex around a response cache. My &amp;ldquo;thread-safe map&amp;rdquo; protected JSON encoding under the lock, turning a short critical section into serialized CPU work.&lt;/p&gt;
&lt;p&gt;The repair copied the cached value&amp;rsquo;s reference while holding the lock, unlocked, and encoded afterward. That shortened scheduler queues during bursts. I verified the data was immutable before doing this; moving mutable state outside a lock would have exchanged latency for corruption, a poor bargain even by my standards.&lt;/p&gt;
&lt;p&gt;One test run then looked roughly like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;before: p50=4ms p95=37ms p99=128ms
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;after: p50=3ms p95=11ms p99=42ms
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The remaining tail still correlated with collections. This was not a proof that all pauses were GC pauses: kernel scheduling, packet loss, disk activity, and clients contribute too. I kept wall-clock request histograms beside allocation and GC observations rather than assigning every gap to the runtime.&lt;/p&gt;
&lt;p&gt;The mechanism suggests a practical diagnostic order. First, establish whether time is spent runnable, blocked on synchronization, parked on network I/O, or stopped globally. Goroutine profiles reveal blocking locations, mutex and channel structure reveal serialization, GC traces reveal collection timing, and CPU profiles reveal actual execution. Then change the source of delay.&lt;/p&gt;
&lt;p&gt;I also capped the work queue. Before that change, a burst created more live request state than workers could consume, raising heap size and GC cost after traffic subsided. Back pressure made overload visible to callers instead of translating it into a delayed collector problem. A goroutine waiting in a queue is still retained work.&lt;/p&gt;
&lt;p&gt;My direct opinion is that &amp;ldquo;goroutines are cheap&amp;rdquo; is useful sales material and terrible capacity planning. Their stacks consume memory, their work must be scheduled, their objects burden the collector, and their shared locks serialize. The runtime makes concurrency remarkably approachable, but it cannot make an unbounded queue or a giant critical section reasonable. Mine was a runtime mystery only until I measured my own code.&lt;/p&gt;</description></item><item><title>Draining HTTP response bodies</title><link>https://rselbach.com/draining-http-response-bodies/</link><pubDate>Tue, 04 Mar 2014 16:40:00 +0000</pubDate><guid>https://rselbach.com/draining-http-response-bodies/</guid><description>&lt;p&gt;I had a polling client that became steadily slower. The server was fine, DNS was fine, and my diagnostic method of staring at it was producing limited returns.&lt;/p&gt;
&lt;p&gt;The client checked only the status:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;resp&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Get&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;resp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Body&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Close&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;resp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusCode&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusOK&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Errorf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;status: %s&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;resp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Status&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Closing the body is necessary, but for persistent HTTP connections it is not always sufficient. If I do not read the response body to EOF, the transport may be unable to reuse that TCP connection. My server returned a small explanatory body for an error status; I ignored it and paid for a new connection on the next poll.&lt;/p&gt;
&lt;p&gt;For responses whose contents I deliberately discard, I now do this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;resp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Body&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Close&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Copy&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ioutil&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Discard&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;resp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Body&lt;/span&gt;); &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The transport owns a pool of persistent connections keyed by destination. Reading through EOF lets its body wrapper observe completion and return the connection to the idle pool. &lt;code&gt;Close&lt;/code&gt; still matters because it releases resources on every exit path.&lt;/p&gt;
&lt;p&gt;This is not permission to drain an unbounded hostile response. If I only need to preserve reuse for a known-small error body, I put a limit around it and accept that an oversized body may force the connection closed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;LimitReader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;resp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Body&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;_&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Copy&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ioutil&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Discard&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The same ownership rule applies on success. The caller that receives &lt;code&gt;*http.Response&lt;/code&gt; generally owns &lt;code&gt;Body&lt;/code&gt; and must close it. A helper that consumes the body should close it itself and return decoded data instead. Splitting those responsibilities is how bodies remain open.&lt;/p&gt;
&lt;p&gt;I verified reuse by wrapping &lt;code&gt;Transport.Dial&lt;/code&gt; with a counter. Ten requests to the same server produced ten dials before the fix and one after it. That is a crude instrument, but better than inferring connection behavior from elapsed time. A server can close persistent connections itself, so one dial is not a universal expected value.&lt;/p&gt;
&lt;p&gt;There is a related server-side rule: a handler should not assume unread request bytes are harmless. &lt;code&gt;net/http&lt;/code&gt; often manages request bodies for handlers, but large or malformed input still needs explicit size limits. Connection reuse is valuable; reading arbitrary quantities merely to obtain it is not.&lt;/p&gt;
&lt;p&gt;After draining the tiny responses, repeated requests settled onto the same few connections instead of continually dialing. It was not an exotic kernel problem. It was one unread paragraph of error text, patiently converting itself into sockets.&lt;/p&gt;</description></item><item><title>How Go waits for the network</title><link>https://rselbach.com/how-go-waits-for-the-network/</link><pubDate>Fri, 12 Apr 2013 16:52:00 +0000</pubDate><guid>https://rselbach.com/how-go-waits-for-the-network/</guid><description>&lt;p&gt;I wrote an echo server with one goroutine per connection and then wondered where all the operating-system threads were. I had accepted the pleasant API before understanding the trick beneath it.&lt;/p&gt;
&lt;p&gt;The simple server is almost suspiciously simple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;serve&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;c&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;net&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Conn&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Close&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;buf&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; make([]&lt;span style="color:#66d9ef"&gt;byte&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;4096&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Read&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;buf&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Write&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;buf&lt;/span&gt;[:&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;]); &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;l&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;net&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Listen&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;tcp&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;:9000&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		panic(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;l&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Accept&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;go&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;serve&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;c&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Read&lt;/code&gt; appears to block the goroutine until bytes arrive. If every blocked goroutine required a blocked kernel thread, this design would approach the old thread-per-connection model with nicer syntax but similar scaling limits. The runtime instead integrates network descriptors with an operating-system readiness notification facility.&lt;/p&gt;
&lt;p&gt;The toy server exits on an &lt;code&gt;Accept&lt;/code&gt; error. A real daemon may retry temporary errors with a delay, but blindly continuing on every error turns a closed listener or exhausted descriptor table into a very efficient busy loop.&lt;/p&gt;
&lt;p&gt;On Linux that facility is &lt;code&gt;epoll&lt;/code&gt;; other systems provide equivalents such as &lt;code&gt;kqueue&lt;/code&gt;. The exact backend is platform-specific, but the shape is similar. The socket is placed in non-blocking mode. A read attempts the system call. If the kernel says the operation would block, the runtime records that the goroutine is waiting for readiness on that descriptor and parks it. The underlying thread is then free to run another goroutine.&lt;/p&gt;
&lt;h2 id="parking-is-not-polling"&gt;Parking is not polling&lt;/h2&gt;
&lt;p&gt;Parking a goroutine removes it from runnable work. It does not spin around asking the socket whether it feels conversational yet. The runtime&amp;rsquo;s network poller waits for the kernel to report descriptors that may now make progress. When an event arrives, the corresponding goroutine is made runnable and eventually tries the operation again.&lt;/p&gt;
&lt;p&gt;Readiness is not completion. A descriptor reported readable may yield some bytes, an end-of-file condition, or another transient result by the time code runs. The runtime and &lt;code&gt;net&lt;/code&gt; package retain the usual loop around non-blocking operations. The poller says “worth trying,” not “your complete application message has arrived on a silver tray.”&lt;/p&gt;
&lt;p&gt;This distinction also explains partial reads. TCP is a byte stream. One &lt;code&gt;Write&lt;/code&gt; at the sender does not imply one equally sized &lt;code&gt;Read&lt;/code&gt; at the receiver. The poller reports available progress; framing remains the protocol&amp;rsquo;s responsibility. My echo loop can return each chunk because its protocol is deliberately trivial. A real message parser must accumulate and delimit data.&lt;/p&gt;
&lt;h2 id="deadlines-join-the-machinery"&gt;Deadlines join the machinery&lt;/h2&gt;
&lt;p&gt;Network code also needs a way to stop waiting. &lt;code&gt;SetReadDeadline&lt;/code&gt; and &lt;code&gt;SetWriteDeadline&lt;/code&gt; associate times with operations on a connection. The runtime combines descriptor waiting with timers so a parked goroutine can become runnable because the socket is ready or because its deadline expired.&lt;/p&gt;
&lt;p&gt;Without deadlines, a slow or vanished peer can retain a goroutine and everything reachable from its stack indefinitely. Small goroutine stacks make this cheaper than a thread leak, not desirable. A server that accepts untrusted connections should make an explicit decision about idle time rather than interpreting silence as a lifelong subscription.&lt;/p&gt;
&lt;p&gt;Closing a connection is another wake-up path. Code blocked in an operation must be released with an error rather than left attached to a descriptor that no longer has meaning. This is one reason descriptor state belongs in runtime-coordinated structures instead of being only an integer passed casually among goroutines.&lt;/p&gt;
&lt;h2 id="what-still-blocks-a-thread"&gt;What still blocks a thread&lt;/h2&gt;
&lt;p&gt;The network poller helps operations the runtime knows how to put into non-blocking mode and register. It does not transform every possible system call into asynchronous work. A call into C, an ordinary blocking file operation, or an unfamiliar kernel interface may occupy an operating-system thread. The scheduler can create or use another thread so runnable goroutines continue, but blocked threads still have a cost.&lt;/p&gt;
&lt;p&gt;Regular disk files are particularly different from sockets. Readiness interfaces commonly report them ready even when the operation may wait on storage. Treating every &lt;code&gt;Read&lt;/code&gt; method as equivalent because it satisfies the same interface would be tidy and wrong.&lt;/p&gt;
&lt;p&gt;There is a nice Linux comparison here. An application written in C can build an event loop directly around &lt;code&gt;epoll&lt;/code&gt;, maintain a state machine per connection, and achieve excellent performance. It also has to preserve that state across every partial operation and error path. Go&amp;rsquo;s runtime uses the same broad kernel capability, then presents the waiting state as an ordinary goroutine stack. The state machine has not vanished; the runtime and compiler help store it in a form humans generally prefer reading.&lt;/p&gt;
&lt;h2 id="a-practical-test"&gt;A practical test&lt;/h2&gt;
&lt;p&gt;I connected many idle clients to the echo server and watched thread count and memory rather than merely measuring requests per second. The number of goroutines rose with connections. Threads did not rise one-for-one. Memory grew, because every connection and goroutine is real, but not as though each connection had received a traditional large thread stack.&lt;/p&gt;
&lt;p&gt;This does not prove every Go server scales automatically. File descriptors have process limits. Buffers consume memory. A goroutine can retain a large object graph. Handlers can block on locks, allocate furiously, or perform CPU work that has nothing to do with the poller. The mechanism solves efficient waiting, not careless architecture.&lt;/p&gt;
&lt;p&gt;Still, it changes how I write network services. I can begin with one goroutine per connection because it is clear and maps directly to the protocol conversation. Then I measure descriptor use, memory, scheduler behaviour, and latency. I do not begin by translating the protocol into callback soup merely because the C implementation had to.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;net&lt;/code&gt; package&amp;rsquo;s blocking appearance is therefore honest from the goroutine&amp;rsquo;s perspective. That goroutine really does stop until it can proceed. The useful deception is that the operating-system thread need not stop with it.&lt;/p&gt;</description></item><item><title>Backpressure Is Not an Error Message</title><link>https://rselbach.com/backpressure-is-not-an-error-message/</link><pubDate>Fri, 16 Oct 2009 20:09:00 +0000</pubDate><guid>https://rselbach.com/backpressure-is-not-an-error-message/</guid><description>&lt;p&gt;My event-driven server stayed responsive under load, but its memory use climbed steadily. I had removed blocking and accidentally built an extremely efficient machine for accepting work it could not finish.&lt;/p&gt;
&lt;p&gt;Each connection had an output buffer. Producers appended responses faster than the network drained them, so those buffers became an unbounded queue distributed across clients. Nothing was technically stuck. The server was merely saving enough unfinished work to become stuck later.&lt;/p&gt;
&lt;p&gt;Backpressure means carrying limited downstream capacity toward the producer. When a connection&amp;rsquo;s output crosses a high-water mark, I stop reading more requests from it or stop scheduling new work. Once buffered output falls below a lower mark, reading resumes. Separate thresholds avoid switching state on every small write.&lt;/p&gt;
&lt;p&gt;The same rule applies between the event loop and worker pool. A bounded job queue makes overload visible. If it is full, the loop must defer input, reject work or shed a connection according to policy. Adding another unbounded queue does not increase capacity; it increases the delay before admitting there is none.&lt;/p&gt;
&lt;p&gt;I prefer bounded queues and explicit overload behavior, even when rejection feels impolite. The exact limits require measurement, and short bursts deserve some room. But a service that says &amp;ldquo;not now&amp;rdquo; can recover. One that accepts everything may respond to nobody, which is very accommodating in principle and less so in practice.&lt;/p&gt;</description></item><item><title>Keeping Work Out of a Network Interrupt</title><link>https://rselbach.com/keeping-work-out-of-a-network-interrupt/</link><pubDate>Tue, 27 May 2003 05:47:36 +0000</pubDate><guid>https://rselbach.com/keeping-work-out-of-a-network-interrupt/</guid><description>&lt;p&gt;I put packet bookkeeping into a network device&amp;rsquo;s interrupt handler because the information was conveniently available there. Under load, the machine reminded me that convenience inside interrupt context is borrowed at unreasonable interest.&lt;/p&gt;
&lt;p&gt;The interrupt handler should acknowledge the device, collect enough status to know what happened, move completed descriptors along, and arrange deferred processing where appropriate. It cannot sleep, and time spent there delays other interrupt work. Long parsing, allocation loops, or diagnostic printing are especially poor guests.&lt;/p&gt;
&lt;p&gt;Linux 2.4 offers deferred mechanisms including tasklets and the network receive path&amp;rsquo;s softirq processing. A driver can prepare received skbs and pass them to &lt;code&gt;netif_rx()&lt;/code&gt;, allowing the protocol stack to process packets outside the hard interrupt handler. For driver-specific deferred work, I put the tasklet in the device&amp;rsquo;s private state:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; sample_dev {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; tasklet_struct rx_tasklet;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;/* rings, locks, and device state */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sample_tasklet&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;unsigned&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;long&lt;/span&gt; data)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; sample_dev &lt;span style="color:#f92672"&gt;*&lt;/span&gt;dev &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; sample_dev &lt;span style="color:#f92672"&gt;*&lt;/span&gt;)data;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;service_completed_packets&lt;/span&gt;(dev);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sample_init_tasklet&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; sample_dev &lt;span style="color:#f92672"&gt;*&lt;/span&gt;dev)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;tasklet_init&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;dev&lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;rx_tasklet, sample_tasklet,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; (&lt;span style="color:#66d9ef"&gt;unsigned&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;long&lt;/span&gt;)dev);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;sample_init_tasklet()&lt;/code&gt; runs after &lt;code&gt;dev&lt;/code&gt; is allocated and before the interrupt can schedule &lt;code&gt;dev-&amp;gt;rx_tasklet&lt;/code&gt;. Teardown disables that interrupt source, calls &lt;code&gt;tasklet_kill(&amp;amp;dev-&amp;gt;rx_tasklet)&lt;/code&gt;, and only then frees &lt;code&gt;dev&lt;/code&gt;. The callback therefore never receives the null value that a static &lt;code&gt;DECLARE_TASKLET(..., 0)&lt;/code&gt; would have supplied.&lt;/p&gt;
&lt;p&gt;Shared rings and status fields need protection across the interrupt, deferred function, transmit entry point, and process-context control operations. I use spinlocks where the data can be touched in interrupt context and choose the interrupt-disabling variant when the same processor could otherwise interrupt a lock holder. I keep the protected section short and never call a function that can sleep while holding it.&lt;/p&gt;
&lt;p&gt;There is a balance. Deferring every tiny operation adds overhead and can complicate ordering, while doing everything in the handler harms latency. I measure under sustained receive and transmit load, watch dropped packets, and check that another device&amp;rsquo;s interrupts are not starved.&lt;/p&gt;
&lt;p&gt;Interrupt acknowledgement order is device-specific and worth documenting beside the handler. A level-triggered interrupt that remains asserted can immediately enter again; acknowledging too early on other hardware may lose status that has not been copied. I read the device manual rather than deriving this order from whichever experiment happened not to hang.&lt;/p&gt;
&lt;p&gt;Shutdown receives the same scrutiny. The driver disables the device&amp;rsquo;s interrupt source, prevents new deferred work, frees the IRQ with the required synchronization, and only then releases rings and private data. Otherwise a late tasklet can faithfully process memory that has already acquired a new purpose.&lt;/p&gt;
&lt;p&gt;I want the hard interrupt path to be visibly bounded. One caveat: &lt;code&gt;printk&lt;/code&gt; can completely distort timing here. A hundred helpful messages per second soon become the performance problem, a promotion few debug statements deserve.&lt;/p&gt;</description></item><item><title>Building a Tiny KIO Slave</title><link>https://rselbach.com/building-a-tiny-kio-slave/</link><pubDate>Thu, 17 Apr 2003 22:09:28 +0000</pubDate><guid>https://rselbach.com/building-a-tiny-kio-slave/</guid><description>&lt;p&gt;I had an application talking to a small read-only service and was about to put the protocol code directly into its file dialog path. A KIO slave turned out to be a cleaner experiment: teach KDE one URL scheme, then let Konqueror and any KIO-aware application use it.&lt;/p&gt;
&lt;p&gt;The slave is a separate process driven through KIO&amp;rsquo;s protocol. A class derived from &lt;code&gt;KIO::SlaveBase&lt;/code&gt; implements operations such as &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;stat&lt;/code&gt;, or &lt;code&gt;listDir&lt;/code&gt;, depending on what the scheme supports. For a tiny read-only resource, &lt;code&gt;get&lt;/code&gt; is enough to prove the mechanism:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; NoteProtocol&lt;span style="color:#f92672"&gt;::&lt;/span&gt;get(&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; KURL &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;url)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; QByteArray bytes &lt;span style="color:#f92672"&gt;=&lt;/span&gt; loadNote(url.path());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mimeType(&lt;span style="color:#e6db74"&gt;&amp;#34;text/plain&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; totalSize(bytes.size());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data(bytes);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data(QByteArray());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; finished();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Real code must report failures with &lt;code&gt;error()&lt;/code&gt; rather than returning silently. It should also send data in useful chunks for large objects instead of reading everything into memory as this demonstration does. The empty data block marks the end of data before &lt;code&gt;finished()&lt;/code&gt; completes the operation.&lt;/p&gt;
&lt;p&gt;KIO discovers the slave through an installed protocol description and service information. Those files state the scheme, executable, supported operations, and whether the protocol works with files, directories, reading, or writing. If Konqueror says the protocol is unknown, I check installation paths and run &lt;code&gt;kbuildsycoca&lt;/code&gt; before blaming the C++.&lt;/p&gt;
&lt;p&gt;The process boundary is the interesting part. The client starts a KIO job. KIO selects and communicates with the slave, and the slave translates operations into the remote or special resource&amp;rsquo;s rules. A failure in the protocol handler need not take the client application down with it, and authentication or progress can use KDE&amp;rsquo;s common machinery.&lt;/p&gt;
&lt;p&gt;I am careful not to claim operations the protocol cannot honor. Advertising rename for an immutable service only moves the error from an absent menu item to an annoyed user. &lt;code&gt;stat&lt;/code&gt; results must also be consistent with &lt;code&gt;get&lt;/code&gt;; applications rely on metadata for decisions before fetching.&lt;/p&gt;
&lt;p&gt;I test the scheme directly in Konqueror and with a tiny KIO client, including a missing object, an empty object, and a path containing escaped characters. URL decoding must happen at the protocol boundary exactly once. Decoding twice turns a perfectly legal percent sign into an accidental instruction.&lt;/p&gt;
&lt;p&gt;My preference is a KIO slave when a resource genuinely behaves like a hierarchy of files or directories and should be available across KDE. For one private command, DCOP or an application-specific class is smaller. Giving every service a URL is attractive, but some services are verbs wearing a hat.&lt;/p&gt;</description></item><item><title>Reading an skb Without Regretting It</title><link>https://rselbach.com/reading-an-skb-without-regretting-it/</link><pubDate>Fri, 20 Dec 2002 20:14:39 +0000</pubDate><guid>https://rselbach.com/reading-an-skb-without-regretting-it/</guid><description>&lt;p&gt;I added a packet check to a Linux 2.4 network path and initially treated &lt;code&gt;skb-&amp;gt;data&lt;/code&gt; as if it always began with the header I wanted. It did in my first test. Networking code has a generous way of rewarding that assumption just long enough to become embarrassing.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;sk_buff&lt;/code&gt; carries packet data plus bookkeeping used as the packet moves through layers. Its &lt;code&gt;head&lt;/code&gt;, &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;tail&lt;/code&gt;, and &lt;code&gt;end&lt;/code&gt; pointers describe the allocated area and the currently occupied bytes. Protocol processing can pull a consumed header from the front or push space for a new one without copying the whole packet.&lt;/p&gt;
&lt;p&gt;Before reading a header, I check that the required bytes are present and use the header position appropriate to that point in the stack. In a simple driver receive path, the driver allocates an skb, reserves alignment if needed, copies or maps packet bytes, sets the protocol, and hands it upward:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;skb &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;dev_alloc_skb&lt;/span&gt;(length &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;skb)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt;ENOMEM;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;skb_reserve&lt;/span&gt;(skb, &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;memcpy&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;skb_put&lt;/span&gt;(skb, length), packet, length);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;skb&lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;dev &lt;span style="color:#f92672"&gt;=&lt;/span&gt; dev;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;skb&lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;protocol &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;eth_type_trans&lt;/span&gt;(skb, dev);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;netif_rx&lt;/span&gt;(skb);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Calling &lt;code&gt;skb_put()&lt;/code&gt; extends the valid data at the tail and returns the start of the added area. Writing beyond that area corrupts memory, so the allocation and length must agree. &lt;code&gt;eth_type_trans()&lt;/code&gt; interprets the Ethernet header, sets the protocol, and adjusts the skb for the network layer.&lt;/p&gt;
&lt;p&gt;Ownership changes at &lt;code&gt;netif_rx()&lt;/code&gt;. After handing the skb to the network stack, the driver must not free or inspect it as though it still belongs to the receive routine. Error paths before that handoff must free what they allocated.&lt;/p&gt;
&lt;p&gt;Transmit has the opposite pressure. The driver&amp;rsquo;s &lt;code&gt;hard_start_xmit&lt;/code&gt; receives an skb and must obey the device queue rules. If hardware resources are exhausted, stopping the queue and waking it when descriptors become available is better than quietly dropping ownership conventions. Interrupt and bottom-half paths also require locking suited to their contexts; sleeping locks are not available there.&lt;/p&gt;
&lt;p&gt;I prefer using skb helpers over pointer arithmetic, even when the arithmetic looks obvious. The helpers document whether code is adding, removing, or reserving bytes and retain their checks in debugging builds. The caveat is that not every skb is necessarily laid out as one convenient linear region at every layer, so code must respect the guarantees of its exact call site. Packets are simple on diagrams. The diagrams do not have cache lines.&lt;/p&gt;</description></item><item><title>Do Not Block the KIO Job</title><link>https://rselbach.com/do-not-block-the-kio-job/</link><pubDate>Fri, 28 Jun 2002 23:11:05 +0000</pubDate><guid>https://rselbach.com/do-not-block-the-kio-job/</guid><description>&lt;p&gt;I changed a file-opening routine from local paths to URLs and immediately made the window feel stuck. The transfer was not especially slow. My code was waiting synchronously in the user-interface path, so even a short delay became visible.&lt;/p&gt;
&lt;p&gt;KIO is built around jobs. A call such as &lt;code&gt;KIO::get()&lt;/code&gt; starts work and returns a &lt;code&gt;KIO::TransferJob&lt;/code&gt;; data and completion arrive through signals while the event loop continues processing input and painting.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;KIO&lt;span style="color:#f92672"&gt;::&lt;/span&gt;TransferJob &lt;span style="color:#f92672"&gt;*&lt;/span&gt;job &lt;span style="color:#f92672"&gt;=&lt;/span&gt; KIO&lt;span style="color:#f92672"&gt;::&lt;/span&gt;get(url, false, false);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;connect(job, SIGNAL(data(KIO&lt;span style="color:#f92672"&gt;::&lt;/span&gt;Job &lt;span style="color:#f92672"&gt;*&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; QByteArray &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;)),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;, SLOT(slotData(KIO&lt;span style="color:#f92672"&gt;::&lt;/span&gt;Job &lt;span style="color:#f92672"&gt;*&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; QByteArray &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;)));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;connect(job, SIGNAL(result(KIO&lt;span style="color:#f92672"&gt;::&lt;/span&gt;Job &lt;span style="color:#f92672"&gt;*&lt;/span&gt;)),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;, SLOT(slotResult(KIO&lt;span style="color:#f92672"&gt;::&lt;/span&gt;Job &lt;span style="color:#f92672"&gt;*&lt;/span&gt;)));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The job chooses an appropriate KIO slave from the URL&amp;rsquo;s protocol. The slave runs separately, speaks the protocol, and reports data, progress, redirections, and errors back through KIO. My application consumes a common job interface instead of containing FTP, HTTP, and file code.&lt;/p&gt;
&lt;p&gt;There are two details I now handle every time. First, data may arrive in several chunks. The &lt;code&gt;data&lt;/code&gt; signal is not a promise that one byte array equals one document. I append or parse incrementally. Second, I treat &lt;code&gt;result&lt;/code&gt; as the final authority. An empty final chunk does not mean success, and a non-empty first chunk does not mean the transfer will finish.&lt;/p&gt;
&lt;p&gt;For a tiny operation, &lt;code&gt;KIO::NetAccess&lt;/code&gt; can provide a convenient synchronous wrapper. I still avoid it from slots reached directly through buttons and menus. Nested event loops and blocked windows are an expensive price for saving two slots.&lt;/p&gt;
&lt;p&gt;Cancellation also matters. I keep the job pointer, clear it when the job finishes, and kill the job when the owning view goes away. Because jobs are &lt;code&gt;QObject&lt;/code&gt;s, sensible parentage helps, but explicit user cancellation should still be reflected in the interface.&lt;/p&gt;
&lt;p&gt;Redirection is another reason not to reduce a transfer to one blocking read. The final URL may differ from the requested one, and policy about accepting it belongs in the job flow. Authentication and certificate questions can also require interaction supplied by KDE. A hand-written socket loop tends to rediscover these cases individually, generally when somebody is trying to use the program rather than when I am prepared to debug it.&lt;/p&gt;
&lt;p&gt;For uploads I apply the same rule in reverse: feed data when the job requests it and finish according to the job&amp;rsquo;s protocol. I do not assume that writing a local temporary file and copying it afterward has identical overwrite and error behavior.&lt;/p&gt;
&lt;p&gt;I prefer KIO jobs even for local URLs when the operation already accepts a &lt;code&gt;KURL&lt;/code&gt;. The mechanism remains uniform and remote files stop being an awkward special case. The caveat is that asynchronous code forces the state to be honest. That is inconvenient for about an hour and useful for the rest of the program&amp;rsquo;s life.&lt;/p&gt;</description></item></channel></rss>