CNA-BUG-169: NetworkSession::GamerJoined reports a gamer added with AddLocalGamer twice to a handler subscribed between that call and the next Update

CNA snapshot 009d40f5  ·  Known Issues › Current bugs  ·  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.

AddLocalGamer inserts the new gamer into AllGamers at once and queues its GamerJoin event for the next Update, while subscribing replays GamerJoined for every gamer already in AllGamers, so a handler subscribed in that window is called twice for the new gamer.

Identifier
CNA-BUG-169
Category
Bug
Subsystem
Networking & gamer services
Status
Open
Verified against
CNA 009d40f5 (009d40f5dd085c4e674d3479675fac84b12b3e0a)
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
Microsoft::Xna::Framework::Net::NetworkSession::GamerJoined (subscription replay) with NetworkSession::AddLocalGamer and the transport's AddRemoteGamer

Expected behaviour

Each subscriber sees one GamerJoined per gamer: subscribing replays the gamers already present, later joins are raised once. XNA's AddLocalGamer is asynchronous, so the gamer enters the collections and its event is raised inside one Update call and user code cannot subscribe between the two. CNA's constructor comment states the same intent, avoiding a queued event that would double-fire a replayed join.

Actual behaviour at TARGET

AddLocalGamer adds the gamer to LocalGamers and AllGamers immediately and pushes a GamerJoin event that Update raises later. The replay hook installed by the constructor calls each new handler once for every gamer in allGamers_ at subscription time, so a handler subscribed after AddLocalGamer and before the next Update receives the new gamer's join twice. The AddRemoteGamer path (used by the transport inside Update) inserts and queues the same way, but a handler can subscribe in that window only from inside another GamerJoined handler, which XNA's own add accessor and ProcessGamerJoined reproduce, so it is not part of this entry.

Source locations

Evidence

Checked by reading NetworkSession.cpp and NetworkSessionTests.cpp at 009d40f5; not executed. The replay semantics (the hook is invoked with the new handler on every Add, before the handler is stored) were read in System/EventHandler.hpp of a sibling Sharp Runtime checkout (next at 41b918c9, not pinned by TARGET). The existing tests subscribe before the add, or replay only construction-time gamers, so none opens the window.

Independent re-verification: Checked by reading NetworkSession.cpp (constructor replay hook, AddLocalGamer, AddRemoteGamer, Update) and the decompiled XNA NetworkSession (GamerJoined add accessor, ProcessGamerJoined, Update, RaiseUpdateEvents, AddLocalGamer); not executed. The replay semantics were read in an unpinned sibling Sharp Runtime checkout.

Focused reproduction

// Illustrative; not compiled or run for this entry. One signed-in gamer, room for two.
NetworkSession* session = NetworkSession::Create(NetworkSessionType::Local, 2, 8);
session->AddLocalGamer(secondSignedInGamer);
int joins = 0;
session->GamerJoined += [&](System::Object*, const GamerJoinedEventArgs&) { ++joins; };
// joins == 2: the replay reported both gamers
session->Update();
// joins == 3: the queued GamerJoin reported the second gamer again

Current tests

The three tests named above; none subscribes between an add and Update.

Regression test

The reproduction as a test expecting two calls, one per gamer; fixed by not replaying a gamer whose join is still queued, or by replaying from the queue's point of view.

Blast radius

Handlers that allocate per-gamer state in GamerJoined (UI slots, maps keyed by gamer) get a duplicate for split-screen players added before subscription, and for remote joins in the nested-subscription case. Handlers subscribed before any add, or after the next Update, are unaffected.

Workaround

Subscribe to GamerJoined immediately after Create or Join, before adding local gamers, and keep handlers idempotent per gamer pointer.

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

Known issues
Bug index