Forum: Ruby sockets and threads

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
67e8ce0cbef63cba879ae84170318e1e?d=identicon&s=25 Dominik Werder (Guest)
on 2006-03-31 13:40
(Received via mailing list)
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
67e8ce0cbef63cba879ae84170318e1e?d=identicon&s=25 Dominik Werder (Guest)
on 2006-03-31 14:26
(Received via mailing list)
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
47b1910084592eb77a032bc7d8d1a84e?d=identicon&s=25 Joel VanderWerf (Guest)
on 2006-03-31 23:40
(Received via mailing list)
Dominik Werder wrote:
> 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.
This topic is locked and can not be replied to.