What should TCPSocket#close do to a blocked #readline?

Take the following code:

  require 'socket'
  require 'thread'

  server = TCPServer.new("localhost", 0)
  serv_thread = Thread.new{ server.accept }
  sleep(0.1)
  sock = TCPSocket.new("localhost", server.addr[1])

  q = Queue.new
  client_thread = Thread.new{
    begin
      sock.readline
    rescue StandardError => e
      q.push(e)
    end
  }

  sleep(0.1) while client_thread.status == "run"
  sock.close
  # client_thread.wakeup rescue nil
  err = q.pop
  client_thread.join
  fail "Failure!" unless err.is_a?(IOError)
  puts "Success!"

So, we've got one thread blocked on a #readline, and another thread
closes the socket out from under it. On 1.8.7-p302, this completes
successfully. On 1.9.2-p136, it hangs at q.pop.

If I uncomment the client_thread.wakeup call, both complete (although
the rescue is necessary to prevent a ThreadError on 1.8.7).

Is this expected? Which is "correct"?

Thanks,

···

--
Alex

--
Posted via http://www.ruby-forum.com/.

Take the following code:

require 'socket'
require 'thread'

server = TCPServer.new("localhost", 0)
serv_thread = Thread.new{ server.accept }
sleep(0.1)
sock = TCPSocket.new("localhost", server.addr[1])

q = Queue.new
client_thread = Thread.new{
begin
sock.readline
rescue StandardError => e
q.push(e)
end
}

sleep(0.1) while client_thread.status == "run"
sock.close
# client_thread.wakeup rescue nil
err = q.pop
client_thread.join
fail "Failure!" unless err.is_a?(IOError)
puts "Success!"

So, we've got one thread blocked on a #readline, and another thread
closes the socket out from under it. On 1.8.7-p302, this completes
successfully. On 1.9.2-p136, it hangs at q.pop.

I cannot confirm this:

15:40:10 Temp$ ruby19 -v
ruby 1.9.2p136 (2010-12-25 revision 30365) [i386-cygwin]
15:40:50 Temp$ ruby19 s.rb
#<IOError: closed stream>
Success!
15:41:31 Temp$ cat s.rb
require 'socket'
require 'thread'

server = TCPServer.new("localhost", 0)
serv_thread = Thread.new{ server.accept }
sleep(0.1)
sock = TCPSocket.new("localhost", server.addr[1])

q = Queue.new
client_thread = Thread.new{
   begin
     sock.readline
     q.push "OK"
   rescue StandardError => e
     p e
     q.push(e)
   end
}

sleep(0.1) while client_thread.status == "run"
sock.close
# client_thread.wakeup rescue nil
err = q.pop
# client_thread.join
fail "Failure!" unless err.is_a?(IOError)
puts "Success!"

15:41:36 Temp$

If I uncomment the client_thread.wakeup call, both complete (although
the rescue is necessary to prevent a ThreadError on 1.8.7).

Is this expected? Which is "correct"?

Hm... I would expect your 1.9.2 to also throw.

Btw, one thing is odd about your test case: you have two mechanisms to
synchronize threads - you read from the queue and you join
client_thread. If you really only want to track status of the thread
you could as well use Thread#value:

client_thread = Thread.new{
   begin
     sock.readline
     nil
   rescue StandardError => e
     e
   end
}

err = client_thread.value

Kind regards

robert

···

On Thu, Feb 10, 2011 at 12:59 PM, Alex Young <alex@blackkettle.org> wrote:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Robert Klemme wrote in post #980828:

sleep(0.1) while client_thread.status == "run"
successfully. On 1.9.2-p136, it hangs at q.pop.

I cannot confirm this:

15:40:10 Temp$ ruby19 -v
ruby 1.9.2p136 (2010-12-25 revision 30365) [i386-cygwin]

$ ruby -v
ruby 1.9.2p136 (2010-12-25 revision 30365) [x86_64-linux]

is mine. Might it be an architecture issue? I don't have a 32bit machine
handy to compare.

15:40:50 Temp$ ruby19 s.rb
#<IOError: closed stream>
Success!
15:41:31 Temp$ cat s.rb
require 'socket'
require 'thread'

server = TCPServer.new("localhost", 0)
serv_thread = Thread.new{ server.accept }
sleep(0.1)
sock = TCPSocket.new("localhost", server.addr[1])

q = Queue.new
client_thread = Thread.new{
   begin
     sock.readline
     q.push "OK"
   rescue StandardError => e
     p e
     q.push(e)
   end
}

sleep(0.1) while client_thread.status == "run"
sock.close
# client_thread.wakeup rescue nil
err = q.pop
# client_thread.join
fail "Failure!" unless err.is_a?(IOError)
puts "Success!"

15:41:36 Temp$

If I uncomment the client_thread.wakeup call, both complete (although
the rescue is necessary to prevent a ThreadError on 1.8.7).

