CNA-BUG-164: HttpNotificationChannel's listener reads listenSocket_ unsynchronised while Close resets it, and spins without backoff on a persistent accept error

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.

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 full speed.

Identifier
CNA-BUG-164
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
HttpNotificationChannel::Close() and the channel's internal listener thread

Expected behaviour

Close is designed to run while the worker is blocked in accept (its comment says the shutdown call is what wakes it), and listening_ is a std::atomic<bool> for that reason. The other state the two threads share should be synchronised as well, and a failure the worker cannot recover from should not consume a core.

Actual behaviour at TARGET

listenSocket_ is an int. Listen reads it for every accept, while Close, on another thread, closes the descriptor and writes -1 with no lock or atomic: a data race, undefined behaviour under the C++ memory model. In practice a worker between its while (listening_) check and accept can use the old number after it was closed, and if another thread has meanwhile been given the same descriptor number, the worker calls accept on that unrelated descriptor. Separately, when accept fails while listening_ is still true (EMFILE or ENFILE when descriptors run out, ENOBUFS), Listen continues at once; EMFILE leaves the pending connection queued, so the loop spins at full speed until the condition clears. Not part of this entry: Open's plain if (listening_) check, which is not a compare-exchange; the header promises no thread safety for Open, and the Development page tells callers to keep Open and Close on one owning thread.

Source locations

Evidence

Checked by reading HttpNotificationChannel.cpp at 009d40f5; the file is unchanged since the earlier recorded pin. Not executed.

Focused reproduction

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

Current tests

Every notification test ends with Close running while the worker is blocked in accept, which is exactly the racy path, but no ThreadSanitizer run of the phone suites is recorded, and descriptor exhaustion is not tested.

Regression test

Run HttpNotificationChannelTest.* under ThreadSanitizer (it should report the race today); make listenSocket_ atomic or stop the worker through a wake-up descriptor before closing the listener; and add a test that lowers RLIMIT_NOFILE and checks that the worker backs off instead of spinning.

Blast radius

Every channel at Close (formal undefined behaviour, rarely visible); the busy loop only under descriptor or kernel buffer exhaustion.

Workaround

No workaround is known.

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

Maintainer workflow
Thread and callback map
Known issues
Bug index