Skip to content

uws: fail the build when two flags of HttpResponseData::state have the same bit - #47147

Draft
robobun wants to merge 2 commits into
claude/http-consolidatedfrom
robobun/f75ebd5b/uws-state-distinct-flag-bits
Draft

robobun wants to merge 2 commits into
claude/http-consolidatedfrom
robobun/f75ebd5b/uws-state-distinct-flag-bits

Conversation

@robobun

@robobun robobun commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Behaviour change: none

Draft, stacked on #46528.

Problem

Fix

Background

  • Rust reads the word that each of the three shims returns as one State.
  • C++ cannot list enumerators. The macro pastes the list twice: as the enum, and as struct members that a constexpr function scans.
  • A static_assert over a second hand-written list does not see a flag that a merge adds. A source lint does not block a merge.

Downsides

  • Build time: +79 M compiler instructions (+0.055%) over the 8 translation units that include the header. Object code is identical.
  • A merge that creates a duplicate now breaks the build where it lands. That can be main.
  • Not checked: a flag added later to a copy, and other hand-numbered sets (Notes).
Notes

Why stacked. #46528 rewrites this enum and its three copies, and both changes touch the closing line of the enum. So this PR is cut on top of #46528 and stays a draft until #46528 merges. Then I cut it again on main.

The guard is preventive. No commit on main has two flags on one bit, and no copy ever differed from HttpResponseData (28 and 53 commits checked). The word grew from 8 flags on 2026-06-27 to 24 on 2026-10-21, and #46528 takes it to 29.

Git wrote one duplicate already. Commits 43bce61, 816f734, cda2e43 and a307bdd of the #43557 branch have HTTP_NODE_PEER_ENDED = 1 << 19 (line 162) and HTTP_NODE_NOTIFY_READ_PARSED = 1 << 19 (line 169). 781971d ("node:http: reconcile with main after the rebase") moved HTTP_NODE_PEER_ENDED to 1 << 22 by hand.

Replay: the header of each open PR merged onto this branch (git merge-file, this file only, 21 open PRs change it).

PR Conflict hunks Markers inside the flag list Duplicate when the hunks take this branch's side Duplicate when a hunk keeps both sides
#44321 2 0 bit 26 bit 26
#33871 2 1 bits 23, 24 bits 9, 10, 23, 24
#24912 1 1 none bit 7
#38107, #41602 1, 2 1 none bit 18
#38128 4 2 none bit 23
#38181 3 3 none bits 9, 10, 12, 13, 14
#43850 2 1 none bits 19, 20, 21
#43867 2 1 none bits 19, 20
#44244 2 2 none bit 25
#44732 3 1 none bit 27
10 others 0 to 4 0 none none

#44321 and #33871 leave a duplicate that no conflict marker points at.

Real translation unit (UnifiedSource-src_uws_sys-0.cpp, debug flags, clang 23.1.2). The tree file is swapped, the unit is compiled with -fsyntax-only, and the file is restored.

branch as committed                              0 errors
header of #33871 merged onto the branch          1 error
  packages/bun-uws/src/DistinctFlagBits.h:24:19: error: static assertion failed due to requirement 'noTwoFlagsAre1ShiftedLeftBy(23)': two flags of this enum have the same bit
  packages/bun-uws/src/HttpResponseData.h:129:5: note: in instantiation of template class 'uWS::flagbits::Distinct<23>' requested here
only the flag line of #44321 added               1 error   ...'noTwoFlagsAre1ShiftedLeftBy(26)'...
a second READ_PAUSE flag on bit 1                1 error   ...'noTwoFlagsAre1ShiftedLeftBy(1)'... (HttpResponseData.h:227:5)
Http2ResponseData::HTTP_WROTE_DATE_HEADER = 128  1 error   libuwsockets_h2.cpp:79: requirement '+uWS::Http2ResponseData::HTTP_WROTE_DATE_HEADER == +uWS::HttpResponseData<false, false>::HTTP_WROTE_DATE_HEADER'
Http3ResponseData::HTTP_CONNECTION_CLOSE = 32    1 error   libuwsockets_h3.cpp:104: requirement '+uWS::Http3ResponseData::HTTP_CONNECTION_CLOSE == ...'
HTTP_NODE_RECEIVED_FIN moved to bit 29           1 error   libuwsockets.cpp:1492: requirement 'uWS::HttpResponseData<false, false>::HTTP_NODE_RECEIVED_FIN == 1 << 15'
state widened to uint64_t                        1 error   libuwsockets.cpp:1485: requirement 'sizeof (uWS::HttpResponseData<false, false>::state) == sizeof(unsigned int)'

The whole header of #44321 merged onto the branch gives the same noTwoFlagsAre1ShiftedLeftBy(26) error, plus 11 errors from the rest of that PR, which is not merged.

The macro. The error has the bit, not the two names: find them with a search for << 26. A list without its trailing comma fails with expected ';' at end of declaration list at listEndsWithAComma. An enumerator without an initializer fails with constexpr variable 'everyFlagHasAnInitializer' must be initialized by a constant expression. An intended second name for a bit goes in an enum of its own. The header passes under -std=gnu++23, -std=c++23, -std=c++20 and in clang's MSVC mode (--target=x86_64-pc-windows-msvc -fms-compatibility, and the clang-cl driver, both with stand-in standard headers). CI builds the branch on all 14 build lanes: Linux (x64, aarch64, musl, Android, ASAN, hardened), FreeBSD, macOS and Windows (x64 and aarch64 each).

Cost. User instructions of one clang run per unit (perf_event_open, ASLR off, -fsyntax-only, debug flags, 2 runs, the tree before and after this commit).

Translation unit base this branch delta
bindings.cpp 23,914.1 M 23,902.8 M -11.4 M
jsc_bindings-13 17,380.0 M 17,387.8 M +7.8 M
jsc_bindings-17 19,734.6 M 19,735.0 M +0.4 M
jsc_bindings-22 18,213.0 M 18,212.4 M -0.6 M
jsc_bindings-3 14,860.4 M 14,867.8 M +7.4 M
jsc_bindings-7 25,823.1 M 25,827.1 M +4.0 M
jsc_bindings_node-0 15,101.4 M 15,114.8 M +13.5 M
uws_sys-0 10,171.4 M 10,229.6 M +58.2 M
total 145,197.9 M 145,277.2 M +79.3 M (+0.055%)

Two runs of one unit differ by at most 0.04 M. A comment line added to the header moves the total by 0.07 M. The uws unit is near a step: one empty function template in an otherwise empty new header costs +34 M there, and between -1 M and +6 M in two other units.

Object code: the 8 units compiled at -O2 -g0 without sanitizers, for this branch and for this branch with the guard taken out and every line number kept. All 8 objects have the same sha256.

Conflicts. git merge-file of every open PR that changes one of the five existing files, this branch against #46528 alone: 0 new conflict hunks (21 PRs for HttpResponseData.h, 61 for libuwsockets.cpp, 8 and 17 for the HTTP/2 and HTTP/3 shims, 29 for Response.rs).

Not covered.

  • A flag added later to Http2ResponseData or Http3ResponseData: no assert line lists it. Both words are full today (8 flags in a uint8_t).
  • A constant added later to the Rust State, and a change of a Rust value alone: the asserts see the C++ side.
  • A flag on a bit of a mask whose members are not single bits. HTTP_BODY_CHUNKED and HTTP_BODY_UNTIL_CLOSE are single bits, so bits 9 and 10 are taken.
  • Other hand-numbered sets: LIBUS_LISTEN_* in packages/bun-usockets/src/libusockets.h (a C header, repeated in src/uws_sys/lib.rs), packages/bun-uws/src/WebSocketProtocol.h, and Rust bitflags! sets (Add bun_core::assert_distinct_flag_bits!, a compile-time check that no two flags of a set share a bit #47097).

Other designs.

  • A for-each macro that names both flags in the error: 46 lines and 17 macros, against one #define here.
  • An X-macro list that generates the enum: it rewrites every flag line, so each open PR that adds a flag gains a conflict (7 of 23 when measured on main).
  • The HTTP/2 and HTTP/3 enumerators taken from HttpResponseData<false>: that instantiates the class template in units that did not need it (+34 M instructions in one unit when measured on main).
  • A check in the build graph that parses the four files, or one table that generates the C++ enum and the Rust set: new build machinery for one word.

Tests run on the debug build: test/js/bun/http/http-server-chunking.test.ts (25 pass), node-http-pipeline-callbacks.test.ts (16 pass), node-http-halfclose-midupload.test.ts (2 pass), bun-server.test.ts (96 pass, 1 skip with --timeout 120000. With the default 5 s timeout, 7 to 9 tests that start a child process time out on my machine). clang-format and rustfmt report no change.


no test proof · iteration 1 · the description declares no behaviour change, so there is no failing test to prove; the existing suite in CI is the check

…same bit

UWS_DISTINCT_FLAG_BITS declares the two flag enums of HttpResponseData.
The build fails when two of their single-bit values are equal.

Static asserts beside uws_res_state, uws_h2_res_state and uws_h3_res_state
compare the HTTP/2 and HTTP/3 copies of the flags with HttpResponseData,
and pin the values and the width that the Rust State repeats.
@robobun

robobun commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 6:23 AM PT - Oct 29th, 2026

❌ @robobun, your commit d989656 has 3 failures in Build #135054 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 47147

That installs a local version of the PR into your bun-47147 executable, so you can run:

bun-47147 --bun

@robobun

robobun commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: draft, stacked on #46528. It stays a draft until #46528 merges. Then I cut it again on main.

How I reproduced it (claude/http-consolidated at 5e76035): git merge-file -p of packages/bun-uws/src/HttpResponseData.h with the header of #44321 leaves HTTP_NODE_CLOSE_BY_DEFAULT and HTTP_NODE_SHUTDOWN_AFTER_DRAIN on 1 << 26, with no conflict marker in the flag list. Without this change the uws translation unit compiles that with 0 errors. On this branch it fails: static assertion failed due to requirement 'noTwoFlagsAre1ShiftedLeftBy(26)': two flags of this enum have the same bit.

Platforms: CI builds the change on all 14 build lanes: Linux (x64, aarch64, musl, Android, ASAN, hardened), FreeBSD, macOS and Windows (x64 and aarch64 each).

CI (build 135054): no failure comes from this diff. Three tests are red, and each is red without this change:

  • test/aot/engine/ir-6.test.ts: also red on main (builds 135376 and 135526).
  • test/aot/parallel/async/for-await-releases-values/main.ts: also red in build 134988 of the base branch at the same commit.
  • test/js/bun/gc/a-failed-stream-is-collected.test.ts (macOS aarch64): also red in builds 135042 and 135542 of the base branch.

PR: #47147

…obobun/f75ebd5b/uws-state-distinct-flag-bits

This branch has not been deployed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant