CNA-BUG-050: Guide message-box and keyboard-input results expose an AsyncWaitHandle that is already signalled while the answer is pending

CNA snapshot c1c316b9  ·  Known Issues › Current bugs  ·  source links pinned to c1c316b9

✓

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 c1c316b9; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.

Guide's message-box and keyboard-input results construct their AsyncWaitHandle already signalled, so WaitOne returns while IsCompleted is still false; End* now waits by running the game's modal frames and refuses only when no running game shows the Guide.

Identifier
CNA-BUG-050
Category
Bug
Subsystem
Networking & gamer services
Status
Narrowed (partially fixed; describes only what survives)
Verified against
CNA c1c316b9 (c1c316b9c7a846ce8002809c151fcd1af14942c9)
Severity
Low (a triage suggestion, not a project priority)
Evidence basis
Source-verified: read at TARGET, not executed
Tests touching this area
Yes: see Current tests
Affected contract
getAsyncWaitHandleProperty() and getIsCompletedProperty() of the IAsyncResult returned by Guide::BeginShowMessageBox and Guide::BeginShowKeyboardInput; EndShowMessageBox and EndShowKeyboardInput

Expected behaviour

The IAsyncResult contract: AsyncWaitHandle is signalled when the operation completes, so waiting on it and then calling End* yields the result. XNA's GamerServicesDispatcher.Update advances pending gamer-services work, and the synchronous-wrapper pattern CNA inherited from FNA (while (!result->IsCompleted) UpdateAsync();) relies on something advancing it.

Actual behaviour at TARGET

Guide.cpp's GuideAction, the base of the message-box and keyboard-input results, constructs its EventWaitHandle signalled (asyncWaitHandle_(true, ManualReset)), and nothing resets it, so WaitOne() returns at once while IsCompleted is still false. End* no longer throws while the answer is pending: since 70d6f109e it claims the result and runs the game's modal frames until the Guide is answered, and it throws InvalidOperationException only when no running game presents the Guide. GamerServicesDispatcher::Update now pumps service work, but the answer comes only from the Guide drawn in the game's frames, so a thread that waits on the handle and then calls End* still blocks in End*, or gets the exception, and a game-thread loop that pumps GamerServicesDispatcher::UpdateAsync() until IsCompleted never ends.

Source locations

Evidence

Checked by reading at c1c316b9; not executed. The pre-signalled wait handle was found while re-verifying this entry; the EventWaitHandle(bool initialState, EventResetMode) constructor stores initialState as the signalled state in a sibling Sharp Runtime checkout (next at 41b918c9, not pinned by CNA).

Re-verified at b0e97bb1: GamerServicesDispatcher::Update pumps the service backend (0ab6cc3fe, 73011288e), so the original hang (session and achievement results that nothing advanced) is gone, and End* of a Guide result waits through PrepareForEnd (70d6f109e); GuideAction still constructs its wait handle signalled. Not executed.

Focused reproduction

// Illustrative; not compiled or run for this entry.
using namespace Microsoft::Xna::Framework::GamerServices;
System::IAsyncResult* r = Guide::BeginShowMessageBox(
    "Quit?", "Unsaved progress will be lost.", {"Quit", "Cancel"}, 1,
    MessageBoxIcon::None, System::AsyncCallback{}, std::any{});
r->getAsyncWaitHandleProperty().WaitOne();    // returns at once: constructed signalled, IsCompleted still false
auto choice = Guide::EndShowMessageBox(r);    // runs the game's modal Guide frames until answered;
                                              // InvalidOperationException when no running game presents the Guide
// Pumping the dispatcher instead never ends on the game thread (it does not draw the Guide):
// while (!r->getIsCompletedProperty()) GamerServicesDispatcher::UpdateAsync();

Current tests

GamerServicesServiceTests.cpp covers pending state, completion through the simulation helpers and injected text input, callbacks and re-entrancy; no test reads the wait handle of a Guide result or waits through the dispatcher. The dispatcher harness covers session creation and achievements (plus two initialisation modes), not the Guide.

Regression test

A test that begins a message box and asserts that a zero-timeout wait on its handle reports not signalled until SimulateMessageBoxClickEXT, and signalled afterwards; the same for a keyboard request completed by an injected Enter. For the dispatcher, either document Update/UpdateAsync as unable to complete Guide operations, or give them something observable to drive, with a harness case like the existing ones.

Blast radius

Code that waits for a Guide message box or keyboard input synchronously: by pumping the dispatcher while polling IsCompleted (FNA's wrapper pattern) or with WaitOne() followed by End. Callback-driven code and code that polls IsCompleted across frames while drawing the dialog, the pattern CNA documents, are unaffected, as are network, achievement, leaderboard and profile calls.

Workaround

Poll IsCompleted once per frame, call RenderPendingMessageBoxEXT/RenderPendingKeyboardInputEXT from Draw, and call End only when IsCompleted is true, or use the completion callback.

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

Known issues
Bug index