Skip to content

fix(browser-relay): unhang zig build test (#29)#30

Merged
mattmezza merged 3 commits intomainfrom
fix/browser-relay-test-hang-29
Apr 11, 2026
Merged

fix(browser-relay): unhang zig build test (#29)#30
mattmezza merged 3 commits intomainfrom
fix/browser-relay-test-hang-29

Conversation

@mattmezza
Copy link
Copy Markdown
Owner

@mattmezza mattmezza commented Apr 11, 2026

Summary

Real root cause of #29 was not the local IPv6/localhost resolution I initially suspected — it was that dsn_auth_test/cors_test's sendRequest helpers built POST requests with no Content-Length header. In Zig 0.15 std.http.Server, bodyReader for a body-bearing method without Content-Length or Transfer-Encoding falls back to the raw connection reader (see std/http.zig bodyReader, .none branch). The first test in each file that passes auth then calls readSliceShort on the body, which blocks indefinitely waiting for client bytes that never arrive — meanwhile the test client is blocked reading the response. Classic deadlock.

Tests 1–4 in each file all reject at auth before touching the body, so they passed. Test 5 onward (the first valid-auth POST) deadlocked, which is exactly where local and CI runs hung. forward_test was unaffected because it uses sendRequestWithBody, which already sets Content-Length.

CI was hanging on main too — recent runs were all cancelled with multi-hour durations, the issue's "CI is green" claim referred to a clean local container only.

Changes

  • browser-relay/src/dsn_auth_test.zig + browser-relay/src/cors_test.zig: add Content-Length: 0 to the sendRequest request format strings. This is the actual fix.
  • browser-relay/src/{forward,dsn_auth,cors}_test.zig (TestServer hardening): defensive shutdown for TestServerstop atomic + self-connect in waitAndDeinit so any future request-count mismatch fails fast instead of hanging on accept(). Not strictly required to fix browser-relay: zig build test hangs locally (pre-existing) #29, but prevents the next foot-gun in the same harness.
  • browser-relay/src/{dsn_auth,cors}_test.zig (URL change): switch upstream URLs from http://localhost:5010/5012 to http://127.0.0.1:19999/19998. Removes a real-but-secondary fragility (real services running on 5010/5012 on a dev box would have changed the test outcome) and matches forward_test.

Test plan

  • cd browser-relay && API_KEY=x ADMIN_API_KEY=y zig build test — exit 0 in ~30s
  • All 14 test binaries pass: dsn_auth_test 123/123, cors_test 118/118, forward_test 113/113, plus 11 others
  • CI green

Closes #29

🤖 Generated with Claude Code

mattmezza and others added 3 commits April 11, 2026 23:02
dsn_auth_test and cors_test pointed forwarder upstreams at
http://localhost:5010/5012. On dev boxes where ::1 is in /etc/hosts
but IPv6 SYNs are dropped (instead of refused), std.http.Client stalls
indefinitely on the IPv6 attempt, causing zig build test to hang.
forward_test already uses 127.0.0.1 literals and works fine.

Switch the two affected files to 127.0.0.1 with high unused ports so
the connect fails fast with ECONNREFUSED, matching forward_test.

Refs #29

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously TestServer.waitAndDeinit unconditionally joined the
acceptLoop thread. If a test sent fewer requests than expected
(or any future bug left the server waiting on a missing client),
accept() would block forever and the entire `zig build test` run
would hang with no error.

Add a stop atomic flag and have waitAndDeinit signal it, then
self-connect once to unblock any pending accept(). The accept loop
checks the flag before and after accept() and exits cleanly. The
happy path (all expected requests received) is unaffected since
the loop has already exited by the time waitAndDeinit runs; the
self-connect just lands in the listen backlog and is discarded
when the server is deinitialized.

Refs #29

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Real root cause of #29: the dsn_auth_test/cors_test sendRequest helpers
build POST/PUT/PATCH requests with no Content-Length header. In Zig
0.15's std.http.Server, when a body-bearing method has neither
Content-Length nor Transfer-Encoding, bodyReader falls back to the raw
connection stream (http.zig bodyReader, .none branch). The first test
that successfully passes auth then calls reader.readSliceShort which
blocks indefinitely waiting for client bytes that never come — the
client is meanwhile blocked reading the response. Classic deadlock.

Tests 1-4 in each file did not hit this because they reject at auth
before reading the body. Test 5 onward (the first valid-auth POST)
was the first to deadlock, which is exactly where local + CI runs hung.

forward_test was unaffected because it uses sendRequestWithBody which
already sets Content-Length.

Add Content-Length: 0 to the request format strings in both helpers.
All 14 browser-relay test binaries now pass under `zig build test`
(123 + 118 + 113 + 11 others tests, total ~30s).

Refs #29

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mattmezza mattmezza merged commit f4b3cc1 into main Apr 11, 2026
4 checks passed
@mattmezza mattmezza deleted the fix/browser-relay-test-hang-29 branch April 11, 2026 22: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.

browser-relay: zig build test hangs locally (pre-existing)

1 participant