Skip to content

[Bug]: connectionsBeyondTheCapAreClosed is flaky - it opens 128 connections against a listen backlog of 16 #151

Description

@FRO777

Commit

fd3c0b8 (Add optional Gemma 4 image support (#144)). Both constants involved are unchanged since e65da9e.

Mac hardware and memory

Apple M3 Pro, 18 GB

macOS and Swift versions

macOS 27.0 (26A5416b), Swift 6.4 (swiftlang-6.4.0.27.1)

Does this require the installed model?

No.

Reproduction steps

for i in $(seq 1 20); do Scripts/test.sh --filter connectionsBeyondTheCapAreClosed; done

Expected behavior

The test passes on every run.

Observed behavior

It fails 17 of 20 runs on this machine:

✘ Test connectionsBeyondTheCapAreClosed() recorded an issue at
  ServerIngressHardeningTests.swift:301:6: Caught error: .systemCall("connect", 54)

54 is ECONNRESET, thrown by connectedSocket (Tests/TurboFieldfareServer/HTTPServerTests.swift:907) when the blocking Darwin.connect fails.

It passes on GitHub Actions, so it shows up as a test that is green in CI and mostly red locally rather than as an obvious break.

Cause

Two values disagree:

  • connectionsBeyondTheCapAreClosed opens TurboFieldfareHTTPServer.maximumConnections = 128 sockets in a tight synchronous loop, before it waits for the accept loop at all.
  • TurboFieldfareHTTPServer.start sets .serverChannelOption(ChannelOptions.backlog, value: 16) (Sources/TurboFieldfareServer/Core/HTTPServer.swift:59).

So the test can need up to 128 connections sitting in the listen queue while NIO's accept loop is still draining it, but the queue holds 16. Once it is full, Darwin answers further SYNs with RST rather than queueing them, and the blocking connect() returns ECONNRESET. Whether the accept loop stays ahead of the test's loop is a race, which is why the failure is intermittent rather than constant.

Confirming experiment

Changing only that one line to value: 256 and re-running the same 20 iterations:

ChannelOptions.backlog passes
16 (current) 3 / 20
256 20 / 20

Nothing else was changed, and the line was reverted afterwards.

Possible directions

Not sending a patch, because the right fix depends on what the backlog of 16 is for:

  1. If 16 is deliberate, the test is asserting something the server does not promise. It could pace its connects, or tolerate and retry ECONNRESET while filling up to the cap.
  2. If 16 is incidental, it looks low for a server whose own connection cap is 128 — a real client burst would meet the same RST. kern.ipc.somaxconn is 128 on this machine, so a backlog of 128 would be accepted as requested.

I have not measured whether a backlog of 16 causes trouble for real clients. The only thing established here is that it is what makes this test unstable.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions