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.
I get even worse output:
["EOFError", "Errno::EBADF", "Errno::EPIPE", "IOError", "Java::JavaLang::NullPointerException"]
Digging in.