RSpec: Testing with Threads - ruby

In RSpec, I have function that creates a new thread, and inside that thread performs some action–in my case, calls TCPSocket#readline. Here's the function as it is right now:
def read
Thread.new do
while line = #socket.readline
#TODO: stuff
end
end
end
Due to thread scheduling, my test will fail if written as such:
it "reads from socket" do
subject.socket.should_receive(:readline)
subject.read
end
Currently the only way I know to hack around this is to use sleep 0.1. Is there a way to properly delay the test until that thread is running?

If your goal is to assert the system state is changed by the execution of your second thread, you should join on the second thread in your main test thread:
it "reads from socket" do
subject.socket.should_receive(:readline)
socket_thread = subject.read
socket_thread.join
end

This is a bit of a hack, but here's a before block you can use in case you'd like the thread to yield but be able to call join at the end of the thread.
before do
allow(Thread).to receive(:new).and_yield.and_return(Class.new { def join; end }.new)
end

Related

Easiest way to make sure a lot of threads finish in Ruby?

I'm creating a lot of threads:
(1..255).each do |n|
Thread.new do
sleep(10) # does a lot of work
end
end
# at this point I need to make sure all the threads completed
I would've hoped I could add each thread to a ThreadGroup and call a function like wait_until_all_threads_complete on that ThreadGroup. But I don't see anything obvious in the Ruby docs.
Do I have to add each thread to an array and then iterate over each one calling thread.join? There must be an easier way for such an extremely common use case.
threads = (1..255).map do |n|
Thread.new do
sleep(10) # does a lot of work
end
end
threads.each do |thread|
thread.join
end
Use ThreadGroup#list
If you assign threads to a ThreadGroup with ThreadGroup#add, you can map Thread#join or other Thread methods onto each member of the group as returned by the ThreadGroup#list method. For example:
thread_group = ThreadGroup.new
255.times do
thread_group.add Thread.new { sleep 10 }
end
thread_group.list.map &:join
This will only join threads belonging to thread_group, rather than to ThreadGroup::Default.

How to make a ruby thread execute a function of my choosing?

Is it possible to create a "worker thread" so to speak that is on standby until it receives a function to execute asynchronously?
Is there a way to send a function like
def some_function
puts "hi"
# write something
db.exec()
end
to an existing thread that's just sitting there waiting?
The idea is I'd like to pawn off some database writes to a thread which runs asynchronously.
I thought about creating a Queue instance, then have a thread do something like this:
$command = Queue.new
Thread.new do
while trigger = $command.pop
some_method
end
end
$command.push("go!")
However this does not seem like a particularly good way to go about it. What is a better alternative?
The thread gem looks like it would suit your needs:
require 'thread/channel'
def some_method
puts "hi"
end
channel = Thread.channel
Thread.new do
while data = channel.receive
some_method
end
end
channel.send("go!")
channel.send("ruby!") # Any truthy message will do
channel.send(nil) # Non-truthy message to terminate other thread
sleep(1) # Give other thread time to do I/O
The channel uses ConditionVariable, which you could use yourself if you prefer.

Is it OK not to call Thread#join?

Is it OK not to call Thread#join? In this case, I don't care if the thread blows up - I just want Unicorn to keep processing.
class MyMiddleware
def initialize(app)
#app = app
end
def call(env)
t = Thread.new { sleep 1 }
t.join # is it ok if I skip this?
#app.call env
end
end
Will I get "zombie threads" or something like that?
It's perfectly fine to not call join - in fact, join is often not needed at all with multithreaded code. You should only call join if you need to block until the new thread completes.
You won't get a "zombie" thread. The new thread will run until completion, then clean itself up for you.

conditional variable in main thread

In my main thread, I am trying to wait for two resources from two separate threads. The way I implemented is as below:
require 'thread'
def new_thread
Thread.current[:ready_to_go] = false
puts "in thread: new thread"
sleep(5)
puts "in thread: sleep finished"
Thread.current[:ready_to_go] = true
sleep(2)
puts "in thread: back to thread again!"
end
thread1 = Thread.new do
new_thread
end
thread2 = Thread.new do
new_thread
end
# the main thread wait for ready_to_go to start
while (!(thread1[:ready_to_go] && thread2[:ready_to_go]))
sleep(0.5)
end
puts "back to main!"
sleep(8)
puts "main sleep over!"
thread1.join
thread2.join
Is there any better way to implement this? I tried to use conditional variables: the two threads signal the conditional variables and the main thread waits for them. But the wait method requires a mutex in my main thread, which I am trying to avoid.
I'm not familiar with Ruby, but the first Google result for "Ruby wait for thread" says:
you can wait for a particular thread to finish by calling that thread's Thread#join method. The calling thread will block until the given thread is finished. By calling join on each of the requestor threads, you can make sure that all three requests have completed before you terminate the main program.
It's generally best to use synchronization methods to wait for something to complete, rather than looping until a particular state is reached.
One easy way would be to get a Queue (let's call it myqueue). It's threadsafe and located in the Thread module
Instead of
Thread.current[:ready_to_go] = true
... do
myqueue.push :ready_to_go
And then your main thread would be:
junk = myqueue.pop # wait for thread one to push
junk = myqueue.pop # wait for thread two to push
# go on with your work

testing threaded code in ruby

I'm writing a delayed_job clone for DataMapper. I've got what I think is working and tested code except for the thread in the worker process. I looked to delayed_job for how to test this but there are now tests for that portion of the code. Below is the code I need to test. ideas? (I'm using rspec BTW)
def start
say "*** Starting job worker #{#name}"
t = Thread.new do
loop do
delay = Update.work_off(self) #this method well tested
break if $exit
sleep delay
break if $exit
end
clear_locks
end
trap('TERM') { terminate_with t }
trap('INT') { terminate_with t }
trap('USR1') do
say "Wakeup Signal Caught"
t.run
end
see also this thread
The best approach, I believe, is to stub the Thread.new method, and make sure that any "complicated" stuff is in it's own method which can be tested individually. Thus you would have something like this:
class Foo
def start
Thread.new do
do_something
end
end
def do_something
loop do
foo.bar(bar.foo)
end
end
end
Then you would test like this:
describe Foo
it "starts thread running do_something" do
f = Foo.new
expect(Thread).to receive(:new).and_yield
expect(f).to receive(:do_something)
f.start
end
it "do_something loops with and calls foo.bar with bar.foo" do
f = Foo.new
expect(f).to receive(:loop).and_yield #for multiple yields: receive(:loop).and_yield.and_yield.and_yield...
expect(foo).to receive(:bar).with(bar.foo)
f.do_something
end
end
This way you don't have to hax around so much to get the desired result.
You could start the worker as a subprocess when testing, waiting for it to fully start, and then check the output / send signals to it.
I suspect you can pick up quite a few concrete testing ideas in this area from the Unicorn project.
Its impossible to test threads completely. Best you can do is to use mocks.
(something like)
object.should_recieve(:trap).with('TERM').and yield
object.start
How about just having the thread yield right in your test.
Thread.stub(:new).and_yield
start
# assertions...

Resources