Skip to content

Update ioxide to 0.14.239 and pin the new TCP timeouts - #916

Merged
Kaliumhexacyanoferrat merged 1 commit into
mainfrom
update-ioxide-0.14.239
Sep 21, 2026
Merged

Kaliumhexacyanoferrat merged 1 commit into
mainfrom
update-ioxide-0.14.239

Conversation

@MDA2AV

@MDA2AV MDA2AV commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator

Bumps all six ioxide package references from 0.14.236 to 0.14.239, and pins two new transport options so the bump does not change behaviour on its own.

Why this is not just a version bump

ioxide 0.14.236 gave TcpOptions an idle timeout and a send timeout, both defaulting to 60 seconds. Before that, a TCP connection had no clock at all.

Server.WithTcp maps six TCP knobs and set neither, so simply moving the version would have inherited both defaults — and this listener carries upgraded connections as well as keep-alive HTTP. A websocket that goes quiet in both directions for a minute is a healthy websocket, and the transport cannot tell it apart from a dead one. Only a ping can, and that is the application's business.

So both are now on TcpTransportOptions and wired through, defaulting to 0 (disabled). The bump is behaviour-neutral, and the capability is configurable rather than inherited.

Why the defaults are off rather than "sensible"

IdleTimeoutMs — closes a connection quiet in both directions. Correct for HTTP keep-alive, wrong for a websocket whose ping interval is longer than it. A server with no upgrades can turn it on; a server with upgrades should set it comfortably above the ping interval, so pings keep the connection marked alive.

SendTimeoutMs — bounds a client that stops reading. Its receive window shuts, the send never completes, and nothing in TCP ends that: a zero window is a legitimate state a peer can hold indefinitely. Genuinely worth having. But while ioxide sends with MSG_WAITALL it is a deadline for the whole response rather than for progress within it, so a large download to a slow client counts against it. That number depends on what you serve, so it is not one this project should pick for you.

Both are documented in place with that reasoning.

What the ioxide release brings

Recv buffers a reader still holds are now reclaimed at connection teardown. TcpDriver.CloseAsync already completes both halves before DecRef, so the common path here was never leaking.

It still matters, though: completing a reader does not disarm a read that is still parked. The awaiter stays armed, the next completion resumes it, and items ingested after that had nobody left to return them. That case needs no mistake by the caller, and it is the shape a TLS teardown produces.

Also in the release: the version constant is generated from the csproj rather than hand-kept, XML docs now ship with the packages, and TcpConnectionStream finally has a Dispose that returns its buffer — which matters to anyone wrapping it in an SslStream.

Verification

Full solution builds clean, 0 warnings, 0 errors.

The acceptance suite passes everything it runs — 1,181 / 1,199 across the two target frameworks on one pass, 756 / 810 on another — and then aborts with io_uring_setup failed: -1. That reproduces identically on unmodified main, at a different point each run (it got to 2,130 there), so it is this machine's io_uring instance limits rather than anything in this change. Zero test failures on either branch; I could not get a complete local run on either.

Housekeeping

The update-ioxide-0-14-236 branch is superseded by this one and can be deleted — main is already on 0.14.236, so its commit is a no-op now.

ioxide 0.14.236 gave TcpOptions an idle and a send timeout, both defaulting to
60s. Taking that default silently would start closing idle websockets: this
listener carries upgraded connections as well as keep-alive HTTP, and the
transport cannot tell them apart. An idle websocket is a healthy one.

So both are exposed on TcpTransportOptions and wired through, defaulting to 0 -
disabled. The bump is therefore behaviour-neutral, and the capability becomes
configurable rather than inherited.

The defaults are off rather than "sensible" on purpose:

  IdleTimeoutMs closes a connection quiet in both directions. Right for HTTP
  keep-alive, wrong for a websocket whose ping interval exceeds it. A server
  with no upgrades can turn it on; one with upgrades should set it above the
  ping interval so pings keep the connection marked alive.

  SendTimeoutMs bounds a client that stops reading - its window shuts, the send
  never completes, and nothing in TCP ends that. But while ioxide sends with
  MSG_WAITALL it is a deadline for the WHOLE response, not for progress within
  it, so a large download to a slow client counts against it. That wants sizing
  against the slowest legitimate response, which is a per-deployment number.

What 0.14.239 brings that matters here: recv buffers a reader still holds are
reclaimed at teardown. TcpDriver.CloseAsync already completes both halves before
DecRef, so the common path was never leaking - but completing does not disarm a
read that is still parked, and items ingested after that had nobody left to
return them. That case needed no mistake by the caller.

Full solution builds clean, 0 warnings. The acceptance suite passes everything it
runs and then aborts on io_uring_setup - which reproduces identically on
unmodified main, at a different point each time, so it is this machine's io_uring
limits rather than anything in this change.
@Kaliumhexacyanoferrat
Kaliumhexacyanoferrat merged commit 69b250e into main Sep 21, 2026
10 checks passed
@Kaliumhexacyanoferrat
Kaliumhexacyanoferrat deleted the update-ioxide-0.14.239 branch September 21, 2026 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants