Skip to content

Commit 5b2db29

Browse files
committed
FEATURE-ci-v3-runtime-green: poll QUIC fixtures through the host helper on Windows
1 parent db7cefe commit 5b2db29

4 files changed

Lines changed: 33 additions & 23 deletions

File tree

‎ChangeLog‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
Version 3.0.0 - 2026-10-10
2+
Poll the QUIC interop endpoint and UDP fixture through the shared
3+
cross-platform host helper on Windows.
24
Open the QUIC interop test keylog with Windows-compatible flags
35
instead of O_NOFOLLOW/O_CLOEXEC/fchmod.
46
Complete the first FreeBSD native make-check exposure: portable

‎specs/tasks/FEATURE-ci-v3-runtime-green.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,10 @@ Full local `make check -j1` (TLS=no, g++ 15.2, WSL2): 232/235 pass, 2 platform s
179179
- [x] Evidence (mingw64 job 124232738670, Windows acceptance job 124233097485, both at b080f744): with stage 10 in place the mingw64 package lane printed `PASS installed consumer native TLS=no` — the entire installed tls-no matrix including public-tls and public-http2 passed on Windows for the first time. The tls-yes lane then failed building `libquic_interop_provider_a`: `src/detail/tls_session.cpp:127/129` use `O_NOFOLLOW`, `O_CLOEXEC` and `fchmod`, none of which exist on Windows.
180180
- [x] Repair: `open_keylog()` gains a `_WIN32` branch using `_O_NOINHERIT` (the close-on-exec analog) and dropping `fchmod` (the creating open already owns the file); the POSIX branch is unchanged. This is the only site in `src/` using those identifiers (`quic_tls_session.cpp` is clean). POSIX-branch syntax check with the interop define passes locally; cpplint clean.
181181
- [ ] Full Windows tls-yes CI evidence past the interop provider build (SNI/rotation scenarios and the tls-yes public audits remain first-exposure).
182+
183+
## Stage 13: Windows QUIC fixture polls (2026-10-30)
184+
185+
- [x] Evidence (mingw64 job at db7cefec, 41112829610): with the stage-12 keylog fix the tls-yes `make -j2` advanced past `libquic_interop_provider_a` and failed at the next noinst program: `test/integ/quic_interop_endpoint.cpp:3: 'poll.h' file not found`.
186+
- [x] Repair: the interop endpoint's hand-rolled poll loop now goes through the shared `examples/host_poll.hpp` helper (WSAPoll on Windows, the same contract every external-loop consumer uses), preserving the connection-deadline folding and the one-second park bound; `chmod` of the log directory is POSIX-only. `test/integ/http3_udp_fixture.cpp` — the next `poll.h` includer in the same build set — takes a `_WIN32` branch on the same helper while its POSIX stdin-poll path stays byte-identical (the stdin command channel is driven only by the POSIX parity lanes).
187+
- [x] Local gates: both files pass cpplint and `g++ -std=c++20 -fsyntax-only` with the full build flags; `integ/http3_full_gate_runner_test.py` 27/27 (it drives the gate runner that builds and runs these fixtures).
188+
- [ ] Full Windows tls-yes CI evidence past the QUIC fixtures.

‎test/integ/http3_udp_fixture.cpp‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
/* Copyright (C) 2026 Sebastiano Merlino; SPDX-License-Identifier: LGPL-2.1-or-later */
2+
#if defined(_WIN32)
3+
#include "../../examples/host_poll.hpp"
4+
#else
25
#include <poll.h>
6+
#endif
37
#include <unistd.h>
48

59
#include <algorithm>
@@ -376,14 +380,22 @@ struct fixture {
376380
// its successful completion is committed in the next owner turn.
377381
if (executor.pending() || (send && send->state()->applied()) || receive->state()->applied()) return true;
378382
auto snapshot = backend.interests();
379-
if (snapshot.wake) snapshot.sockets.push_back(*snapshot.wake);
380-
std::vector<pollfd> descriptors{{STDIN_FILENO, POLLIN, 0}};
381-
for (auto& interest : snapshot.sockets)
382-
descriptors.push_back({static_cast<int>(interest.handle.value), static_cast<std::int16_t>((interest.readable ? POLLIN : 0) | (interest.writable ? POLLOUT : 0)), 0});
383383
auto due = lifetime;
384384
for (auto& [id, connection] : connections)
385385
if (auto deadline = connection->deadline()) due = std::min(due, *deadline);
386386
if (snapshot.next_deadline) due = std::min(due, *snapshot.next_deadline);
387+
#if defined(_WIN32)
388+
// The stdin command channel is driven only by the POSIX parity
389+
// lanes; on Windows the fixture builds but never serves commands.
390+
const auto park = std::min(due, clock_type::now() + std::chrono::milliseconds(1000));
391+
if (!backend.dispatch(v3_host::wait(snapshot, park), clock_type::now()).ok())
392+
throw std::runtime_error("backend dispatch failed");
393+
return true;
394+
#else
395+
if (snapshot.wake) snapshot.sockets.push_back(*snapshot.wake);
396+
std::vector<pollfd> descriptors{{STDIN_FILENO, POLLIN, 0}};
397+
for (auto& interest : snapshot.sockets)
398+
descriptors.push_back({static_cast<int>(interest.handle.value), static_cast<std::int16_t>((interest.readable ? POLLIN : 0) | (interest.writable ? POLLOUT : 0)), 0});
387399
int timeout = std::max<std::int64_t>(std::int64_t{0}, std::chrono::ceil<std::chrono::milliseconds>(due - clock_type::now()).count());
388400
if (poll(descriptors.data(), descriptors.size(), timeout) < 0) throw std::runtime_error("poll failed");
389401
if (descriptors[0].revents & (POLLIN | POLLHUP)) {
@@ -401,6 +413,7 @@ struct fixture {
401413
}
402414
if (!backend.dispatch(events, clock_type::now()).ok()) throw std::runtime_error("backend dispatch failed");
403415
return true;
416+
#endif
404417
}
405418
};
406419
} // namespace

