Stopping a thread, ensuring that certain final code is run - ruby

For this answer I wrote code like:
def show_wait_spinner
dirty = false
spinner = Thread.new{
loop{
print "*"
dirty = true
sleep 0.1
print "\b"
dirty = false
}
}
yield
spinner.kill
print "\b" if dirty
end
print "A"
show_wait_spinner{ sleep rand }
puts "B"
The goal is to ensure that the final output was "AB"—to print a final "\b" if it was not already printed by the thread. That code seems messy to me in Ruby where begin/rescue/ensure exists. So I tried some other implementations of show_wait_spinner; all of them fail to ensure that "AB" is always the output, and never "A*B" or "AB*".
Is there a cleaner, more Ruby-esque way to implement this logic?
Stop at end of loop via Mutex
def show_wait_spinner
stop = false
stopm = Mutex.new
spinner = Thread.new{
loop{
print "*"
sleep 0.1
print "\b"
stopm.synchronize{ break if stop }
}
}
yield
stopm.synchronize{ stop = true }
STDOUT.flush
end
…but my logic must be off, since this always results in "A*B".
Stop at end of loop via Thread-local variable
This second attempt results in sometimes "A*B" being printed, sometimes "AB":
def show_wait_spinner
stop = false
spinner = Thread.new{
Thread.current[:stop] = false
loop{
print "*"
sleep 0.1
print "\b"
stopm.synchronize{ break if Thread.current[:stop] }
}
}
yield
spinner[:stop] = true
STDOUT.flush
end
Kill and Ensure the Thread
def show_wait_spinner
spinner = Thread.new{
dirty = false
begin
loop{
print "*"
dirty = true
sleep 0.1
print "\b"
dirty = false
}
ensure
print "\b" if dirty
end
}
yield
spinner.kill
STDOUT.flush
end
Raise and Rescue the Thread
def show_wait_spinner
spinner = Thread.new{
dirty = false
begin
loop{
print "*"
dirty = true
sleep 0.1
print "\b"
dirty = false
}
rescue
puts "YAY"
print "\b" if dirty
end
}
yield
spinner.raise
STDOUT.flush
end

Instead of killing your thread, why don't you flip a variable that causes it to stop at a pre-defined point? If you let it cycle through and exit at the end of the loop you won't have nearly as much trouble.
For instance:
def show_wait_spinner
running = true
spinner = Thread.new do
while (running) do
print "*"
sleep 0.1
print "\b"
end
end
yield
running = false
spinner.join
end
print "A"
show_wait_spinner{ sleep rand }
puts "B"
When you call Thread#kill you have no idea where the thread is, and the thread isn't given an opportunity to clean up what it's doing. You can always kill the thread if your polite "stop running" request isn't respected.

I prefer your synchronized stop condition approach but you have a couple bugs:
after you set the stop variable, you almost immediately end the
program, so the thing that stops the thread is program exit, not the
conditional test in the loop; use Thread#join to wait for the thread
to exit normally and you'll get the consistent output you want.
break in the synchronize block breaks out of the block, not the
loop.
def show_wait_spinner
stop = false
stopm = Mutex.new
spinner = Thread.new{
loop{
print "*"
sleep 0.1
print "\b"
break if stopm.synchronize{ stop }
}
}
yield
stopm.synchronize{ stop = true }
spinner.join
STDOUT.flush
end
print "A"
show_wait_spinner{ sleep rand }
puts "B"
I would avoid any solution involving Thread#raise and Thread#kill as their behavior can never be predictable and correct, see Charles Nutter's rant about the brokenness of these methods.
The Mutex#synchronize is only necessary for this simple boolean flipping if you really care a lot about the precise timing around the race condition when the parent thread sets the var, which in this example isn't likely, so you could avoid the overhead and just set and read stop normally.

In your mutex example, you need to wait for the Thread to finish before exiting the method. Currently you set stop to true, then exit the method, print B and end your script before the spinner thread is able to wake up and print the last backspace to delete the * character.
Also, the break in stopm.synchronize{ break if stop } only exits the inner block, not the loop, so you need to use catch/throw or something.
However, you don't need the mutex. This works for me in 1.9.3:
def show_wait_spinner
exit = false
spinner = Thread.new{
loop{
print "*"
sleep 0.1
print "\b"
break if exit
}
}
yield
exit = true
spinner.join
end
Adding $stdout.sync = true at the top makes it work in 1.8.7.

Related

Trigger a Loop to pause and continue when space bar is pressed

