[ruby-core:126555] [Ruby 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
Issue #22286 has been reported by docelic (Davor Ocelic). ---------------------------------------- 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 https://bugs.ruby-lang.org/issues/22286 * Author: docelic (Davor Ocelic) * Status: Open * ruby -v: 4.0.6 * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- 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) -- https://bugs.ruby-lang.org/
participants (1)
-
docelic (Davor Ocelic)