Skip to content

Add opt-in session-scoped FIX socket write timeout - #22

Draft
0xAmaan wants to merge 3 commits into
mainfrom
write-deadline-teardown
Draft

Add opt-in session-scoped FIX socket write timeout#22
0xAmaan wants to merge 3 commits into
mainfrom
write-deadline-teardown

Conversation

@0xAmaan

@0xAmaan 0xAmaan commented Jul 22, 2026

Copy link
Copy Markdown

Summary

Add a session-scoped, opt-in SocketWriteTimeout setting to prevent a FIX connection from remaining silently blocked forever when its peer stops reading.

This addresses the deadlock class seen after the emulator and fix-router reconnected with two-way sequence gaps: both session goroutines could synchronously produce replay traffic, stop consuming inbound messages, fill the TCP buffers, and leave both write loops blocked indefinitely. Because disconnect/reconnect handling runs on the blocked session goroutine, the connection could not self-heal.

Behavior and scope

  • The setting is disabled by default. Unconfigured QuickFIX sessions do not set a socket write deadline and retain the existing non-fatal write-error path.
  • Callers may set SocketWriteTimeout to a positive Go duration such as 30s on selected sessions.
  • A configured write arms a fresh deadline before each socket write.
  • Failure to set the deadline, a write timeout, or any other write error is fatal to that connection.
  • Teardown closes the connection before draining messageOut. Closing unblocks the read loop, lets the session process its disconnect path, closes messageOut, and terminates the drain. This also unwedges a session producer blocked mid-replay.

The dependent Kalshi change is peak6-labs/kalshi#1793. It opts in only the emulator acceptor's order and drop-copy sessions. Normal Kalshi venue sessions do not receive this setting.

Validation

  • Timeout disabled: no deadline is set.
  • Timeout disabled: an ordinary write error follows the original log-and-continue behavior.
  • Timeout enabled: per-write deadlines are set.
  • Invalid/non-positive configured values are rejected.
  • Deadline setter errors and write errors tear down the connection.
  • A real net.Pipe blocked-write test verifies the deadline terminates the write and unblocks teardown.
  • Focused tests pass under -race; go vet passes.
  • All CI linter, build, FIX-version, MemoryStore, FileStore, and MongoStore suites pass.

The single red CI job is a baseline repository failure: TestSessionFactorySuite/TestConfigureSocketConnectAddress and TestConfigureSocketConnectAddressMulti fail with the same assertions on unmodified main (run 25184216827). No new timeout or teardown test fails.

0xAmaan and others added 2 commits July 22, 2026 10:43
A socket write to a peer that has stopped reading blocks forever: writeLoop
called connection.Write with no deadline, and on error it logged and kept
looping. When both sides of a session answer each other's resend requests
simultaneously (two-way sequence gap after an unclean disconnect), each
side's session goroutine blocks pushing its replay into messageOut while
its peer does the same — mutual TCP backpressure with no timeout anywhere.
This is the deadlock class of quickfixgo/quickfix#169; the responding side
was left unbounded, and it froze a production-path session for 29.5 hours
on 2026-07-07/08 (peak6-labs/kalshi incident).

writeLoop now takes the net.Conn (both call sites already pass one), arms a
30s write deadline before every write, and treats any write error as fatal
to the connection:

  1. Close the conn — readLoop's blocked Read fails and it closes msgIn.
  2. Drain messageOut — a session goroutine blocked in a sendBytes channel
     send (e.g. mid-resendMessages) unblocks, returns to its event loop,
     sees the closed msgIn, and runs onDisconnect, which closes messageOut
     and ends the drain.

The close-before-drain order is load-bearing: the drain terminates only via
that disconnect path. Callers need no changes — the initiator wrapper and
acceptor handleConnection already Close on their own paths (second close is
logged and harmless), so a wedged session now becomes a clean disconnect +
standard reconnect within seconds.

Note: TestSessionFactorySuite/TestConfigureSocketConnectAddress fails on
the unmodified base commit (d520d75) as well — pre-existing, untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@0xAmaan 0xAmaan changed the title Add write deadline and fatal-write teardown to writeLoop Add opt-in session-scoped FIX socket write timeout Jul 22, 2026
@0xAmaan
0xAmaan marked this pull request as draft July 22, 2026 21:04
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.

1 participant