Is this expected? Which is "correct"?

Hm... I would expect your 1.9.2 to also throw.

That's what I thought, but rubinius doesn't throw either, so I wasn't
sure.

Btw, one thing is odd about your test case: you have two mechanisms to
synchronize threads - you read from the queue and you join
client_thread.

Yes, the #join was a bit of leftover that I forgot to trim.

If you really only want to track status of the thread
you could as well use Thread#value:

client_thread = Thread.new{
   begin
     sock.readline
     nil
   rescue StandardError => e
     e
   end
}

err = client_thread.value

That's a bit neater, though. I like that.

Thanks,

···

On Thu, Feb 10, 2011 at 12:59 PM, Alex Young <alex@blackkettle.org> > wrote:

--
Alex

--
Posted via http://www.ruby-forum.com/\.

Alex Young wrote in post #980861:

Robert Klemme wrote in post #980828:

sleep(0.1) while client_thread.status == "run"
successfully. On 1.9.2-p136, it hangs at q.pop.

I cannot confirm this:

15:40:10 Temp$ ruby19 -v
ruby 1.9.2p136 (2010-12-25 revision 30365) [i386-cygwin]

$ ruby -v
ruby 1.9.2p136 (2010-12-25 revision 30365) [x86_64-linux]

Ok, so this also fails on i686-linux. Now, where's the bug? :slight_smile:

···

On Thu, Feb 10, 2011 at 12:59 PM, Alex Young <alex@blackkettle.org> >> wrote:

--
Alex

--
Posted via http://www.ruby-forum.com/\.

Maybe the network stack behaves different on these different platforms.

Kind regards

robert

···

On Fri, Feb 11, 2011 at 1:01 PM, Alex Young <alex@blackkettle.org> wrote:

Alex Young wrote in post #980861:

Robert Klemme wrote in post #980828:

On Thu, Feb 10, 2011 at 12:59 PM, Alex Young <alex@blackkettle.org> >>> wrote:

sleep(0.1) while client_thread.status == "run"
successfully. On 1.9.2-p136, it hangs at q.pop.

I cannot confirm this:

15:40:10 Temp$ ruby19 -v
ruby 1.9.2p136 (2010-12-25 revision 30365) [i386-cygwin]

$ ruby -v
ruby 1.9.2p136 (2010-12-25 revision 30365) [x86_64-linux]

Ok, so this also fails on i686-linux. Now, where's the bug? :slight_smile:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Robert Klemme wrote in post #981080:

···

On Fri, Feb 11, 2011 at 1:01 PM, Alex Young <alex@blackkettle.org> > wrote:

ruby 1.9.2p136 (2010-12-25 revision 30365) [i386-cygwin]

$ ruby -v
ruby 1.9.2p136 (2010-12-25 revision 30365) [x86_64-linux]

Ok, so this also fails on i686-linux. Now, where's the bug? :slight_smile:

Maybe the network stack behaves different on these different platforms.

That doesn't explain why 1.8.7 on Linux behaves the same as cygwin
1.9.2, though.

--
Alex

--
Posted via http://www.ruby-forum.com/\.

Alex Young wrote in post #981138:

Robert Klemme wrote in post #981080:

ruby 1.9.2p136 (2010-12-25 revision 30365) [i386-cygwin]

$ ruby -v
ruby 1.9.2p136 (2010-12-25 revision 30365) [x86_64-linux]

Ok, so this also fails on i686-linux. Now, where's the bug? :slight_smile:

Maybe the network stack behaves different on these different platforms.

That doesn't explain why 1.8.7 on Linux behaves the same as cygwin
1.9.2, though.

Well, that was quick :slight_smile:

http://redmine.ruby-lang.org/issues/show/4390

···

On Fri, Feb 11, 2011 at 1:01 PM, Alex Young <alex@blackkettle.org> >> wrote:

--
Alex

--
Posted via http://www.ruby-forum.com/\.

Yep, Nobu is great! Thanks for filing the bug, Alex.

Cheers

robert

···

On Sat, Feb 12, 2011 at 5:06 PM, Alex Young <alex@blackkettle.org> wrote:

Alex Young wrote in post #981138:

Robert Klemme wrote in post #981080:

On Fri, Feb 11, 2011 at 1:01 PM, Alex Young <alex@blackkettle.org> >>> wrote:

ruby 1.9.2p136 (2010-12-25 revision 30365) [i386-cygwin]

$ ruby -v
ruby 1.9.2p136 (2010-12-25 revision 30365) [x86_64-linux]

Ok, so this also fails on i686-linux. Now, where's the bug? :slight_smile:

Maybe the network stack behaves different on these different platforms.

That doesn't explain why 1.8.7 on Linux behaves the same as cygwin
1.9.2, though.

Well, that was quick :slight_smile:

http://redmine.ruby-lang.org/issues/show/4390

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/