Build1 publisher3 min readPublished
A Rust author pins his proxy's socket exhaustion on getrandom, arriving via ring via trust-dns, and later on a single into_inner() call in his own connection pool. Only one of the two comes with a mechanism.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
The connection-pool line carries a mechanism. `let _ = socket.into_inner();` appears in the post under a comment saying it drops the socket without closing it [13]. That is what descriptor exhaustion looks like from the inside: `into_inner` consumes the wrapper and hands back the inner stream, binding it to `_` drops it immediately, and with a tokio `TcpStream` built through `FromRawFd` the descriptor's ownership is whatever the author wired up [14]. The published text stops mid-sentence at the clause that would have explained the rest [14].
The getrandom attribution has no comparable chain. The post says a transitive dependency of `ring`, which `trust-dns` used for DNS-over-HTTPS, was holding onto entropy pools differently [5]. Entropy pool handling and thousands of open sockets under `lsof` [2] are never joined anywhere in the piece. The chain also runs through the library that had already been dropped: `trust-dns` felt heavy, so the author wrote his own resolver [1], and the leak is then pinned on something `trust-dns` pulled in via `ring` [5]. Either both were still in the graph after the replacement, or the attribution does not hold.
`cargo tree -e features` returned 87 crates for a DNS resolver [7]. Half arrived behind `tokio-postgres`, added to log resolution metrics, and half of those behind `serde_json`, serializing a struct that was never sent anywhere [8]. Half of 87 is about 44, which puts roughly 22 crates on the far side of a serializer whose output had no destination [1]. Deleting `tokio-postgres`, hand-rolling a 40-line `fmt::Write` serializer and removing three debugging macros [9] took the binary from 4.2 MB to 1.8 MB [10] and the clean build from 92 seconds to 37 [11]. That is 2.4 MB, or 57 percent [2], and 55 seconds, or 60 percent [3].
Whether those numbers transfer turns on one condition: your graph has to contain a database client behind an observability feature. If it does, the shape repeats. If your dependencies are all load-bearing, deleting them costs features, not seconds.
Replacing a heavy library relocated the audit problem rather than removing it, but in a narrower form than the post's framing suggests. The author says as much, that he replaced one dependency with another and forgot to audit the entire tree [6]. What the rewrite did not remove was the obligation to read the tree, and the two days spent instrumenting every `.await` point and building a 50k-connection fuzz harness [4] were spent on code that was not where the fd count was coming from.
Rust's default release profile panics on integer overflow while the debug profile wraps, and the test suite ran in debug, so an overflow in a packet length calculation reached production before anything caught it [15]. The fix, `#![deny(arithmetic_side_effects)]` plus a CI job that runs the tests in release mode [16], does not make the arithmetic correct. It makes the tests run the semantics that ship.
He kept the resolver [17]. That is defensible on the terms he states: faster for his use case, smaller, and he knows which RFCs he skipped [17][18]. Faster for his use case is a claim about his traffic.
Ranked by verification strength, evidence, and original report placement.
Three months after release, a user reported that their proxy was leaking file descriptors; the author ran lsof and saw thousands of open sockets, initially assuming a leak in his async runtime integration.
The author describes the socket leak as one that only manifests under 10,000 concurrent connections.
The author wrote his own DNS resolver in Rust because trust-dns felt heavy.
He spent two days instrumenting every .await point, wrapping every TcpStream in a custom guard, and writing a fuzz harness that opened 50,000 connections, which reproduced the leak in under a second.
Running cargo tree -e features showed 87 crates in the dependency graph of the DNS resolver.
Half of the 87 crates were pulled in by tokio-postgres, which the author had added to log resolution metrics, and half of those were pulled in by serde_json, used to serialize a struct he never actually sent anywhere.
Follow any of these and your For You feed starts watching them — no settings page required.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
One self-published account that disagrees with itself
Everything here comes from a single dev.to post cross-posted from the author's own site, and that post names two incompatible causes for one descriptor leak without ever withdrawing the first. The getrandom attribution arrives with no crate version, no reproduction and no mechanism beyond entropy pools being held 'differently'; the connection-pool line at least explains how a descriptor survives its stream. There is no repository, commit or issue to check any figure against, and the piece stops mid-sentence while describing a later latency regression.
One maintainer plus one downstream user
Disclosed usage stops at the author's own machine and the single user whose proxy surfaced the exhaustion. Outside involvement is real but thin, limited to a panic fix and a fuzz target, plus a CVE pointer that arrived unnamed, and the post cannot decide whether the resolver is still in service or was swapped for trust-dns-resolver. Nothing indicates anyone else depends on the crate.
Diagnostic certainty outruns the diagnosis
'It wasn't my code. It was getrandom.' is stated as settled fact and then quietly abandoned when the same post finds the leak in a line the author wrote himself. The self-measured wins are the least oversold part of the piece: 4.2 MB down to 1.8 MB and 92 seconds down to 37 are modest and plausible for dropping a database client. The confidence about causes outstrips what the numbers about outcomes actually support.
Newsletter funnel attached to the confession
The post closes by asking readers to follow 'Tamiz's Insights' for more unfiltered takes, which is the ordinary economics of a dev.to cross-post rather than anything concealed. Worth noting that candour about one's own bugs is the product here, and that shapes which failures get written up and how cleanly they appear to resolve on the page. No vendor, employer or sponsor has a stake in the outcome, which caps how far the incentive reaches.
Clear claims, unproven engineering
Judging what this post claims is straightforward, because the contradiction sits in plain view inside one piece. Judging whether either cause is correct is impossible from what we have: no code to inspect, no versions to pin, and an ending that cuts off before the library comparison lands. So this assessment can state the reporting's claims with reasonable certainty, while the resolver's actual behavior stays an open question.
build
Rust's from_str waves through a tenth of the JSON that RFC 8259 says to reject1 publisher
build
98ms repo maps: what moving symbol indexing out of Node actually buys an agent1 publisher
build
The Elixir arbitrage roadmap that puts the Rust parser third, not first1 publisher
build
The failure modes of AI agents in low-level code have names, and names can be gated in CI1 publisher
Publishers with included, body-backed reporting in this cluster.