Ruby - catch SIGINT to allow finishing of file writing - ruby

I know how to catch a SIGINT, but I only know how to put the block somewhere random in my code. I need to be able to catch SIGINT because if file writing is in progress (my database writes to a file frequently), it corrupts the file. What's the ideal way to handle this?
Edit
In one instance, I handled this by executing my writing function if I detect a SIGINT, but I'm realizing this won't help if I'm literally in the middle of writing to a file using Marshal.
Is there no way in Ruby to catch a SIGINT and tell it to ignore it until it, for example, finishes writing?
Edit 2
Nevermind, just figured out a possible answer... see below.

I ended up using a rescue block, which rescues Interrupt. In the block, I turn a boolean to true. The boolean is checked at the beginning of each of my thread pool tasks and if its true, skips the entire function.
Edit
In regards to my edit in my original post: Would the best way to handle interrupts in file writing be to use a boolean again? For example, create a boolean called interrupt and at the end of writing to the file, check to see if boolean is true and if so, abort the program? I couldn't use a rescue to catch the interrupt then, but I could use trap, no?

That's what I would do :
Signal.trap("INT"){
if $lock
puts "Not yet done. Please don't interrupt"
else
puts "Goodbye!"
exit
end
}
def something_long_that_shouldnt_be_interrupted
$lock = true
10.times do |i|
puts "Writing #{i+1}/10. Don't interrupt"
sleep 1
end
$lock = false
end
def some_other_process_that_can_be_interrupted
10.times do |i|
puts "Unimportant stuff #{i+1}/10."
sleep 1
end
end
something_long_that_shouldnt_be_interrupted
some_other_process_that_can_be_interrupted
# Writing 1/10. Don't interrupt
# Writing 2/10. Don't interrupt
# ^CNot yet done. Please don't interrupt
# Writing 3/10. Don't interrupt
# Writing 4/10. Don't interrupt
# ^CNot yet done. Please don't interrupt
# Writing 5/10. Don't interrupt
# ^CNot yet done. Please don't interrupt
# ^CNot yet done. Please don't interrupt
# Writing 6/10. Don't interrupt
# ^CNot yet done. Please don't interrupt
# Writing 7/10. Don't interrupt
# Writing 8/10. Don't interrupt
# ^CNot yet done. Please don't interrupt
# Writing 9/10. Don't interrupt
# Writing 10/10. Don't interrupt
# Unimportant stuff 1/10.
# Unimportant stuff 2/10.
# Unimportant stuff 3/10.
# Unimportant stuff 4/10.
# ^CGoodbye!
If you use it more than once, you could define :
def do_not_disturb(&block)
$lock = true
block.yield
$lock = false
end
do_not_disturb do
# code that shouldn't be interrupted
end

Related

How to stop a process from within the tests, when testing a never-ending process?

