Bug #22286
openClosing a TCPServer while another thread is blocked in accept can discard a just-accepted connection's file descriptor, leaving the peer connected to nothing
Description
Closing a TCPServer while another thread is blocked in accept can discard a just-accepted connection's file descriptor, leaving the peer connected to nothing.
The script to reproduce the issue in each iteration binds a listener, starts a thread blocked in accept, connects a client, then tears the listener down in one of three orders. It then closes whatever the accept thread actually received and checks whether the client sees the peer go away
within half a second. After a GC it reports how many descriptors the process still holds beyond its starting count. Results on Ruby 4.0.6, Linux 6.18.
┌─────────────────────┬─────────────────────┬─────────────────────┐
│ mode │ stuck clients / 300 │ leaked fds after GC │
├─────────────────────┼─────────────────────┼─────────────────────┤
│ close │ 89 │ 89 │
├─────────────────────┼─────────────────────┼─────────────────────┤
│ shutdown │ 127 │ 127 │
├─────────────────────┼─────────────────────┼─────────────────────┤
│ shutdown-join-close │ 0 │ 0 │
└─────────────────────┴─────────────────────┴─────────────────────┘
Every stuck client is one the accept thread never handed back, and the leaked descriptor count matches. So accept(2) returned a socket, but Ruby raised the "closed in another thread" interrupt in that thread before wrapping the descriptor, and the descriptor was dropped. Joining the accept thread before calling close avoids the issue.
Run the script with:
Files
No data to display