I want to pause the running loop when i hit space and resume it again on doing so. How do i do it?
require 'rubygems'
require 'rest-client'
URL="some url here"
data1 = 0
data2 = 0
sign = ["up","down","equal"]
i=3
while true
response = RestClient.get(URL)
arr = (response.body).split(/"/)
data2=arr[9].to_i
if data2>data1
i = 0
elsif data2<data1
i = 1
else data2 == data1
i = 2
end
puts "#{arr[9]} #{sign[i]}"
data1=arr[9].to_i #marker<<<---Here
end
Simpler saying i want the loop to come to the marker and not run again till space is pressed.
Edit
I tried putting a puts there but obviously it pauses there and waits for me to give an input every time. Please try to make things as less complex as possible. I am kind of a beginner.
Use threads? This is not a direct answer but will give you a picture of how to use them.
$key_hit = false
t1 = Thread.new{
loop{
puts "Hello"
break if $key_hit
}
}
t2 = Thread.new {
x = gets
$key_hit = true
}
t1.join
t2.join
puts "Done, exiting"

ruby: finish loop iteration before raising Interrupt

I'm looping through a lot of items and I want to periodically interrupt the loop to save and continue at a later time like this:
begin
big_list.each do |i|
# sensitive stuff
sensitive_method(i)
# other sensitive stuff
end
rescue Interrupt
# finish the current iteration
# then do something else (save)
# don't raise (print done)
end
By sensitive I mean that, if Interrupt is raised in the middle of an iteration, data will be corrupted so I need to guarantee that the iteration finishes before exiting.
Also, if another exception is raised, it should still finish the loop but raise it afterwards
EDIT:
Using the answer by mudasobwa in a test scenario:
while true
result = begin
puts "start"
sleep 1
puts "halfway"
sleep 1
puts "done\n\n"
nil
rescue Exception => e
e
end
case result
when Interrupt
puts "STOPPED"
break
when Exception then raise result
end
end
I get:
start
halfway
done
start
^C: /...
STOPPED
which is my exact problem, I need it to finish the loop (sleep, print halfway, sleep, print done) and only then break out (wrapping the puts, sleep... in a method does not help)
TL;DR: There is no way to continue the execution of the method from inside the middle of it.
big_list.each do |i|
# sensitive stuff
result = begin
sensitive_method(i)
nil
rescue Exception => e
e
end
# other sensitive stuff
case result
when Interrupt
puts "done"
break "done"
when Exception then raise result
end
end
Sidenote: you probably don’t want to rescue the topmost Exception, but some subclass that makes sense to rescue.
To make it possible to finish the chunk of operations:
operations = [
-> { puts "start" },
-> { sleep 1 },
-> { puts "halfway" },
-> { sleep 1 },
-> { puts "done\n\n" }
]
def safe_chunk(operations, index = 0)
result = operations[index..-1].each_with_index(index) do |op, idx|
begin
op.()
rescue Exception => e
safe_chunk(operations, idx) # or idx + 1
break e
end
end
result.is_a?(Array) ? nil : result
end
The Interrupt exception is raised in the main thread. If you use a worker thread to process the list it will never be interrupted. You will need a way to tell the worker thread to terminate though. Rescuing Interrupt in the main thread and setting a flag that's checked by the child can accomplish this.
BigList = (1..100)
def sensitive_method(item)
puts "start #{item}"
sleep 1
puts "halfway #{item}"
sleep 1
puts "done #{item}"
puts
end
#done = false
thread = Thread.new do
begin
BigList.each do |item|
break if #done
sensitive_method item
end
end
end
begin
thread.join
rescue Interrupt
#done = true
thread.join
end
The keyword ensure, used in rescue clauses, is available for situation such as this one, where code must be executed after an exception occurs.
[-1, 0, 1].each do |i|
begin
puts "i=#{i} before exception"
# <additional code>
n = 1/i
rescue ZeroDivisionError => e
puts "Exception: #{e}"
exit
ensure
puts "Just executed 1/#{i}"
# <additional code>
end
end
i=-1 before exception
Just executed 1/-1
i=0 before exception
Exception: divided by 0
Just executed 1/0
Notice that begin/rescue/ensure/end must be inside the loop and that the code after ensure is executed for each i regardless of whether a zero-divide exception occurs.

Ruby ThreadsWait timeout

I have the following code to block until all threads have finished (Gist):
ThreadsWait.all_waits(*threads)
What's the simplest way to set a timeout here, ie kill any threads if they are still running after e.g. 3 seconds?
Thread#join accepts an argument after which it will time out. Try this, for example:
5.times.map do |i|
Thread.new do
1_000_000_000.times { |i| i } # takes more than a second
puts "Finished" # will never print
end
end.each { |t| t.join(1) } # times out after a second
p 'stuff I want to execute after finishing the threads' # will print
If you have some things you want to execute before joining, you can do:
5.times.map do |i|
Thread.new do
1_000_000_000.times { |i| i } # takes more than a second
puts "Finished" # will never print
end
end.each do |thread|
puts 'Stuff I want to do before join' # Will print, multiple times
thread.join(1)
end

Printing an ASCII spinning "cursor" in the console

I have a Ruby script that does some long taking jobs. It is command-line only and I would like to show that the script is still running and not halted. I used to like the so called "spinning cursor" in the old days and I managed to reproduce it in Ruby under Windows.
Question: does this work in the other OS's? If not, is there an OS-independent way to accomplish this?
No IRB solutions please.
10.times {
print "/"
sleep(0.1)
print "\b"
print "-"
sleep(0.1)
print "\b"
print "\\"
sleep(0.1)
print "\b"
print "|"
sleep(0.1)
print "\b"
}
Yes, this works on Windows, OS X, and Linux. Improving on Niklas' suggestion, you can make this more general like so:
def show_wait_cursor(seconds,fps=10)
chars = %w[| / - \\]
delay = 1.0/fps
(seconds*fps).round.times{ |i|
print chars[i % chars.length]
sleep delay
print "\b"
}
end
show_wait_cursor(3)
If you don't know how long the process will take, you can do this in another thread:
def show_wait_spinner(fps=10)
chars = %w[| / - \\]
delay = 1.0/fps
iter = 0
spinner = Thread.new do
while iter do # Keep spinning until told otherwise
print chars[(iter+=1) % chars.length]
sleep delay
print "\b"
end
end
yield.tap{ # After yielding to the block, save the return value
iter = false # Tell the thread to exit, cleaning up after itself…
spinner.join # …and wait for it to do so.
} # Use the block's return value as the method's
end
print "Doing something tricky..."
show_wait_spinner{
sleep rand(4)+2 # Simulate a task taking an unknown amount of time
}
puts "Done!"
This one outputs:
Doing something tricky...|
Doing something tricky.../
Doing something tricky...-
Doing something tricky...\
(et cetera)
Doing something tricky...done!
# First define your chars
pinwheel = %w{| / - \\}
# Rotate and print as often as needed to "spin"
def spin_it
print "\b" + pinwheel.rotate!.first
end
EDIT from peter: here a working version
def spin_it(times)
pinwheel = %w{| / - \\}
times.times do
print "\b" + pinwheel.rotate!.first
sleep(0.1)
end
end
spin_it 10
I wrote a gem spin_to_win that displays a spinner while yielding a block. For example:
SpinToWin.with_spinner('Zzzz') do |spinner|
spinner.banner('sleepy')
sleep 1
end
Zzzz \ [sleepy]
It can also track work pending vs. work completed:
SpinToWin.with_spinner('Zzzz') do |spinner|
spinner.increment_todo!(3)
spinner.banner('snore')
sleep 1
spinner.increment_done!
spinner.banner('dream')
sleep 1
spinner.increment_done!
spinner.banner('wake up!')
sleep 1
spinner.increment_done!
end
Zzzz \ 3 of 3 [wake up!]
I use rainbow gem to print color-changing string to indicate the code is working.
require 'rainbow'
def self.print_with_random_color
content = "I am still working on it. Please wait..."
colors = ["aqua","chartreuse","crimson","fuchsia","gold","lawngreen","palegoldenrod","powderblue","sandybrown","deepskyblue"]
loop do
print Rainbow("[#{Time.now}] " + content).send(colors.sample)
(content.length + 1).times {print "\r"}
sleep 0.3
end
end

Threads in Ruby

Why does this code work (I see the output 1 2 3):
for i in 1..3
Thread.new{
puts i
}
end
However, the following code does not produce the same output (I do not see the output 1 2 3)?
for i in 1..3
Thread.new{
sleep(5)
puts i
}
end
When you hit the end of the script, Ruby exits. If you add sleep 10 after the final loop, you can see the output show up. (Albeit, as 3 each time, because the binding to i reflects the value at the end of processing, and the sleep causes a thread switch back to the loop.)
You might want something like:
threads = []
for i in 1..3
threads << Thread.new {
sleep 5
puts i
}
end
threads.map {|t| t.join }
That will wait for all the threads to terminate before exiting.

Resources