Skip to content

feat(rpc): add webzockets dependency for json rpc websockets#1222

Open
jbuckmccready wants to merge 49 commits intomainfrom
jbuckmccready/webzockets
Open

feat(rpc): add webzockets dependency for json rpc websockets#1222
jbuckmccready wants to merge 49 commits intomainfrom
jbuckmccready/webzockets

Conversation

@jbuckmccready
Copy link

@jbuckmccready jbuckmccready commented Feb 9, 2026

Summary

Adds webzockets package to path src/rpc/webzockets. webzockets implements websockets in zig, built on top of libxev for network IO. See src/rpc/webzockets/README.md for details on the package itself.

This is only adds a websocket implementation needed to implement the json RPC websocket API. The json RPC API will be in a different PR.

Changes

Updated the libxev version used by sig in build.zig.zon to new commit (current HEAD of zig-0.14 branch): Syndica/libxev@11a00ee
This commit is on top of a slightly later version of libxev than we were previously using that is still compatible with zig 0.14.1, and the commit fixes error returns for the kqueue backend (associated pull request upstream: mitchellh/libxev#207).

Updated build.zig and build.zig.zon with webzockets dependency, and added import to src/rpc/lib.zig so webzockets is part of the build.

Ran docs/generate.py so webzockets README.md is included in the published docs.

Updated scripts/style.py to skip cache dirs.

Update 2026-02-13:

Fixed another bug in libxev where io_uring gave unexpected error on shutdown when socket already closed.
Upstream PR: mitchellh/libxev#211
Syndica fork commit: Syndica/libxev@c16162f
NOTE: for kqueue/epoll backends it could just issue std.posix.shutdown directly to avoid the event loop processing entirely, but io_uring backend actually queues the shutdown command to avoid a syscall.

Update 2026-02-14:

Added read pause/resume (0028053)

  • Added heuristic for compaction to avoid small TCP reads (e.g., want to avoid a full TCP read cycle through IO loop if there is only 46 bytes remaining in the read buffer but compaction would free 8KB).
  • Added ability for connections to pause reads to exert back pressure on the TCP connection. This is helpful for future jrpc layer to allow server handlers to not have any queue of received messages by having the control flow: receive sub/unsub request from client -> pause reads -> send sub/unsub response -> write complete -> resume reads.

Removed BufferPool (51641cc), it will not be used by the jrpc server (embedded read buffer will cover sub/unsub requests) and adds complexity.

Update 2026-02-19:

Optimized close initiated by caller to not wait for handshake for the close frame response (not required by websockets RFC) and simplified logic a bit for closing. Commit: 19cf287

Update 2026-02-22:

Added sendRaw to api to allow the caller to prepare the web socket frames (header + payload), this avoids the 2 sends per message required for zero-copy use (one for header, one for payload) without having to change the backend to support vectored writes. Jrpc broadcasting benchmarks show about 50% increase in performance with this small change.

Note even with vectored writes this API is useful as it allows the caller to batch an arbitrary number of websocket messages into a single TCP write buffer, avoids creating the frame on the IO loop, and can support the caller performing per-message deflate compression in the future (allowing for shared broadcasting across web socket clients utilizing per-message deflate).

Update 2026-02-24:

Fix missing timeout on handshake upgrade: 3a7db8f
Needed otherwise TCP socket can stay open indefinitely and not release associated resources.

@github-project-automation github-project-automation bot moved this to 🏗 In progress in Sig Feb 9, 2026
@jbuckmccready jbuckmccready self-assigned this Feb 9, 2026
@jbuckmccready jbuckmccready requested a review from kprotty February 9, 2026 18:36
@jbuckmccready jbuckmccready force-pushed the jbuckmccready/webzockets branch from 87e052b to 35e2914 Compare February 9, 2026 18:40
- Add webzockets as a local path dependency in build.zig.zon
- Wire up webzockets module in build.zig
- Re-export webzockets module from src/rpc/lib.zig to include in build
- Update libxev dependency with fix for error returns (compatible with
zig 0.14):
Syndica/libxev@11a00ee
@codecov
Copy link

codecov bot commented Feb 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
see 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Not particularly useful and takes up a lot of space in the README.md.
- Define test_webzockets job for Linux environment
- Define test_webzockets_macos job for macOS environment
- Update workflows to run webzockets tests on both platforms
- add RawClient wait helpers for message type, close frames, and disconnects
- make read timeout configurable and default to 2s to reduce jitter flakes
- update close, fragmentation, handshake, and timeout tests to use bounded waits
Comment on lines 11 to 15
/// Manages read buffer state with automatic tier escalation:
/// - Starts with an embedded buffer for small messages
/// - Upgrades to pooled or dynamic buffer when needed
/// - Retains larger buffer for subsequent messages (performance optimization)
/// - Restores to embedded only on explicit reset()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not convinced that we need any of this

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ties in with #1222 (comment)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed buffer pool, commit: 51641cc

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would tend to agree with the original comment here, I don't quite understand why we need this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments have been cleaned up. We need a read buffer of some kind, what is the suggested change here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the merge of the 0.15 update, I think it would be worth looking at trying to use std.Io.Reader instead, and simplifying some of the requirements. My gut instinct is that most of what we actually need can be expressed through that interface.

@jbuckmccready jbuckmccready force-pushed the jbuckmccready/webzockets branch from 83cd274 to 96a343e Compare February 11, 2026 01:18
Also removes need for pool_buffer_size field in Reader.
- remove pending_data from client and server write state
- add outstandingUserWrite helper for busy, defer, and cleanup checks
- clear header_len in finishWrite before invoking onWriteComplete
Update libxev dependency hash in both root and webzockets build.zig.zon
…ertNoLeaks with check()

- Rename fd_leak.zig to fd_leak_detector.zig; import is now the module
directly
- Replace assertNoLeaks() (panics inline) with check() returning a
Result enum
- Call sites use: defer std.testing.expect(fd_check.check() == .ok)
catch @Panic("FD leak")
- Leak details logged via std.log.err instead of embedded in panic
message
- Replace manual crypto random seed with std.testing.random_seed in
stress test
- Replace std.crypto.random.bytes with seeded PRNG in raw_client
WebSocket key generation
- Remove redundant default_read_buf_size constant, inline the value
- Remove unnecessary @intcast on timeval fields
…G in e2e clients

- Add RawClient.rng field seeded at connect time; use it for mask key
generation
- Seed TestEnv ClientMaskPRNG from std.testing.random_seed instead of
std.crypto.random
- Change loop and server fields from pointers to inline values
- Remove heap allocations and matching destroy calls for both fields
@jbuckmccready jbuckmccready force-pushed the jbuckmccready/webzockets branch from 231cbaf to f463465 Compare February 21, 2026 06:33
- Remove comptime frameStatic and calculateFrameLen utilities with all associated tests
- Extract anonymous fuzz test structs into named helpers for clarity
…d struct and @Splat

- Replace required-header bitmask constants with FoundRequired packed struct in http.zig
- Replace "x" ** N string literal patterns with typed [N]u8 = @Splat(...) in tests
- Add sendRaw() to both server and client connections for sending
  pre-built frame data as a single write with no validation
- Use header_len=1 as sentinel to distinguish raw from normal deferred
  writes (1 is impossible for real frame headers)
- Add server and client e2e tests for text, binary, 16-bit length,
  and batched raw sends
- Update README with sendRaw API and buffer lifetime
Also avoid panic in tests so they continue running and just std.log.err
for failures.
Test runner code for Zig 0.14.1 indicates it should reset
std.testing.log_level between each test but the testing level holds
across all tests (bug). Removed comments for behavior that may
change/break in the future.
- Add upgrade_timeout_ms config to client and server with 10_000ms default
- Enforce absolute handshake deadlines in client/server handshake state machines
- Cancel active I/O safely before terminal callbacks to avoid completion races
- Add e2e coverage for stalled and partial upgrade responses and update docs
@jbuckmccready jbuckmccready force-pushed the jbuckmccready/webzockets branch 2 times, most recently from 7e0691f to 588472a Compare February 25, 2026 17:30
@jbuckmccready jbuckmccready force-pushed the jbuckmccready/webzockets branch from 588472a to 008a8ff Compare February 25, 2026 18:06
- std.DoublyLinkedList is no longer generic; use @fieldParentPtr to recover connection from node
- std.time.sleep moved to std.Thread.sleep
- std.net.Address format specifier {} is now ambiguous; use {f}
- std.ArrayList drops stored allocator; init() removed, use .empty; deinit/append now take allocator
@jbuckmccready jbuckmccready force-pushed the jbuckmccready/webzockets branch from 1f8b9da to 2af680e Compare February 25, 2026 18:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 👀 In review

Development

Successfully merging this pull request may close these issues.

3 participants