http://bugs.winehq.org/show_bug.cgi?id=60315 Bug ID: 60315 Summary: NotifyAddrChange event is signaled while OVERLAPPED state remains pending after the issuing thread exits Product: Wine Version: 11.17 Hardware: x86-64 OS: Linux Status: UNCONFIRMED Severity: normal Priority: P2 Component: ntdll Assignee: wine-bugs@list.winehq.org Reporter: steve.kontakt@outlook.de Target Milestone: --- Distribution: --- Created attachment 82070 --> http://bugs.winehq.org/attachment.cgi?id=82070 Experimental server APC lifecycle rule (unified diff; not signed off or merge-ready) A terminal asynchronous NotifyAddrChange() completion can be lost when the thread that issued the request exits while another thread in the same process survives. This is reproducible on Wine 11.0 and on unmodified Wine master commit 490f6d5dcbb2a5047345b8af88d114bbcaad69a8 (reported by that build as Wine 11.17), in both x86 and x64. Steam is not required for the reproducer. Reproduction contract --------------------- 1. A worker thread creates a private event and issues asynchronous NotifyAddrChange() using an OVERLAPPED structure. 2. NotifyAddrChange() returns ERROR_IO_PENDING. 3. The worker exits with a known exit code. 4. The main thread waits for the worker and then for the private event. 5. The main thread inspects OVERLAPPED.Internal and calls GetOverlappedResult(). Step 3 is an explicit self-exit through ExitThread(42), not an external TerminateThread() call. In the pinned Wine source, ExitThread() reaches RtlExitUserThread(), which calls NtTerminateThread(GetCurrentThread(), status). NtTerminateThread() then submits the Wine server terminate_thread request. The experimental rule therefore changes the handler used by this reproduced self-exit path. Result on unmodified Wine master -------------------------------- Both x86 and x64 fail in the first round: owner thread exit: expected exit code observed private OVERLAPPED event: signaled OVERLAPPED.Internal: STATUS_PENDING GetOverlappedResult: FALSE / ERROR_IO_INCOMPLETE (996) The event therefore reports completion while the caller-visible I/O status is still pending. Native Windows reference ------------------------ The identical C source was compiled with MSVC and run on a fresh GitHub-hosted Windows Server 2025 Datacenter 24H2 runner, build 26100.33296. It completed 8/8 x86 and 8/8 x64 rounds. In every native round: owner thread exit: expected exit code observed private OVERLAPPED event: signaled OVERLAPPED.Internal: no longer STATUS_PENDING GetOverlappedResult: FALSE / ERROR_OPERATION_ABORTED (995) No fixed delay or network configuration change was used. The source SHA-256 was 6bbb38db24ec446bc1ef6659a5642b14b911dffd909b7c18a0136e88fecc830c. Isolated Wine A/B evidence -------------------------- An A/B build changed only the Wine server. WinHTTP, kernel32, the test program, and all other runtime files were byte-identical. Variant x86 x64 Total Stock pinned master failed round 1 failed incomplete Experimental server lifecycle rule 32/32 32/32 64/64 Instrumented diagnostic runs indicated that the system APC responsible for publishing terminal I/O state can be assigned after termination has already been requested for the target thread. The thread is still represented as running at selection time but can be destroyed before dequeuing that APC. The required behavioral invariant is that a system APC which publishes terminal asynchronous I/O state must not be newly assigned to a thread after termination has become irrevocable. The exact upstream implementation is deliberately left to a Wine developer. In the experimental rule, the terminating field is written by the terminate_thread request handler and read by queue_apc() within the Wine server's serialized request/event dispatch. Client threads do not access this field directly. Real application impact ----------------------- The defect was discovered while reducing a SteamCMD self-update hang. A separate WinHTTP rundown defect is also required to explain that full application symptom. It will be filed separately and linked here after it receives a bug ID. Related reports checked ----------------------- Bug 50704 corrected the public QueueUserAPC() error for a terminating thread. It is useful lifecycle precedent, but it does not cover system-APC completion routing or the inconsistent OVERLAPPED state reported here: https://bugs.winehq.org/show_bug.cgi?id=50704 Bug 45116 concerns the historical absence of a working NotifyAddrChange() implementation. It does not report this owner-thread-exit completion inconsistency: https://bugs.winehq.org/show_bug.cgi?id=45116 Bug 60245 concerns Wine retiring a threadpool worker while that worker still owns pending overlapped I/O. Its native reference explicitly confirms that ordinary issuing-thread termination cancels event-backed I/O with ERROR_OPERATION_ABORTED. The present report begins at that expected cancellation and concerns Wine signaling the event without publishing the terminal status to the issuing process's OVERLAPPED state: https://bugs.winehq.org/show_bug.cgi?id=60245 Bug 37962 concerns ws2_32 being unloaded while an asynchronous DNS callback remains pending, resulting in a crash. It is related module-lifetime precedent, but does not cover owner-thread exit, system-APC completion routing, or this event/status inconsistency: https://bugs.winehq.org/show_bug.cgi?id=37962 A direct Wine Bugzilla search, including closed reports, found no exact duplicate for the measured state. Test controls ------------- - x86 and x64 were tested. - Wine 11.0 and pinned Wine master were tested. - Fresh prefixes and isolated containers were used. - The decisive test used no Steam, WinHTTP patch, ptrace, strace, inotify, diagnostic logging, process polling, or fixed sleep as a repair. - Stock and experimental variants were identity-checked by SHA-256. - Native Windows source and result identities were recorded. Provenance and request ---------------------- This investigation, its diagnostic programs, and the experimental downstream patch were developed through substantial collaboration between me and OpenAI Codex. I directed the investigation, ran the tests, challenged hypotheses, and checked the resulting behavior. Codex materially assisted with source analysis, test construction, and construction of the experimental patch. The exact experimental unified diff is attached for technical inspection. It is not presented as a signed-off or merge-ready Wine contribution, and it has no Signed-off-by line. A Wine developer is invited to review it and decide whether to adapt it, reimplement the rule independently, or take another approach. To the best of my knowledge, no external implementation was copied; the work was based on public Wine source and the measured behavior described here. The diff contains only the implementation change and one ordinary source comment on the new terminating field: "termination has been requested". The comment records the field's lifecycle meaning so that reviewers can identify the affected state quickly; provenance is disclosed here rather than embedded in the source code. The complete raw A/B output, exact source identities, and a prose description of the required lifecycle invariant are available as attachments. No Microsoft binaries were disassembled and no Microsoft source code was examined during this investigation. -- Do not reply to this email, post in Bugzilla using the above URL to reply. You are receiving this mail because: You are watching all bug changes.