I am developing a long-running program in Ruby. I am writing some integration tests for this. These tests need to kill or stop the program after starting it; otherwise the tests hang.
For example, with a file bin/runner
#!/usr/bin/env ruby
while true do
puts "Hello World"
sleep 10
end
The (integration) test would be:
class RunReflectorTest < TestCase
test "it prints a welcome message over and over" do
out, err = capture_subprocess_io do
system "bin/runner"
end
assert_empty err
assert_includes out, "Hello World"
end
end
Only, obviously, this will not work; the test starts and never stops, because the system call never ends.
How should I tackle this? Is the problem in system itself, and would Kernel#spawn provide a solution? If so, how? Somehow the following keeps the out empty:
class RunReflectorTest < TestCase
test "it prints a welcome message over and over" do
out, err = capture_subprocess_io do
pid = spawn "bin/runner"
sleep 2
Process.kill pid
end
assert_empty err
assert_includes out, "Hello World"
end
end
. This direction also seems like it will cause a lot of timing-issues (and slow tests). Ideally, a reader would follow the stream of STDOUT and let the test pass as soon as the string is encountered and then immediately kill the subprocess. I cannot find how to do this with Process.
Test Behavior, Not Language Features
First, what you're doing is a TDD anti-pattern. Tests should focus on behaviors of methods or objects, not on language features like loops. If you must test a loop, construct a test that checks for a useful behavior like "entering an invalid response results in a re-prompt." There's almost no utility in checking that a loop loops forever.
However, you might decide to test a long-running process by checking to see:
If it's still running after t time.
If it's performed at least i iterations.
If a loop exits properly given certain input or upon reaching a boundary condition.
Use Timeouts or Signals to End Testing
Second, if you decide to do it anyway, you can just escape the block with Timeout::timeout. For example:
require 'timeout'
# Terminates block
Timeout::timeout(3) { `sleep 300` }
This is quick and easy. However, note that using timeout doesn't actually signal the process. If you run this a few times, you'll notice that sleep is still running multiple times as a system process.
It's better is to signal the process when you want to exit with Process::kill, ensuring that you clean up after yourself. For example:
pid = spawn 'sleep 300'
Process::kill 'TERM', pid
sleep 3
Process::wait pid
Aside from resource issues, this is a better approach when you're spawning something stateful and don't want to pollute the independence of your tests. You should almost always kill long-running (or infinite) processes in your test teardown whenever you can.
Ideally, a reader would follow the stream of STDOUT and let the test pass as soon as the string is encountered and then immediately kill the subprocess. I cannot find how to do this with Process.
You can redirect stdout of spawned process to any file descriptor by specifying out option
pid = spawn(command, :out=>"/dev/null") # write mode
Documentation
Example of redirection
With the answer from CodeGnome on how to use Timeout::timeout and the answer from andyconhin on how to redirect Process::spawn IO, I came up with two Minitest helpers that can be used as follows:
it "runs a deamon" do
wait_for(timeout: 2) do
wait_for_spawned_io(regexp: /Hello World/, command: ["bin/runner"])
end
end
The helpers are:
def wait_for(timeout: 1, &block)
Timeout::timeout(timeout) do
yield block
end
rescue Timeout::Error
flunk "Test did not pass within #{timeout} seconds"
end
def wait_for_spawned_io(regexp: //, command: [])
buffer = ""
begin
read_pipe, write_pipe = IO.pipe
pid = Process.spawn(command.shelljoin, out: write_pipe, err: write_pipe)
loop do
buffer << read_pipe.readpartial(1000)
break if regexp =~ buffer
end
ensure
read_pipe.close
write_pipe.close
Process.kill("INT", pid)
end
buffer
end
These can be used in a test which allows me to start a subprocess, capture the STDOUT and as soon as it matches the test Regular Expression, it passes, else it will wait 'till timeout and flunk (fail the test).
The loop will capture output and pass the test once it sees matching output. It uses a IO.pipe because that is most transparant for subprocesses (and their children) to write to.
I doubt this will work on Windows. And it needs some cleaning up of the wait_for_spawned_io which is doing slightly too much IMO. Antoher problem is that the Process.kill('INT') might not reach the children which are orphaned but still running after this test has ran. I need to find a way to ensure the entire subtree of processes is killed.

Run code when Signal is sent, but do not trap the signal in Ruby

I have code that is running on a server, before the server is hard shut down, a signal SIGTERM is sent to let my code know it needs to clean up. I want to run code when this happens and send the signal back to the same program so any other code that needs to clean up can do so. I do not want to trap the signal or change signal behavior, I only need to run something before the rest of my program interprets the SIGTERM.
Currently I can do something like
Signal.trap('TERM') do
puts "Graceful shutdown"
exit
end
but it doesn't work if multiple pieces of code in the same app try to do the same thing. For example:
Signal.trap('TERM') do
puts "Graceful shutdown"
exit
end
Signal.trap('TERM') do
puts "Another graceful shutdown"
exit
end
You will only ever see "Another graceful shutdown" and the first code block will not run.
Ideally I would be able to invoke current behavior with something like:
Signal.trap('TERM') do
puts "another graceful shutdown"
super
end
But this doesn't work for obvious reasons. So the question is this: how can I run code when i get a SIGTERM without trapping it and preventing other code from doing the same?
Signal.trap returns the previous handler so you can do something like
def prepend_handler(signal, &handler)
previous = Signal.trap(signal) do
previous = -> { raise SignalException, signal} unless previous.respond_to?(:call)
handler.call(previous)
end
end
prepend_handler("TERM") do |old|
do_something
old.call
end
The respond_to? business is because a handler is either a callable or a string (the string values are documented here). Unless you use those string handlers yourself you are most likely to run into 'DEFAULT', i.e. the default ruby behaviour

Rufus-Scheduler, DaemonKit and traps

I daemonized a Ruby scheduler script (using Rufus) with Rufus-Scheduler DaemonKit and I'm trying to trap the TERM or INT signals to have the application try to save state before quitting.
DaemonKit has its own trap_state (private) method and it catches the signal before the daemon script so even though I have this block, it doesn't do much.
DaemonKit::Application.running! do |config|
surprise = Surprise.new(interval, frequency, false)
surprise.start
config.trap( 'SIGINT' ) do #tried INT and TERM as well
puts 'Exiting'
surprise.stop
File.delete($lock)
end
end
As a side effect (maybe a mistake in my implementation ?) after sigterm the .rufus lockfile is still there
The behavior on ctrl-c right now is this
[daemon-kit]: DaemonKit (0.3.1) booted, now running surprise
log writing failed. can't be called from trap context
[daemon-kit]: Running signal traps for INT
log writing failed. can't be called from trap context
[daemon-kit]: Running shutdown hooks
log writing failed. can't be called from trap context
[daemon-kit]: Shutting down surprise
The start method is a pretty simple schedule
def start
#scheduler = Rufus::Scheduler.new(:lockfile => $lock)
#scheduler.every '1d', :first_at => #first, :overlap => false do |job|
... # some work
end
#scheduler.join
end
def stop
# save state
#scheduler.shutdown
end
Looking at your own answer, and the following code you pasted:
def start
#scheduler = Rufus::Scheduler.new(:lockfile => $lock)
# ...
#scheduler.join # <- NOT NEEDED
end
DaemonKit's DaemonKit::Application.running! block actually never finishes running, so you could safely skip calling #join on any thread.
We should work on making this use-case more clear, as I would love see it used more widely for this kinda work.
So it's very simple, I need to configure the trap proc (or block in my case) BEFORE I run the scheduler in the start method. Not feeling very clever right about now, but the following code works as expected. For reference, the set_trap is private in DK but the public trap method overrides the defaults that come with the DK startup.
DaemonKit::Application.running! do |config|
surprise = Surprise.new(interval, frequency, false)
config.trap("TERM") { surprise.stop }
config.trap( "INT" ) { surprise.stop }
surprise.start
end
Interestingly I saw this line on startup that I hadn't noticed before
[daemon-kit]: Trapping SIGINT signals not supported on this platform
INT and TERM both work though

How to test signal handling in RSpec, particularly handling of SIGTERM?

Heroku may send a SIGTERM to your application for various reasons, so I have created a handler to take care of some cleanup in case this happens. Some googling hasn't yielded any answers or examples on how to test this in RSpec. Here's the basic code:
Signal.trap('TERM') do
cleanup
end
def cleanup
puts "doing some cleanup stuff"
...
exit
end
What's the best way to test that this cleanup method is called when the program receives a SIGTERM?
Send the signal to RSpec with Process.kill 'TERM', 0 and test that the handler is called. It's true that if the signal isn't trapped the test will crash rather than nicely reporting a failure, but at least you'll know there's a problem in your code.
For example:
class SignalHandler
def self.trap_signals
Signal.trap('TERM') { term_handler }
end
def self.term_handler
# ...
end
end
describe SignalHandler do
describe '#trap_signals' do
it "traps TERM" do
# The MRI default TERM handler does not cause RSpec to exit with an error.
# Use the system default TERM handler instead, which does kill RSpec.
# If you test a different signal you might not need to do this,
# or you might need to install a different signal's handler.
old_signal_handler = Signal.trap 'TERM', 'SYSTEM_DEFAULT'
SignalHandler.trap_signals
expect(SignalHandler).to receive(:term_handler).with no_args
Process.kill 'TERM', 0 # Send the signal to ourself
# Put the Ruby default signal handler back in case it matters to other tests
Signal.trap 'TERM', old_signal_handler
end
end
end
I merely tested that the handler was called, but you could equally well test a side effect of the handler.

Fork callback in ruby using trap

I'm looking for a reliable way of implementing a callback on a forked process, once it has finished.
I tried using trap (see the code below), but it appears to fail from time to time.
trap("CLD") {
pid = Process.wait
# do stuff
}
pid = fork {
# do stuff
}
While I did found (via google) possible explanations why this may be happening, I'm having a hard time figuring out a possible solution.
The only solution I see so far is to open a pipe between processes (parent - read end, child - write end). Then put the parent processes thread on blocking read and trap "broken pipe" or "pipe closed" exceptions.
Any of these exceptions will obviously mean that child process is dead.
UPDATE: If I'm not wrong normally closed pipe will result into EOF result of blocking read, and broken pipe (if child process crashed) will result into Errno::EPIPE exception.
#Openning a pipe
p_read, p_write = IO.pipe
pid = fork {
#We are only "using" write end here, thus closing read end in child process
#and let the write end hang open in the process
p_read.close
}
#We are only reading in parent, thus closing write end here
p_write.close
Thread.new {
begin
p_write.read
#Should never get here
rescue EOFError
#Child normally closed its write end
#do stuff
rescue Errno::EPIPE
#Chiled didn't normally close its write end
#do stuff (maybe the same stuff as in EOFError handling)
end
#Should never get here
}
#Do stuff in parents main thread

Resources