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;
5865constexpr 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+
61178inline 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