I want to create a Ruby script, which will start like this:
$ ruby script.rb &
Then, I will close the console and it must stay alive, working in the background. At the moment I have to run it like this, in order to achive that:
$ nohup ruby script.rb &
I want to get rid of nohup and deal with SIGHUP directly inside the script -- simply ignore it. Is it possible?
Sure, just Signal.trap HUP signal:
def do_fork
$pid = fork do
Signal.trap("HUP") do
puts "Received HUP, ignoring..."
end
Signal.trap("TERM") do
puts "Received TERM, terminating..."
exit(0)
end
while true do sleep(10_000) end
end
Process.detach($pid)
end
do_fork
Copy the code above to some file and run it with ruby file.rb to see it ignores kill -HUP pid and closes on kill -TERM pid.
Related
What I need is:
Execute something before calling a system command.
Execute my system command
that involve prompting and getting answers from the user
keeping the effects of ctrl-c on the called command intact
Get the result of my system command and carry on with more ruby code execution
So far I tried something that looks like:
#!/usr/bin/env ruby
p "Foo"
exit_value = exec 'heroku run console'
p "Bar"
exit exit_value
This one fails because exec replaces and terminate current process, so no more ruby code is executed after exec
I've already read this post:
How to run code after ruby Kernel.exec
And I tried to make do with a Kernel#system call:
#!/usr/bin/env ruby
p "Foo"
system 'heroku run console'
p "Bar"
exit $?
This one also fails, because ctrl-c is apparently caught by my ruby process and kills it instead of reaching its intended target.
So, is there a way to deal with these peculiar requirements?
Thanks a lot to hek2mgl for pointing in the right direction:
include Signal
include Process
# Handling SIGINT by doing nothing prevents default behaviour
# (killing both processes)
Signal.trap("INT") {}
# Fork off subprocess (so exec won't exit from your main process)
pid = fork
if pid == nil then
# Child code. Use exec(!) to avoid the signal handler
# getting called in the child.
exec 'heroku run console'
else
# Wait for subprocess to exit
wait pid
# "wait" sets the $? according to the subprocess exit status
exit_status = $?.exitstatus
p "Execute more ruby code"
exit exit_status
end
I would install a signal trap for SIGINT, fork off the sub process, exec the command (to prevent the signal handler from running in parent and child) and kill the subprocess if SIGINT occurs:
include Signal
include Process
# Define a signal handler for SIGINT
Signal.trap("INT") do
if $pid != nil then
# Kill the subprocess
Process.kill("INT", $pid)
else
# Terminate ourself
exit 1
end
end
# Fork off subprocess
# The $ marks the variable as global
$pid = fork
if $pid == nil then
# Child code. Use exec(!) to avoid the signal handler
# getting called in the child.
exec 'bash -c "echo test; sleep 3; echo test"'
end
# Wait for subprocess to exit
wait $pid
# Reset $pid
$pid = nil
exit_status = $?.exitstatus
# Detect whether the child process has been killed
if exit_status == nil then
p "Child process has been killed."
end
p "Execute more ruby code"
...
I was just thinking about how great it would be to be able to run a program and then hit a keystroke to invoke pry and debug. Maybe there is a gem out there that injects binding.pry dynamically during runtime that I don't know about. If there isn't, how would you make a keystroke that inserts binding.pry before the next line of ruby script that is about to execute?
Assuming a POSIX OS, you could try adding a signal handler in your ruby program. The ruby documentation even gives an example of your use case:
.. your process may trap the USR1 signal and use it to toggle debugging (http://www.ruby-doc.org/core-2.1.3/Signal.html)
Signal.trap('USR1') do
binding.pry
end
Then, to send the signal:
kill -s SIGUSR1 [pid]
Edit: A more complete example: application.rb
My naïve suggestion above will fail with a ThreadError: current thread not owner. Here's a better example using a global $debug flag.
#!/usr/bin/env ruby
require 'pry'
$debug = false
Signal.trap('USR1') do
puts 'trapped USR1'
$debug = true
end
class Application
def run
while true
print '.'
sleep 5
binding.pry if $debug
end
end
end
Application.new.run
This seems to work best when application.rb is running in the foreground in one shell, and you send the SIGUSR1 signal from a separate shell.
Tested in Mac OS 10.9.5. YMMV.
I'm using Ruby 1.9.2-p180 on Ubuntu 12.04. I don't understand why the following code doesn't work. The expected behaviour is that I should see the text: "TRAPPED" on my screen when I run the code, and the program should terminate. But the ruby program doesn't terminate when I run it and nothing is printed on the screen. What am I missing?
pid = fork do
exec "trap 'echo TRAPPED' TERM; while :; do :; done"
end
Process.kill("TERM", pid)
Process.wait(pid)
The reason why it did not work as expected was that the kill was delivered before the exec commenced. Introducing a little sleep before delivering the kill ensures that the program will behave as expected.
If ruby myapp.rb starts sinatra previewing at localhost:4567, how can I programatically stop/halt/kill it? Terminal command (other than Ctrl-C), or Rake tasks would be fine.
I need to incorporate this into a Rake task or terminal.
In myapp.rb, add this before sinatra starts:
puts "This is process #{Process.pid}"
When you want to kill it, do this in a shell:
kill <pid>
Where <pid> is the number outputted by myapp.rb. If you want to do it in ruby:
Process.kill 'TERM', <pid>
Both of these will let sinatra run it's exit routine. If you don't want to type in the pid every time, have myapp.rb open a file and put it's pid in it. Then when you want to stop it, read the file and use that. Example:
# myapp.rb:
File.open('myapp.pid', 'w') {|f| f.write Process.pid }
# shell:
kill `cat myapp.pid`
# ruby:
Process.kill 'TERM', File.read('myapp.pid')
In OS X, from the command line (Terminal.app, or DTerm) just enter:
$ killall ruby
every ruby process will stop. Sinatra too.
In Linux (and other UNIXes), you can:
$ ps aux | grep ruby
$ kill <ruby-process-id>
The simples way to do that:
kill #{Process.pid}
To do this in a simple repeatable way, there's a few methods.
Record the PID as you start your Sinatra server, e.g.
# Run the Sinatra server and send it to background (using &)
ruby my_sinatra_server.rb &
# Record the PID of the last background process (using $!)
MY_SINATRA_SERVER_PID=$!
# Now go ahead and do your stuff...
# When finished, kill the sinatra server (from the same shell)
kill $MY_SINATRA_SERVER_PID
Instead of using an env variable ($MY_SINATRA_SERVER) you can use a temporary file e.g. my_sinatra_server.pid
# Run the Sinatra server and send it to background (using &)
ruby my_sinatra_server.rb &
# Record the PID of the last background process (using $!)
echo $! > my_sinatra_server.pid
# Now go ahead and do your stuff...
# When finished, kill the sinatra server (from the same shell)
kill $(< my_sinatra_server.pid)
I have a long-running process with some child processes that must be restarted if they exit. To handle clean restarts of these child processes, I trap the exit signal with
trap("CLD") do
cpid = Process.wait
... handle cleanup ...
end
The long-running process occasionally needs to invoke 'curl' using a backquote as in
`/usr/bin/curl -m 60 http://localhost/central/expire`
The problem is that the backquote invocation is causing me to get a SIGCHLD and making my trap fire. This then gets stuck in the CLD trap because Process.wait does not finish. If there happen to be no (non-backquote) child processes at that time, the Process.wait instead gives an Errno::ECHILD exception.
I can circumvent this problem by wrapping the backquote call with this line before:
sig_handler = trap("CLD", "IGNORE") # Ignore child traps
and this line after the backquote invocation:
trap("CLD", sig_handler) # replace the handler
but this means that I may miss a signal from the (non-backquote) child processes during that window, so I'm not really happy with that.
So, is there a better way to do this?
(I am using ruby 1.9.1p243 on GNU/Linux 2.6.22.6 if it matters)
Update:
The code below illustrates the problem (and my current solution for it).
There seems to be some strange timing issue here since I don't always get the ECHILD exception. But just once is enough to mess things up.
#!/usr/bin/env ruby
require 'pp'
trap("CLD") do
cpid = nil
begin
puts "\nIn trap(CLD); about to call Process.wait"
cpid = Process.wait
puts "In trap(CLD); Noting that ssh Child pid #{cpid}: terminated"
puts "Finished Child termination trap"
rescue Errno::ECHILD
puts "Got Errno::ECHILD"
rescue Exception => excep
puts "Exception in CLD trap for process [#{cpid}]"
puts PP.pp(excep, '')
puts excep.backtrace.join("\n")
end
end
#Backtick problem shown (we get an ECHILD most of the time)
puts "About to invoke backticked curl"
`/usr/bin/curl -m 6 http://developer.yahooapis.com/TimeService/V1/getTime?appid=YahooDemo`
sleep 2; sleep 2 # Need two sleeps because the 1st gets terminated early by the trap
puts "Backticked curl returns"
# Using spawn
puts "About to invoke curl using spawn"
cpid = spawn("/usr/bin/curl -m 6 http://developer.yahooapis.com/TimeService/V1/getTime?appid=YahooDemo")
puts "spawned child pid is #{cpid} at #{Time.now}"
Start monitored subprocesses from a subprocess
Just start your tracked and monitored children from a child of your main process that never exits. That way it won't notice the backtick children exiting...
And if you do this, you could avoid the use of SIGCHLD entirely, as you could just use a loop with a wait in it to notice children exit events.
Other ideas:
ignore one SIGCHLD every time you execute a backtick command. ISTM that you might ignore a "real" SIGCHLD by accident this way, but that won't matter, because you would then get a "spurious" one that you would process.