Skip to content

fix(resolver): keep Winsock initialized for the process lifetime - #356

Merged
sgerbino merged 1 commit into
cppalliance:developfrom
sgerbino:fix/resolver-winsock-lifetime
Sep 10, 2026
Merged

sgerbino merged 1 commit into
cppalliance:developfrom
sgerbino:fix/resolver-winsock-lifetime

Conversation

@sgerbino

@sgerbino sgerbino commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

win_wsa_init reference counts WSAStartup/WSACleanup across the services that derive from it, so the count reaches zero every time the last io_context in a process is destroyed. An asynchronous GetAddrInfoExW is still settling inside ws2_32 well past its completion routine, with no way to observe when, and the WSACleanup lands under it: the fault is an access violation on a system DNS thread, with no corosio frame on the stack. That is the intermittent boost.corosio.resolver SEGFAULT on Windows.

Hold one reference until static destruction so the count never falls back to zero between one io_context and the next. Suppressing the WSACleanup alone took the failure rate from 9/15 runs to 0/15 on a repro that tears down a context with a lookup still in flight.

The earlier reorder of post() against work_finished() addressed a different, and as it turns out inapplicable, theory. Drop it in favour of the reactor's own convention: the work credit taken at initiation rides with the op and is released where the op is consumed, on the scheduler thread, so no foreign thread balances the count and the count cannot reach zero with a completion still queued. Both directions now match posix_resolver_service.

Two further defects on the same path:

  • The GetAddrInfoExCancel handle is valid only until the completion routine is entered or the first cancel consumes it, but it was cleared in do_complete, which runs on the scheduler and may never run at all. A resolver destroyed with a lookup pending therefore handed Windows a handle it had already reclaimed. Retire it in the completion routine, and let cancel() claim it, so exactly one of the two owns it. The claim is taken under a lock but the cancel itself is issued outside it, because GetAddrInfoExCancel can wait on the completion routine.

  • The ADDRINFOEXW hints were a local of resolve(), passed to a lookup that outlives that frame. Move them into the op.

The new test tears down a context with the lookup still live in the system resolver rather than already queued, which the existing test does not reach: it resolves localhost, which the hosts file answers synchronously.

win_wsa_init reference counts WSAStartup/WSACleanup across the services
that derive from it, so the count reaches zero every time the last
io_context in a process is destroyed. An asynchronous GetAddrInfoExW is
still settling inside ws2_32 well past its completion routine, with no
way to observe when, and the WSACleanup lands under it: the fault is an
access violation on a system DNS thread, with no corosio frame on the
stack. That is the intermittent boost.corosio.resolver SEGFAULT on
Windows.

Hold one reference until static destruction so the count never falls back
to zero between one io_context and the next. Suppressing the WSACleanup
alone took the failure rate from 9/15 runs to 0/15 on a repro that tears
down a context with a lookup still in flight.

The earlier reorder of post() against work_finished() addressed a
different, and as it turns out inapplicable, theory. Drop it in favour of
the reactor's own convention: the work credit taken at initiation rides
with the op and is released where the op is consumed, on the scheduler
thread, so no foreign thread balances the count and the count cannot
reach zero with a completion still queued. Both directions now match
posix_resolver_service.

Two further defects on the same path:

  - The GetAddrInfoExCancel handle is valid only until the completion
    routine is entered or the first cancel consumes it, but it was
    cleared in do_complete, which runs on the scheduler and may never run
    at all. A resolver destroyed with a lookup pending therefore handed
    Windows a handle it had already reclaimed. Retire it in the
    completion routine, and let cancel() claim it, so exactly one of the
    two owns it. The claim is taken under a lock but the cancel itself is
    issued outside it, because GetAddrInfoExCancel can wait on the
    completion routine.

  - The ADDRINFOEXW hints were a local of resolve(), passed to a lookup
    that outlives that frame. Move them into the op.

The new test tears down a context with the lookup still live in the
system resolver rather than already queued, which the existing test does
not reach: it resolves localhost, which the hosts file answers
synchronously.
@cppalliance-bot

Copy link
Copy Markdown

An automated preview of the documentation is available at https://356.corosio.prtest3.cppalliance.org/index.html

If more commits are pushed to the pull request, the docs will rebuild at the same URL.

2026-09-09 20:34:50 UTC

@cppalliance-bot

Copy link
Copy Markdown

GCOVR code coverage report https://356.corosio.prtest3.cppalliance.org/gcovr/index.html
LCOV code coverage report https://356.corosio.prtest3.cppalliance.org/genhtml/index.html
Coverage Diff Report https://356.corosio.prtest3.cppalliance.org/diff-report/index.html

Build time: 2026-09-09 20:54:17 UTC

@sgerbino
sgerbino merged commit 039fe65 into cppalliance:develop Sep 10, 2026
43 checks passed
@sgerbino
sgerbino deleted the fix/resolver-winsock-lifetime branch September 10, 2026 13:56
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Beast2 Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants