I'm trying to do something like this, where I have two loops going in seperate threads. The problem I am having is that in the main thread, when I use gets and the script is waiting for user input, the other thread is stopped to wait as well.
class Server
def initialize
#server = TCPServer.new(8080)
run
end
def run
#thread = Thread.new(#server) { |server|
while true
newsock = server.accept
puts "some stuff after accept!"
next if !newsock
# some other stuff
end
}
end
end
def processCommand
# some user commands here
end
test = Server.new
while true do
processCommand(STDIN.gets)
end
The above is just a sample of what I want to do.
Is there a way to make the main thread block while waiting for user input?
You might want to take a look at using the select method of the IO class. Take a look at
good select example for handling select with asynchronous input. Depending upon what version of ruby you're using you might have issues with STDIN though, I'm pretty sure it always triggers the select in 1.8.6.
I'm not sure if this is what you are looking for, but I was looking for something similar and this example does exactly what I wanted. The thread will continue processing until the user hits enter, and then the thread will be able to handle your user input as desired.
user_input = nil
t1 = Thread.new do
while !user_input
puts "Running"
end
puts "Stopping per user input: #{user_input}"
end
user_input = STDIN.gets
t1.join
Related
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.
I found another question very similar to mine with a solution that worked for me when I wrote it all in one simple script. I even wrote a second simple example sort of simulating what I'm trying to do, and it seemed to still work.
My simulation was:
class A
def looper(&block)
Thread.new do
loop do
exit if gets.chomp == 'q'
end
end
loop do
block.call
end
end
end
class B < A
def looper
super do
puts 'howddyyyy from B'
end
end
end
This works fine, exiting when you press q<Enter>. However, when I tried to implement this into my actual project, it fails to work. I'll post the code from the method in question in the child class, as the parent class is literally exactly the same as the example above.
def looper
super do
if obj = Object.first(:process_status => STATUS_UNPROCESSED)
puts "[Object ##{obj.id}] Processing..."
puts "-" * 60
obj.set_failed
if #obj.process(obj)
obj.set_processed
end
puts "-" * 60
puts "[Object ##{obj.id}] Finished!"
puts
puts
else
sleep 10
end
end
end
So, for some reason, this doesn't work. I put a puts into the new Thread (listening for q), and it seems to output the puts before every loop of block.call. Maybe it just isn't able to get the key, by which I mean, maybe the timeframe in which you have to enter q<Enter> is way too small? I'm not sure, which is why I'm asking some advice here. My only other guess is that it has something to do with the methods called within this method (process, or possible the Sequel calls to the database) blocking the other thread(s)?
I'm new to threading, so I have no clue.
Okay, everybody. I feel a little stupid for typing all that up, as I came to a solution not five minutes later (and one I had overlooked here on Stack Overflow).
For anyone facing a similar issue in the future, this is what I ended up doing (in the parent class):
def looper(&block)
interrupted = false
trap("INT") { interrupted = true }
until interrupted do
block.call
end
exit
end
This manages to achieve what I was essentially trying to do.
Thanks for reading!
I'm using RSpec to test the behavior of a simple REPL. The REPL just echoes back whatever the input was, unless the input was "exit", in which case it terminates the loop.
To avoid hanging the test runner, I'm running the REPL method inside a separate thread. To make sure that the code in the thread has executed before I write expectations about it, I've found it necessary to include a brief sleep call. If I remove it, the tests fail intermittently because the expectations are sometimes made before the code in the thread has run.
What is a good way to structure the code and spec such that I can make expectations about the REPL's behavior deterministically, without the need for the sleep hack?
Here is the REPL class and the spec:
class REPL
def initialize(stdin = $stdin, stdout = $stdout)
#stdin = stdin
#stdout = stdout
end
def run
#stdout.puts "Type exit to end the session."
loop do
#stdout.print "$ "
input = #stdin.gets.to_s.chomp.strip
break if input == "exit"
#stdout.puts(input)
end
end
end
describe REPL do
let(:stdin) { StringIO.new }
let(:stdout) { StringIO.new }
let!(:thread) { Thread.new { subject.run } }
subject { described_class.new(stdin, stdout) }
# Removing this before hook causes the examples to fail intermittently
before { sleep 0.01 }
after { thread.kill if thread.alive? }
it "prints a message on how to end the session" do
expect(stdout.string).to match(/end the session/)
end
it "prints a prompt for user input" do
expect(stdout.string).to match(/\$ /)
end
it "echoes input" do
stdin.puts("foo")
stdin.rewind
expect(stdout.string).to match(/foo/)
end
end
Instead of letting :stdout be a StringIO, you could back it by a Queue. Then when you try to read from the queue, your tests will just wait until the REPL pushes something into the queue (aka. writes to stdout).
require 'thread'
class QueueIO
def initialize
#queue = Queue.new
end
def write(str)
#queue.push(str)
end
def puts(str)
write(str + "\n")
end
def read
#queue.pop
end
end
let(:stdout) { QueueIO.new }
I just wrote this up without trying it out, and it may not be robust enough for your needs, but it gets the point across. If you use a data structure to synchronize the two threads like this, then you don't need to sleep at all. Since this removes the non-determinism, you shouldn't see the intermittent failures.
I've used a running? guard for situations like this. You probably can't avoid the sleep entirely, but you can avoid unnecessary sleeps.
First, add a running? method to your REPL class.
class REPL
...
def running?
!!#running
end
def run
#running=true
loop do
...
if input == 'exit
#running = false
break
end
...
end
end
end
Then, in your specs, sleep until the REPL is running:
describe REPL do
...
before { sleep 0.01 until REPL.running? }
...
end
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
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...