Sockets and threads

Hello,

I observed that even if every thread in a program creates it's own
Socket, but all to the same Server, then this is not thread-safe.

It is the same situation as if every thread shared the same Socket
without any mutexes. I observed this with TCPSockets, UNIXSockets ans
UDPSockets.

I run Kernel 2.6.14.2, ruby 1.8.4 (2005-12-24) [i686-linux]

The short source code below produces the following output on the server.
If I enabled the mutex (the 3 lines commented out) it would work better.

Got: thread[16] says 65
Got: thread[19] says 68thread[17] says 20
Got:
Got: thread[24] says 97
Got: thread[8] says 64thread[26] says 17thread[13] says 43
Got:
Got:
Got: thread[7] says 84
Got: thread[0] says 78thread[18] says 37
Got: thread[27] says 98
Got:
Got: thread[18] says 13
Got: thread[11] says 19
Got: thread[1] says 91thread[26] says 18
Got:
Got: thread[1] says 53
Got: thread[19] says 42

# First start the server with:
# ruby tcpsockets.rb server
# Then start the client which spawns several threads:
# ruby tcpsockets.rb

require 'socket'
require 'monitor'

if ARGV[0] == 'server'
  threads = []
  server = TCPServer.new 'localhost', 8889
  putslock = Monitor.new
  while socket = server.accept
    t = Thread.new(socket) do |sock|
      while str = sock.gets
        putslock.synchronize do
          puts "Got: #{str}"
        end
      end
    end
    threads << t
  end
  threads.each do |t| t.join end
else
  Thread.abort_on_exception = true
  #socketlock = Monitor.new
  threads = []
  30.times do |i|
    t = Thread.new(i) do |ii|
      count = 0
      socket = TCPSocket.new 'localhost', 8889
      puts "Using socket #{socket.object_id}"
      while count < 10
        #socketlock.synchronize do
          socket.puts "thread[#{ii}] says #{Kernel.rand 100}"
          socket.flush
        #end
        sleep Kernel.rand * 0.3
        count += 1
      end
    end
    threads << t
  end
  threads.each do |t| t.join end
end

Is this known/intended behavior?

thanks and a good day!
Dominik

Hello,

I found the bug: In the client code the variable "socket" was already
known to the interpreter, but anyway not in the same scope, so every
thread overwrote the socket and in the end all used the same connection..

I didn't know that

if false
  yo = 123
else
  puts yo.class.name
end

would output "NilClass"..

bye!
Dominik

Dominik Werder wrote:

Hello,

I found the bug: In the client code the variable "socket" was already
known to the interpreter, but anyway not in the same scope, so every
thread overwrote the socket and in the end all used the same connection..

I didn't know that

if false
  yo = 123
else
  puts yo.class.name
end

would output "NilClass"..

I'm getting more and more defensive about this kind of bug. I usually
just do

Thread.new(...) { |...| some_method(...)}

and then I can code some_method without worrying about local variable
sharing.

Another example is when threads are created in a loop. Even if the
thread code is very small, local vars with shared binding can be
devastating:

a =
i = 0
(0...100).map do
  Thread.new {sleep 0.001; a << i}
  i += 1
end
sleep 1
p a.uniq.size

Output is unpredictable, 74, 86, etc. (YMMV)

Obviously, this could be solved by making i local to the block, or
passing it to Thread.new. Without the sleep 0.001, I don't seem to get
this behavior (the output is 100), but I wouldn't rely on that: the ruby
thread scheduler could still reasonably schedule threads so that two or
more of them saw the same value of i. So it's still best to pass
arguments to Thread.new.

···

--
      vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407