Skip to content

Commit b801a05

Browse files
committed
FEATURE-ci-darwin-header-deadline: Defer native diagnostic output until suite shutdown
Capture bounded typed metadata with per-event monotonic timestamps in one compiled-library buffer, preserving errno and ordinary-build elision. Flush only after all integration fixtures stop, and record notify/signal/ack state for the observed no-deadline handoff. Keep the same exact archive overlay, assertions and budgets. Native live tracing passed without establishing a cause; this remains a diagnostic checkpoint. RED/GREEN deferred-output contract, enabled/disabled shared-library checks, actual archive overlay, extracted-source syntax, workflow syntax and changed-file lint passed. Await one reviewed native execution before any behavior repair.
1 parent 6472d18 commit b801a05

6 files changed

Lines changed: 131 additions & 10 deletions

File tree

‎.github/workflows/v3-package-validation.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ jobs:
115115
CPPFLAGS='-DHTTPSERVER_DIAG_HEADER_DEADLINE',
116116
LDFLAGS='-Wl,-rpath,' + str(provider / 'lib'))
117117
receipt = dict(kind='diagnostic-only; not complete package validation', tls='no', linkage='shared',
118+
trace_mode='bounded memory; stderr emitted after all suite fixtures stop',
118119
revision=command(['git', 'rev-parse', 'HEAD'], checkout, env).strip(),
119120
compiler=command([compiler, '--version'], checkout, env),
120121
provider_header_sha256=hashlib.sha256((provider / 'include/openssl/opensslv.h').read_bytes()).hexdigest())

‎specs/tasks/FEATURE-ci-darwin-header-deadline.md‎

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,26 @@ LLVM toolchain and `-O0 -g`. Checkout versus extracted archive/execution context
3131
is now an observed distinction; it is not an established cause. The CA issue is
3232
queued separately and is outside this task.
3333

34+
## First native diagnostic result and remaining uncertainty
35+
36+
Native diagnostic run `39974555288`, job `120485650697`, passes all four existing
37+
cases with zero failures using the exact c0 archive plus the live stderr trace
38+
overlay. The header case takes 303.347 ms. Its actual timer sequence 8 fires,
39+
applies result 0, resumes, matches the current deadline, enforces timeout,
40+
releases the connection and closes the native socket successfully near 300 ms.
41+
A snapshot with no deadline occurs between timer application and watchdog
42+
resumption; a later host wait receives a wake. The original f91/c0 untraced
43+
archive failures remain unexplained. Passing under tracing is neither a fix nor
44+
proof that the failure is independent of this path.
45+
46+
Source review of that handoff finds registration retirement/reset under the
47+
backend mutex, followed by notify; notify and wake acknowledgment share that
48+
mutex. Snapshots borrow handles; dispatch-held leases end before the host returns
49+
to waiting. No violated race invariant or decisive forced-interleaving regression
50+
was established from this successful trace. The next checkpoint reduces trace
51+
perturbation and records actual notify/signal/ack states to distinguish missing
52+
wake from retained lease when a failure is observed.
53+
3454
## Action items
3555

3656
- [x] Preserve and read prior/current evidence without repeating the closed Linux probe.
@@ -42,20 +62,30 @@ queued separately and is outside this task.
4262
jobs explicitly on manual dispatch.
4363
- [x] Check enabled/disabled trace compilation, bounded output, errno preservation,
4464
ordinary-build argument elision, workflow syntax and changed-file lint.
45-
- [ ] Execute the native diagnostic and retain actual installed-library provenance.
65+
- [x] Execute the first native diagnostic and retain actual installed-library provenance.
66+
- [x] Prepare bounded in-memory capture with deferred output and explicit event timestamps.
67+
- [ ] Execute one reviewed buffered-trace native diagnostic against the same exact archive.
4668
- [ ] Establish cause from native evidence, then write and observe a failing
4769
regression before applying a narrow repair.
4870
- [ ] Remove all temporary trace code/header and workflow edits before final merge.
4971
- [ ] Run task-relevant final checks and Groundwork validation/finalization.
5072

5173
## Checkpoint scope and checks
5274

53-
`HTTPSERVER_DIAG_HEADER_DEADLINE` enables temporary stderr records, capped at 64
54-
per call site. Correlation uses connection IDs, operation pointers/sequences,
55-
socket descriptors and registration generations. Time points are converted with
56-
`duration_cast<nanoseconds>`; header durations are explicit milliseconds. No
57-
request bytes, credentials or peer content are logged. Ordinary builds erase
58-
trace argument evaluation.
75+
`HTTPSERVER_DIAG_HEADER_DEADLINE` enables temporary typed metadata capture,
76+
capped at 64 records per call site and 2,048 records overall. Each record stores
77+
an explicit monotonic `event_ns`, a static format and copied scalar/pointer
78+
values in fixed storage in the compiled library. Capture performs no formatting,
79+
stderr writes, allocation or application mutex locking. The installed test and
80+
library use that same buffer. Only after all four test fixtures have stopped and
81+
the suite has completed does the test call flush to format and emit stderr.
82+
Correlation uses connection IDs, operation pointers/sequences, socket descriptors
83+
and registration generations; new notify/signal/ack metadata covers the observed
84+
wake/retirement handoff. Time points use `duration_cast<nanoseconds>`; header
85+
durations are explicit milliseconds. No request bytes, credentials or peer
86+
content are retained. Capture and flush preserve errno; ordinary builds erase
87+
trace argument evaluation, storage and diagnostic symbols. A discarded compile
88+
branch retains format checking without executing stderr writes during capture.
5989

