Skip to content

fix cancel/accept race in NetAccept that causes EBADF abort - #13702

Open
JakeChampion wants to merge 4 commits into
apache:masterfrom
JakeChampion:jake/ccc
Open

JakeChampion wants to merge 4 commits into
apache:masterfrom
JakeChampion:jake/ccc

Conversation

@JakeChampion

Copy link
Copy Markdown
Contributor

NetAcceptAction::cancel() closes the server socket before setting the cancelled flag
In the window between close and flag set, net_accept() can get EBADF from accept4(), see cancelled=false, and dispatch
EVENT_ERROR to handlers that don't expect it

now we check the atomic server pointer in all three accept paths before dispatching EVENT_ERROR

`NetAcceptAction::cancel()` closes the server socket before setting the cancelled flag
In the window between close and flag set, `net_accept()` can get `EBADF` from `accept4()`, see `cancelled=false`, and dispatch
`EVENT_ERROR` to handlers that don't expect it

now we check the atomic server pointer in all three accept paths before dispatching `EVENT_ERROR`
Copilot AI lite review requested due to automatic review settings September 17, 2026 15:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The listening-state check is not synchronized with cancellation, so the fatal accept-error race can still occur.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Fixes a cancellation/accept race that can produce EBADF and fatal accept errors.

Changes:

  • Adds atomic listening-server state tracking.
  • Applies checks across all three accept error paths.
  • Updates cancellation and socket ownership handling.
File summaries
File Reviewed changes
src/iocore/net/UnixNetAccept.cc Adds listening-state guards; cancellation remains unsynchronized with error dispatch, leaving the critical race unresolved.
src/iocore/net/P_NetAccept.h Adds atomic server-state tracking and listening checks.
Review details

Suppressed comments (2)

src/iocore/net/UnixNetAccept.cc:390

  • The is_listening() load is outside the mutex acquired on the next line. Cancellation can clear _server, close the socket, set cancelled, and release that mutex after this load but before this thread acquires it, so this branch can still deliver EVENT_ERROR to a cancelled continuation. Acquire the mutex before checking is_listening() (or recheck after acquiring it) so the check and callback are serialized with cancel().
        if (action_->is_listening()) {

src/iocore/net/UnixNetAccept.cc:583

  • This path has no action/continuation mutex around the check or callback. is_listening() is only an atomic pointer load, so it can return true, then cancel() can exchange and close the server and set cancelled before the next line executes; EVENT_ERROR is still delivered after cancellation. The guard and callback need to be serialized with cancellation (or use a cancellation state that reserves callback delivery).
      if (action_->is_listening()) {
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

goto Ldone;
}
if (na->server.sock.is_ok() && !na->action_->cancelled) {
if (na->action_->is_listening()) {

@JosiahWI JosiahWI Sep 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's implied by the Action API that the caller MUST hold the action mutex when cancelling. If NetAccept::stop_accept does not, that may be another bug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants