Timeout if no user input for `x` seconds - ruby

How would I program a chunk of code in Ruby, which would timeout or exit if no user input is entered for x amount of time?
I don't have a half completed script too better convey my question, or even a pseudo-code concept algorithm.

You can use the Timeout module that is included in the standard libraries. It will raise a Timeout::Error on timeout if you want to rescue it.
require 'timeout'
x = 10
begin
status = Timeout::timeout(x) {
printf "Input: "
gets
}
puts "Got: #{status}"
rescue Timeout::Error
puts "Input timed out after #{x} seconds"
end

require "timeout"
Timeout.timeout(x) do
s = gets
...
end

Related

How to Run .gets but Continue Running Program after a certain delay in Ruby

I would like to know how I would be able to run .gets but continue to run the program after a certain delay.
For example:
variable=gets.chomp
#somehow continue running program to do for example:
#after a delay (sleep 10)
puts "Hello? Are you still here? The program will timeout in 10 seconds."
#asks for input again
puts "Please enter ..."
variable=gets.chomp
Thank you for your time.
require 'timeout'
print "Input something within 10 seconds: "
begin
foo = Timeout::timeout(10) {
gets.chomp
}
puts "You said #{foo}"
rescue Timeout::Error
puts
puts "You are too late."
end

Curb doesn't respond

I parse RSS stream with Feedjira.
When I used a fetch_and_parse method it sometimes blocked and doesn't respond.
The same thing happens with manual curb downloading.
I write in a loop:
#my_logger.info "--- Before perform ---"
easy = Curl::Easy.new
easy.follow_location = true
easy.max_redirects = 3
easy.connect_timeout = 120
easy.url = url
easy.useragent = "Ruby/Curb"
easy.perform
#my_logger.info "--- After perform ---"
doc = easy.body_str
easy.close
After some time (it may be a day or an hour), process stops on the easy.perform line and doesn't respond. E.g. process outputs --- Before perform --- and nothing else.
It can be related to a network issue happening randomly.
If you use a timeout you can skip this kind of situations in long running tasks.
require 'timeout'
begin
Timeout.timeout(5) do
easy.perform
end
rescue Timeout::Error
puts 'timeout'
end

ruby exit tail and continue to next part of script from keypress

I found this snippet of code I am having trouble trying to find a good way to exit the tail and move on to the next part of the script. Essentially I ant the first part to make a change for the person running the scripts, show log output using the below code, then move on to the next part of the script on user keypress. I cant get out of the tail without CTRL-C.
def do_tail( session, file )
session.open_channel do |channel|
channel.on_data do |ch, data|
puts "[#{file}] -> #{data}"
end
channel.exec "tail -f #{file}"
end
end
Net::SSH.start("server", "user", :keys => ["/user/.ssh/id_dsa"]) do |session|
do_tail session, "/var/log/apache2/error.log"
do_tail session, "/var/log/apache2/access.log"
session.loop
end
UPDATE
The -f takes over I/O and makes it difficult to exit that ssh channel. I decided to move towards the suggestions and modify it. Here is the result in case someone else would like help on this topic.
require 'rubygems'
require 'net/ssh'
def exit?
begin
while input = STDIN.read_nonblock(1)
return true if input == 'q'
end
false
rescue Errno::EINTR
false
rescue Errno::EAGAIN
false
rescue EOFError
true
end
end
def do_tail( session, file )
session.open_channel do |channel|
channel.on_data do |ch, data|
puts "[#{file}]\n\n#{data}"
end
channel.exec "tail -n22 #{file}"
end
end
def loggy
iteration = 0
loop do
iteration = (iteration + 1)
Net::SSH.start("server", "user", :keys => ["/user/.ssh/id_dsa"]) do |session|
do_tail session, "/var/log/apache2/error.log"
end
puts "\n\nType 'q' and <ENTER> to exit log stream when you are done!\n\n"
sleep 5
break if exit? or iteration == 3
end
end
loggy
loop do
puts "\nDo you need to view more of the log? (y/n)\n"
confirm = gets.chomp
if confirm =="y"
loggy
else
end
break if confirm == "n"
end
puts "Part Deaux!"
You've given the -f command line option to tail(1). That explicitly instructs tail(1) to exit when the user types ^C or otherwise kills the program. If you just want a specific amount of the file to be shown and not followed, then you might wish to use the -n command line option instead:
channel.exec "tail -n 24 #{file}"
24 will show roughly one terminal's worth of data, though if your terminals are larger or smaller -- or you're interested in different amounts of data -- then you might wish to tweak it further.
tail(1) is powerful; it'd be worth reading its documentation just in case there's an even better way to do what you're trying to accomplish.
The solution I found was to use less instead of tail. Try this:
less +F filename; echo 'after less'
When you hit ctrl+c, q in less it will quit and then echo what you want.

Increase timeout.rb timer within block

I'm communicating with an RS232 keypad that has an LCD. On each keypress I write the key that was pressed to the LCD to provide feedback to the user.
If no key is pressed within 10 seconds I'd like to abandon waiting for input.
I've written some code that will timeout if the user isn't done entering a multi-character value within 10 seconds, what I'd rather do is give the user another 10 seconds to complete input after each keypress.
Is this possible using timeout.rb?
require 'rubygems'
require 'serialport'
require 'timeout'
sp = SerialPort.new('/dev/tty.usbserial', 9600, 8, 1, SerialPort::NONE)
sp.write("Input:")
begin
timeout(10) do
input = ""
sp.each_byte do |byte|
#call to increase timeout.rb timer would go here
input << byte.chr
sp.write("Input:" + input)
end
end
rescue Timeout::Error
puts "Timed out!"
exit
end
puts input
I found an alternative way of achieving the desired effect by using Threads instead of timeout.rb but would be interested to learn alternative methods.
require 'rubygems'
require 'serialport'
sp = SerialPort.new('/dev/tty.usbserial', 9600, 8, 1, SerialPort::NONE)
sp.write("Input:")
input = ""
timer = Time.now + 10
t = Thread.new do
sp.each_byte do |byte|
timer = Time.now + 10
input << byte.chr
sp.write("Input:" + input)
end
end
t.kill unless Time.now < timer while t.alive?

Why is IO::WaitReadable being raised differently for STDOUT than STDERR?

Given that I wish to test non-blocking reads from a long command, I created the following script, saved it as long, made it executable with chmod 755, and placed it in my path (saved as ~/bin/long where ~/bin is in my path).
I am on a *nix variant with ruby 1.9.2p290 (2011-07-09 revision 32553) [x86_64-darwin11.0.0] compiled with RVM defaults. I do not use Windows, and am therefore unsure if the test script will work for you if you do.
#!/usr/bin/env ruby
3.times do
STDOUT.puts 'message on stdout'
STDERR.puts 'message on stderr'
sleep 1
end
Why does long_err produce each STDERR message as it is printed by "long"
def long_err( bash_cmd = 'long', maxlen = 4096)
stdin, stdout, stderr = Open3.popen3(bash_cmd)
begin
begin
puts 'err -> ' + stderr.read_nonblock(maxlen)
end while true
rescue IO::WaitReadable
IO.select([stderr])
retry
rescue EOFError
puts 'EOF'
end
end
while long_out remains blocked until all STDOUT messages are printed?
def long_out( bash_cmd = 'long', maxlen = 4096)
stdin, stdout, stderr = Open3.popen3(bash_cmd)
begin
begin
puts 'out -> ' + stdout.read_nonblock(maxlen)
end while true
rescue IO::WaitReadable
IO.select([stdout])
retry
rescue EOFError
puts 'EOF'
end
end
I assume you will require 'open3' before testing either function.
Why is IO::WaitReadable being raised differently for STDOUT than STDERR?
Workarounds using other ways to start subprocesses also appreciated if you have them.
In most OS's STDOUT is buffered while STDERR is not. What popen3 does is basically open a pipe between the exeutable you launch and Ruby.
Any output that is in buffered mode is not sent through this pipe until either:
The buffer is filled (thereby forcing a flush).
The sending application exits (EOF is reached, forcing a flush).
The stream is explicitly flushed.
The reason STDERR is not buffered is that it's usually considered important for error messages to appear instantly, rather than go for for efficiency through buffering.
So, knowing this, you can emulate STDERR behaviour with STDOUT like this:
#!/usr/bin/env ruby
3.times do
STDOUT.puts 'message on stdout'
STDOUT.flush
STDERR.puts 'message on stderr'
sleep 1
end
and you will see the difference.
You might also want to check "Understanding Ruby and OS I/O buffering".
Here's the best I've got so far for starting subprocesses. I launch a lot of network commands so I needed a way to time them out if they take too long to come back. This should be handy in any situation where you want to remain in control of your execution path.
I adapted this from a Gist, adding code to test the exit status of the command for 3 outcomes:
Successful completion (exit status 0)
Error completion (exit status is non-zero) - raises an exception
Command timed out and was killed - raises an exception
Also fixed a race condition, simplified parameters, added a few more comments, and added debug code to help me understand what was happening with exits and signals.
Call the function like this:
output = run_with_timeout("command that might time out", 15)
output will contain the combined STDOUT and STDERR of the command if it completes successfully. If the command doesn't complete within 15 seconds it will be killed and an exception raised.
Here's the function (2 constants you'll need defined at the top):
DEBUG = false # change to true for some debugging info
BUFFER_SIZE = 4096 # in bytes, this should be fine for many applications
def run_with_timeout(command, timeout)
output = ''
tick = 1
begin
# Start task in another thread, which spawns a process
stdin, stderrout, thread = Open3.popen2e(command)
# Get the pid of the spawned process
pid = thread[:pid]
start = Time.now
while (Time.now - start) < timeout and thread.alive?
# Wait up to `tick' seconds for output/error data
Kernel.select([stderrout], nil, nil, tick)
# Try to read the data
begin
output << stderrout.read_nonblock(BUFFER_SIZE)
puts "we read some data..." if DEBUG
rescue IO::WaitReadable
# No data was ready to be read during the `tick' which is fine
print "." # give feedback each tick that we're waiting
rescue EOFError
# Command has completed, not really an error...
puts "got EOF." if DEBUG
# Wait briefly for the thread to exit...
# We don't want to kill the process if it's about to exit on its
# own. We decide success or failure based on whether the process
# completes successfully.
sleep 1
break
end
end
if thread.alive?
# The timeout has been reached and the process is still running so
# we need to kill the process, because killing the thread leaves
# the process alive but detached.
Process.kill("TERM", pid)
end
ensure
stdin.close if stdin
stderrout.close if stderrout
end
status = thread.value # returns Process::Status when process ends
if DEBUG
puts "thread.alive?: #{thread.alive?}"
puts "status: #{status}"
puts "status.class: #{status.class}"
puts "status.exited?: #{status.exited?}"
puts "status.exitstatus: #{status.exitstatus}"
puts "status.signaled?: #{status.signaled?}"
puts "status.termsig: #{status.termsig}"
puts "status.stopsig: #{status.stopsig}"
puts "status.stopped?: #{status.stopped?}"
puts "status.success?: #{status.success?}"
end
# See how process ended: .success? => true, false or nil if exited? !true
if status.success? == true # process exited (0)
return output
elsif status.success? == false # process exited (non-zero)
raise "command `#{command}' returned non-zero exit status (#{status.exitstatus}), see below output\n#{output}"
elsif status.signaled? # we killed the process (timeout reached)
raise "shell command `#{command}' timed out and was killed (timeout = #{timeout}s): #{status}"
else
raise "process didn't exit and wasn't signaled. We shouldn't get to here."
end
end
Hope this is useful.

Resources