Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions src/iocore/net/P_NetAccept.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,23 +62,38 @@ AcceptFunction net_accept;
class UnixNetVConnection;

struct NetAcceptAction : public Action, public RefCountObjInHeap {
std::atomic<Server *> server{nullptr};

NetAcceptAction(Continuation *cont, Server *s)
{
continuation = cont;
if (cont != nullptr) {
mutex = cont->mutex;
}
server.store(s, std::memory_order_release);
_server.store(s, std::memory_order_release);
}

/** Whether this action still owns an open listening socket.

Accept paths must consult this before dispatching EVENT_ERROR. It is
cleared by cancel() before Action::cancel() sets @c cancelled, so it goes
false no later than the cancellation the continuation is aware of, and it
is safe to read from any thread. Reading @c cancelled instead is both a
data race on a plain bool and too late: accept() reports EBADF as soon as
the socket closes, which is before @c cancelled is set.

*/
bool
is_listening() const
{
return _server.load(std::memory_order_acquire) != nullptr;
}

void
cancel(Continuation *cont = nullptr) override
{
// Use atomic exchange so only one thread closes the server, preventing
// use-after-free races between cancel() and acceptEvent() cleanup.
Server *s = server.exchange(nullptr, std::memory_order_acq_rel);
// use-after-free races between cancel() and acceptEvent() cleanup. This
// must stay ahead of Action::cancel(), see is_listening().
Server *s = _server.exchange(nullptr, std::memory_order_acq_rel);
if (s != nullptr) {
s->close();
}
Expand All @@ -92,6 +107,11 @@ struct NetAcceptAction : public Action, public RefCountObjInHeap {
static DbgCtl dbg_ctl{"net_accept"};
Dbg(dbg_ctl, "NetAcceptAction dying");
}

private:
/// Cleared exactly once, by cancel(). Private so the ordering above is the
/// only way this transitions to null.
std::atomic<Server *> _server{nullptr};
};

//
Expand Down
6 changes: 3 additions & 3 deletions src/iocore/net/UnixNetAccept.cc
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ net_accept(NetAccept *na, void *ep, bool blockable)
if (res == -EAGAIN || res == -ECONNABORTED || res == -EPIPE) {
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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@JosiahWI Agreed. That's a separate bug, we can open a separate PR for it 👍

if (!blockable) {
na->action_->continuation->handleEvent(EVENT_ERROR, reinterpret_cast<void *>(res));
} else {
Expand Down Expand Up @@ -387,7 +387,7 @@ NetAccept::do_blocking_accept(EThread *t)
case -1:
[[fallthrough]];
default:
if (!action_->cancelled) {
if (action_->is_listening()) {
SCOPED_MUTEX_LOCK(lock, action_->mutex ? action_->mutex : t->mutex, t);
action_->continuation->handleEvent(EVENT_ERROR, reinterpret_cast<void *>(res));
Warning("accept thread received fatal error: errno = %d", errno);
Expand Down Expand Up @@ -580,7 +580,7 @@ NetAccept::acceptFastEvent(int event, void *ep)
check_transient_accept_error(res);
goto Ldone;
}
if (!action_->cancelled) {
if (action_->is_listening()) {
action_->continuation->handleEvent(EVENT_ERROR, reinterpret_cast<void *>(res));
}
goto Lerror;
Expand Down