Can I stop Sinatra from within a Sinatra application? - ruby

I'd like to know how to programmatically exit a Sinatra application from within the application itself. (There's another SO thread about stopping it from outside the app.)
I want to use Sinatra as a means of receiving control and configuration commands while my application does something unrelated to the Sinatra stuff. I'd like one of the control commands to be 'exit'. Ruby's 'exit' method just seems to result in Sinatra recovering and resuming. I found this in base.rb that I think confirms this:
at_exit { Application.run! if $!.nil? && Application.run? }
So far, the only way I've found is to call Ruby's exit! method, but that bypasses exit hooks and is not a very clean solution.
Is there no way to programmatically tell Sinatra to stop?

I used the following code:
# Exit 'correctly'
get '/exit' do
# /exit causes:
# 15:20:24 web.1 | Damn!
# 15:20:24 web.1 | exited with code 1
Process.kill('TERM', Process.pid)
end
# Just terminate
get '/fail' do
Process.kill('KILL', Process.pid)
end
Take a look at at_exit in config.ru it works for TERM signal:
at_exit do
puts 'Damn!'
exit false
end
Full example is here.
Cheers.

That sort of goes against the grain of Sinatra, but this is just Ruby so you can easily do this via open classes/monkey patching.
Just re-open the base.rb at_exit method and override the Application.run! behavior.

Why not just raise an exception? That will mean that $! is not nil, so the at_exit handler won't restart Sinatra.
An easy way is just to run fail or raise. You can also pass a message, such as fail "Exiting due to x".

Sinatra::Base::quit! or its alias ::stop! is what you are looking for:
require 'sinatra'
get '/quit' do
self.class.quit!
end
get '/stop' do
self.class.stop!
end

Related

Ruby display backtrace when killed

How do I get my programm to display the backtrace / caller when killed ?
I have an issue with an infinite loop in a gem that isn't mine and need to know where the issue is to report it
def hello
puts 'hello'
end
def test
while true
sleep 2
hello
end
end
test
In this exemple, when a kill signal is sent to the programm, I wish to know what the programm what doing ( display the caller )
Currently all I get displayed is "Killed" on the output
I guess you need to catch a signal,
Did you try
https://gist.github.com/sauloperez/6592971
It is not possible to catch sigkill signals as they are sent to the kernel and not the process : https://major.io/2010/03/18/sigterm-vs-sigkill/
you can, however, use a "parent" process that watches the "children" and will react accordingly SIGKILL signal Handler
This means that I cannot display the backtrace, I will probably have to use a log file or something like that.
I suppose anti-virus have a different way of working to avoid the issue of being killed
Taken from https://robots.thoughtbot.com/using-gdb-to-inspect-a-running-ruby-process
As a last resort you can use gdb to connect to your running ruby process
gdb </path/to/ruby> <PID>
# inside ~/.gdbinit
define redirect_stdout
call rb_eval_string("$_old_stdout, $stdout = $stdout,
File.open('/tmp/ruby-debug.' + Process.pid.to_s, 'a'); $stdout.sync = true")
end
define ruby_eval
call(rb_p(rb_eval_string_protect($arg0,(int*)0)))
end

Kill all threads on terminate

I'm trying to create an app in ruby which can be started from command line and it does two things: runs a continous job (loop with sleep which runs some action [remote feed parsing]) with one thread and sinatra in a second thread. My code (simplified) looks like that:
require 'sinatra'
class MyApp < Sinatra::Base
get '/' do
"Hello!"
end
end
threads = []
threads << Thread.new do
loop do
# do something heavy
sleep 10
end
end
threads << Thread.new do
MyApp.run!
end
threads.each { |t| t.join }
The above code actually does it's job very well - the sinatra app is started an available under 4567 port and the do something heavy task is beeing fired each 10 seconds. However, i'm not able to kill that script.
I'm running it with ruby app.rb but killing it with ctrl + c is not working. It kills just the sinatra thread but the second one is still running and, to stop the script, i need to close the terminal window.
I was trying to kill all the threads on SIGNINT but it's also not working as expected
trap "SIGINT" do
puts "Exiting"
threads.each { |t| Thread.kill t }
exit 130
end
Can you help me with this? Thanks in advance.
To trap ctrl-c, change "SIGINT" to "INT".
trap("INT") {
puts "trapping"
threads.each{|t|
puts "killing"
Thread.kill t
}
}
To configure Sinatra to skip catching traps:
class MyApp < Sinatra::Base
configure do
set :traps, false
end
...
Reference: Ruby Signal module
To list the available Ruby signals: Signal.list.keys
Reference: Sinatra Intro
(When I run your code and trap INT, I do get a Sinatra socket warning "Already in use". I presume that's fine for your purposes, or you can solve that by doing a Sinatra graceful shutdown. See Sinatra - terminate server from request)
Late to the party, but Trap has one big disadvantage - it gets overriden by the webserver. For example, Puma sets several traps which basically makes your one never to be called.
The best workaround is to use at_exit which can be defined multiple times and Ruby makes sure all blocks are called. I haven't tested this if it would work for your case tho.

Is there a way to autoexecute code when Pry is triggered?

Is there a way to automatically execute code when binding.pry is encountered?
For example, if I want to execute puts "Hey, I'm debugging!" every time binding.pry is called?
I felt like having a look at the code and found something that might be useful, you either have to add it to the gemfile or you might be able to add it to your .pryrc (I don't know if this gets called from binding.pry or not). From github there appears to be:
# #example Adding a hook for the `:before_session` event.
Pry.config.hooks.add_hook(:before_session, :say_hi) do
puts "hello"
end
From the pry github hooks file.
EDIT: Here's an example to register the hook and execute it (i.e. init your app) from another part of the pry github hooks file:**
my_hook = Pry::Hooks.new.add_hook(:before_session, :say_hi) { puts "hi!" }
my_hook.exec_hook(:before_session) #=> OUTPUT: "hi!"

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.

Running RSpec in debug mode

This is a short question: I am looking for a way to run specs in debug mode, with the -u switch, so that RSpec would drop to console whenever it failed, without having to add a debugger line into the code. Any pointers?
Will answer my own question.
Following this tutorial, I created a custom formatter, as in:
require "spec/runner/formatter/specdoc_formatter"
class DebuggerFormatter < Spec::Runner::Formatter::SpecdocFormatter
def example_failed(example, counter, failure)
super
debugger if Kernel.respond_to?(:debugger)
end
end
hakanensari, your code seems to break inside rspec. It'd be nice if we could break at the failing assert line.

Resources