Skip to content

Instantly share code, notes, and snippets.

@tompave
Last active December 16, 2015 00:09
Show Gist options
  • Save tompave/5345351 to your computer and use it in GitHub Desktop.
Save tompave/5345351 to your computer and use it in GitHub Desktop.
A simple implementation of a Ruby program that: works with a continuous event loop, is managed through UNIX signals, stops and reboots in response to file system changes (but that can be easily customized).
#! /usr/bin/env ruby
=begin
How to launch
$ chmod 700 rebooter.rb
$ ./rebooter.rb
It will create two files (will delete them on SIGTERM and SIGINT)
./rebooter.pid
./rebooter.control
Then, it will start to print output to stdout:
doing stuff. instance_id: 1, iteration: 1
doing stuff. instance_id: 1, iteration: 2
doing stuff. instance_id: 1, iteration: 3
(...)
It handlses these signals
TERM exit with exit code 0 (success)
INT exit with exit code 1 (failure)
USR1 enable the periodic task
USR2 disable the periodic task
How you should send them
$ kill -s USR2 `cat rebooter.pid`
The file ./rebooter.control contains the ID of the running "object instance" (OOP-ly speaking).
At any time you can delete the file, and the program will stop the running instance,
will instantiate a new one, and reboot the event loop.
At the same time a new ./rebooter.control will be generated (with the new instance ID)
=end
CURRENT_DIR = File.dirname(__FILE__)
PID_FILE = File.join(CURRENT_DIR, "rebooter.pid")
CONTROL_FILE = File.join(CURRENT_DIR, "rebooter.control")
# utility method to clean up on shoutdown
def remove_files
File.delete(PID_FILE, CONTROL_FILE)
end
Signal.trap("TERM") do
puts "Received TERM. exiting with exit code 0 (success)"
remove_files
exit(true)
end
Signal.trap("INT") do
puts "Received INT. exiting with exit code 1 (failure)"
remove_files
exit(false)
end
Signal.trap("USR1") do
puts "USR1: start the loop (unless it's already running)"
MainRunner.current_runner.start if MainRunner.current_runner
end
Signal.trap("USR2") do
MainRunner.current_runner.stop if MainRunner.current_runner
puts "USR2: stop the loop"
end
# ---------------------------------
# The Runner class.
class MainRunner
# instance @variables
attr_accessor :instance_id
attr_reader :task_c
# instance @variables of the metaclass.
# if you are confused just think of them
# as class @@variables with accessors.
class << self
attr_accessor :instance_counter
attr_accessor :current_runner
end
# Contructor
def initialize
self.class.instance_counter ||= 1
@instance_id = self.class.instance_counter
create_control_file
end
# startup logic
def create_control_file
File.open(CONTROL_FILE, "w") { |file| file.puts("instance_id: #{@instance_id}") }
end
# the periodic task. pretty dumb
def periodic_task
@task_c ||= 0
puts "doing stuff. instance_id: #{@instance_id}, iteration: #{@task_c += 1}"
end
# Check wether it should reboot
# You can override this method with whatever logic you need
def reboot_control
unless File.exists?(CONTROL_FILE)
puts "the control file disappeared. rebooting"
stop!
self.class.reboot
end
end
# the event loop
def run_event_loop
@loop_is_running = true
@should_exit = false
loop {
begin
break if @should_exit
reboot_control
periodic_task if @should_work
sleep 1.5
rescue StandardError => e
puts "MainRunner#run_event_loop, StandardError trapped:\n #{e.inspect}"
end
}
@loop_is_running = false
end
## event loop controls
# starts the eventloop (if it's not running yet)
# and enables the periodic task
def start
@should_work = true
run_event_loop unless @loop_is_running
end
# pauses the periodic task
def stop
@should_work = false
end
# pauses the periodic task, then brings
# the event loop to stop (and the program to exit)
def stop!
@should_work = false
@should_exit = true
end
# --------------------------
# business Class logic
def self.write_process_pid
File.open(PID_FILE, "w") { |file| file.puts(Process.pid) }
end
# starter method
def self.kick_start
begin
write_process_pid
puts "My PID is: #{Process.pid}"
self.current_runner = MainRunner.new()
self.current_runner.start
rescue StandardError => e
puts "StandardError trapped: #{e.inspect}"
end
end
# reboot logic
def self.reboot
begin
self.instance_counter += 1
self.current_runner = nil
self.current_runner = MainRunner.new()
self.current_runner.start
rescue StandardError => e
puts "StandardError trapped: #{e.inspect}"
end
end
end
# starts the program
MainRunner.kick_start
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment