Loading test/httpd_h2_concurrency_test.cpp +50 −22 Changes for test/httpd_h2_concurrency_test.cpp: 50 added lines, 22 removed lines. Original line number Diff line number Diff line Loading @@ -189,27 +189,53 @@ std::vector<std::chrono::steady_clock::time_point> runConcurrentRequest( return times; } // Binds an ephemeral-port TCP listener, starts a SlowServer on it in a // background thread, and returns the bound port. The server thread is // intentionally never joined — the test process exits via std::exit once // all assertions are done, which is fine for a short-lived test binary and // avoids netplus::event::Running (a process-wide static) making a second, // sequential runEventloop() call impossible after the first is stopped. int startServer(std::unique_ptr<netplus::tcp> &sockOwner, size_t h2OffloadThreads) { sockOwner = std::make_unique<netplus::tcp>("127.0.0.1", 0, 16, -1); sockOwner->bind(); // A running server plus everything needed to tear it down deterministically. // netplus::event's connection registry, wakeup eventfd and Running/Restart // flags are process-wide statics (needed elsewhere so the H2-offload dispatch // can look up/rearm a connection from any thread by fd). That means a // SlowServer's background threads keep touching that shared static state for // as long as they're alive — they must be fully stopped and joined before the // next server starts (or before the process exits), or a worker thread can be // mid-access when those statics get destroyed, which segfaults. struct RunningServer { std::unique_ptr<netplus::tcp> sock; std::unique_ptr<SlowServer> server; std::thread thread; }; // Binds an ephemeral-port TCP listener and starts a SlowServer on it in a // background thread. RunningServer startServer(int &port, size_t h2OffloadThreads) { RunningServer rs; rs.sock = std::make_unique<netplus::tcp>("127.0.0.1", 0, 16, -1); rs.sock->bind(); sockaddr_in bound{}; socklen_t blen = sizeof(bound); ::getsockname(sockOwner->fd(), reinterpret_cast<sockaddr*>(&bound), &blen); int port = ntohs(bound.sin_port); sockOwner->listen(); auto *server = new SlowServer({sockOwner.get()}, h2OffloadThreads); std::thread([server] { server->runEventloop(); }).detach(); ::getsockname(rs.sock->fd(), reinterpret_cast<sockaddr*>(&bound), &blen); port = ntohs(bound.sin_port); rs.sock->listen(); rs.server = std::make_unique<SlowServer>( std::vector<netplus::socket*>{rs.sock.get()}, h2OffloadThreads); // requestStop() (see stopServer()) leaves this false; a fresh // runEventloop() call needs it true again to not exit its worker loop // immediately. netplus::event::Running = true; SlowServer *serverPtr = rs.server.get(); rs.thread = std::thread([serverPtr] { serverPtr->runEventloop(); }); std::this_thread::sleep_for(std::chrono::milliseconds(50)); return port; return rs; } // Signals the server's event loop to stop and blocks until its worker // threads have actually joined and torn down (see RunningServer above for // why this must happen before the next server starts or the process exits). void stopServer(RunningServer &rs) { netplus::event::requestStop(); if (rs.thread.joinable()) rs.thread.join(); } } // namespace Loading @@ -223,9 +249,10 @@ int main() { // Run 1: h2OffloadThreads = 0 — must reproduce the staircase, proving // the untouched synchronous fallback path still behaves as before. { std::unique_ptr<netplus::tcp> sock; int port = startServer(sock, /*h2OffloadThreads=*/0); int port = 0; RunningServer rs = startServer(port, /*h2OffloadThreads=*/0); auto times = runConcurrentRequest(port, streamIds, delayMs); stopServer(rs); auto totalMs = std::chrono::duration_cast<std::chrono::milliseconds>( times.back() - times.front()).count(); std::cout << "[sync] spread=" << totalMs << "ms (expect >= ~" Loading @@ -246,9 +273,10 @@ int main() { // Run 2: h2OffloadThreads > 0 with shouldOffloadH2Dispatch() true — // must show the streams completing with overlapping wall clocks. { std::unique_ptr<netplus::tcp> sock; int port = startServer(sock, /*h2OffloadThreads=*/4); int port = 0; RunningServer rs = startServer(port, /*h2OffloadThreads=*/4); auto times = runConcurrentRequest(port, streamIds, delayMs); stopServer(rs); auto totalMs = std::chrono::duration_cast<std::chrono::milliseconds>( times.back() - times.front()).count(); std::cout << "[offload] spread=" << totalMs << "ms (expect < ~" Loading @@ -262,5 +290,5 @@ int main() { } std::cout << (failures == 0 ? "PASS" : "FAIL") << std::endl; std::exit(failures == 0 ? 0 : 1); return failures == 0 ? 0 : 1; } Loading
test/httpd_h2_concurrency_test.cpp +50 −22 Changes for test/httpd_h2_concurrency_test.cpp: 50 added lines, 22 removed lines. Original line number Diff line number Diff line Loading @@ -189,27 +189,53 @@ std::vector<std::chrono::steady_clock::time_point> runConcurrentRequest( return times; } // Binds an ephemeral-port TCP listener, starts a SlowServer on it in a // background thread, and returns the bound port. The server thread is // intentionally never joined — the test process exits via std::exit once // all assertions are done, which is fine for a short-lived test binary and // avoids netplus::event::Running (a process-wide static) making a second, // sequential runEventloop() call impossible after the first is stopped. int startServer(std::unique_ptr<netplus::tcp> &sockOwner, size_t h2OffloadThreads) { sockOwner = std::make_unique<netplus::tcp>("127.0.0.1", 0, 16, -1); sockOwner->bind(); // A running server plus everything needed to tear it down deterministically. // netplus::event's connection registry, wakeup eventfd and Running/Restart // flags are process-wide statics (needed elsewhere so the H2-offload dispatch // can look up/rearm a connection from any thread by fd). That means a // SlowServer's background threads keep touching that shared static state for // as long as they're alive — they must be fully stopped and joined before the // next server starts (or before the process exits), or a worker thread can be // mid-access when those statics get destroyed, which segfaults. struct RunningServer { std::unique_ptr<netplus::tcp> sock; std::unique_ptr<SlowServer> server; std::thread thread; }; // Binds an ephemeral-port TCP listener and starts a SlowServer on it in a // background thread. RunningServer startServer(int &port, size_t h2OffloadThreads) { RunningServer rs; rs.sock = std::make_unique<netplus::tcp>("127.0.0.1", 0, 16, -1); rs.sock->bind(); sockaddr_in bound{}; socklen_t blen = sizeof(bound); ::getsockname(sockOwner->fd(), reinterpret_cast<sockaddr*>(&bound), &blen); int port = ntohs(bound.sin_port); sockOwner->listen(); auto *server = new SlowServer({sockOwner.get()}, h2OffloadThreads); std::thread([server] { server->runEventloop(); }).detach(); ::getsockname(rs.sock->fd(), reinterpret_cast<sockaddr*>(&bound), &blen); port = ntohs(bound.sin_port); rs.sock->listen(); rs.server = std::make_unique<SlowServer>( std::vector<netplus::socket*>{rs.sock.get()}, h2OffloadThreads); // requestStop() (see stopServer()) leaves this false; a fresh // runEventloop() call needs it true again to not exit its worker loop // immediately. netplus::event::Running = true; SlowServer *serverPtr = rs.server.get(); rs.thread = std::thread([serverPtr] { serverPtr->runEventloop(); }); std::this_thread::sleep_for(std::chrono::milliseconds(50)); return port; return rs; } // Signals the server's event loop to stop and blocks until its worker // threads have actually joined and torn down (see RunningServer above for // why this must happen before the next server starts or the process exits). void stopServer(RunningServer &rs) { netplus::event::requestStop(); if (rs.thread.joinable()) rs.thread.join(); } } // namespace Loading @@ -223,9 +249,10 @@ int main() { // Run 1: h2OffloadThreads = 0 — must reproduce the staircase, proving // the untouched synchronous fallback path still behaves as before. { std::unique_ptr<netplus::tcp> sock; int port = startServer(sock, /*h2OffloadThreads=*/0); int port = 0; RunningServer rs = startServer(port, /*h2OffloadThreads=*/0); auto times = runConcurrentRequest(port, streamIds, delayMs); stopServer(rs); auto totalMs = std::chrono::duration_cast<std::chrono::milliseconds>( times.back() - times.front()).count(); std::cout << "[sync] spread=" << totalMs << "ms (expect >= ~" Loading @@ -246,9 +273,10 @@ int main() { // Run 2: h2OffloadThreads > 0 with shouldOffloadH2Dispatch() true — // must show the streams completing with overlapping wall clocks. { std::unique_ptr<netplus::tcp> sock; int port = startServer(sock, /*h2OffloadThreads=*/4); int port = 0; RunningServer rs = startServer(port, /*h2OffloadThreads=*/4); auto times = runConcurrentRequest(port, streamIds, delayMs); stopServer(rs); auto totalMs = std::chrono::duration_cast<std::chrono::milliseconds>( times.back() - times.front()).count(); std::cout << "[offload] spread=" << totalMs << "ms (expect < ~" Loading @@ -262,5 +290,5 @@ int main() { } std::cout << (failures == 0 ? "PASS" : "FAIL") << std::endl; std::exit(failures == 0 ? 0 : 1); return failures == 0 ? 0 : 1; }