Skip to content

Commit 6d69f85

Browse files
committed
FEATURE-ci-darwin-focused-diagnostics: capture six failures in one native batch
Add temporary scalar observations at unchanged test assertions and timer/readiness/storage boundaries. Prepare an existing dispatch-capable release workflow as a manual-only read-only Darwin diagnostic job; bind its source and archive identity and launch each selected real program once with LLDB failure evidence. This diagnostic branch must never merge into v3. Verification: enabled/disabled C++20 syntax and format checks, logger-only errno/operand smoke checks, changed-file cpplint, actionlint, workflow shell and embedded Python/LLDB parsing, and git diff checks passed locally. Darwin compilation/execution and caller validation remain pending; no native suite, push, merge or dispatch was performed.
1 parent a5a7235 commit 6d69f85

19 files changed

Lines changed: 515 additions & 272 deletions

‎.github/workflows/release.yml‎

Lines changed: 267 additions & 263 deletions
Large diffs are not rendered by default.

‎examples/host_poll.hpp‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
#include <vector>
4545

4646
#include <httpserver/server/readiness.hpp>
47+
#ifdef LHS_DARWIN_FOCUSED_DIAGNOSTICS
48+
#include "../src/detail/darwin_focused_diagnostics.hpp"
49+
#endif
4750

4851
namespace v3_host {
4952
namespace srv = httpserver::server;
@@ -97,12 +100,20 @@ inline std::vector<srv::readiness_event> wait(
97100
if (snapshot.wake) interests.push_back(*snapshot.wake);
98101
std::vector<slot> fds;
99102
for (const auto& interest : interests) fds.push_back(registration(interest));
103+
const auto selected_timeout = timeout(deadline);
100104
#if defined(_WIN32)
101-
const auto ready = ::WSAPoll(fds.data(), static_cast<ULONG>(fds.size()), timeout(deadline));
105+
const auto ready = ::WSAPoll(fds.data(), static_cast<ULONG>(fds.size()), selected_timeout);
102106
if (ready < 0 && ::WSAGetLastError() != WSAEINTR) throw std::runtime_error("WSAPoll failed");
103107
#else
104-
const auto ready = ::poll(fds.data(), static_cast<nfds_t>(fds.size()), timeout(deadline));
108+
const auto ready = ::poll(fds.data(), static_cast<nfds_t>(fds.size()), selected_timeout);
105109
if (ready < 0 && errno != EINTR) throw std::runtime_error("poll failed");
110+
#endif
111+
#ifdef LHS_DARWIN_FOCUSED_DIAGNOSTICS
112+
LHS_DARWIN_HEADER_DIAG("header host poll now_us=%lld result=%d errno=%d deadline_us=%lld timeout_ms=%d count=%zu\n",
113+
LHS_DARWIN_NOW, ready, errno, deadline ? LHS_DARWIN_TICK(*deadline) : -1LL, selected_timeout, fds.size());
114+
for (const auto& fd : fds) {
115+
LHS_DARWIN_HEADER_DIAG("header host poll fd=%llu events=%d revents=%d\n", LHS_DARWIN_ID(fd.fd), fd.events, fd.revents);
116+
}
106117
#endif
107118
std::vector<srv::readiness_event> events;
108119
for (std::size_t i = 0; i < fds.size(); ++i) {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# FEATURE-ci-darwin-focused-diagnostics
2+
3+
Status: In Progress — implementation prepared; caller validation and the one native batch remain pending.
4+
5+
Base: local `v3` at `a5a72356741300e5da16b94090f0f26a67a2d1f7`.
6+
Branch: `task/FEATURE-ci-darwin-focused-diagnostics`.
7+
Plan: `.groundwork-plans/FEATURE-ci-darwin-focused-diagnostics-plan.md` in the primary workspace.
8+
Journal: `.debug/ci-darwin-focused-diagnostics-cn_lss1c/journal.md` in the primary workspace.
9+
10+
This is temporary diagnostic instrumentation. This branch, including its temporary replacement of `.github/workflows/release.yml`, must never merge into `v3`. The source assertions, budgets, timeout values, allocation fault countdowns and runtime decisions remain authoritative. A passing native observation establishes intermittency only. No root cause or production repair is claimed.
11+
12+
- [x] Read literal journal, selected historical assertion logs and owning tests/boundaries. Prior batches stay closed.
13+
- [x] Instrument actual continuation/local noop values; outbox capacities and abort frames; QPACK allocation attempts and original error result; resource push kind/capacity/budget; peer bind/connect operation/address/errno; header watchdog, timer submission/claim/application, published readiness, actual poll and close timestamps.
14+
- [x] Keep private scalar tracing compile-time disabled in ordinary builds. The helper uses one fixed 512-byte stack buffer and one write, preserves errno, creates no event store/lock and performs no C++ allocation. Runtime flags narrow library traces to the header or storage processes.
15+
- [x] Prepare manual-only, read-only native Darwin batch under existing default-branch dispatch-capable `release.yml` filename. The required `tag` is a fixed diagnostic sentinel; it never selects a ref, creates a tag or publishes anything.
16+
- [x] Compile syntax, diagnostic logger smoke checks, workflow/embedded Python/shell syntax, actionlint, changed-file cpplint and diff checks locally; no native tests or full suites in this phase.
17+
- [ ] Caller independently validates the diagnostic diff and reviews the workflow before publishing this branch only.
18+
- [ ] Caller dispatches the command below ONCE and retains source-bound receipt, raw logs and LLDB abort frames.
19+
- [ ] Caller records actual native results, context differences and causal assessment without reopening closed probes.
20+
21+
Proposed single dispatch after caller review/publication:
22+
23+
```bash
24+
gh workflow run release.yml --ref task/FEATURE-ci-darwin-focused-diagnostics -f tag=darwin-focused-once-a5a72356
25+
```
26+
27+
The workflow rejects other event/ref/sentinel values, dirty checkout and rerun attempts, binds checkout and Git source archive to `github.sha`, and uses the existing macOS 15/compiler-wrapper/provider prerequisite owners and GNU make. Root independently verified the current default-branch `release.yml` exists and supports dispatch. There are no push, tag, schedule, release or deployment triggers/jobs in this temporary replacement.
28+
29+
The five historical TLS-no programs (`task_resume_signal_lifetime`, `http1_response_outbox`, `http3_qpack`, `resource_limits`, `consumer_v3_peer_policy_runtime`) build through their declared native make targets, retaining the target's real shared/convenience-static linkage. `external_http1_e2e` builds once against an installed TLS-yes shared library using the same real test/parity translation units and private integration include context as the historical package suite. All execute at EUID 0, matching the recurring header failure. There is no extra static-only header execution. All six execute at most once, each with a 180-second outer LLDB bound; later programs still build/run after an earlier failure or abort. Existing test case assertions and their own 300 ms/10 s deadlines remain unchanged.
30+
31+
The artifact contains exact archive bytes/hash, commit/run identity, selected tools/environment, configure/compiler/link commands and original command logs, binaries/link map, provider preparation record and LLDB exit/stop/backtrace/local-variable evidence. A launch/setup failure remains incomplete. This is a bootstrapped Git source archive, not the historical generated release tarball. Current selected tools/provider and temporary tracing/debugging can change timing. This evidence is diagnostic only and cannot substitute for release acceptance. The existing independent CI monitor is untouched.
32+
33+
Narrow source-storage tracing was approved by the caller because returned sink state cannot reveal a destroyed rejected allocation. It observes only existing `bound`, `actual`, `excess`, provisional charge and successful result locals in `reserve_buffer_locked`; it adds no capacity/reserve probes or accounting changes. The real host poll helper is instrumented at its syscall boundary to capture the actual submitted timeout and returned revents, without an alternate poll implementation.
34+
35+
Local evidence: enabled C++20 syntax/format checking succeeded for the six test TUs and changed source TUs using the existing primary Linux `build/config.h` only to satisfy `connection_engine.cpp`'s generated include. The first syntax attempt lacked that header; corrected execution setup passed. Linux excludes the native kqueue body; its Darwin compilation and all native execution remain pending. Logger smoke checks confirmed enabled output, preserved errno and disabled operand elision (no test suites executed). Shell/embedded Python/LLDB-script parsing and actionlint passed. Final complete changed-file lint/diff results are recorded in the primary journal.
36+
37+
Implementation notes for future task executors: Autotools requires out-of-tree builds; `./bootstrap`, configure with selected CC/CXX/MAKE, then GNU make. These six tests use the existing littletest harness (`-Itest` for `./littletest.hpp`), except the peer consumer's `require` checks. `src/libhttpserver_v3core.la` supplies private implementation tests; normal consumers link `src/libhttpserver.la` or installed shared library. Public headers must not include private/platform/provider headers. Worktree creation required sandbox escalation for Git metadata even though the task files are within the writable repository. No preexisting task-executor agent memory was available at the usual agent-memory locations; this record retains the setup facts.

‎src/detail/connection_engine.cpp‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include <httpserver/detail/drain_scope.hpp>
4141
#include <httpserver/detail/diagnostics_access.hpp>
4242
#include <httpserver/http/outcome.hpp>
43+
#include "./darwin_focused_diagnostics.hpp"
4344

4445
namespace httpserver {
4546

@@ -740,6 +741,8 @@ void connection_engine::note_transport_activity(bool written) {
740741
}
741742

742743
void connection_engine::enforce_timeout(std::chrono::steady_clock::time_point deadline) noexcept {
744+
LHS_DARWIN_HEADER_DIAG("header enforce_timeout now_us=%lld id=%llu deadline_us=%lld\n",
745+
LHS_DARWIN_NOW, LHS_DARWIN_ID(id_), LHS_DARWIN_TICK(deadline));
743746
bool global_expiry = false, close_expiry = false;
744747
{
745748
std::lock_guard<std::mutex> lock(mu_);
@@ -776,13 +779,17 @@ task<void> connection_engine::watchdog_loop(
776779
std::lock_guard<std::mutex> lock(self->mu_);
777780
plan = self->plan_watchdog_locked();
778781
}
782+
LHS_DARWIN_HEADER_DIAG("header watchdog plan now_us=%lld id=%llu exit=%d defer=%d deadline_us=%lld\n",
783+
LHS_DARWIN_NOW, LHS_DARWIN_ID(self->id_), plan.exit, plan.defer, LHS_DARWIN_TICK(plan.deadline));
779784
if (plan.exit) co_return;
780785
const auto deadline = plan.defer
781786
? std::chrono::steady_clock::now() + k_watchdog_tick
782787
: plan.deadline;
783788
timer_operation op(self->owner_, self->id_, deadline);
784789
if (!self->arm_watchdog(op, plan)) continue;
785790
const io_result r = co_await std::move(op);
791+
LHS_DARWIN_HEADER_DIAG("header watchdog resumed now_us=%lld id=%llu result=%d deadline_us=%lld\n",
792+
LHS_DARWIN_NOW, LHS_DARWIN_ID(self->id_), static_cast<int>(r.code), LHS_DARWIN_TICK(deadline));
786793
{
787794
std::lock_guard<std::mutex> lock(self->mu_);
788795
self->pending_timer_.reset();
@@ -810,6 +817,7 @@ void connection_engine::loop_finished() {
810817
}
811818

812819
void connection_engine::finalize() {
820+
LHS_DARWIN_HEADER_DIAG("header connection finalize now_us=%lld id=%llu\n", LHS_DARWIN_NOW, LHS_DARWIN_ID(id_));
813821
// The engine's counted unit drops first: by the time the listener
814822
// erases its record, a drain waiting on the scope observes
815823
// completion with every loop finished and the writer flushed.

‎src/detail/connection_engine_websocket.cpp‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <utility>
2626
#include <httpserver/detail/connection_engine.hpp>
2727
#include <httpserver/detail/http1_websocket_handshake.hpp>
28+
#include "./darwin_focused_diagnostics.hpp"
2829
namespace httpserver::detail {
2930
namespace {
3031
bool retire_completed_front(http1_response_outbox& outbox) {
@@ -239,6 +240,9 @@ bool connection_engine::arm_watchdog(timer_operation& timer, const watchdog_plan
239240
if (current.exit) return false;
240241
if (current.defer != plan.defer || current.deadline != plan.deadline) return false;
241242
pending_timer_ = timer.state();
243+
LHS_DARWIN_HEADER_DIAG("header watchdog submit now_us=%lld id=%llu op=%p deadline_us=%lld phase=%d head_routed=%d last_activity_us=%lld\n",
244+
LHS_DARWIN_NOW, LHS_DARWIN_ID(id_), static_cast<void*>(pending_timer_.get()),
245+
LHS_DARWIN_TICK(std::get<timer_payload>(pending_timer_->payload()).deadline), static_cast<int>(phase_), head_routed_, LHS_DARWIN_TICK(last_activity_));
242246
timer.submit(backend_);
243247
return true;
244248
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/* Copyright (C) 2026 Sebastiano Merlino; SPDX-License-Identifier: LGPL-2.1-or-later */
2+
#ifndef SRC_DETAIL_DARWIN_FOCUSED_DIAGNOSTICS_HPP_
3+
#define SRC_DETAIL_DARWIN_FOCUSED_DIAGNOSTICS_HPP_
4+
#include <cstdio>
5+
// Temporary, noninstalled instrumentation for one authorized Darwin batch.
6+
#ifdef LHS_DARWIN_FOCUSED_DIAGNOSTICS
7+
#include <unistd.h>
8+
#include <cerrno>
9+
#include <chrono>
10+
#include <cstdlib>
11+
// One bounded write, no allocator, retry, new mutex or retained event state.
12+
#define LHS_DARWIN_DIAG(...) do { \
13+
const int lhs_saved_errno = errno; \
14+
char lhs_line[512]; \
15+
const int lhs_length = std::snprintf(lhs_line, sizeof(lhs_line), __VA_ARGS__); \
16+
if (lhs_length > 0) { \
17+
const auto lhs_count = static_cast<std::size_t>(lhs_length) < sizeof(lhs_line) \
18+
? static_cast<std::size_t>(lhs_length) : sizeof(lhs_line) - 1; \
19+
static_cast<void>(::write(STDERR_FILENO, lhs_line, lhs_count)); \
20+
} \
21+
errno = lhs_saved_errno; \
22+
} while (false)
23+
#define LHS_DARWIN_HEADER_DIAG(...) do { \
24+
const int lhs_trace_errno = errno; \
25+
if (std::getenv("LHS_DARWIN_HEADER_TRACE")) { \
26+
LHS_DARWIN_DIAG(__VA_ARGS__); \
27+
} \
28+
errno = lhs_trace_errno; \
29+
} while (false)
30+
#define LHS_DARWIN_STORAGE_DIAG(...) do { \
31+
const int lhs_storage_errno = errno; \
32+
if (std::getenv("LHS_DARWIN_STORAGE_TRACE")) { \
33+
LHS_DARWIN_DIAG(__VA_ARGS__); \
34+
} \
35+
errno = lhs_storage_errno; \
36+
} while (false)
37+
// Match printf's ll formats on both LP64 and LLP64 hosts.
38+
#define LHS_DARWIN_ID(value) static_cast<unsigned long long>(value) // NOLINT(runtime/int)
39+
#define LHS_DARWIN_TICK(point) \
40+
static_cast<long long>(std::chrono::duration_cast<std::chrono::microseconds>((point).time_since_epoch()).count()) // NOLINT(runtime/int)
41+
#define LHS_DARWIN_NOW LHS_DARWIN_TICK(std::chrono::steady_clock::now())
42+
#else
43+
#define LHS_DARWIN_DIAG(...) do {} while (false)
44+
#define LHS_DARWIN_HEADER_DIAG(...) do {} while (false)
45+
#define LHS_DARWIN_STORAGE_DIAG(...) do {} while (false)
46+
#endif
47+
#endif // SRC_DETAIL_DARWIN_FOCUSED_DIAGNOSTICS_HPP_

‎src/detail/http1_response_outbox.cpp‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <vector>
88

99
#include <httpserver/detail/http1_response_outbox.hpp>
10+
#include "./darwin_focused_diagnostics.hpp"
1011

1112
namespace httpserver::detail {
1213

@@ -147,8 +148,14 @@ auto http1_response_outbox::reserve_buffer_locked(http1_response_sink& sink, std
147148
if (size > maximum) return false;
148149
const auto capacity = sink.bytes_.capacity();
149150
const auto bound = std::max(size, capacity > maximum / 2 ? maximum : 2 * capacity);
151+
LHS_DARWIN_STORAGE_DIAG("storage reserve candidate outbox=%p sink=%p size=%zu old_capacity=%zu bound=%zu charge=%zu queue_budget=%zu\n",
152+
static_cast<void*>(this), static_cast<void*>(&sink), size, capacity, bound, storage_charge_.units(), budget_.max_queue_bytes);
150153
if (bound > std::numeric_limits<std::size_t>::max() - storage_charge_.units() ||
151-
!storage_charge_.grow(bound)) return false;
154+
!storage_charge_.grow(bound)) {
155+
LHS_DARWIN_STORAGE_DIAG("storage refused provisional outbox=%p sink=%p bound=%zu charge=%zu\n",
156+
static_cast<void*>(this), static_cast<void*>(&sink), bound, storage_charge_.units());
157+
return false;
158+
}
152159
// Destroy the private candidate before rolling back its admission.
153160
struct provisional_charge {
154161
resource_charge& charge;
@@ -159,14 +166,22 @@ auto http1_response_outbox::reserve_buffer_locked(http1_response_sink& sink, std
159166
replacement.reserve(bound);
160167
const auto actual = replacement.capacity();
161168
const auto excess = actual - bound;
169+
LHS_DARWIN_STORAGE_DIAG("storage allocated candidate outbox=%p sink=%p bound=%zu actual=%zu excess=%zu provisional_charge=%zu\n",
170+
static_cast<void*>(this), static_cast<void*>(&sink), bound, actual, excess, storage_charge_.units());
162171
if (excess > std::numeric_limits<std::size_t>::max() - storage_charge_.units() ||
163-
!storage_charge_.grow(excess)) return false;
172+
!storage_charge_.grow(excess)) {
173+
LHS_DARWIN_STORAGE_DIAG("storage refused excess outbox=%p sink=%p actual=%zu excess=%zu charge=%zu\n",
174+
static_cast<void*>(this), static_cast<void*>(&sink), actual, excess, storage_charge_.units());
175+
return false;
176+
}
164177
pending.units = actual;
165178
replacement.append(sink.bytes_);
166179
sink.bytes_.swap(replacement);
167180
std::string().swap(replacement);
168181
storage_charge_.shrink(std::exchange(sink.storage_units_, actual));
169182
pending.units = 0;
183+
LHS_DARWIN_STORAGE_DIAG("storage accepted candidate outbox=%p sink=%p actual=%zu charge=%zu\n",
184+
static_cast<void*>(this), static_cast<void*>(&sink), actual, storage_charge_.units());
170185
return true;
171186
}
172187

‎src/detail/io_connection_owner.cpp‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <memory>
2626
#include <exception>
2727
#include <utility>
28+
#include "./darwin_focused_diagnostics.hpp"
2829

2930
namespace httpserver {
3031
namespace detail {
@@ -87,6 +88,9 @@ datagram_enqueue_code io_connection_owner::datagram_port::enqueue(
8788

8889
void io_connection_owner::apply_record(record entry, const std::shared_ptr<shared_state>& pending) {
8990
if (entry.state) {
91+
LHS_DARWIN_HEADER_DIAG("header owner apply now_us=%lld id=%llu op=%p kind=%d code=%d bytes=%zu\n", LHS_DARWIN_NOW,
92+
LHS_DARWIN_ID(entry.state->connection()), static_cast<void*>(entry.state.get()),
93+
static_cast<int>(entry.state->kind()), static_cast<int>(entry.result.code), entry.result.transferred);
9094
entry.state->apply(entry.result);
9195
return;
9296
}
@@ -106,6 +110,8 @@ void io_connection_owner::apply_record(record entry, const std::shared_ptr<share
106110

107111
void io_connection_owner::enqueue(std::shared_ptr<op_state> state,
108112
io_result result) {
113+
LHS_DARWIN_HEADER_DIAG("header owner enqueue now_us=%lld id=%llu op=%p kind=%d code=%d bytes=%zu\n", LHS_DARWIN_NOW,
114+
LHS_DARWIN_ID(state->connection()), static_cast<void*>(state.get()), static_cast<int>(state->kind()), static_cast<int>(result.code), result.transferred);
109115
bool need_post = false;
110116
{
111117
std::lock_guard<std::mutex> lock(state_->mu);

‎src/detail/io_external_adapter.cpp‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <unordered_map>
3131
#include <utility>
3232
#include <vector>
33+
#include "./darwin_focused_diagnostics.hpp"
3334

3435
namespace httpserver {
3536
namespace detail {
@@ -121,6 +122,9 @@ server::interest_snapshot io_poll_backend::interests() const {
121122
borrowed_handle(wake_.read_handle()), true, false};
122123
std::unordered_map<std::uint64_t, pollsys::event_mask> masks;
123124
snapshot.next_deadline = scan_interest_locked(masks);
125+
LHS_DARWIN_HEADER_DIAG("header poll published now_us=%lld pending=%zu deadline_us=%lld wake_pending=%d wake_fd=%llu\n",
126+
LHS_DARWIN_NOW, pending_.size(), snapshot.next_deadline ? LHS_DARWIN_TICK(*snapshot.next_deadline) : -1LL,
127+
wake_pending_, LHS_DARWIN_ID(wake_.read_handle()));
124128
for (const auto& entry : connections_) {
125129
const auto& record = entry.second;
126130
const auto& lifetime = record.lifetime;
@@ -161,6 +165,7 @@ http::outcome io_poll_backend::dispatch(
161165
close();
162166
return status;
163167
}
168+
LHS_DARWIN_HEADER_DIAG("header poll dispatch now_us=%lld host_now_us=%lld events=%zu\n", LHS_DARWIN_NOW, LHS_DARWIN_TICK(now), events.size());
164169
for (const auto& event : events) dispatch_event(event);
165170
expire_due_timers(now);
166171
return ready();
@@ -213,6 +218,9 @@ void io_poll_backend::dispatch_event(const server::readiness_event& event) {
213218
std::lock_guard<std::mutex> lock(mu_);
214219
if (closed_) return;
215220
std::tie(id, lease) = find_registration_locked(event);
221+
LHS_DARWIN_HEADER_DIAG("header poll event now_us=%lld id=%llu generation=%llu matched=%d read=%d write=%d closed=%d error=%d\n",
222+
LHS_DARWIN_NOW, LHS_DARWIN_ID(id), LHS_DARWIN_ID(event.generation),
223+
static_cast<bool>(lease), event.readable, event.writable, event.closed, event.error);
216224
if (!lease) return;
217225
datagram = connections_.at(id).datagram;
218226
// Prepare both directions before detaching either one. A failure in

0 commit comments

Comments
 (0)