CNA-GAP-070: NetworkSession's Begin/End pairs are synchronous: the result is complete and the callback has run when Begin returns, and End does the work on the caller's thread

CNA snapshot 009d40f5  ·  Known Issues › Functional gaps  ·  source links pinned to 009d40f5

✓

Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.

NetworkSession's BeginCreate, BeginFind and BeginJoin each return an already-completed IAsyncResult (CompletedSynchronously still false) and run their AsyncCallback inline; End* then blocks the caller: 150 ms for a native SystemLink Find, up to 5 s for a native Join. BeginJoinInvited is not part of this: it refuses outright.

Identifier
CNA-GAP-070
Category
Functional gap
Subsystem
Networking & gamer services
Status
Open
Verified against
CNA 009d40f5 (009d40f5dd085c4e674d3479675fac84b12b3e0a)
Evidence basis
Source-verified: read at TARGET, not executed
Tests touching this area
Yes: see Current tests
Affected contract
Microsoft::Xna::Framework::Net::NetworkSession::BeginCreate/BeginFind/BeginJoin/BeginJoinInvited and the matching End* methods

Expected behaviour

XNA 4.0 (read from the decompiled Microsoft.Xna.Framework.Net assembly and its async dispatcher; not executed) registers each Begin* operation as pending: IsCompleted stays false until a dispatcher thread finishes it, the wait handle is signalled then, the AsyncCallback is queued to the thread pool, and End* returns the finished result.

Actual behaviour at TARGET

In NetworkSession.cpp the NetworkSessionAction is constructed already complete (IsCompleted true, wait handle signalled) and BeginCreate, BeginFind and BeginJoin invoke the AsyncCallback inline on the calling thread before they return, while CompletedSynchronously stays false as in XNA (the header comment says the class "never completes synchronously"). The real work runs in End* on the caller's thread: on native builds EndFind waits a fixed 150 ms discovery window for SystemLink and EndJoin pumps the session for up to 5 s until the host's welcome arrives. A game that polls IsCompleted every frame and calls End* when it turns true therefore blocks there. The deviation is deliberate and recorded in source comments (a pending action hung the polling loop once a GamerServicesComponent existed). BeginJoinInvited (both overloads) is the exception: it throws GamerServicesNotAvailableException before creating an action, and EndJoinInvited throws ArgumentException.

Source locations

Evidence

Read at 009d40f5; nothing executed. The published deep dive on network sessions records the completed-at-construction behaviour as an implementation oddity; this entry records its consequence for callers who port XNA's asynchronous pattern. Reported as a narrow restore by the D2 dismissal reviewer, who had confirmed that CompletedSynchronously being false matches XNA, and re-verified by the orchestrator.

Focused reproduction

No focused reproduction is known. Nothing has been invented here; the evidence above is what exists.

Current tests

NetworkSessionTests.BeginCreateInvokesCallbackExactlyOnceWithCorrectIdentity pins the inline callback; no test measures the time EndFind or EndJoin block.

Blast radius

Games and samples that start a Begin* operation and keep rendering while they wait: with CNA the wait moves into End*, so the frame that calls it stalls for up to the discovery window or the join timeout.

Workaround

Call End* from a worker thread or accept the stall on a loading screen; do not call it from the frame loop.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.