Repository navigation
Conversation
|
Since last review: 0 resolved, 3 still open, 0 new. PR #7639 updates server logging and run paths; the existing warnings remain. Not re-run: api-compat, jsg-gc, kj-style (no author changes in their files since the last review) Reviewed commit: f9e1c1c2 · github run |
098b0ed to
a027f27
Compare
| } | ||
| // SAFETY: borrowed for the duplication only; the C runtime keeps owning the handle. | ||
| let inherited = unsafe { BorrowedHandle::borrow_raw(handle as RawHandle) }; | ||
| inherited.try_clone_to_owned().map(std::fs::File::from) |
There was a problem hiding this comment.
[WARNING] The Windows --control-fd implementation has no platform test: socket_fd.rs selects this module on Windows, but only unix.rs declares a test module. This new C-runtime-fd-to-HANDLE conversion is therefore never exercised on Windows, so regressions in descriptor ownership or control-event writes will go undetected. Add a Windows regression test that creates a CRT pipe descriptor, writes through the returned duplicate, and verifies that the original descriptor remains usable.
7f95fda to
3bde8c5
Compare
4159b41 to
159bd71
Compare
3bde8c5 to
4be1cdd
Compare
159bd71 to
f17efcc
Compare
4be1cdd to
7872d99
Compare
f17efcc to
d15887c
Compare
7872d99 to
a72f2f0
Compare
d15887c to
fdf033d
Compare
a72f2f0 to
7857393
Compare
guybedford
left a comment
There was a problem hiding this comment.
Checked out 78573938 on Linux x86_64. //src/workerd/server/... passes (the 38 default-size targets plus server-test@, workerd-server_test, workerd-cli_test, rust-io-link-check, inspector, module-fallback, structured-logging, socket-close and both UDP e2e tests run explicitly with --test_size_filters=), and //src/workerd/api/tests/... //src/workerd/api/node/tests/... //src/workerd/io/... //src/workerd/jsg/... passes 1258 of 1261 under the new binary. The three failures are one real regression, inline on services/mod.rs: the default internet service can no longer verify a TLS chain through a P-521 CA, which kj-http verified. It would not show on a macOS run.
One low note inline on listen/mod.rs (the capnp-over-CONNECT tunnel without its hang-up), and one observation for the follow-up, not a request here: header_table borrows the factory per call, which is what forces a kj-hyper Client<'t> per event and so the known no-connection-reuse gap; if the Factory owned the header table separately from the WorkerFactory, the client could live in the service.
I agree with the three parity-by-design replies to the earlier blocking comments.
fdf033d to
5e47c8a
Compare
7857393 to
fd1bb36
Compare
5e47c8a to
d9de001
Compare
fd1bb36 to
0e0bf29
Compare
d9de001 to
179fb93
Compare
0e0bf29 to
88ed7c3
Compare
179fb93 to
5fd5152
Compare
88ed7c3 to
2d26895
Compare
| // TODO(soon): start the Worker from the snapshot artifact. | ||
| (void)artifact; |
There was a problem hiding this comment.
[WARNING] The zygote executes the module's top-level code, but the extracted artifact is discarded and the real isolate receives the still-empty snapshotConfig. With STARTUP_SNAPSHOT enabled, every eligible worker therefore evaluates global code twice instead of booting from the snapshot.
| // TODO(soon): start the Worker from the snapshot artifact. | |
| (void)artifact; | |
| snapshotConfig = jsg::SnapshotConfig( | |
| jsg::FinalizedSnapshot{.artifact = artifact->addRef()}); |
5fd5152 to
1a44543
Compare
2d26895 to
fe90d55
Compare
| impl Disk { | ||
| /// The path below the root the segments name; the segments passed validation. | ||
| fn join(&self, segments: &[String]) -> PathBuf { | ||
| segments |
There was a problem hiding this comment.
[WARNING] join() produces a host path, and all subsequent std::fs operations follow symlinks. A configured disk directory containing escape -> /etc therefore lets an inbound GET /escape/passwd read outside the configured service directory; writable PUT requests can similarly traverse a symlinked parent. The prior implementation retained a kj::Directory and performed its tryOpenFile, replaceFile, and tryRemove operations relative to that directory. Keep the root as a directory capability and resolve request segments through it rather than converting them to PathBuf paths.
fe90d55 to
25e7081
Compare
25e7081 to
481aab0
Compare
`server.c++`, `server.h` and `cli-main.{h,c++}` are deleted. The `workerd`
binary is the Rust command line (`cli/`, `workerd-cli`) over the new
`workerd-server` crate (`server/`), which drives a C++ `WorkerFactory`
(`factory/`) through one cxx bridge (`server/bridge.rs`), the one module
of the crate that allows `unsafe`. The bridge's async functions that
borrow their arguments are declared `async unsafe fn f<'a>`, the form the
in-tree cxx requires of a future that is not `'static`.
The factory keeps everything that touches the isolate: compiling a worker,
starting a request on it, constructing actors with their storage, alarms
and containers, the capnp RPC servers, channel-token encoding, the
inspector, and the single-tenant policy objects. The server owns the rest:
config interpretation, bindings and channel numbering, services,
listeners, actor lifecycle and eviction, dynamic workers, drain and the
test runner. A worker's bindings cross as one message: the server
interprets `Worker.bindings` and `ctx.exports` into `Globals`
(`compiled-bindings.capnp`), every capability a channel number, and
`WorkerdApi::compileGlobals` reads that message in place of the removed
`WorkerdApi::Global` struct. `server/entry.rs` owns the process's schedule:
`factory/bootstrap.c++` sets up logging around the command, a
`kj_rs_tokio::Runtime` builds the KJ loop with its tokio runtime, the
bootstrap sets up perfetto, autogates and V8 on it and builds the factory,
and the command blocks on the server as a task on that loop. Sockets are bound
and external servers dialed through kj-rs-io's Rust API, and HTTP in both
directions is hyper through kj-hyper; this is the first use of kj-hyper in
the workerd binary. src/workerd/server/AGENTS.md has the design in detail.
Parity is shown by `server-test.c++`, which is kept and runs the Rust
server inside the test process. Each `TestServer` builds a `WorkerFactory`
on the test's V8, mock timer, network and a temp directory and hands it to
`InProcessServer` (`server/in_process.rs`). The config's socket and
external addresses become `loopback:` names the test connects to and
accepts on, so no connection but UDP's leaves the process. An unexpected
error log, config error or warning fails a case, as does a factory still
referenced once the server is closed. main has 109 cases and this tree
111 (3 are Linux-only in both): 1 moved, 3 added, 9 changed, and every
other body identical.
- Moved `UDP listener drops truncated datagrams` to a unit test in
`server/listen/udp-test.rs`: only main's mock port could deliver a
truncated datagram to a whole listener, and a real socket cannot overfill
its 65,535-byte buffer, so the test receives into a 4-byte buffer.
- Added `a durableObjectClass binding without props can be sent over RPC`:
a configured binding without props is not a `ctx.exports` template.
- Added `Workflow engine configuration is checked field by field`: new
coverage of the `workflowsEngine` config errors.
- Added `a service that fails to start does not leak the services before
it`: the factory is unreferenced after a failed start.
- `Durable Objects (on disk)`: shared storage is a real temp directory
symlinked into the test root (`newSharedDirectory()`), since the server
opens real files; it holds 9 files, not 6 (`-shm`).
- `Durable Object alarm persistence (on disk)`, `Durable Object evictions
when callback scheduled`, `Durable Object facets` and `Durable Object
facet cloning` use the same shared directory.
- `Durable Objects websocket constructor blockConcurrencyWhile throws
after send` reads the `pending` frame before EOF, because the in-memory
connection is buffered.
- `network outbound with allow/deny`: the lists are CIDRs, because the
server parses the filter.
- `disk service` sets modification times with `test.setModified` where
main assigned `test.fakeDate`, because real files take the real time.
- `disk service allow dotfiles` sets `.dot`'s time the same way, and
expects 204, not 403, for `PUT /%2e%2e/secret` (disk service URLs,
below).
- Harness: `receiveSubrequest` no longer asserts a `network` service's
peer filter, since the service dials the loopback registry first; four
unit tests in `server/services/network-test.rs` cover the filter.
In the end-to-end tests, `tests/server-harness.mjs` splits the
`--control-fd` stream on newlines before parsing, since one read may carry
several events or part of one, and the three UDP configs bind
`127.0.0.1:0` in place of `*:0`, which their `udp4` clients reach inside
Bazel's macOS sandbox.
Behaviour changes against main's C++ server follow.
Process and command line:
- Every config error is reported and every worker compiled, then exit 1;
main exited at the first error.
- A refused config writes no `listen` event to `--control-fd`; main wrote
the earlier sockets'.
- A socket that cannot be bound is a config error (`Socket "main":
bind(): ...`); main died with `*** Fatal uncaught kj::Exception`.
- Structured logging keeps its two streams: worker consoles and KJ logs
are JSON lines on stdout, and the command line's own messages (config
errors and warnings, `Tests failed!`) are JSON lines on stderr. What
differs: the run failure (`*** Uncaught exception ***`) is a JSON line
on stderr too (main: plain text), and the stderr lines carry `source`
`src/workerd/server/server/entry.rs:133` (raw `file!()`, with a `src/`
prefix no other log line has) where main's named `json-logger.c++`.
- An error that passed through Rust prints as `file:line: type:
description` without `stack:`; one made in Rust has the bridge's
`file:line`.
- A failure before the server runs (a bad V8 flag) prints its description
only: no `file:line`, type or stack.
- V8 is torn down on every exit (main: only with `KJ_CLEAN_SHUTDOWN`), and
the perfetto session on the config-error exit too.
- `workerd test` filters are `glob` crate patterns over the whole name:
`[...]` and `**` are special, `*` crosses `/`, a suffix after `/` no
longer matches, and a bad pattern is a run failure.
- `[ PASS ]` / `[ FAIL ]` durations are Rust's `Duration` debug form
(`3.520417ms`, the micro sign as U+00B5); main printed `5.708ms` and
U+03BC.
- `analyticsEngine` and `unsafeEval` bindings without `--experimental` are
left out of `env` with the shared wording; main kept the
`analyticsEngine` binding.
- `--perfetto-trace`: of main's 33 server-layer `TRACE_EVENT`s three
remain, in the factory (`Bootstrap()`, `worker_start_request()`,
`factory_new_worker()`); the other 30 (startup and drain phases,
bindings, listeners, services, requests) are gone.
- Windows: stdio is not put in binary mode.
HTTP on the config's sockets and to `external` and `network` services is
hyper in place of kj-http; the inspector, the fallback service, the
container client and the Pyodide bundle download (`pyodide.c++`, with
kj's TLS) keep kj-http. The rule applied: hyper's behaviour is accepted
where hyper conforms to the RFC, where both conform and where both
deviate; hyper is patched where only kj conformed, and the explanation
body on a parse-error response (in kj-hyper) is the one such patch.
- The connection closes after a request with `Connection: close` or
HTTP/1.0 without keep-alive.
- An HTTP/1.0 request gets an HTTP/1.0 status line and an unchunked body
ended by close.
- `Content-Length` with `Transfer-Encoding` is read as chunked, with
`Content-Length` hidden, and the connection then closes.
- `400` for an invalid or differing `Content-Length` and a
`Transfer-Encoding` not ending in `chunked` (main: 500), for
`Transfer-Encoding` on HTTP/1.0 and an unknown version (main: served),
and for a garbage request line (main: 501).
- Accepted where main answered 500 or 501: identical duplicate
`Content-Length`, `Transfer-Encoding: gzip, chunked`, absolute-form
targets, `OPTIONS *`, a leading empty line.
- HEAD of a streamed response omits `Transfer-Encoding`; a header's
repeated values are written adjacent.
- An unsupported or missing `Sec-WebSocket-Version` gets `426` with
`Sec-WebSocket-Version: 13` (main: 400); a WebSocket handshake by POST
gets `400` (main: 500).
- Unparsable requests get `400`, `414` or `431` with hyper's description
as the body; a target over 65,534 bytes is `414` (main served 70,000).
- Head limit: main refused at 128 KiB; hyper's is nominally 417,792 bytes
(500,000 accepted, 520,000 refused), and more than 16,384 headers is
`431`.
- An idle keep-alive connection closes after 15 s (main: 5 s).
- A GET or HEAD request body of unknown length is not forwarded upstream
(main sent it chunked).
Outbound, TLS, disk, config:
- `external` and `network` services open a connection per subrequest (a
service builds its kj-hyper client per event); main reused connections.
- An external server's address is parsed and resolved at each dial; main
resolved once at startup.
- A `network` service resolves with tokio's `lookup_host` (no
`AI_ADDRCONFIG` / `AI_V4MAPPED`) and dials IP addresses only.
- `unix` / `unix-abstract` in a network service's `allow` or `deny` do
nothing; on main `connect({hostname: "unix:sock", port: 1})` reached a
unix socket named `sock:1` (names with `/` are refused on both).
- TLS is rustls: `cipherList` is ignored (main failed startup on an
unusable list), and a `minVersion` below TLS 1.2 means 1.2.
- `requireClientCerts` with `trustBrowserCas` is refused at startup, as a
run failure; main accepted it.
- Disk service URLs go through the `url` crate: `.` and `..` resolve even
percent-encoded (`PUT /%2e%2e/secret` is 204 inside the directory; main:
403), `\` separates segments, and `GET /sub//f.txt` is 404 (main: 200).
- CryptoKey `pkcs8` / `spki` PEM is read by rustls' parser, not main's
`decodePem`.
- A `fromEnvironment` binding whose value is not UTF-8 is converted
lossily; main passed the bytes through.
Outside src/workerd/server:
- kj-rs-io loses what only `cli-main.c++` called: `loopback.rs`
(`TokioNetwork::enableLoopback()`, `AddressKind::Loopback`) and
`signal.rs` (`kj_rs_io::onSignal`), with their tests. The server keeps
its own `loopback:` registry (`server/listen/loopback.rs`, over
`tokio::io::duplex`) and takes SIGTERM from `tokio::signal`. In exchange
the Rust address and listener API is public, for a caller that serves
tokio's sockets itself: `TokioAddress::parse_str`, `listen`,
`connect_first` and `bind_udp`, `TokioListener::accept` and `port`,
`wrap_listener` for an inherited socket, the `Socket` they hand back,
and `KjIoError` into `std::io::Error`.
- `build/wd_rust_crate.bzl` gains `cxx_bridge_visibility`, so that the
factory's C++, in another package, can include the server crate's
bridge header.
- `deps/rust/Cargo.toml` adds `httpdate`, `percent-encoding` and `url`
(the disk service), `data-encoding` (CryptoKey bindings), `glob` (test
filters) and `ipnet` (`allow` / `deny` ranges).
- `clippy.toml` adds `SQLite` and `WebSockets` to `doc-valid-idents`, for
the new doc comments.
- Comments and docs that named the deleted files name their replacements:
`src/workerd/api/restore.c++`, `src/workerd/util/setup-async-io*`,
`docs/jsg.md`, `docs/reference/detail/new-module-registry.md`, the
AGENTS.md files, bonk's rust-first paths, and `just
test-compile-flags`, which now checks `factory/worker-factory.c++`.
Known gaps:
- Outbound connections are not reused (above): three fetches through an
`external` service open three connections where main opened one.
- The config inputs that are accepted and ignored, above (`cipherList`, a
low `minVersion`, `unix` / `unix-abstract` filter entries), produce no
config error or warning.
- `StructuredLoggingProcessContext` (`json-logger.h`) has no user left
but its three cases in `json-logger-test.c++`.
- Three comments still name the deleted server:
`src/workerd/server/log-schema.capnp`, `src/workerd/api/sockets.h` and
`src/workerd/api/tests/worker-loader-test.js`.
What has run: `bazel test --test_size_filters= //src/...` on macOS arm64
only, with 2,132 passing, 8 skipped and 8 failing. Six are
`container-client` (`testPidNamespace` and `testSetEgressHttp`, against a
hand-built substitute image) and `worker-loader-test`'s four Python cases
(a certificate error fetching the Pyodide bundle, under main's server
too), three variants each. The other two (`http-nodejs-test` and
`http-client-cpp`, one variant each) failed on timing while the machine
was loaded and pass in 5 reruns on their own. Not run: anything
on Linux or Windows, which leaves the three `#if __linux__` cases,
`unix-abstract:` addresses, the `--socket-fd` listening check and
fuzzilli to CI; clang-tidy on `factory/*`; `@gc-stress`, ASAN, TSAN and
coverage, including the `KJ_CLEAN_SHUTDOWN` exit path; CryptoKey PEM on
malformed input; and miniflare against this binary.
Size, by `wc -l` without the `-test.rs` files: the server crate is 9,779
lines, the factory 3,821 and `compiled-bindings.capnp` 107, together
13,707 against the 9,193 they replace (`server.c++`, `server.h`,
`cli-main.{h,c++}`): 1.49x. The crate's unit tests are another 1,412
lines in eight `-test.rs` files.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
481aab0 to
f9e1c1c
Compare
The last PR of the Rust-server split: workerd's server in Rust. It is stacked on #7535 (kj-hyper): this PR's base is that branch, so its diff is the server commit alone. It runs on the
kj_rs_tokio::Runtimefrom #7625, already in main. It does not depend on #7533: the bridge's borrowing async functions use theasync unsafe fn f<'a>form that main's cxx accepts. #7540 (the worker crate pieces it uses) is already in main.What it does
server.c++,server.handcli-main.{h,c++}are deleted. Theworkerdbinary is the Rust command line (cli/,workerd-cli) over the newworkerd-servercrate (server/), which drives a C++WorkerFactory(factory/) through one cxx bridge (server/bridge.rs), the one module of the crate that allowsunsafe. The bridge's async functions that borrow their arguments are declaredasync unsafe fn f<'a>, the form the in-tree cxx requires of a future that is not'static.The factory keeps everything that touches the isolate: compiling a worker, starting a request on it, constructing actors with their storage, alarms and containers, the capnp RPC servers, channel-token encoding, the inspector, and the single-tenant policy objects. The server owns the rest: config interpretation, bindings and channel numbering, services, listeners, actor lifecycle and eviction, dynamic workers, drain and the test runner. A worker's bindings cross as one message: the server interprets
Worker.bindingsandctx.exportsintoGlobals(compiled-bindings.capnp), every capability a channel number, andWorkerdApi::compileGlobalsreads that message in place of the removedWorkerdApi::Globalstruct.server/entry.rsowns the process's schedule:factory/bootstrap.c++sets up logging around the command, akj_rs_tokio::Runtimebuilds the KJ loop with its tokio runtime, the bootstrap sets up perfetto, autogates and V8 on it and builds the factory, and the command blocks on the server as a task on that loop. Sockets are bound and external servers dialed through kj-rs-io's Rust API, and HTTP in both directions is hyper through kj-hyper; this is the first use of kj-hyper in the workerd binary. src/workerd/server/AGENTS.md has the design in detail.Reading order
src/workerd/server/AGENTS.md: the design, the split between server and factory, and the rules each side keeps.server/bridge.rs: the server crate's whole Rust/C++ surface in one file, both directions.server/config.rsandserver/bindings.rswithcompiled-bindings.capnp: how a config becomes services and a worker's bindings.server/worker.rs,server/actor.rs,server/loader.rs,server/channels.rs: workers, Durable Objects, dynamic workers and the channel objects the factory calls back into.server/listen/andserver/services/: sockets, HTTP, UDP, and theexternal,networkanddiskservices.server/run.rs,server/entry.rs,server/in_process.rsandcli/: startup, drain,workerd test, and the test shim.factory/: the C++ that stays.server-test.c++: theTestServerharness; all but nine of the test bodies are main's (listed below).Parity:
server-test.c++Parity is shown by
server-test.c++, which is kept and runs the Rust server inside the test process. EachTestServerbuilds aWorkerFactoryon the test's V8, mock timer, network and a temp directory and hands it toInProcessServer(server/in_process.rs). The config's socket and external addresses becomeloopback:names the test connects to and accepts on, so no connection but UDP's leaves the process. An unexpected error log, config error or warning fails a case, as does a factory still referenced once the server is closed. main has 109 cases and this tree 111 (3 are Linux-only in both): 1 moved, 3 added, 9 changed, and every other body identical.UDP listener drops truncated datagramsto a unit test inserver/listen/udp-test.rs: only main's mock port could deliver a truncated datagram to a whole listener, and a real socket cannot overfill its 65,535-byte buffer, so the test receives into a 4-byte buffer.a durableObjectClass binding without props can be sent over RPC: a configured binding without props is not actx.exportstemplate.Workflow engine configuration is checked field by field: new coverage of theworkflowsEngineconfig errors.a service that fails to start does not leak the services before it: the factory is unreferenced after a failed start.Durable Objects (on disk): shared storage is a real temp directory symlinked into the test root (newSharedDirectory()), since the server opens real files; it holds 9 files, not 6 (-shm).Durable Object alarm persistence (on disk),Durable Object evictions when callback scheduled,Durable Object facetsandDurable Object facet cloninguse the same shared directory.Durable Objects websocket constructor blockConcurrencyWhile throws after sendreads thependingframe before EOF, because the in-memory connection is buffered.network outbound with allow/deny: the lists are CIDRs, because the server parses the filter.disk servicesets modification times withtest.setModifiedwhere main assignedtest.fakeDate, because real files take the real time.disk service allow dotfilessets.dot's time the same way, and expects 204, not 403, forPUT /%2e%2e/secret(disk service URLs, below).receiveSubrequestno longer asserts anetworkservice's peer filter, since the service dials the loopback registry first; four unit tests inserver/services/network-test.rscover the filter.In the end-to-end tests,
tests/server-harness.mjssplits the--control-fdstream on newlines before parsing, since one read may carry several events or part of one, and the three UDP configs bind127.0.0.1:0in place of*:0, which theirudp4clients reach inside Bazel's macOS sandbox.Behaviour changes against main's C++ server
Process and command line
listenevent to--control-fd; main wrote the earlier sockets'.Socket "main": bind(): ...); main died with*** Fatal uncaught kj::Exception.Tests failed!) are JSON lines on stderr. What differs: the run failure (*** Uncaught exception ***) is a JSON line on stderr too (main: plain text), and the stderr lines carrysourcesrc/workerd/server/server/entry.rs:133(rawfile!(), with asrc/prefix no other log line has) where main's namedjson-logger.c++.file:line: type: descriptionwithoutstack:; one made in Rust has the bridge'sfile:line.file:line, type or stack.KJ_CLEAN_SHUTDOWN), and the perfetto session on the config-error exit too.workerd testfilters areglobcrate patterns over the whole name:[...]and**are special,*crosses/, a suffix after/no longer matches, and a bad pattern is a run failure.[ PASS ]/[ FAIL ]durations are Rust'sDurationdebug form (3.520417ms, the micro sign as U+00B5); main printed5.708msand U+03BC.analyticsEngineandunsafeEvalbindings without--experimentalare left out ofenvwith the shared wording; main kept theanalyticsEnginebinding.--perfetto-trace: of main's 33 server-layerTRACE_EVENTs three remain, in the factory (Bootstrap(),worker_start_request(),factory_new_worker()); the other 30 (startup and drain phases, bindings, listeners, services, requests) are gone.HTTP on the config's sockets and to
externalandnetworkservices is hyper in place of kj-http; the inspector, the fallback service, the container client and the Pyodide bundle download (pyodide.c++, with kj's TLS) keep kj-http. The rule applied: hyper's behaviour is accepted where hyper conforms to the RFC, where both conform and where both deviate; hyper is patched where only kj conformed, and the explanation body on a parse-error response (in kj-hyper) is the one such patch.HTTP
Connection: closeor HTTP/1.0 without keep-alive.Content-LengthwithTransfer-Encodingis read as chunked, withContent-Lengthhidden, and the connection then closes.400for an invalid or differingContent-Lengthand aTransfer-Encodingnot ending inchunked(main: 500), forTransfer-Encodingon HTTP/1.0 and an unknown version (main: served), and for a garbage request line (main: 501).Content-Length,Transfer-Encoding: gzip, chunked, absolute-form targets,OPTIONS *, a leading empty line.Transfer-Encoding; a header's repeated values are written adjacent.Sec-WebSocket-Versiongets426withSec-WebSocket-Version: 13(main: 400); a WebSocket handshake by POST gets400(main: 500).400,414or431with hyper's description as the body; a target over 65,534 bytes is414(main served 70,000).431.Outbound, TLS, disk, config
externalandnetworkservices open a connection per subrequest (a service builds its kj-hyper client per event); main reused connections.networkservice resolves with tokio'slookup_host(noAI_ADDRCONFIG/AI_V4MAPPED) and dials IP addresses only.unix/unix-abstractin a network service'sallowordenydo nothing; on mainconnect({hostname: "unix:sock", port: 1})reached a unix socket namedsock:1(names with/are refused on both).cipherListis ignored (main failed startup on an unusable list), and aminVersionbelow TLS 1.2 means 1.2.requireClientCertswithtrustBrowserCasis refused at startup, as a run failure; main accepted it.urlcrate:.and..resolve even percent-encoded (PUT /%2e%2e/secretis 204 inside the directory; main: 403),\separates segments, andGET /sub//f.txtis 404 (main: 200).pkcs8/spkiPEM is read by rustls' parser, not main'sdecodePem.fromEnvironmentbinding whose value is not UTF-8 is converted lossily; main passed the bytes through.Outside
src/workerd/servercli-main.c++called:loopback.rs(TokioNetwork::enableLoopback(),AddressKind::Loopback) andsignal.rs(kj_rs_io::onSignal), with their tests. The server keeps its ownloopback:registry (server/listen/loopback.rs, overtokio::io::duplex) and takes SIGTERM fromtokio::signal. In exchange the Rust address and listener API is public, for a caller that serves tokio's sockets itself:TokioAddress::parse_str,listen,connect_firstandbind_udp,TokioListener::acceptandport,wrap_listenerfor an inherited socket, theSocketthey hand back, andKjIoErrorintostd::io::Error.build/wd_rust_crate.bzlgainscxx_bridge_visibility, so that the factory's C++, in another package, can include the server crate's bridge header.deps/rust/Cargo.tomladdshttpdate,percent-encodingandurl(the disk service),data-encoding(CryptoKey bindings),glob(test filters) andipnet(allow/denyranges).clippy.tomladdsSQLiteandWebSocketstodoc-valid-idents, for the new doc comments.src/workerd/api/restore.c++,src/workerd/util/setup-async-io*,docs/jsg.md,docs/reference/detail/new-module-registry.md, the AGENTS.md files, bonk's rust-first paths, andjust test-compile-flags, which now checksfactory/worker-factory.c++.Known gaps
externalservice open three connections where main opened one.cipherList, a lowminVersion,unix/unix-abstractfilter entries), produce no config error or warning.StructuredLoggingProcessContext(json-logger.h) has no user left but its three cases injson-logger-test.c++.src/workerd/server/log-schema.capnp,src/workerd/api/sockets.handsrc/workerd/api/tests/worker-loader-test.js.What has run, and what CI will show
What has run:
bazel test --test_size_filters= //src/...on macOS arm64 only, with 2,132 passing, 8 skipped and 8 failing. Six arecontainer-client(testPidNamespaceandtestSetEgressHttp, against a hand-built substitute image) andworker-loader-test's four Python cases (a certificate error fetching the Pyodide bundle, under main's server too), three variants each. The other two (http-nodejs-testandhttp-client-cpp, one variant each) failed on timing while the machine was loaded and pass in 5 reruns on their own. Not run: anything on Linux or Windows, which leaves the three#if __linux__cases,unix-abstract:addresses, the--socket-fdlistening check and fuzzilli to CI; clang-tidy onfactory/*;@gc-stress, ASAN, TSAN and coverage, including theKJ_CLEAN_SHUTDOWNexit path; CryptoKey PEM on malformed input; and miniflare against this binary.Expected on this PR's CI:
patches/rust/crates(as for kj-hyper: hyper, rustls and kj's WebSockets behind kj-typed seams #7535) and repins for the six crates new todeps/rust/Cargo.toml. edgeworker's only build dependency onsrc/workerd/serverbesides the binary is:actor-id-impl, which is unchanged.factory/*; it cannot run on the macOS machine this was developed on.Size
Size, by
wc -lwithout the-test.rsfiles: the server crate is 9,779 lines, the factory 3,821 andcompiled-bindings.capnp107, together 13,707 against the 9,193 they replace (server.c++,server.h,cli-main.{h,c++}): 1.49x. The crate's unit tests are another 1,412 lines in eight-test.rsfiles.🤖 Generated with Claude Code