Skip to content

Commit cbceed7

Browse files
committed
FEATURE-ci-native-missing-receipts: Prepare four bounded native CI observations
Prepare the registered diagnostic workflow with only Windows and Darwin jobs, durable one-use target permits, independent fixed allowances and preserved original failures. Reuse the bounded Winsock trace and add PSK/root-open state plus primary-process versus pipe EOF evidence without production repairs. Verified 23 offline contracts, GNU/LLVM C++ objects, genuine Windows PE object compilation, actionlint, Python syntax, changed C++ cpplint and diff checks. Original native target invocations remain zero. Full caller validation and the single authorized CI dispatch remain pending; this diagnostic branch must never merge into v3.
1 parent 37095c2 commit cbceed7

11 files changed

Lines changed: 1389 additions & 175 deletions

File tree

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

Lines changed: 79 additions & 171 deletions
Large diffs are not rendered by default.

‎examples/host_poll.hpp‎

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,22 @@
3030
#define NOMINMAX
3131
#endif
3232
#include <winsock2.h>
33+
#include <windows.h>
3334
#else
3435
#include <poll.h>
3536
#endif
3637

3738
#include <algorithm>
39+
#include <array>
40+
#include <atomic>
41+
#include <cstdio>
42+
#include <cstring>
43+
#include <type_traits>
3844
#include <cerrno>
3945
#include <chrono>
4046
#include <climits>
4147
#include <cstdint>
48+
#include <cinttypes>
4249
#include <optional>
4350
#include <stdexcept>
4451
#include <vector>
@@ -58,6 +65,116 @@ constexpr std::int16_t readable = POLLIN;
5865
constexpr std::int16_t writable = POLLOUT;
5966
#endif
6067

68+
// Diagnostic branch only. Stop/error rows are emitted immediately; dispatch
69+
// history is bounded in memory. Clocks, sequence writes and I/O perturb timing.
70+
struct capture_row {
71+
std::uint64_t sequence = 0;
72+
std::int64_t ticks = 0;
73+
char detail[256]{};
74+
};
75+
inline std::atomic<std::uint64_t> capture_sequence{0};
76+
inline std::atomic<unsigned> capture_count{0};
77+
inline std::array<capture_row, 64> capture_rows{};
78+
inline std::array<capture_row, 16> capture_dispatch_rows{};
79+
inline bool capture_wait_reported = false; // Written only by the host thread.
80+
inline unsigned capture_dispatch_count = 0; // Written only by the host thread.
81+
82+
inline capture_row capture_stamp() noexcept {
83+
capture_row row;
84+
row.sequence = capture_sequence.fetch_add(1, std::memory_order_relaxed) + 1;
85+
#if defined(_WIN32)
86+
LARGE_INTEGER ticks{};
87+
::QueryPerformanceCounter(&ticks);
88+
row.ticks = ticks.QuadPart;
89+
#endif
90+
return row;
91+
}
92+
93+
inline void capture_emit_early(const capture_row& row) noexcept {
94+
std::fprintf(stderr, "WAIT_CAPTURE_EARLY seq=%" PRIu64 " ticks=%" PRId64 " %s\n",
95+
row.sequence, row.ticks, row.detail);
96+
std::fflush(stderr);
97+
}
98+
99+
inline void capture_event(const char* event) noexcept {
100+
const auto index = capture_count.fetch_add(1, std::memory_order_relaxed);
101+
if (index >= capture_rows.size()) return;
102+
auto row = capture_stamp();
103+
std::snprintf(row.detail, sizeof(row.detail), "%s", event);
104+
capture_rows[index] = row;
105+
capture_emit_early(row);
106+
}
107+
108+
inline void capture_dispatch(const char* event) noexcept {
109+
auto row = capture_stamp();
110+
std::snprintf(row.detail, sizeof(row.detail), "%s", event);
111+
capture_dispatch_rows[capture_dispatch_count++ % capture_dispatch_rows.size()] = row;
112+
}
113+
114+
#if defined(_WIN32)
115+
inline void capture_wait_failure(int ready, int error, const std::vector<slot>& fds,
116+
const std::vector<srv::socket_interest>& interests) noexcept {
117+
if (!capture_wait_reported) {
118+
capture_wait_reported = true;
119+
for (const auto& row : capture_dispatch_rows) if (row.sequence) capture_emit_early(row);
120+
}
121+
capture_event("wait_failure");
122+
const auto index = capture_count.fetch_add(1, std::memory_order_relaxed);
123+
if (index < capture_rows.size()) {
124+
auto row = capture_stamp();
125+
std::snprintf(row.detail, sizeof(row.detail), "wait_result rc=%d error=%d fd_count=%" PRIu64,
126+
ready, error, static_cast<std::uint64_t>(fds.size()));
127+
capture_rows[index] = row;
128+
capture_emit_early(row);
129+
}
130+
for (std::size_t i = 0; i < fds.size(); ++i) {
131+
const auto entry = capture_count.fetch_add(1, std::memory_order_relaxed);
132+
if (entry >= capture_rows.size()) break;
133+
// socket_key is opaque. Retain its exact object bytes, not a forged key.
134+
static_assert(std::is_trivially_copyable_v<srv::socket_key>);
135+
static_assert(sizeof(srv::socket_key) == sizeof(std::uint64_t));
136+
std::uint64_t key_bytes = 0;
137+
std::memcpy(&key_bytes, &interests[i].key, sizeof(key_bytes));
138+
int type = 0, size = sizeof(type);
139+
const int valid_rc = ::getsockopt(fds[i].fd, SOL_SOCKET, SO_TYPE,
140+
reinterpret_cast<char*>(&type), &size);
141+
const int valid_error = valid_rc < 0 ? ::WSAGetLastError() : 0;
142+
auto row = capture_stamp();
143+
std::snprintf(row.detail, sizeof(row.detail),
144+
"wait_fd index=%" PRIu64 " handle=%" PRIu64 " events=%d revents=%d key_bytes=%016" PRIx64
145+
" generation=%" PRIu64 " declared_valid=%d kind=%u validity_rc=%d validity_error=%d type=%d",
146+
static_cast<std::uint64_t>(i), static_cast<std::uint64_t>(fds[i].fd),
147+
fds[i].events, fds[i].revents, key_bytes,
148+
static_cast<std::uint64_t>(interests[i].generation), interests[i].handle.valid,
149+
static_cast<unsigned>(interests[i].handle.kind), valid_rc, valid_error, type);
150+
capture_rows[entry] = row;
151+
capture_emit_early(row);
152+
}
153+
}
154+
#endif
155+
156+
inline void capture_flush() noexcept {
157+
// Both writers have finished. Sorting pointers cannot allocate.
158+
std::array<const capture_row*, 80> rows{};
159+
std::size_t count = 0;
160+
for (const auto& row : capture_rows) if (row.sequence) rows[count++] = &row;
161+
for (const auto& row : capture_dispatch_rows) if (row.sequence) rows[count++] = &row;
162+
std::sort(rows.begin(), rows.begin() + count, [](const auto* left, const auto* right) {
163+
return left->sequence < right->sequence;
164+
});
165+
#if defined(_WIN32)
166+
LARGE_INTEGER frequency{};
167+
::QueryPerformanceFrequency(&frequency);
168+
std::fprintf(stderr, "WAIT_CAPTURE clock=QPC frequency=%" PRId64 " rows=%" PRIu64 " dropped=%u\n",
169+
static_cast<std::int64_t>(frequency.QuadPart), static_cast<std::uint64_t>(count),
170+
capture_count.load() > 64 ? capture_count.load() - 64 : 0);
171+
#endif
172+
for (std::size_t i = 0; i < count; ++i) {
173+
std::fprintf(stderr, "WAIT_CAPTURE seq=%" PRIu64 " ticks=%" PRId64 " %s\n",
174+
rows[i]->sequence, rows[i]->ticks, rows[i]->detail);
175+
}
176+
}
177+
61178
inline slot registration(const srv::socket_interest& interest) {
62179
#if defined(_WIN32)
63180
if (!interest.handle.valid || interest.handle.kind != srv::native_handle_kind::winsock_socket) {
@@ -99,7 +216,9 @@ inline std::vector<srv::readiness_event> wait(
99216
for (const auto& interest : interests) fds.push_back(registration(interest));
100217
#if defined(_WIN32)
101218
const auto ready = ::WSAPoll(fds.data(), static_cast<ULONG>(fds.size()), timeout(deadline));
102-
if (ready < 0 && ::WSAGetLastError() != WSAEINTR) throw std::runtime_error("WSAPoll failed");
219+
const int native_error = ready < 0 ? ::WSAGetLastError() : 0;
220+
if (ready < 0) capture_wait_failure(ready, native_error, fds, interests);
221+
if (ready < 0 && native_error != WSAEINTR) throw std::runtime_error("WSAPoll failed");
103222
#else
104223
const auto ready = ::poll(fds.data(), static_cast<nfds_t>(fds.size()), timeout(deadline));
105224
if (ready < 0 && errno != EINTR) throw std::runtime_error("poll failed");

0 commit comments

Comments
 (0)