Summary
1.5.0 segfaults on MSVC / Windows (IOCP) the moment a WebSocket client connects to a ws::server.
1.4.0 is clean with identical source. Reproduced with your own examples/15-WS.cpp + examples/16-WS-Client.cpp, unmodified, against a pristine git worktree of tag 1.5.0 (60290b4) — git status clean, no local patches of any kind.
Flagging it because 1.5.0 is tagged stable and frozen, and — from #35 — you're testing on MinGW + Wine, so this may be invisible on your side. We have a real MSVC box and are happy to run anything you want against it.
Environment
MSVC 2022 (v19.x), x64, /std:c++20 /EHsc /GR /O2 /MT, -DNOMINMAX -DWIN32_LEAN_AND_MEAN. OpenSSL + zlib from vcpkg x64-windows-static.
Result
Pristine tag 1.5.0 (60290b4):
[t= 0 ms] server started at http://localhost:8000
[t= 511 ms] alive
[t= 1012 ms] alive
[t= 1513 ms] alive
[t= 2014 ms] alive
[t= 2280 ms] connected
SIGSEGV: Segmentation Violation
[t= 2280 ms] closed
Control — identical source, identical compiler and flags, only the nodepp include path changed — pristine tag 1.4.0 (e9b1866):
[t= 2073 ms] connected
ping from client <-- client -> server data arrives fine
[t= 8073 ms] closed
[t= 10002 ms] survived to 10s -- clean exit
| tree |
WS client connects |
1.4.0 (e9b1866) |
clean — bidirectional traffic, 10s, exit 0 |
1.5.0 (60290b4) |
SIGSEGV, immediately, every run |
Layer isolation on 1.5.0: an HTTP client hitting a plain http::server is clean. The crash only appears once ws::server() is layered on. So the accept/socket path is healthy; it's the WS upgrade path.
Where we'd look — a suspect, not a diagnosis
1.5.0 changed onSocket from event_t<socket_t> to event_t<ptr_t<tcp_t>, socket_t> (tcp.h:44, tls.h:45), and ws::server() now does its work inside a deferred process::add(...) task that captures that self pointer:
skt.onSocket([=]( ptr_t<tcp_t> self, socket_t raw ){
http_t hrv = raw;
if( !generator::ws::server( hrv ) ){ self->onConnect.skip(); return; }
ws_t cli = raw;
process::add([=](){
cli.set_timeout(0); cli.resume();
cli.set_mask(0);
self->onConnect.resume();
self->onConnect.emit(cli); // <-- self used after onSocket has already returned
stream::pipe(cli);
return -1; });
});
The crash lands right at onConnect. self being dereferenced from the deferred task, after the onSocket handler has returned, is our first suspect — but we have no debugger on this box and have not confirmed it. Treat it as a pointer, not a conclusion.
One trap worth knowing about
nodepp's own SIGSEGV handler (signal.h:36) prints and then calls process::exit(0) — so a crashed process still exits with status 0. Any CI or test harness grading on exit code will score a segfault as a pass. It briefly fooled us. Grade on the log line.
Repro
Your examples/15-WS.cpp and examples/16-WS-Client.cpp, split into two processes so the client is genuinely separate, with a heartbeat and a timed exit added so the run terminates. Nothing else changed.
server (your 15-WS.cpp)
#include <nodepp/nodepp.h>
#include <nodepp/timer.h>
#include <nodepp/http.h>
#include <nodepp/ws.h>
using namespace nodepp;
static ulong T0 = 0;
static void mark( const char* w ){ console::log("[t=",(ulong)(process::millis()-T0),"ms]",string_t(w)); }
void onMain() {
T0 = process::millis();
auto server = http::server([=]( http_t cli ){
cli.write_header( 200, header_t({ { "Content-Security-Policy", "*" } }) );
cli.write("Hello World!");
}); ws::server( server );
server.onConnect([=]( ws_t cli ){
mark("connected");
cli.onData([=]( string_t data ){ cli.write( "<: received" ); console::log( data ); });
cli.onClose([=](){ mark("closed"); });
});
server.onError([=]( except_t err ){ console::log( ">>", err.what() ); });
server.listen( "localhost", 8000, [=]( socket_t server ){
mark("server started at http://localhost:8000");
});
timer::interval( [](){ mark("alive"); }, 500 );
timer::timeout ( [](){ mark("survived to 10s -- clean exit"); process::exit(0); }, 10000 );
}
client (your 16-WS-Client.cpp)
#include <nodepp/nodepp.h>
#include <nodepp/timer.h>
#include <nodepp/ws.h>
using namespace nodepp;
void onMain() {
auto client = ws::client( "ws://localhost:8000/" );
client.onConnect([=]( ws_t cli ){
console::log("connected" );
cli.onData([]( string_t data ){ console::log( data ); });
cli.onClose([=](){ console::log("closed"); });
timer::timeout([=](){ ws_t c = cli; c.write("ping from client"); }, 500 );
});
client.onError([=]( except_t err ){ console::log( "<>", err.data() ); });
timer::timeout( [](){ console::log("client done"); process::exit(0); }, 6000 );
}
Start the server, then run the client.
Summary
1.5.0segfaults on MSVC / Windows (IOCP) the moment a WebSocket client connects to aws::server.1.4.0is clean with identical source. Reproduced with your ownexamples/15-WS.cpp+examples/16-WS-Client.cpp, unmodified, against a pristinegit worktreeof tag1.5.0(60290b4) —git statusclean, no local patches of any kind.Flagging it because
1.5.0is tagged stable and frozen, and — from #35 — you're testing on MinGW + Wine, so this may be invisible on your side. We have a real MSVC box and are happy to run anything you want against it.Environment
MSVC 2022 (v19.x), x64,
/std:c++20 /EHsc /GR /O2 /MT,-DNOMINMAX -DWIN32_LEAN_AND_MEAN. OpenSSL + zlib from vcpkgx64-windows-static.Result
Pristine tag
1.5.0(60290b4):Control — identical source, identical compiler and flags, only the nodepp include path changed — pristine tag
1.4.0(e9b1866):1.4.0(e9b1866)1.5.0(60290b4)Layer isolation on
1.5.0: an HTTP client hitting a plainhttp::serveris clean. The crash only appears oncews::server()is layered on. So the accept/socket path is healthy; it's the WS upgrade path.Where we'd look — a suspect, not a diagnosis
1.5.0changedonSocketfromevent_t<socket_t>toevent_t<ptr_t<tcp_t>, socket_t>(tcp.h:44,tls.h:45), andws::server()now does its work inside a deferredprocess::add(...)task that captures thatselfpointer:The crash lands right at
onConnect.selfbeing dereferenced from the deferred task, after theonSockethandler has returned, is our first suspect — but we have no debugger on this box and have not confirmed it. Treat it as a pointer, not a conclusion.One trap worth knowing about
nodepp's own
SIGSEGVhandler (signal.h:36) prints and then callsprocess::exit(0)— so a crashed process still exits with status 0. Any CI or test harness grading on exit code will score a segfault as a pass. It briefly fooled us. Grade on the log line.Repro
Your
examples/15-WS.cppandexamples/16-WS-Client.cpp, split into two processes so the client is genuinely separate, with a heartbeat and a timed exit added so the run terminates. Nothing else changed.server (your 15-WS.cpp)
client (your 16-WS-Client.cpp)
Start the server, then run the client.