Steve Gooberman-Hill
Steve Gooberman-Hill

Reputation: 513

ruby Thread#allocate TypeError

I was looking in detail at the Thread class. Basically, I was looking for an elegant mechanism to allow thread-local variables to be inherited as threads are created. For example the functionality I am looking to create would ensure that

Thread.new do
   self[:foo]="bar"
   t1=Thread.new { puts self[:foo] }
end

=> "bar"

i.e. a Thread would inherit it's calling thread's thread-local variables

So I hit upon the idea of redefining Thread.new, so that I could add an extra step to copy the thread-local variables into the new thread from the current thread. Something like this:

class Thread
  def self.another_new(*args)
    o=allocate
    o.send(:initialize, *args)
    Thread.current.keys.each{ |k| o[k]=Thread.current[k] }
    o
  end
end

But when I try this I get the following error:

:in `allocate': allocator undefined for Thread (TypeError)

I thought that as Thread is a subclass of Object, it should have a working #allocate method. Is this not the case?

Does anyone have any deep insight on this, and on how to achieve the functionality I am looking for.

Thanks in advance

Steve

Upvotes: 3

Views: 194

Answers (3)

Nick Hyland
Nick Hyland

Reputation: 357

I was looking for the same thing recently and was able to come up with the following answer. Note I am aware the following is a hack and not recommended, but for the sake of answering the specific question on how you could alter the Thread.new functionality, I have done as following:

class Thread
  class << self
    alias :original_new :new

    def new(*args, **options, &block)
      original_thread = Thread.current
      instance = original_new(*args, **options, &block)
      original_thread.keys.each do |key|
        instance[key] = original_thread[key]
      end
      instance
    end
  end
end

Upvotes: 0

Steve Gooberman-Hill
Steve Gooberman-Hill

Reputation: 513

Here is a revised answer based on @CodeGroover's suggestion, with a simple unit test harness

ext/thread.rb

class Thread
  def self.inherit(*args, &block)
    parent = Thread.current
    t = Thread.new(parent, *args) do |parent|
      parent.keys.each{ |k| Thread.current[k] = parent[k] }
      yield *args
    end
    t
  end
end

test/thread.rb

require 'test/unit'
require 'ext/thread'

class ThreadTest < Test::Unit::TestCase
  def test_inherit
    Thread.current[:foo]=1
    m=Mutex.new
    
    #check basic inheritence
    t1= Thread.inherit do 
      assert_equal(1, Thread.current[:foo])
    end
      
    #check inheritence with parameters - in this case a mutex
    t2= Thread.inherit(m) do |m|
      assert_not_nil(m)
      m.synchronize{ Thread.current[:bar]=2 }
             
      assert_equal(1, Thread.current[:foo])
      assert_equal(2, Thread.current[:bar])
      
      sleep 0.1 
    end
      
    #ensure t2 runs its mutexs-synchronized block first
    sleep 0.05
    
    #check that the inheritence works downwards only - not back up in reverse
    m.synchronize do
      assert_nil(Thread.current[:bar])
    end
    
    [t1,t2].each{|x| x.join }
        
  end
  
end

Upvotes: 0

CodeGroover
CodeGroover

Reputation: 2187

Thread.new do
  Thread.current[:foo]="bar"
    t1=Thread.new(Thread.current) do |parent|
    puts parent[:foo] ? parent[:foo] : 'nothing'
  end.join
end.join 

#=> bar

UPDATED:

Try this in irb:

thread_ext.rb

class Thread
  def self.another_new(*args)
    parent = Thread.current
    a = Thread.new(parent) do |parent|
      parent.keys.each{ |k| Thread.current[k] = parent[k] }
      yield
    end
    a
  end
end

use_case.rb

A = Thread.new do
  Thread.current[:local_a]="A"
  B1 =Thread.another_new do
    C1 = Thread.another_new{p Thread.current[:local_a] }.join
  end

  B2 =Thread.another_new do
    C2 = Thread.another_new{p Thread.current[:local_a] }.join
  end

  [B1, B2].each{|b| b.join }
end.join

output

"A"
"A"

Upvotes: 1

Related Questions