‎test/integ/quic_interop_endpoint.cpp‎

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/* Copyright (C) 2026 Sebastiano Merlino; SPDX-License-Identifier: LGPL-2.1-or-later */
22
#include <openssl/pem.h>
3-
#include <poll.h>
43
#include <sys/stat.h>
54
#include <unistd.h>
65
#include <csignal>
@@ -20,6 +19,7 @@
2019
#include <httpserver/concurrency/resume_signal.hpp>
2120
#include <httpserver/detail/io_poll_backend.hpp>
2221
#include "../support/http3_network_owner.hpp"
22+
#include "../../examples/host_poll.hpp"
2323
#include "../unit/io_udp_backend_contract.hpp"
2424
namespace {
2525
volatile std::sig_atomic_t stopping = 0;
@@ -294,30 +294,16 @@ struct fixture {
294294
if (executor.pending() || (send && send->state()->applied()) || receive->state()->applied())
295295
return true;
296296
auto snapshot = backend.interests();
297-
if (snapshot.wake)
298-
snapshot.sockets.push_back(*snapshot.wake);
299-
std::vector<pollfd> descriptors;
300-
for (auto& interest : snapshot.sockets)
301-
descriptors.push_back(
302-
{static_cast<int>(interest.handle.value), static_cast<std::int16_t>((interest.readable ? POLLIN : 0) | (interest.writable ? POLLOUT : 0)), 0});
303297
auto due = lifetime;
304298
for (auto& [id, connection] : connections)
305299
if (auto deadline = connection->deadline())
306300
due = std::min(due, *deadline);
307301
if (snapshot.next_deadline)
308302
due = std::min(due, *snapshot.next_deadline);
309-
int timeout = std::min<std::int64_t>(1000, std::max<std::int64_t>(std::int64_t{0}, std::chrono::ceil<std::chrono::milliseconds>(due - clock_type::now()).count()));
310-
if (poll(descriptors.data(), descriptors.size(), timeout) < 0 && !stopping)
311-
throw std::runtime_error("poll failed");
312-
std::vector<h::server::readiness_event> events;
313-
for (unsigned i = 0; i < snapshot.sockets.size(); ++i)
314-
if (descriptors[i].revents) {
315-
auto& interest = snapshot.sockets[i];
316-
auto revents = descriptors[i].revents;
317-
events.push_back({interest.key, interest.generation, static_cast<bool>(revents & POLLIN), static_cast<bool>(revents & POLLOUT),
318-
static_cast<bool>(revents & POLLHUP), static_cast<bool>(revents & (POLLERR | POLLNVAL))});
319-
}
320-
if (!backend.dispatch(events, clock_type::now()).ok())
303+
// Park at most one second past the earliest due work; the shared
304+
// host helper owns the platform poll (WSAPoll on Windows).
305+
const auto park = std::min(due, clock_type::now() + std::chrono::milliseconds(1000));
306+
if (!backend.dispatch(v3_host::wait(snapshot, park), clock_type::now()).ok())
321307
throw std::runtime_error("backend dispatch failed");
322308
return true;
323309
}
@@ -333,7 +319,9 @@ int main() {
333319
hqnet::files root_check(env("LIBHTTPSERVER_INTEROP_WWW", "/www"));
334320
const auto logs = env("LIBHTTPSERVER_INTEROP_LOGS", "/logs");
335321
std::filesystem::create_directories(logs);
322+
#if !defined(_WIN32)
336323
if (chmod(logs.c_str(), 0700) != 0) throw std::runtime_error("log permissions failed");
324+
#endif
337325
const auto pcap = logs + "/packets.pcap";
338326
std::signal(SIGTERM, stop);
339327
std::signal(SIGINT, stop);

0 commit comments

Comments
 (0)