Details
-
Type:
Bug
-
Status:
Closed
-
Priority:
Minor
-
Resolution: Fixed
-
Affects Version/s: JRuby 1.5.1, JRuby 1.5.6, JRuby 1.6RC2
-
Fix Version/s: JRuby 1.6RC3
-
Component/s: None
-
Labels:None
-
Environment:HideLinux desk4 2.6.32-28-generic #55-Ubuntu SMP Mon Jan 10 23:42:43 UTC 2011 x86_64 GNU/Linux
java version "1.6.0_22"
Java(TM) SE Runtime Environment (build 1.6.0_22-b04)
Java HotSpot(TM) 64-Bit Server VM (build 17.1-b03, mixed mode)ShowLinux desk4 2.6.32-28-generic #55-Ubuntu SMP Mon Jan 10 23:42:43 UTC 2011 x86_64 GNU/Linux java version "1.6.0_22" Java(TM) SE Runtime Environment (build 1.6.0_22-b04) Java HotSpot(TM) 64-Bit Server VM (build 17.1-b03, mixed mode)
-
Number of attachments :
Description
When TCPSocket#readline is called after a TCPSocket#close on the same client socket, if the #close comes from another thread, sometimes an Errno::EBADF is the result instead of an IOError: closed stream.
Tracing through the code, what's happening is that the ChannelDescriptor responds to the getline() call with a BadDescriptorException because the underlying Channel responds false to isOpen(), which on the face of it seems wrong but I'm not familiar enough with the semantics to make a convincing argument. Also, given that the readStream.getline() call is guarded by a beforeBlockingCall(), I suspect that this actually indicates a race somewhere else in the code.
It is proving difficult to narrow this down to a single test case, but the following code at least demonstrates the problem:
require 'socket' server = TCPServer.new(0) server_thread = Thread.new{ server.accept while true } exceptions = [] 1000.times do |i| client_socket = TCPSocket.new("localhost", server.addr[1]) close_thread = Thread.new{ sleep(Kernel.rand/100); client_socket.close } read_thread = Thread.new do sleep(Kernel.rand/100); begin client_socket.readline rescue Exception => e exceptions << e end end close_thread.join read_thread.join end p exceptions.map{|e| e.class.to_s}.inject({}){|m,r| m.merge r => true}.keys.sort
The final output is, for me:
["EOFError", "Errno::EBADF", "Errno::EPIPE", "IOError"]
EOFError and IOError are expected. EBADF and EPIPE are not.
Activity
| Field | Original Value | New Value |
|---|---|---|
| Description |
When TCPSocket#readline is called after a TCPSocket#close on the same client socket, if the #close comes from another thread, sometimes an Errno::EBADF is the result instead of an IOError: closed stream.
Tracing through the code, what's happening is that the ChannelDescriptor responds to the getline() call with a BadDescriptorException because the underlying Channel responds false to isOpen(), which on the face of it seems wrong but I'm not familiar enough with the semantics to make a convincing argument. Also, given that the readStream.getline() call is guarded by a beforeBlockingCall(), I suspect that this actually indicates a race somewhere else in the code. It is proving difficult to narrow this down to a single test case, but the following code at least demonstrates the problem: require 'socket' server = TCPServer.new(0) server_thread = Thread.new{ server.accept while true } exceptions = [] 1000.times do |i| client_socket = TCPSocket.new("localhost", server.addr[1]) close_thread = Thread.new{ sleep(Kernel.rand/100); client_socket.close } read_thread = Thread.new do sleep(Kernel.rand/100); begin client_socket.readline rescue Exception => e exceptions << e end end close_thread.join read_thread.join end p exceptions.map{|e| e.class.to_s}.inject({}){|m,r| m.merge r => true}.keys.sort The final output is, for me: ["EOFError", "Errno::EBADF", "Errno::EPIPE", "IOError"] EOFError and IOError are expected. EBADF and EPIPE are not. |
When TCPSocket#readline is called after a TCPSocket#close on the same client socket, if the #close comes from another thread, sometimes an Errno::EBADF is the result instead of an IOError: closed stream.
Tracing through the code, what's happening is that the ChannelDescriptor responds to the getline() call with a BadDescriptorException because the underlying Channel responds false to isOpen(), which on the face of it seems wrong but I'm not familiar enough with the semantics to make a convincing argument. Also, given that the readStream.getline() call is guarded by a beforeBlockingCall(), I suspect that this actually indicates a race somewhere else in the code. It is proving difficult to narrow this down to a single test case, but the following code at least demonstrates the problem: {code} require 'socket' server = TCPServer.new(0) server_thread = Thread.new{ server.accept while true } exceptions = [] 1000.times do |i| client_socket = TCPSocket.new("localhost", server.addr[1]) close_thread = Thread.new{ sleep(Kernel.rand/100); client_socket.close } read_thread = Thread.new do sleep(Kernel.rand/100); begin client_socket.readline rescue Exception => e exceptions << e end end close_thread.join read_thread.join end p exceptions.map{|e| e.class.to_s}.inject({}){|m,r| m.merge r => true}.keys.sort {code} The final output is, for me: ["EOFError", "Errno::EBADF", "Errno::EPIPE", "IOError"] EOFError and IOError are expected. EBADF and EPIPE are not. |
| Status | Open [ 1 ] | Resolved [ 5 ] |
| Assignee | Thomas E Enebo [ enebo ] | Charles Oliver Nutter [ headius ] |
| Fix Version/s | JRuby 1.6RC3 [ 17147 ] | |
| Resolution | Fixed [ 1 ] |
| Status | Resolved [ 5 ] | Closed [ 6 ] |