Skip to content

Move async socket readiness to WSAPoll backends

Historical reference only: stale-v0.72a-experimental-clean and analysis\stale-v0.72a-experimental-clean are retired reference sources, not active branch targets or current baselines. Use them only as provenance or idea-extraction sources; landed status is determined against main. See Historical References.

Summary

Move eMuleBB's async socket readiness away from legacy helper-window / WSAAsyncSelect dispatch and toward explicit WSAPoll-owned backends.

The first concrete implementation target remains UDP via CAsyncDatagramSocket, because that is the available experimental reference and the least invasive way to remove helper-window readiness from high-volume datagram paths. The backlog scope is now explicitly broader: after the UDP bridge is validated, the TCP CAsyncSocketEx readiness path should be audited and either aligned to the same WSAPoll ownership model or retained with a documented reason.

CClientUDPSocket and CUDPSocket currently use WSAAsyncSelect (window-message-based socket readiness) inherited from CAsyncSocketEx. This model is:

  • Tied to the hidden helper window of CAsyncSocketEx, a legacy Win32 design
  • Serialized through the main thread's message pump for readiness events
  • Single-threaded for all UDP I/O readiness notifications
  • Subject to message-queue back-pressure under high load

The experimental branch implements CAsyncDatagramSocket, a new class that owns a UDP socket on a dedicated WSAPoll backend thread, marshalling readiness back to the main thread via PostMessage only for protocol handling. This eliminates the helper-window dependency for UDP while retaining backward compatibility with the protocol handler layer.

The async-socket modernization lane should use this UDP work as the first repeatable pattern, then review TCP readiness, listen/accept, connect, close, and send-buffer paths for the same helper-window and message-queue pressure risks.

Scope Update - Shared Async Socket Lane

The implementation should be treated as staged socket infrastructure work:

  1. UDP WSAPoll bridge: introduce or revalidate CAsyncDatagramSocket, switch CClientUDPSocket and CUDPSocket, and preserve app-thread protocol handling.
  2. TCP readiness audit: document the current CAsyncSocketEx helper-window, readiness, connect, close, and send paths against current main.
  3. TCP WSAPoll alignment: if the audit confirms the helper-window path is still active or still a bottleneck, move TCP readiness to the same explicit poll backend while preserving current socket owner and teardown semantics.
  4. Lifecycle hardening: ensure socket registration, deregistration, close, and owner teardown cannot dispatch callbacks into freed socket or owner objects.
  5. Future IOCP decision: keep IOCP or a larger socket rewrite as a separate future decision. WSAPoll is the pragmatic compatibility-preserving bridge, not necessarily the final high-scale Windows socket design.

Experimental Reference Implementation

Source: stale-v0.72a-experimental-clean, commit b6ffaa6 FIX: move UDP sockets onto the WSAPoll backend (2026-04-01, 595 insertions / 378 deletions)

New files: - srchybrid/AsyncDatagramSocket.hCAsyncDatagramSocket class inheriting from CAsyncSocketEx, owning a WSAPoll-based receive thread - srchybrid/AsyncDatagramSocket.cpp — implementation (101 lines) - srchybrid/AsyncDatagramSocketSeams.h — test seam interface (24 lines)

Modified files (major changes): - srchybrid/ClientUDPSocket.cpp — switched to CAsyncDatagramSocket; reorganized send/receive paths (245 lines changed) - srchybrid/UDPSocket.cppCUDPSocket switched to WSAPoll backend (391 lines changed) - srchybrid/AsyncSocketEx.cpp / .h — small extensions to support the datagram subclass - srchybrid/EmuleDlg.cpp / .h — WSAPoll backend lifecycle management

Architecture:

[Old]  UDP socket → WSAAsyncSelect → helper window → WM_SOCKET → OnReceive/OnSend
[New]  UDP socket → WSAPoll thread → PostMessage(WM_UDPREADY) → OnReceive/OnSend

The WSAPoll thread polls the socket fd with a configurable timeout, fires PostMessage to the main window on readiness, and the main thread calls RecvFrom/SendTo as before.

Benefits

  • Removes UDP I/O from the main message pump hot path
  • WSAPoll scales better than WSAAsyncSelect under many concurrent events
  • Matches the community-0.72 direction (they use IOCP for disk; WSAPoll is the next step)
  • Prerequisite for any future multi-socket or IPv6/dual-stack work
  • Eliminates the hidden helper window dependency for UDP (partial WWMOD_013 resolution)
  • Creates one documented async-socket readiness pattern for later TCP review
  • Makes socket stress evidence easier to reason about by separating readiness ownership from UI message dispatch
  • WWMOD_013 — historical WSAAsyncSelect networking model: REF-029 resolves the UDP half of this; TCP (CAsyncSocketEx) remains on WSAAsyncSelect until REF-008 (Boost.Asio)
  • CI-001 (CMake) — easier to validate cross-platform with CMake + CTest
  • BUG-017 (UDP throttler deadlock) — the WSAPoll migration changes the send path; port BUG-017 fix before or in the same PR
  • FEAT-035 — IPv6 dual-stack work should not grow the old readiness model; this refactor is a prerequisite or companion for broad socket-family changes
  • CI-019, CI-021, and CI-029 — socket adversity and leak-churn evidence should be reused or extended for this migration

Acceptance Criteria

  • [ ] CAsyncDatagramSocket implemented and passes smoke tests
  • [ ] CClientUDPSocket switched to CAsyncDatagramSocket
  • [ ] CUDPSocket switched to CAsyncDatagramSocket
  • [ ] Helper window not used for UDP readiness events
  • [ ] TCP CAsyncSocketEx readiness, connect, send, close, and teardown paths audited against current main
  • [ ] If TCP remains on helper-window dispatch, the reason is documented with measurable risk and migration blockers
  • [ ] If TCP is moved to WSAPoll, socket callbacks preserve existing owner threading and lifetime contracts
  • [ ] No regression in Kad UDP (send/receive, obfuscation, throughput)
  • [ ] No regression in eMule UDP (QueueFull, ReAsk, FileNotFound)
  • [ ] Raw socket adversity, WebSocket/REST socket churn, and live UDP protocol smoke evidence pass after migration

Validation

  • Focused native socket/unit tests for registration, readiness, close, and teardown edge cases.
  • UDP protocol parity smoke for Kad and server/client UDP paths.
  • REST/WebSocket raw socket adversity smoke if TCP readiness is touched.
  • Live E2E release socket adversity suites when the change touches shared socket lifecycle or shutdown behavior.