Search code examples
rubymultithreadingmultiprocessingmutexdrb

Ruby DRb - Thread-Safety


I'm working on a multi-process file processing script. After trying out threads/forking, I found out about IPC (pipe/socket) and last but not least DRb. It seems like the most capable of all the options and relatively user friendly.

I was reading in about thread safety at: https://en.wikibooks.org/wiki/Ruby_Programming/Standard_Library/DRb

But when I tried their examples I don't seem to get a thread-safe result.

Thread safe server:

require 'drb'
require 'thread'

class MyStore
  def initialize
    @hash = { :counter=>0 }
    @mutex = Mutex.new
  end
  def inc(elem)
    @mutex.synchronize do
      self[elem] = self[elem].succ
    end
  end
  def [](elem)
    @hash[elem]
  end
  def []=(elem,value)
    @hash[elem] = value
  end
end

mystore = MyStore.new
DRb.start_service('druby://localhost:9000', mystore)
DRb.thread.join

Client:

require 'drb'    
obj = DRbObject.new(nil, 'druby://localhost:9000')
STDOUT.sync = true 

100.times do
    puts obj[:counter]
    obj.inc(:counter)
    obj[:lastaccess] = Time.now
end

I'm running the server code first in the background. I later launch the client code twice:

ruby client.rb > 1.txt & ; ruby client.rb > 2.txt

Now I'm expecting to see different numbers in files 1.txt and 2.txt since each client takes control of the counter and doesn't release it until it performed the increment.

What obvious issue am I missing? :)


Solution

  • The problem is inside your loop. The server's inc method is thread-safe. However, your access to obj[:counter] is not thread-safe. So you will notice that when you run your example it is doing 200 total increments (100 for each process) because you can see the last number printed is 199. That means the inc requests are properly being queued and executed individually.

    The reason you're not actually seeing all 200 numbers (0-199) individually printed (i.e. you see some duplicate numbers in the two files) is because your loop is just executing puts obj[:counter] as soon as it hits that line of code. The current value of obj[:counter] is being printed regardless of the mutex state because you're not checking to see if its currently locked. That means each file is printing 100 total numbers between 0-199, but they're not guaranteed to be different between the two files.

    To make your example work you would want to do the printing inside of the code that is locked by the mutex, then add an extra argument to the function so you can check which client process it came from. Or in my opinion you already proved it worked because the increment happens 200 times.