JRuby (please use github issues at http://bugs.jruby.org)
  1. JRuby (please use github issues at http://bugs.jruby.org)
  2. JRUBY-5514

Errno::EBADF is sometimes raised instead of IOError when TCPSocket#readline is called after TCPSocket#close

    Details

    • Type: Bug Bug
    • Status: Closed Closed
    • Priority: Minor 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:
    • Number of attachments :
      0

      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

        Charles Oliver Nutter made changes -
        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.
        Charles Oliver Nutter made changes -
        Status Open [ 1 ] Resolved [ 5 ]
        Assignee Thomas E Enebo [ enebo ] Charles Oliver Nutter [ headius ]
        Fix Version/s JRuby 1.6RC3 [ 17147 ]
        Resolution Fixed [ 1 ]
        Charles Oliver Nutter made changes -
        Status Resolved [ 5 ] Closed [ 6 ]

          People

          • Assignee:
            Charles Oliver Nutter
            Reporter:
            Alex Young
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: