Skip to content

tcp_trsp: don't drop sock_mut mid-send in send()/check_connection#558

Open
hecko wants to merge 1 commit into
masterfrom
hecko/tcp-trsp-send-unlock-race
Open

tcp_trsp: don't drop sock_mut mid-send in send()/check_connection#558
hecko wants to merge 1 commit into
masterfrom
hecko/tcp-trsp-send-unlock-race

Conversation

@hecko

@hecko hecko commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Bug

tcp_trsp_socket::send() and check_connection() push the outgoing message onto send_q under sock_mut, then arm the libevent read/write event through the add_write_event_ul() / add_read_event_ul() helpers. Those *_ul helpers unlock sock_mut, call event_add(), and re-lock:

void tcp_trsp_socket::add_write_event_ul(struct timeval* timeout) {
  sock_mut.unlock();
  add_write_event(timeout);
  sock_mut.lock();
}

That temporary unlock opens a race window: the tcp worker can concurrently run on_read() / on_write() — including close() — on the same socket while send() still believes it holds the lock. send() (or check_connection()) can then resume operating on a connection that has just been closed/torn down.

Fix

Use the non-unlocking add_read_event() / add_write_event() variants so sock_mut is held for the whole critical section. This is safe because event_add() does not synchronously call back into our handlers; it only takes libevent's own event-base lock. No new lock ordering is introduced — close() already acquires sock_mut before calling event_del(), so the sock_mut → evbase order is consistent. The obsolete comment about "unlock in add_write_event_ul" is removed. Changed translation unit compiles cleanly.

Acknowledgement

Backport of the fix from yeti-switch/sems — commit 596150a0 ("tcp_trsp: fix deadlock because of racing between session-proc and tcp-worker") by Michael Furmur. sems-server's transport code matches the pre-fix version, so the same race is present here.


Generated by Claude Code

send() and check_connection() queued a message under sock_mut and then armed
the libevent read/write event through the *_ul helpers, which unlock sock_mut,
call event_add(), and re-lock. That temporary unlock opens a race window: the
tcp worker can run on_read()/on_write() (and close()) on the same socket while
send() believes it still holds the lock, so send() may resume operating on a
connection that has just been torn down.

event_add() does not call back into our handlers synchronously and only takes
libevent's own event-base lock, so it is safe to arm the event while holding
sock_mut. Use the non-unlocking add_read_event()/add_write_event() variants so
the socket state stays consistent for the whole critical section. No lock
ordering is introduced: close() already takes sock_mut before event_del().

Backport of the fix from yeti-switch/sems (commit 596150a0, Michael Furmur).
sems-server's transport code matches the pre-fix version, so the same race is
present here.
Copilot AI review requested due to automatic review settings July 13, 2026 03:33

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

2 participants