6090
Manual dispatch runs macOS only, preserving its selected compiler/runtime,
6191
provider setup and artifact upload. It builds and installs TLS-off libraries at
@@ -96,7 +126,16 @@ workflow extraction/overlay block passes, all eight original overlay files match
96126
the exact base, and the private trace header is present. All eight affected
97127
translation units also pass diagnostic-enabled syntax/format checks using only
98128
the extracted source/include paths. Workflow syntax passes again; only workflow provenance and this task record change in the follow-up.
99-
Darwin execution and the original required gate remain incomplete.
129+
The buffered capture contract was tested RED/GREEN: live stderr output first
130+
fails the deferred-output assertion; the new buffer passes. Enabled/disabled
131+
checks link the actual io_operation implementation as a shared library and prove
132+
no output before flush, 64/site caps across concurrent producers, errno
133+
preservation, typed field formatting, positive per-event timestamps, one buffer
134+
shared by executable/library events, and no diagnostic symbols or argument
135+
evaluation in ordinary builds. The updated literal archive overlay and
136+
workflow/test-preservation checks pass; both enabled/disabled extracted-source
137+
syntax checks and changed-file lint pass. Buffered native execution and the
138+
original required gate remain incomplete.
100139

101140
The caller reviews/publishes only this task branch and dispatches the existing
102141
workflow, then resumes this same worktree with actual native evidence. No root

‎src/detail/io_external_adapter.cpp‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,13 @@ void io_poll_backend::notify() {
9494
bool failed = false;
9595
{
9696
std::lock_guard<std::mutex> lock(mu_);
97+
V3_DEADLINE_TRACE("notify backend=%p pending=%d failed=%d\n", static_cast<void*>(this), wake_pending_, wake_failed_);
9798
if (wake_failed_ || wake_pending_) return;
9899
if (wake_.signal()) {
100+
V3_DEADLINE_TRACE("signal backend=%p result=1\n", static_cast<void*>(this));
99101
wake_pending_ = true;
100102
} else {
103+
V3_DEADLINE_TRACE("signal backend=%p result=0\n", static_cast<void*>(this));
101104
wake_failed_ = true;
102105
active_ = false;
103106
failed = true;
@@ -109,8 +112,10 @@ void io_poll_backend::notify() {
109112
}
110113

111114
void io_poll_backend::acknowledge_wake() {
115+
V3_DEADLINE_TRACE("acknowledge backend=%p pending=%d failed=%d\n", static_cast<void*>(this), wake_pending_, wake_failed_);
112116
if (!wake_.acknowledge()) wake_failed_ = true;
113117
wake_pending_ = false;
118+
V3_DEADLINE_TRACE("acknowledged backend=%p failed=%d\n", static_cast<void*>(this), wake_failed_);
114119
}
115120

116121
server::interest_snapshot io_poll_backend::interests() const {

‎src/detail/io_operation.cpp‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,45 @@
2727

2828
#include "httpserver/detail/io_operation.hpp"
2929

30+
#if defined(HTTPSERVER_DIAG_HEADER_DEADLINE)
31+
#include <algorithm>
32+
#include <array>
33+
#endif
3034
#include <exception>
3135
#include <memory>
3236
#include <utility>
3337

3438
#include <httpserver/detail/deadline_trace.hpp>
3539

40+
#if defined(HTTPSERVER_DIAG_HEADER_DEADLINE)
41+
namespace httpserver::detail {
42+
namespace {
43+
constinit std::array<deadline_trace_record, 2048> deadline_records{};
44+
constinit std::atomic<std::size_t> deadline_record_count{0};
45+
} // namespace
46+
47+
deadline_trace_record* deadline_trace_reserve(long long monotonic_ns) noexcept { // NOLINT(runtime/int)
48+
const auto index = deadline_record_count.fetch_add(1, std::memory_order_relaxed);
49+
if (index >= deadline_records.size()) return nullptr;
50+
auto& record = deadline_records[index];
51+
record.monotonic_ns = monotonic_ns;
52+
return &record;
53+
}
54+
55+
void deadline_trace_flush() noexcept {
56+
const int saved_errno = errno;
57+
const auto count = std::min(deadline_record_count.load(std::memory_order_relaxed), deadline_records.size());
58+
for (std::size_t index = 0; index < count; ++index) {
59+
const auto& record = deadline_records[index];
60+
if (!record.ready.load(std::memory_order_acquire)) continue;
61+
std::fprintf(stderr, "V3_DEADLINE event_ns=%lld ", record.monotonic_ns); // NOLINT(build/include_what_you_use)
62+
record.write(record);
63+
}
64+
errno = saved_errno;
65+
}
66+
} // namespace httpserver::detail
67+
#endif
68+
3669
namespace httpserver {
3770
namespace detail {
3871

‎src/httpserver/detail/deadline_trace.hpp‎

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,66 @@
22
#ifndef SRC_HTTPSERVER_DETAIL_DEADLINE_TRACE_HPP_
33
#define SRC_HTTPSERVER_DETAIL_DEADLINE_TRACE_HPP_
44

5-
// Temporary native Darwin diagnostic; each call site emits at most 64 records.
5+
// Temporary native Darwin diagnostic; each site captures at most 64 records.
66
#if defined(HTTPSERVER_DIAG_HEADER_DEADLINE)
77
#include <atomic>
88
#include <cerrno>
99
#include <chrono>
10+
#include <cstddef>
1011
#include <cstdio>
12+
#include <new>
13+
#include <tuple>
14+
#include <type_traits>
1115

1216
namespace httpserver::detail {
1317
using deadline_trace_id = unsigned long long; // NOLINT(runtime/int)
1418
inline long long deadline_trace_ms(std::chrono::milliseconds time) { return time.count(); } // NOLINT(runtime/int)
1519
inline long long deadline_trace_ns(std::chrono::steady_clock::time_point time) { // NOLINT(runtime/int)
1620
return std::chrono::duration_cast<std::chrono::nanoseconds>(time.time_since_epoch()).count();
1721
}
22+
23+
struct deadline_trace_record {
24+
long long monotonic_ns = 0; // NOLINT(runtime/int)
25+
void (*write)(const deadline_trace_record&) = nullptr;
26+
alignas(std::max_align_t) std::byte payload[128]{};
27+
std::atomic<bool> ready{false};
28+
};
29+
30+
// One buffer in the compiled library also receives the installed test's events.
31+
deadline_trace_record* deadline_trace_reserve(long long monotonic_ns) noexcept; // NOLINT(runtime/int)
32+
void deadline_trace_flush() noexcept; // Call only after all producers have stopped.
33+
34+
template<typename... Args>
35+
void deadline_trace_capture(long long monotonic_ns, const char* format, Args... values) noexcept { // NOLINT(runtime/int)
36+
struct payload_type {
37+
const char* format;
38+
std::tuple<Args...> values; // NOLINT(build/include_what_you_use)
39+
};
40+
static_assert(sizeof(payload_type) <= sizeof(deadline_trace_record::payload));
41+
static_assert(alignof(payload_type) <= alignof(std::max_align_t));
42+
static_assert(std::is_trivially_destructible_v<payload_type>);
43+
if (auto* record = deadline_trace_reserve(monotonic_ns)) {
44+
new (record->payload) payload_type{format, {values...}};
45+
record->write = [](const deadline_trace_record& entry) {
46+
const auto& payload = *std::launder(reinterpret_cast<const payload_type*>(entry.payload));
47+
if constexpr (sizeof...(Args) == 0) {
48+
std::fputs(payload.format, stderr); // NOLINT(build/include_what_you_use)
49+
} else {
50+
std::apply([&](auto... args) { std::fprintf(stderr, payload.format, args...); }, payload.values); // NOLINT(build/include_what_you_use)
51+
}
52+
};
53+
record->ready.store(true, std::memory_order_release);
54+
}
55+
}
1856
} // namespace httpserver::detail
1957

2058
#define V3_DEADLINE_TRACE(...) do { \
2159
static std::atomic<unsigned> trace_count{0}; \
2260
if (trace_count.fetch_add(1, std::memory_order_relaxed) < 64) { \
2361
const int trace_errno = errno; \
24-
std::fprintf(stderr, "V3_DEADLINE " __VA_ARGS__); /* NOLINT(build/include_what_you_use) */ \
62+
const auto trace_ns = httpserver::detail::deadline_trace_ns(std::chrono::steady_clock::now()); \
63+
if constexpr (false) { std::fprintf(stderr, __VA_ARGS__); } /* NOLINT(build/include_what_you_use) */ \
64+
httpserver::detail::deadline_trace_capture(trace_ns, __VA_ARGS__); \
2565
errno = trace_errno; \
2666
} \
2767
} while (false)

‎test/integ/external_http1_e2e_test.cpp‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,4 +276,7 @@ LT_END_AUTO_TEST(reconnect_replayed_callbacks_and_host_driven_drain)
276276

277277
LT_BEGIN_AUTO_TEST_ENV()
278278
AUTORUN_TESTS()
279+
#if defined(HTTPSERVER_DIAG_HEADER_DEADLINE)
280+
httpserver::detail::deadline_trace_flush();
281+
#endif
279282
LT_END_AUTO_TEST_ENV()

0 commit comments

Comments
 (0)