Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2eee4c8
Use NT timer packets after Windows rejects high-resolution APC timers
Sep 14, 2026
6082f27
Let NT timer packets provide the sole deadline for IOCP waits
Sep 14, 2026
a8191c2
Implement the Windows IOCP backend and native contracts
Sep 14, 2026
dc4e8d5
Preserve Windows fixtures and use HTTP2-capable curl in CI
Sep 14, 2026
833ebe0
Merge current main and keep curl capability checks on the pinned binary
Sep 14, 2026
228cc51
Validate Windows examples and read path-check inputs as UTF8
Sep 14, 2026
e960b08
Require the pinned curl executable in Windows interop
Sep 14, 2026
4ab629c
Merge current core fixes and validate allocation-free Windows file ba…
Sep 14, 2026
c65a622
Preserve LF in Git attributes
Sep 14, 2026
23d674b
Merge WASI and web integration while retaining Windows contracts
Sep 14, 2026
3eda5f6
Merge main (core4 per-mode CI, core5 UDP fix, allocation counters, fe…
Sep 14, 2026
e4cd9c8
Count IOCP deadline-timer-only wakes as zero-event waits
Sep 14, 2026
286647e
Hold IOCP to the shared pool-exhaustion deadline accounting
Sep 14, 2026
eeb626e
Consume the server's session tickets before closing in the async TLS …
Sep 14, 2026
28a49c8
Run the async curl test with the pinned curl and report a timeout's c…
Sep 14, 2026
87a44cd
Fix the IOCP review findings: batch files, exit races, batch errors, …
Sep 14, 2026
c487796
Treat a terminating IOCP child as killed: close after kill no longer …
Sep 15, 2026
79008a5
Keep the early-417 case of the async expect-continue test off the 2 m…
Sep 15, 2026
8566cd2
Flush conhost's own input records before the console assertions
Sep 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ jobs:
cargo +nightly-2026-08-20 clippy --locked --workspace --all-targets --target "$TARGET" --all-features -- -D warnings -D clippy::undocumented_unsafe_blocks
rustup toolchain install stable --profile minimal --target "$TARGET"
cargo +stable check --locked --workspace --all-targets --all-features --target "$TARGET"
# PENDING Windows shared contracts until the IOCP production provider lands (wave 2).
test-native:
strategy:
fail-fast: false
Expand Down Expand Up @@ -148,6 +147,13 @@ jobs:
package-manager-cache: false
- name: Install pinned Rust
run: rustup toolchain install nightly-2026-08-20 --profile minimal --component rustfmt,clippy
- name: Install checksum-pinned curl with HTTP/2 on Windows
# Shell condition, not a step `if:`: native mode steps stay unconditional for the feature-coverage gate.
run: |
if [ "$RUNNER_OS" = "Windows" ]; then
python3 scripts/ci/install-tools.py curl
echo "TURNLOOP_TEST_CURL=$GITHUB_WORKSPACE/.tools/bin/curl.exe" >> "$GITHUB_ENV"
fi
- name: Workspace and single-threaded contracts in the selected mode
run: python3 scripts/ci/run-tests.py native --mode "$NATIVE_MODE"
- name: HTTP, TLS and WebSocket Node/curl interop
Expand Down Expand Up @@ -176,6 +182,10 @@ jobs:
package-manager-cache: false
- name: Install pinned Rust
run: rustup toolchain install nightly-2026-08-20 --profile minimal --component rustfmt,clippy
- name: Install checksum-pinned curl with HTTP/2
run: |
python3 scripts/ci/install-tools.py curl
echo "TURNLOOP_TEST_CURL=$GITHUB_WORKSPACE/.tools/bin/curl.exe" >> "$GITHUB_ENV"
- name: Native Windows lint and contracts
run: |
cargo +nightly-2026-08-20 clippy --locked --workspace --all-targets --all-features -- -D warnings -D clippy::undocumented_unsafe_blocks
Expand Down
19 changes: 9 additions & 10 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ for each core/protocol/contract member, and runs native Node/curl interop with t
same mode. Independent member runs retain the selected features belonging to that
member or its direct dependencies; a sans-IO crate with no core dependency has
no backend feature to select. The workspace run keeps the full feature selection.
The existing Windows pending-contract marker remains scoped to
that provider. Every arm is required through the matrix job's `ci-gate` result.
Every arm independently requires positive contract counts, including Windows
IOCP. Every arm is required through the matrix job's `ci-gate` result.

`python3 scripts/ci/run-tests.py native` runs all applicable modes on the current
host; `--mode epoll-timerfd` selects just that Linux arm. `interop --mode MODE`
Expand Down Expand Up @@ -381,15 +381,14 @@ protocol member, with default, executor and all features. The existing sans-IO u
SCRAM, SDAM/selection fixture and allocation tests are portable. No unnecessary
Unix test cfg exclusions were found.

Production IOCP contracts are explicitly pending through the contract member's
`windows-contracts-pending = "WINDOWS_HANDOFF.md"` metadata. The runner lists their
scope and handoff in the Windows job summary. Remove this metadata when IOCP lands;
the ordinary independent positive-count contract gate then applies on Windows too.
This marker applies only to Windows contracts; core/protocol zero counts and Unix
contract zero counts always fail.
The production IOCP backend implements revision 2 and runs the shared contracts,
including no-spin and allocation gates, plus Windows process, console, GUI and
lifetime tests. The pending-contract metadata has been removed; zero counts fail
on Windows just as on Unix. Cross-compilation does not establish runtime coverage.

- **Wave 2 Windows:** adapt `spikes/iocp` to the production Backend, instantiate
`turnloop-contract` on IOCP, run all contracts (including no-spin) on Windows.
- **Windows:** run `python3 scripts/ci/run-tests.py native` on Windows for all
three required modes. Keep platform-specific runtime results explicit in lane
reports when developing on another host.
- **WASM providers and contracts:** production p2 and web adapters and the
experimental p3 adapter now live in `crates/turnloop`. Run the mandatory
`run-tests.py wasi --target wasm32-wasip2`, `... wasm32-wasip3`, `run-tests.py web`
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ pub struct Error { pub kind: ErrorKind, pub os: Option<i32> } // host maps to
- **Processes:** `CreateProcessW` with overlapped pipe handles, a Job Object for kill-tree semantics, and `RegisterWaitForSingleObject` on the process handle to post the exit completion.
- **Signals:** `SetConsoleCtrlHandler` for CTRL_C, CTRL_BREAK and CTRL_CLOSE, mapped to `SIGINT`/`SIGBREAK`/`SIGHUP`. SIGTERM has no console equivalent; documented as such, matching Perry today (`os/signal.rs:441–531`).
- **Wake:** `PostQueuedCompletionStatus` with a reserved completion key.
- **Timer precision:** the default system tick is about 15.6 ms and `GetQueuedCompletionStatusEx` timeouts round to it. The plan is a **high-resolution waitable timer** (`CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`, Windows 10 1803+) armed to the next deadline, waking the wait through an alertable `GetQueuedCompletionStatusEx` (APC) or by associating the timer with the port. M1 prototypes both. `timeBeginPeriod` is not an acceptable default because it changes the tick system-wide.
- **Timer precision:** the default system tick is about 15.6 ms and `GetQueuedCompletionStatusEx` timeouts round to it. Use a **high-resolution waitable timer** (`CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`, Windows 10 1803+) armed to the next deadline and associated with the port through dynamically resolved `NtAssociateWaitCompletionPacket`. `GetQueuedCompletionStatusEx` is nonalertable. Cancellation returning `STATUS_PENDING` requires dequeuing the generation-tagged timer packet before reuse. M1 prototyped both APC and NT packet routes; §15 question 3 records the NT packet decision and measured lateness. `timeBeginPeriod` is not an acceptable default because it changes the tick system-wide.
- **Host integration:** `Integration::Event(HANDLE)` via the helper thread (D7).
- **Measuring cost:** there is no `perf`; use `QueryThreadCycleTime`/`QueryProcessCycleTime` (CPU cycles) for A/B comparisons.

Expand Down Expand Up @@ -566,7 +566,7 @@ P5–P7 are independent of each other once P1 and P4 have landed, and can run as

1. **Name.** Settled: `turnloop`.
2. **`sys` layer.** Own thin backends vs building on compio-driver (IOCP and io_uring already done) vs `polling`/mio for Unix. Decided in M1 with instruction numbers.
3. **Windows timer mechanism.** Alertable GQCSEx + waitable timer APC, or timer-to-port association.
3. **Windows timer mechanism.** Use a high-resolution waitable timer with dynamically resolved `NtAssociateWaitCompletionPacket` and nonalertable GQCSEx. Windows 11 build 26200 rejected high-resolution APC callbacks with error 87; 100 NT-packet samples at 250 us measured p50/p95/max lateness of 275.2/285.8/380.9 us. See [the Windows results](spikes/iocp/WINDOWS_RESULTS.md); minimum-version and VM precision validation remain separate work.
4. **Pooled buffer sizing and lease lifetime.** Until the next `turn`, or explicit release only?
5. **io_uring timing.** When, and whether as the default where available.
6. **Mobile CI.** iOS (kqueue) and Android (epoll) come almost free from the Unix backends. Which simulators/emulators run in CI.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ adapters are still being integrated. Perry is the first intended consumer.
| macOS arm64 | kqueue driver, sockets, timers, posts and pool | Native contracts and allocation tests |
| Linux x86_64 / arm64 | epoll, nanosecond waits, timerfd fallback | Cross-checked locally; native CI required |
| FreeBSD / Apple mobile / Android | Unix backend paths | Best effort; runtime validation pending |
| Windows x86_64 | Standalone IOCP spike | Cross-checked; production adapter and native contracts pending |
| Windows x86_64 | IOCP, TCP/UDP/named pipes, stdio, processes, console signals, timers and shared services | Native Windows 11 contracts and allocation/executor tests; see `spikes/iocp/WINDOWS_RESULTS.md` |
| WASI 0.2 / 0.3 | Standalone polling / component async spikes | Production adapters and shared contracts pending |
| Web | Standalone host-callback spike | Production adapter and browser contracts pending |

Expand Down
5 changes: 3 additions & 2 deletions crates/turnloop-contract/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ workspace = true
[target.'cfg(unix)'.dependencies]
libc.workspace = true

[target.'cfg(windows)'.dependencies]
windows-sys = { version = "=0.61.2", features = ["Win32_Foundation", "Win32_UI_WindowsAndMessaging", "Win32_System_Threading", "Win32_System_Console", "Win32_Storage_FileSystem", "Win32_Security", "Win32_System_IO", "Win32_System_Pipes"] }

[package.metadata.turnloop-ci]
role = "contract"
# Remove when the production IOCP provider instantiates the shared contracts.
windows-contracts-pending = "WINDOWS_HANDOFF.md"
web-tests = ["browser"]
node-tests = ["node"]
web-tests-features = ["web-worker", "executor"]
Expand Down
12 changes: 8 additions & 4 deletions crates/turnloop-contract/src/bin/native_child.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ fn blocked_signal() {
target_vendor = "apple",
target_os = "linux",
target_os = "android",
target_os = "freebsd"
target_os = "freebsd",
target_os = "windows"
))]
fn native_stdio() {
use turnloop::*;
Expand Down Expand Up @@ -121,7 +122,8 @@ fn native_stdio() {
target_vendor = "apple",
target_os = "linux",
target_os = "android",
target_os = "freebsd"
target_os = "freebsd",
target_os = "windows"
)))]
fn native_stdio() {
panic!("instantiate stdio fixture on production native backend");
Expand All @@ -130,7 +132,8 @@ fn native_stdio() {
target_vendor = "apple",
target_os = "linux",
target_os = "android",
target_os = "freebsd"
target_os = "freebsd",
target_os = "windows"
))]
fn native_handle(path: &std::ffi::OsStr) {
use turnloop::*;
Expand Down Expand Up @@ -177,7 +180,8 @@ fn native_handle(path: &std::ffi::OsStr) {
target_vendor = "apple",
target_os = "linux",
target_os = "android",
target_os = "freebsd"
target_os = "freebsd",
target_os = "windows"
)))]
fn native_handle(_: &std::ffi::OsStr) {
panic!("instantiate handle fixture on production native backend");
Expand Down
18 changes: 11 additions & 7 deletions crates/turnloop-contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,13 +357,17 @@ pub fn refused_connect_once<B: Backend>() {
l.turn(Timeout::Until(until), &mut out).expect("turn");
for c in out.drain() {
assert_eq!(c.token, Token(7));
assert!(matches!(
c.result,
OpResult::Err(Error {
kind: ErrorKind::ConnectionRefused,
..
})
));
assert!(
matches!(
c.result,
OpResult::Err(Error {
kind: ErrorKind::ConnectionRefused,
..
})
),
"unexpected refused-connect result: {:?}",
c.result
);
assert!(c.terminal);
count += 1;
}
Expand Down
Loading