Project

General

Profile

Actions

Bug #22286

open

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

Bug #22286: 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

Added by docelic (Davor Ocelic) 2 days ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:126555]

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:

# The number of issue occurrences will be reported in hang/not-accepted/fd-delta value.
ruby accept_race.rb close                # issue visible
ruby accept_race.rb shutdown             # confirms issue coming from close-interrupt
ruby accept_race.rb shutdown-join-close  # workaround to fix the issue

Files

tcpserver-close-bug.rb (759 Bytes) tcpserver-close-bug.rb docelic (Davor Ocelic), 09/02/2026 08:55 AM

No data to display

Actions

Also available in: PDF Atom