Phone compatibility internals
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. The 18 tests named here were located and their CTest route read; none was executed. The native Windows notification path, shutdown against a hostile or stalled peer and SIGPIPE exposure remain unverified; the Winsock branches have no test on any host.
modules/phone/ supplies the small Microsoft::Phone surface that Windows Phone 7 XNA titles were written against. PhoneApplicationService re-expresses Game's lifecycle events as the phone's Launching, Activated, Deactivated and Closing, and HttpNotificationChannel with two push-message classes implements a loopback-only HTTP notification path. Nothing installs either adapter automatically, and inside the repository nothing but the test executables links the module. This page is for maintainers porting a phone title, changing Game's activation or exit semantics, or touching the socket code; the user-level context is the Microsoft.Devices row of XNA compatibility, namespace by namespace. Treat the module as a compatibility boundary: it is not a platform backend, a general network stack, an operating-system push service or persistent storage.
Where it sits in the build and execution graph
modules/phone/CMakeLists.txt globs src/*.cpp (three translation units at this snapshot) into cna_add_module(cna_phone Phone …), the helper in modules/CMakeLists.txt that makes a STATIC library cna_phone with the alias CNA::Phone. It links cna_runtime and cna_core_headers publicly (the lifecycle it reports is the one Game already receives) and the Sharp Runtime components Core.Base, Collections.Core and Uri. CNA's own module table in docs/physical-modules.md lists only the first two components; the CMake file is the authority.
modules/CMakeLists.txt adds the directory unconditionally (no CMake option gates it, so CNA's own build compiles it in every configuration) but leaves cna_phone out of the _cna_runtime_parts list that defines the CNA umbrella. Everything else follows from that one omission:
- Games opt in by name. Linking
CNAgives a game neither the phone symbols nor a listener thread; a port namesCNA::Phoneitself (the module layout table says “linked when a game names it”). - Not inside
libcna.so. WithCNA_SHARED_LIBRARYthe shared runtime is assembled from the same parts list, so the phone module stays a separate static archive. - Only tests link it. The focused
CnaPhoneTestsgets it fromCNA_TEST_GROUP_DEPENDENCY_phone(cna_phone cna_runtime, because the tests construct aGame), and the aggregateCnaTestsnames it explicitly incmake/UnitTests.cmake, since it consumes every group's objects. No example, tool or other module links it. - No C routes. CNA's C coverage inventory records
Microsoft::Phoneas out of runtime C scope by owner decision (CBIND-117, ingenerate_coverage_inventory.py), which is what the C API page's measured coverage reports. - The port-facing calls are extensions.
AttachEXT,DetachEXTandDispatchPendingNotificationsEXTcarry theCNAEXTmarker fromCNAHelper.hpp. UnderCNA_STRICT_XNA_APIthat marker expands to[[deprecated]], so a strict build warns on exactly the three calls a phone port cannot avoid.
Game (modules/runtime) PhoneApplicationService (borrowed Game*)
BeforeLoop, FocusGained, DidEnterForeground
-> Game::Activated ---------------------------> ignored until a Deactivated, then
phone Activated(preserved = true)
FocusLost, WillEnterBackground
-> Game::Deactivated ---------------------------> phone Deactivated
loop ends (Exit(), Terminating -> Exit())
-> Game::Exiting ---------------------------> phone Closing
explicit AttachEXT(game) ---------------------------> phone Launching, immediately
Sender (caller's thread) channel worker thread thread that calls dispatch
SendAsync(uri) accept (one at a time)
connect, POST, recv ----> ReadRequestBody
reply 200 / 400, close
pending_.push_back [mutex_] -> DispatchPendingNotificationsEXT
swap under mutex_, then raise
HttpNotificationReceived
A source directory does not imply runtime integration: this is the clearest case in the tree. Compare the Devices module, which the CNA umbrella does include (Devices and sensor lifetime), and the full list in the module index.
Lifecycle translation and ownership
PhoneApplicationService.hpp declares the four events, the State dictionary and the two extension calls; PhoneApplicationService.cpp is about ninety lines. getCurrentProperty returns a function-local static instance, but the constructor is public, so tests and applications can also build their own. Copying is deleted, and because a copy constructor is declared the class is not movable either: the handlers it installs capture this, so the object must stay where it was created.
AttachEXT(Game&) first calls DetachEXT, then stores a borrowed Game*, resets its “has been deactivated” flag, subscribes three lambdas to the game's Deactivated, Activated and Exiting events (keeping the three tokens), and finally raises Launching synchronously on the calling thread. Every phone event is raised with a null sender. The mapping, including where Game.cpp raises each Game event, is:
| Game event | Raised by the runtime when | Phone event |
|---|---|---|
Activated | setIsActiveProperty(true): BeforeLoop at the start of Run, a FocusGained window event, or an AppLifecycleKind::DidEnterForeground event. Raised only on a change of IsActive. | Ignored until the first Deactivated since the attach; after that, Activated with IsApplicationInstancePreserved = true on every activation. |
Deactivated | setIsActiveProperty(false): FocusLost, or WillEnterBackground (which also parks the loop on mobile targets). | Deactivated, every time; sets the flag. |
Exiting | OnExiting after the native loop ends (or in the Emscripten frame callback when RunApplication clears); AppLifecycleKind::Terminating reaches it through Exit(). | Closing. |
| (none) | The application calls AttachEXT. | Launching, once per attach. |
Four consequences are easy to miss:
- On desktop a focus loss is a phone deactivation. Switching to another window and back produces
DeactivatedthenActivated(preserved):Gamereports a window focus change and a mobile backgrounding through the same property, and the adapter cannot tell them apart. A title that saves fromDeactivated(as the header says it should) therefore saves on every task switch. Activatedalways says the instance was preserved. The handler runs in the process that went away, so it passestrue; the default-constructedActivatedEventArgs(preserved = false) is never produced by the adapter. A CNA process that was killed comes back as a new process and a newLaunching.Launchingis per attach, not per process. Re-attaching, or attaching a second game, raises it again. A host driven byRunOneFramenever callsBeforeLoop, so its first activation comes from a focus event; the flag makes the adapter ignore that one as well.Closingcomes early in teardown.Exitingfires beforeDisposeandUnloadContent(see the Game lifecycle diagram), so content is still loaded in aClosinghandler.
DetachEXT returns at once when nothing is attached. Otherwise it removes the three tokens from the attached game's events, then clears the pointer, the tokens and the flag. The destructor calls it. The service may therefore die before the game (the test DestroyingTheServiceUnsubscribesFromTheGame pins that order), but the game must not die first, because DetachEXT dereferences the stored pointer.
The process-wide Current is the risky case. The obvious port, PhoneApplicationService::getCurrentProperty().AttachEXT(game) with the Game on main's stack, leaves the function-static service attached to a game that is destroyed when main returns. The static's destructor then runs DetachEXT against a dead object during static destruction. Nothing in Game or the runtime detaches it. Call DetachEXT after Run returns, or from the game's destructor, before the Game goes away. This is a source-level finding: every test uses a local service, and none covers this order.
getStateProperty returns a member Dictionary<std::string, std::shared_ptr<System::Object>>. It is plain memory: it is not serialized, it does not touch the storage module, and DetachEXT does not clear it. It “survives” a deactivate and reactivate only because the process never went away. A title that must survive process loss writes to storage from Deactivated, as the header advises; see Storage internals and the storage root chain.
Threading: the phone events run synchronously on whichever thread raises the Game event, normally the thread inside Game::Run. Launching runs on the thread that calls AttachEXT. The service has no lock, so attaching or detaching while another thread raises Game events is unsynchronized. The thread and callback map and ownership map give the runtime-wide picture; Runtime lifecycle and Runtime module internals explain where the Game events come from.
Notification path, threads and limits
HttpNotificationChannel.hpp states the design: on Windows Phone the channel URI pointed at Microsoft's push relay, which is retired, so here the channel is the endpoint. What goes with the relay is the ability to reach a device that is not directly addressable and to deliver to an application that is not running. HttpNotificationChannel.cpp implements Open as follows:
- If
listening_(anstd::atomic<bool>) is already set, return. The check is a plain load, not a compare-exchange, so two concurrentOpencalls are not guarded against each other. - Create an IPv4
SOCK_STREAMsocket, setSO_REUSEADDR, bindINADDR_LOOPBACKon port 0 (the system picks the port), andlistenwith a backlog of 4. A failure to create the socket, or a failed bind or listen, logs aLogger::Warnand returns with no URI. A failedgetsocknamereturns silently. - Store the socket and build
http://127.0.0.1:<port>/<channelName>/. The name is spliced in unescaped; howSystem::Uritreats a name that is not a valid path segment is Sharp Runtime behaviour that no phone test covers. Then setlistening_. - Under the registry mutex, assign
Registry()[channelName] = this. This is an assignment, so a duplicate name silently replaces the earlier entry. - Start one
std::threadrunningListen. - Raise
ChannelUriUpdatedon the calling thread. The worker is already running, so a notification can be queued before the handler learns the URI. That is harmless, because it waits in the queue.
Listen loops while listening_ is set. It accepts one connection at a time, reads it with ReadRequestBody, sends a fixed response, closes the connection, and queues the body under mutex_ if it is non-empty. The worker never raises an event. The parser only looks for the header terminator and a content-length: substring in the lower-cased header block (the first occurrence wins). It never inspects the method or the path, so any request to the port reaches the channel, whatever its path.
| What the peer sends | Response | Queued |
|---|---|---|
Headers, Content-Length: N, then N bytes | 200 | Exactly N bytes; anything after them is dropped |
No Content-Length (including a chunked body) | 200 | Nothing: the length is taken as 0 and empty bodies are never queued |
Content-Length: N, then the peer closes after fewer bytes | 200 | The truncated body |
Connection closed or failed before \r\n\r\n, or a header block over 1 MiB | 400 | Nothing |
The parser's comment promises more than the code does. The comment above ReadRequestBody says unsupported encodings are “refused visibly” rather than “delivered truncated”. In the source, a chunked push is acknowledged with 200 and dropped, and a short body is acknowledged with 200 and queued truncated. Only an unreadable header block gets a 400. The body size is not bounded either: the worker keeps reading until Content-Length bytes arrive or the peer stops. A 200 means the socket received the bytes, never that a handler ran.
The listener has no receive timeout: no SO_RCVTIMEO, no poll. A peer that connects and sends nothing parks the only worker in recv, later connections wait in the backlog of 4, and Close cannot wake it, because Close shuts down only the listening socket. The source therefore suggests that Close, and the destructor, can wait on such a peer indefinitely. Shutdown behaviour against a hostile or stalled peer remains unverified: no test exercises it. Two smaller hazards are also visible in the source. A persistent accept error while listening loops again at once, with no backoff. And responses are sent with flags 0, without the MSG_NOSIGNAL/SO_NOSIGPIPE handling that the Inspector's socket layer uses (InternalSocket.cpp), so whether a peer that resets the connection can raise SIGPIPE depends on the process's signal disposition. That has not been exercised either.
The trust boundary is the loopback bind and nothing else. There is no authentication and no channel secret. A remote service cannot reach the advertised URI, but any local process that finds the port can post to it. Offline delivery, retries and a remote relay do not exist.
DispatchPendingNotificationsEXT swaps pending_ into a local vector under mutex_, releases the lock, and raises HttpNotificationReceived once per body, in arrival order, on the caller's thread. No production code calls it; an application chooses its dispatch point, typically once per frame on the game thread. Calling it from a worker moves the handlers to that worker, because the API does not enforce thread affinity. Sharp Runtime's System::EventHandler::Raise iterates a snapshot of the handlers and does not catch, so a handler that throws propagates out of the dispatch call, and the rest of that swapped-out batch is lost.
Close returns early if the channel is not listening. Otherwise it clears listening_, calls shutdown (SHUT_RDWR, or SD_BOTH on Windows) and then closes the listening socket; the code comments that closing alone can leave the worker parked in accept. It then joins the worker, erases the registry entry only if that entry still points at this channel, and resets the URI. The destructor calls Close. Some state deliberately survives Close: pending_ is not cleared, so bodies received before the close can still be dispatched (and a reopened channel still holds them), and shellToastBound_ keeps its value. A reopened channel gets a new port, and so a new URI and a new ChannelUriUpdated; the sending service has to be told.
Find looks the name up under the registry mutex and returns the raw pointer after releasing it. That pointer is not a lease: a concurrent Close or destruction can invalidate it. Duplicate names are not rejected. With two open channels of one name, the second Open overwrites the entry. The first channel's Close leaves that entry alone (the identity check), but the second channel's Close erases the name while the first is still open, so Find then returns null for a live channel.
Concurrency: only listening_ (atomic) and pending_ (under mutex_) are synchronized. channelUri_, shellToastBound_, the thread object and listenSocket_, a plain int that the worker reads and Close resets, are not. Keep Open, Close, the property getters and BindToShellToast on one owning thread, and treat only the queue as a cross-thread structure. The constructor's serviceName is stored and never read.
Sender semantics and failure boundaries
PushNotificationSender.hpp declares MessageSendPriority (Low, Normal, High) and two classes: RawPushNotificationMessage, which sends its RawData bytes, and ToastPushNotificationMessage, which sends Title and SubTitle. No class is called PushNotificationSender; that is only the file name. In PushNotificationSender.cpp both SendAsync methods call one internal Post:
- Take
clientUri.ToString()and split it by string search intohost[:port]and a path. It returns false only if//is missing. The port defaults to 80 and the scheme is never checked, so anhttpsURI would be sent as plain HTTP to port 80. - Call
getaddrinfowithAF_INET(IPv4 only), then create a socket and do a blockingconnect. A lookup, socket or connect failure returns false. - Build the request and write it with one
sendcall. The length is narrowed tointfor the Windows signature, and a short write is not retried: the method returns false. - Do one blocking
recvof up to 512 bytes, discard it, close the socket, and return whether the whole request was sent.
| Request element | Raw message | Toast message |
|---|---|---|
X-NotificationClass | High 3, Normal 13, Low 23 | High 2, Normal 12, Low 22 |
X-WindowsPhone-Target | absent | toast |
Content-Type | text/xml (even for arbitrary bytes) | text/xml |
| Body | RawData verbatim | <wp:Notification xmlns:wp="WPNotification"><wp:Toast><wp:Text1>Title</wp:Text1><wp:Text2>SubTitle…, with an XML declaration |
| Always | POST <path> HTTP/1.1, Host, Content-Length, Connection: close | |
Three semantic points matter to anyone changing reliability or security. First, “Async” is synchronous: name resolution, connect, send and the response wait all block the caller, without timeouts, so a send from the game thread stalls that frame. Second, the header documents the return value as “true when the receiver accepted it”, but the code returns true whenever the bytes were written; a 400 still yields true, and nothing parses the status line. Third, the toast body concatenates Title and SubTitle without XML escaping, so an & or < in a title produces malformed XML. The channel does not parse the XML and delivers the bytes anyway. BindToShellToast on the receiving channel only records a flag; nothing is ever drawn over another application. Both halves of this local protocol have to change together; do not promise internet push delivery or non-blocking sends on top of them.
The Windows path
Both source files carry Winsock branches (closesocket, SD_BOTH). CNA's native-Windows validation plan (plan_win32_native_validation.md, rows F11 and F16) records two compile fixes in these files. F11 undefines the ERROR macro that <windows.h> defines, which collided with CNA::LogLevel::ERROR. F16 replaced a use of ssize_t, which MSVC does not define. What the source does not show:
- No
WSAStartupcall exists in the module (the only one in CNA's tree is in the Inspector's socket layer). Whether Winsock is initialized whenOpenorSendAsyncruns therefore depends on what else the process has done, and nothing establishes it. - The checks
listener < 0andconnection < 0compare an unsignedSOCKET, so they never detectINVALID_SOCKET. Failure still surfaces, later, through the bind, listen or connect paths. HttpNotificationChannelTests.cpp, which also holds thePushNotificationSenderTestcases, is removed from the test sources underWIN32because its test-side client uses POSIX socket headers. The exclusion removes the tests, not the feature.
The native Windows notification path therefore has no test and remains unverified; only the lifecycle tests are in a Windows CnaTests.
What the tests pin down
The module has 18 GoogleTest definitions in two files. They were located and read at 009d40f5; none was executed for this page.
| Suite | Cases | What they assert |
|---|---|---|
PhoneApplicationServiceTest (9, PhoneApplicationServiceTests.cpp) | AttachingReportsAFreshStart, TheFirstActivationOfARunIsNotAReturnFromTheBackground, GoingAwayAndComingBackIsDeactivatedThenActivated, ExitingTheGameClosesTheApplication, DetachingStopsTheServiceFollowingTheGame, AttachingASecondGameLeavesTheFirstOneBehind, DestroyingTheServiceUnsubscribesFromTheGame, StateKeepsWhatTheApplicationPutsThere, CurrentIsOneServiceForTheWholeProcess | The recorded event sequence for attach, first activation, deactivate/activate, exit, detach, re-attach and service destruction; State storage; Current identity. The tests construct a Game and call Raise on its events directly; no platform, window or OS lifecycle is involved. |
HttpNotificationChannelTest (6) | AClosedChannelHasNoUri, OpeningReportsAnAddressAServiceCanPushTo, WhatAServicePostsArrivesAsANotification, NothingIsRaisedUntilTheGameAsksForIt, AnOpenChannelCanBeFoundByName, ShellToastBindingIsRecorded | The URI shape and reset, delivery of a well-formed POST from a bare POSIX client, queue-until-dispatch (checked by a 100 ms sleep), single-channel Find, and the toast flag. |
PushNotificationSenderTest (3) | WhatTheSenderPostsIsWhatTheChannelReceives, AToastCarriesItsTitleToTheReceiver, SendingToAnAddressNobodyIsListeningOnFails | Sender against channel for raw and toast messages; a refused connection on loopback port 1 returns false. |
Registration: cmake/UnitTests.cmake puts both files in the phone group, whose object library feeds the aggregate CnaTests. CTest sees the cases only through gtest_discover_tests(CnaTests … DISCOVERY_MODE PRE_TEST). CnaPhoneTests is an EXCLUDE_FROM_ALL iteration target, which the file states is “not additional CTest registrations”, and unlike core and math it has no build preset. The Windows exclusion is described above.
No test covers the following: malformed, chunked, truncated or oversized requests; the 400 path; duplicate channel names; Close with bodies still pending or with a stalled peer; concurrent Open/Close; a handler that throws during dispatch; the static Current outliving its game; or a real focus or mobile lifecycle driving the adapter. See Test architecture and change recipes for where a new case belongs.
Worked maintainer route: notification never reaches a handler
- Is the module linked and the channel open? Check that the executable links
CNA::Phone(CNAalone does not bring it in) and thatgetChannelUriProperty()is non-null afterOpen. If it is null, look for theHttpNotificationChannel:warnings from socket, bind or listen failure (agetsocknamefailure is silent) before touchingGameor renderer code. - Does the sender use the reported URI? The port changes on every
Open, so a URI cached across aClose/Openis stale. AtruefromSendAsyncproves only that the bytes were written: it does not prove HTTP acceptance, and it does not prove that a handler ran. - Did the worker queue it? Break at the
pending_.push_backinListen. If it is never reached, check what the client sent against the response table above: noContent-Lengthand an empty body both end here silently. - Does anything dispatch? Break in
DispatchPendingNotificationsEXT. If bytes are pending but no handler runs, find the application's dispatch call; the runtime installs none. A handler that throws also discards the rest of its batch. - Wrong thread? If a callback runs on an unsafe thread, trace who calls dispatch rather than moving work onto the listener thread. Keep the mutex-protected queue and the rule that events are raised outside the lock.
- Pin it. Add a focused case reproducing the exact boundary (open, POST, queue, dispatch or close), then run the notification suites on a POSIX host and the lifecycle suite for any
Gameevent change. A real application shutdown and a Windows build and host still need separate checks, because the listener tests do not compile there.
A focused iteration loop, as the CMake files define it (not executed for this page):
cmake --preset unit # STUB renderer, CNA_BUILD_TESTS=ON
cmake --build cmake-build-unit --target CnaPhoneTests
./cmake-build-unit/CnaPhoneTests --gtest_filter='PhoneApplicationServiceTest.*'
./cmake-build-unit/CnaPhoneTests --gtest_filter='HttpNotificationChannelTest.*:PushNotificationSenderTest.*'
For lifetime bugs around Exiting and destruction, I need to debug shutdown and lifetime behavior and Ownership and shutdown cover the Game side. For a new regression case, see I need to add a regression test; the Maintainer Handbook lists the other routes.
Source-reading order and evidence
phone/CMakeLists.txt, the umbrella list inmodules/CMakeLists.txtand the phone lines ofUnitTests.cmake: establish the link opt-in, the two test consumers and theWIN32exclusion before assuming any coverage.PhoneApplicationService.hpp, thenPhoneApplicationService.cpp: trace the borrowedGame*, token removal, the first-activation flag and the in-processState. Then readsetIsActiveProperty,BeforeLoopand the window-event and lifecycle cases ofPollEventsinGame.cppto see which platform events become which phone events.HttpNotificationChannel.hpp, thenHttpNotificationChannel.cpp: draw the worker, queue, dispatch andClose/join order, and check whatReadRequestBodyactually accepts.PushNotificationSender.hppandPushNotificationSender.cpp: compare the synchronous send and its return value with the channel's acknowledgement and handler dispatch.PhoneApplicationServiceTests.cppandHttpNotificationChannelTests.cpp: separate the deterministic adapter tests from the missing native OS, network and Windows coverage.
Evidence level: everything above was checked by reading the TARGET sources at 009d40f5; nothing was built or executed. The Game event sources were read in the runtime module, and the two Sharp Runtime behaviours mentioned (EventHandler::Raise, System::Uri) belong to that sibling repository rather than to CNA.
Known issues in this area
Current defects, gaps and limitations at this snapshot that touch this subject.
- CNA-BUG-083: PhoneApplicationService::getCurrentProperty()'s static instance detaches from an already-destroyed Game during static destruction — The process-wide PhoneApplicationService is a function-local static holding a borrowed Game*; attached to a Game that is destroyed first, its destructor calls DetachEXT on the dead game's events.
- CNA-BUG-156: HttpNotificationChannel answers length-less, chunked and truncated pushes with 200 and reads request bodies of unbounded size — ReadRequestBody's comment says unreadable pushes are refused rather than delivered truncated, but a request without Content-Length gets 200 and is dropped, a short body gets 200 and is queued truncated, and the length is
- CNA-BUG-157: HttpNotificationChannel::Close and the channel destructor block indefinitely while a connected peer sends nothing — The single listener thread reads each accepted connection with a blocking recv and no timeout; Close shuts down only the listening socket and then joins that thread, so a silent peer stalls Close and the destructor.
- CNA-BUG-158: RawPushNotificationMessage::SendAsync and ToastPushNotificationMessage::SendAsync return true for a push the receiver rejected — Both SendAsync methods are documented to return true when the receiver accepted the notification, but they return true whenever the whole request was written; the reply is read and discarded, so an HTTP 400 still yields
- CNA-BUG-159: ToastPushNotificationMessage::SendAsync inserts Title and SubTitle into the toast XML without escaping — The toast body is built by string concatenation, so a Title or SubTitle containing an ampersand, a less-than sign or markup produces malformed or altered XML; the channel delivers the bytes unparsed.
- CNA-BUG-160: Two open HttpNotificationChannel objects with the same name leave Find unable to locate one that is still open — Open assigns the registry entry for the name without checking for an existing channel, and when the later channel closes first it erases the name while the earlier one is still listening, so Find returns null for a live
- CNA-BUG-161: A handler that throws during HttpNotificationChannel::DispatchPendingNotificationsEXT discards every remaining notification of that batch — DispatchPendingNotificationsEXT moves the whole queue into a local vector before raising; an exception from a HttpNotificationReceived handler propagates and destroys the notifications not yet raised.
- CNA-BUG-162: The phone notification channel and sender never initialise Winsock, cannot detect INVALID_SOCKET and have no Windows test — modules/phone calls no WSAStartup, compares the unsigned SOCKET with zero and names no Winsock library, and its socket tests are compiled out on WIN32, so on Windows Open and SendAsync work only if something else initial
- CNA-BUG-163: HttpNotificationChannel writes its reply with ::send flags 0, so a peer that aborts its connection mid-request can end the process with SIGPIPE — The listener's 200 or 400 reply is written with no MSG_NOSIGNAL or SO_NOSIGPIPE, unlike CNA's other socket code. After a peer's abortive close has been reported to the worker as a receive error, Linux delivers SIGPIPE on
- CNA-BUG-164: HttpNotificationChannel's listener reads listenSocket_ unsynchronised while Close resets it, and spins without backoff on a persistent accept error — listenSocket_ is a plain int that the worker reads for every accept while Close closes the descriptor and writes -1 from another thread, a data race; a persistent accept failure such as EMFILE makes the worker loop at fu
- CNA-BUG-235: docs/physical-modules.md's Sharp Runtime column omits components that four modules link: Uri for phone and media, Collections.Core for input, Resources for content — CNA's module table gives phone Core.Base and Collections.Core, input Core.Base, and media and content Core.Base and IO, while the modules' CMake files link Uri (phone, media), Collections.Core (input) and Resources (cont
Related pages
The same subsystem is explained at four altitudes. These are the neighbouring pages at each one.
- User guide
- XNA compatibility: namespace by namespace · Game lifecycle diagram · C API measured coverage (phone is out of C scope)
- Architecture
- Runtime lifecycle · Module layout
- Internals
- Runtime module internals · Ownership and shutdown · Devices and sensor lifetime · Inspector transport internals (the other socket layer) · Storage internals
- Maintainer workflow
- I need to debug shutdown and lifetime behavior · I need to add a regression test · Ownership and lifetime master map · Thread and callback map
- Tests and validation
- Test architecture and change recipes · What to test after changing X
- Reference
- Module index · Test target index