revenant 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/locks/mysql.rb +56 -0
- data/lib/plugins/daemon.rb +115 -0
- data/lib/revenant.rb +72 -0
- data/lib/revenant/manager.rb +62 -0
- data/lib/revenant/pid.rb +24 -0
- data/lib/revenant/task.rb +244 -0
- metadata +50 -0
data/lib/locks/mysql.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# Each lock module must implement a singleton method called +lock_function+
|
2
|
+
# This method should return a proc that will be called with a lock_name argument.
|
3
|
+
# The proc should return true if the lock has been acquired, false otherwise
|
4
|
+
#
|
5
|
+
# Lock modules may expose other methods to users as needed, but only
|
6
|
+
# +lock_function+ is required.
|
7
|
+
# Modules register new lock types by calling Revenant.register(name, klass)
|
8
|
+
module Revenant
|
9
|
+
module MySQL
|
10
|
+
extend self
|
11
|
+
|
12
|
+
def lock_function
|
13
|
+
Proc.new do |lock_name|
|
14
|
+
::Revenant::MySQL.acquire_lock(lock_name)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Expects the connection to behave like an instance of +Mysql+
|
19
|
+
# If you need something else, replace +acquire_lock+ with your own code.
|
20
|
+
# Or define your own lock_function while configuring a new Revenant task.
|
21
|
+
def acquire_lock(lock_name)
|
22
|
+
begin
|
23
|
+
acquired = false
|
24
|
+
sql = lock_query(lock_name)
|
25
|
+
connection.query(sql) do |result|
|
26
|
+
acquired = result.fetch_row.first == "1"
|
27
|
+
end
|
28
|
+
acquired
|
29
|
+
rescue ::Exception
|
30
|
+
false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def lock_query(lock_name)
|
35
|
+
"select get_lock('#{lock_name}',0);"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Currently defaults to the ActiveRecord connection if AR is loaded.
|
39
|
+
# Set this in your task setup block if that is not what you want.
|
40
|
+
def connection
|
41
|
+
@connection ||= if defined?(ActiveRecord)
|
42
|
+
ActiveRecord::Base.connection.raw_connection
|
43
|
+
else
|
44
|
+
raise "No connection established or discovered. Use Revenant::MySQL::connection="
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def connection=(conn)
|
49
|
+
@connection = conn
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# This is how you register a new lock_type
|
54
|
+
register :mysql, MySQL
|
55
|
+
end
|
56
|
+
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'revenant/task'
|
2
|
+
require 'revenant/pid'
|
3
|
+
require 'revenant/manager'
|
4
|
+
|
5
|
+
# "startup" and "shutdown" are the methods Task expects modules like
|
6
|
+
# this one to replace.
|
7
|
+
module ::Revenant::Daemon
|
8
|
+
# Installs this plugin in the given +task+.
|
9
|
+
# Out of the box, this is always to provide daemon support.
|
10
|
+
# +install+ is expected to know when to do nothing.
|
11
|
+
def self.install(task)
|
12
|
+
if task.daemon?
|
13
|
+
class << task
|
14
|
+
include ::Revenant::Daemon
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def startup
|
20
|
+
@original_dir = ::Revenant.working_directory
|
21
|
+
daemonize
|
22
|
+
log "#{name} is starting"
|
23
|
+
end
|
24
|
+
|
25
|
+
def shutdown
|
26
|
+
@pid.remove
|
27
|
+
|
28
|
+
if restart_pending?
|
29
|
+
log "#{name} is restarting"
|
30
|
+
if @original_dir
|
31
|
+
Dir.chdir @original_dir
|
32
|
+
end
|
33
|
+
system script
|
34
|
+
else
|
35
|
+
log "#{name} is shutting down"
|
36
|
+
end
|
37
|
+
|
38
|
+
exit 0
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
## Everything else is a daemon implementation detail.
|
43
|
+
##
|
44
|
+
|
45
|
+
def pid_file
|
46
|
+
@options[:pid_file] ||= File.join("/tmp", "#{@name}.pid")
|
47
|
+
end
|
48
|
+
|
49
|
+
def log_file
|
50
|
+
@options[:log_file]
|
51
|
+
end
|
52
|
+
|
53
|
+
def script
|
54
|
+
@options[:script] ||= File.expand_path($0)
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
def daemonize
|
60
|
+
verify_permissions
|
61
|
+
::Revenant::Manager.daemonize(name, log_file)
|
62
|
+
@pid.create
|
63
|
+
daemon_signals
|
64
|
+
end
|
65
|
+
|
66
|
+
def verify_permissions
|
67
|
+
unless File.executable?(script)
|
68
|
+
error "script file is not executable: #{script.inspect}"
|
69
|
+
exit 1
|
70
|
+
end
|
71
|
+
|
72
|
+
dir = File.dirname(pid_file)
|
73
|
+
unless File.directory?(dir) && File.writable?(dir)
|
74
|
+
error "pid file is not writeable: #{pid_file.inspect}"
|
75
|
+
exit 1
|
76
|
+
end
|
77
|
+
|
78
|
+
@pid = ::Revenant::PID.new(pid_file)
|
79
|
+
if @pid.exists?
|
80
|
+
error "pid file exists: #{pid_file.inspect}. unclean shutdown?"
|
81
|
+
exit 1
|
82
|
+
end
|
83
|
+
|
84
|
+
if log_file && dir = File.dirname(log_file)
|
85
|
+
unless File.directory?(dir) && File.writable?(dir)
|
86
|
+
error "log file is not writeable: #{log_file.inspect}"
|
87
|
+
exit 1
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def daemon_signals
|
93
|
+
trap("TERM") do
|
94
|
+
log "Received TERM signal"
|
95
|
+
shutdown_soon
|
96
|
+
end
|
97
|
+
|
98
|
+
trap("QUIT") do
|
99
|
+
log "QUIT: #{caller.inspect}"
|
100
|
+
shutdown_soon
|
101
|
+
end
|
102
|
+
|
103
|
+
trap("USR1") do
|
104
|
+
log "TRACE: #{caller.inspect}"
|
105
|
+
end
|
106
|
+
|
107
|
+
trap("USR2") do
|
108
|
+
log "Received USR2 signal"
|
109
|
+
restart_soon
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Register this plugin
|
115
|
+
::Revenant.plugins[:daemon] = ::Revenant::Daemon
|
data/lib/revenant.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
module Revenant
|
2
|
+
VERSION = "0.0.1"
|
3
|
+
|
4
|
+
# Register a new type of lock.
|
5
|
+
# User code specifies which by setting lock_type = :something
|
6
|
+
# while configuring a Revenant::Task
|
7
|
+
def self.register(lock_type, klass)
|
8
|
+
@lock_types ||= {}
|
9
|
+
if klass.respond_to?(:lock_function)
|
10
|
+
@lock_types[lock_type.to_sym] = klass
|
11
|
+
else
|
12
|
+
raise ArgumentError, "#{klass} must have a `lock_function` that returns a callable object"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.find_module(lock_type)
|
17
|
+
@lock_types ||= {}
|
18
|
+
@lock_types.fetch(lock_type.to_sym) do
|
19
|
+
raise ArgumentError, "unknown lock type: #{lock_type.inspect}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.plugins
|
24
|
+
@plugins ||= {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.init
|
28
|
+
require 'revenant/task'
|
29
|
+
require_dir "locks"
|
30
|
+
require_dir "plugins"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Given 'locks/foo', will require ./locks/foo/*.rb' with normalized
|
34
|
+
# (relative) paths. (e.g. require 'locks/foo/example' for example.rb)
|
35
|
+
def self.require_dir(relative_path)
|
36
|
+
current = File.expand_path('..', __FILE__)
|
37
|
+
make_relative = /#{current}\//
|
38
|
+
$LOAD_PATH << current unless $LOAD_PATH.include?(current)
|
39
|
+
pattern = File.join(current, relative_path, '*.rb')
|
40
|
+
Dir[pattern].each do |full_path|
|
41
|
+
relative_name = full_path.gsub(make_relative,'').gsub(/\.rb$/,'')
|
42
|
+
require relative_name
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.working_directory
|
47
|
+
# If the 'PWD' environment variable points to our
|
48
|
+
# current working directory, use it instead of Dir.pwd.
|
49
|
+
# It may have a better name for the same destination,
|
50
|
+
# in the presence of symlinks.
|
51
|
+
e = File.stat(env_pwd = ENV['PWD'])
|
52
|
+
p = File.stat(Dir.pwd)
|
53
|
+
e.ino == p.ino && e.dev == p.dev ? env_pwd : Dir.pwd
|
54
|
+
rescue
|
55
|
+
Dir.pwd
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
module Kernel
|
60
|
+
def revenant(name = nil)
|
61
|
+
unless String === name || Symbol === name
|
62
|
+
raise ArgumentError, "Usage: task = revenant('example') {|r| configure_as_needed }"
|
63
|
+
end
|
64
|
+
instance = ::Revenant::Task.new(name)
|
65
|
+
instance.daemon = true # daemonized by default if available
|
66
|
+
yield instance if block_given?
|
67
|
+
instance
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
::Revenant.init
|
72
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Revenant
|
2
|
+
module Manager
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def daemonize(name, log_file = nil)
|
6
|
+
# Firstly, get rid of the filthy dirty original process.
|
7
|
+
exit!(0) if fork
|
8
|
+
|
9
|
+
# Now that we aren't attached to a terminal, we can become
|
10
|
+
# a session leader.
|
11
|
+
begin
|
12
|
+
Process.setsid
|
13
|
+
rescue Errno::EPERM
|
14
|
+
raise SystemCallError, "setsid failed. terminal failed to detach?"
|
15
|
+
end
|
16
|
+
trap 'SIGHUP', 'IGNORE' # don't do anything crazy when this process exits
|
17
|
+
|
18
|
+
# Finally, time to create a daemonized process
|
19
|
+
exit!(0) if fork
|
20
|
+
|
21
|
+
$0 = name.to_s # set the process name
|
22
|
+
close_open_files
|
23
|
+
redirect_io_to(log_file)
|
24
|
+
srand # re-seed the PRNG with our 'final' pid
|
25
|
+
end
|
26
|
+
|
27
|
+
# Close anything that is not one of the three standard IO streams.
|
28
|
+
def close_open_files
|
29
|
+
ObjectSpace.each_object(IO) do |io|
|
30
|
+
next if [STDIN, STDOUT, STDERR].include?(io)
|
31
|
+
begin
|
32
|
+
io.close unless io.closed?
|
33
|
+
rescue ::Exception
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Redirects STDIN, STDOUT, and STDERR to the specified +log_file+
|
39
|
+
# or to /dev/null if none is given.
|
40
|
+
def redirect_io_to(log_file)
|
41
|
+
log_file ||= "/dev/null"
|
42
|
+
reopen_io STDIN, "/dev/null"
|
43
|
+
reopen_io STDOUT, log_file, "a"
|
44
|
+
reopen_io STDERR, STDOUT
|
45
|
+
STDERR.sync = STDOUT.sync = true
|
46
|
+
end
|
47
|
+
|
48
|
+
# Attempts to reopen an IO object.
|
49
|
+
def reopen_io(io, path, mode = nil)
|
50
|
+
begin
|
51
|
+
if mode
|
52
|
+
io.reopen(path, mode)
|
53
|
+
else
|
54
|
+
io.reopen(path)
|
55
|
+
end
|
56
|
+
io.binmode
|
57
|
+
rescue ::Exception
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
data/lib/revenant/pid.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Revenant
|
2
|
+
class PID
|
3
|
+
def initialize(file)
|
4
|
+
@file = file
|
5
|
+
end
|
6
|
+
|
7
|
+
def exists?
|
8
|
+
File.exists?(@file)
|
9
|
+
end
|
10
|
+
|
11
|
+
def create
|
12
|
+
return false if exists?
|
13
|
+
|
14
|
+
File.open(@file, 'w') do |f|
|
15
|
+
f.write(Process.pid)
|
16
|
+
end
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def remove
|
21
|
+
File.unlink(@file) if exists?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
require 'revenant'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module Revenant
|
5
|
+
class Task
|
6
|
+
attr_reader :name
|
7
|
+
attr_accessor :options
|
8
|
+
attr_writer :logger
|
9
|
+
|
10
|
+
def initialize(name = nil)
|
11
|
+
unless String === name || Symbol === name
|
12
|
+
raise ArgumentError, "Usage: new(task_name)"
|
13
|
+
end
|
14
|
+
@name = name.to_sym
|
15
|
+
@options = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
# Generally overridden when Revenant::Daemon is included
|
19
|
+
def startup
|
20
|
+
log "#{name} is starting"
|
21
|
+
trap("INT") { shutdown_soon }
|
22
|
+
end
|
23
|
+
|
24
|
+
## Generally overridden when Revenant::Daemon is included
|
25
|
+
# The stack gets deeper here on every restart; this is here
|
26
|
+
# largely to ease testing.
|
27
|
+
# Implement your own plugin providing +shutdown+ if you want to
|
28
|
+
# make something serious that calls this code after a
|
29
|
+
# restart signal.
|
30
|
+
def shutdown
|
31
|
+
if restart_pending? && @work
|
32
|
+
log "#{name} is restarting"
|
33
|
+
run(&@work)
|
34
|
+
else
|
35
|
+
log "#{name} is shutting down"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Takes actual block of code that is to be guarded by
|
40
|
+
# the lock. The +run_loop+ method does the actual work.
|
41
|
+
#
|
42
|
+
# If 'daemon?' is true, your code (including +on_load+)
|
43
|
+
# will execute after a fork.
|
44
|
+
#
|
45
|
+
# Make sure you don't open files and sockets in the exiting
|
46
|
+
# parent process by mistake. Open them in code that is called
|
47
|
+
# via +on_load+.
|
48
|
+
def run(&block)
|
49
|
+
unless @work = block
|
50
|
+
raise ArgumentError, "Usage: run { while_we_have_the_lock }"
|
51
|
+
end
|
52
|
+
@shutdown = false
|
53
|
+
@restart = false
|
54
|
+
install_plugins
|
55
|
+
startup # typically daemonizes the process, can have various implementations
|
56
|
+
on_load.call(self) if on_load
|
57
|
+
run_loop(&@work)
|
58
|
+
on_exit.call(self) if on_exit
|
59
|
+
shutdown
|
60
|
+
end
|
61
|
+
|
62
|
+
# Code to run just before the task looks for a lock
|
63
|
+
# This code runs after any necessary forks, and is
|
64
|
+
# therefore the proper place to open databases, logfiles,
|
65
|
+
# and any other resources you require.
|
66
|
+
def on_load(&block)
|
67
|
+
@on_load ||= block
|
68
|
+
end
|
69
|
+
|
70
|
+
# Code to run when the task is exiting.
|
71
|
+
def on_exit(&block)
|
72
|
+
@on_exit ||= block
|
73
|
+
end
|
74
|
+
|
75
|
+
# Used to pick the Task's +lock_module+
|
76
|
+
# Particular lock types may offer various helpful features
|
77
|
+
# via this lock module.
|
78
|
+
# Defaults to :mysql
|
79
|
+
def lock_type
|
80
|
+
@lock_type ||= :mysql
|
81
|
+
end
|
82
|
+
|
83
|
+
# Set a new lock type for this Task.
|
84
|
+
def lock_type=(val)
|
85
|
+
@lock_type = val.to_sym
|
86
|
+
end
|
87
|
+
|
88
|
+
# Set your own lock function. Will be called with a lock name as the arg.
|
89
|
+
# Should return true if a lock has been acquired, false otherwise.
|
90
|
+
# task.lock_function {|name| # .. }
|
91
|
+
def lock_function(&block)
|
92
|
+
if block_given?
|
93
|
+
@lock_function = block
|
94
|
+
else
|
95
|
+
@lock_function ||= lock_module.lock_function
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns a module that knows how to do some distributed locking.
|
100
|
+
# May not be the code that actually performs the lock, if this
|
101
|
+
# Task has had a +lock_function+ assigned to it explicitly.
|
102
|
+
def lock_module
|
103
|
+
::Revenant.find_module(lock_type)
|
104
|
+
end
|
105
|
+
|
106
|
+
# How many work loops to perform before re-acquiring the lock.
|
107
|
+
# Defaults to 5.
|
108
|
+
# Setting it to 0 or nil will assume the lock is forever valid after
|
109
|
+
# acquisition.
|
110
|
+
def relock_every
|
111
|
+
@relock_every ||= 5
|
112
|
+
end
|
113
|
+
|
114
|
+
# Set the frequency with which locks are re-acquired.
|
115
|
+
# Setting it to 0 or nil will assume the lock is forever valid after
|
116
|
+
# acquisition.
|
117
|
+
def relock_every=(loops)
|
118
|
+
loops ||= 0
|
119
|
+
if Integer === loops && loops >= 0
|
120
|
+
@relock_every = loops
|
121
|
+
else
|
122
|
+
raise ArgumentError, "argument must be nil or an integer >= 0"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# How many seconds to sleep after each work loop.
|
127
|
+
# When we don't have the lock, how long to sleep before checking again.
|
128
|
+
# Default is 5 seconds.
|
129
|
+
def sleep_for
|
130
|
+
@sleep_for ||= 5
|
131
|
+
end
|
132
|
+
|
133
|
+
# Set the number of seconds to sleep for after a work loop.
|
134
|
+
def sleep_for=(seconds)
|
135
|
+
seconds ||= 0
|
136
|
+
if Integer === seconds && seconds >= 0
|
137
|
+
@sleep_for = seconds
|
138
|
+
else
|
139
|
+
raise ArgumentError, "argument must be nil or an integer >= 0"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# This could be the moment.
|
144
|
+
def shutdown_pending?
|
145
|
+
@shutdown ||= false
|
146
|
+
end
|
147
|
+
|
148
|
+
# At last, back to war.
|
149
|
+
def restart_pending?
|
150
|
+
@restart ||= false
|
151
|
+
end
|
152
|
+
|
153
|
+
# Task will restart at the earliest safe opportunity after
|
154
|
+
# +restart_soon+ is called.
|
155
|
+
def restart_soon
|
156
|
+
@restart = true
|
157
|
+
@shutdown = true
|
158
|
+
end
|
159
|
+
|
160
|
+
# Task will shut down at the earliest safe opportunity after
|
161
|
+
# +shutdown_soon+ is called.
|
162
|
+
def shutdown_soon
|
163
|
+
@restart = false
|
164
|
+
@shutdown = true
|
165
|
+
end
|
166
|
+
|
167
|
+
## Used to lazily store/retrieve options that may be needed by plugins.
|
168
|
+
# We may want to capture, say, +log_file+ before actually loading the
|
169
|
+
# code that might care about such a concept.
|
170
|
+
def method_missing(name, *args)
|
171
|
+
name = name.to_s
|
172
|
+
last_char = name[-1,1]
|
173
|
+
super(name, *args) unless last_char == "=" || last_char == "?"
|
174
|
+
attr_name = name[0..-2].to_sym # :foo for 'foo=' or 'foo?'
|
175
|
+
if last_char == "="
|
176
|
+
@options[attr_name] = args.at(0)
|
177
|
+
else
|
178
|
+
@options[attr_name]
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def log(message)
|
183
|
+
logger.puts "[#{$$}] #{Time.now.iso8601(2)} - #{message}"
|
184
|
+
end
|
185
|
+
|
186
|
+
def error(message)
|
187
|
+
logger.puts "[#{$$}] #{Time.now.iso8601(2)} - ERROR: #{message}"
|
188
|
+
end
|
189
|
+
|
190
|
+
def logger
|
191
|
+
@logger ||= STDERR
|
192
|
+
end
|
193
|
+
|
194
|
+
# Install any plugins that have registered themselves, or a custom
|
195
|
+
# list if the user has set it themselves.
|
196
|
+
def install_plugins
|
197
|
+
::Revenant.plugins.each do |name, plugin|
|
198
|
+
plugin.install(self)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# Run until we receive a shutdown/reload signal,
|
203
|
+
# or when the worker raises an Interrupt.
|
204
|
+
# Runs after a fork when Revenant::Daemon is enabled.
|
205
|
+
def run_loop(&block)
|
206
|
+
acquired = false
|
207
|
+
begin
|
208
|
+
until shutdown_pending?
|
209
|
+
# The usual situation
|
210
|
+
if relock_every != 0
|
211
|
+
i ||= 0
|
212
|
+
# 0 % anything is 0, so we always try to get the lock on the first loop.
|
213
|
+
if (i %= relock_every) == 0
|
214
|
+
acquired = lock_function.call(@name)
|
215
|
+
end
|
216
|
+
else
|
217
|
+
# With relock_every set to 0, only acquire the lock once.
|
218
|
+
# Hope you're sure that lock beongs to you.
|
219
|
+
acquired ||= lock_function.call(@name)
|
220
|
+
i = 0 # no point in incrementing something we don't check.
|
221
|
+
end
|
222
|
+
|
223
|
+
yield if acquired
|
224
|
+
|
225
|
+
# Sleep one second at a time so we can quickly respond to
|
226
|
+
# shutdown requests.
|
227
|
+
sleep_for.times do
|
228
|
+
sleep(1) unless shutdown_pending?
|
229
|
+
end
|
230
|
+
i += 1
|
231
|
+
end # loop
|
232
|
+
rescue ::Interrupt => ex
|
233
|
+
log "shutting down after interrupt: #{ex.class} - #{ex.message}"
|
234
|
+
shutdown_soon # Always shut down from an Interrupt, even mid-restart.
|
235
|
+
return
|
236
|
+
rescue ::Exception => ex
|
237
|
+
error "restarting after error: #{ex.class} - #{ex.message}"
|
238
|
+
error "backtrace: #{ex.backtrace.join("\n")}"
|
239
|
+
restart_soon # Restart if we run into an exception.
|
240
|
+
end # begin block
|
241
|
+
end # run_loop
|
242
|
+
end # Task
|
243
|
+
end # Revenant
|
244
|
+
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: revenant
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Wilson Bilkovich
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2010-06-06 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: A framework for building reliable distributed workers.
|
15
|
+
email: wilson@supremetyrant.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/locks/mysql.rb
|
21
|
+
- lib/plugins/daemon.rb
|
22
|
+
- lib/revenant/manager.rb
|
23
|
+
- lib/revenant/pid.rb
|
24
|
+
- lib/revenant/task.rb
|
25
|
+
- lib/revenant.rb
|
26
|
+
homepage: http://github.com/wilson/revenant
|
27
|
+
licenses: []
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.3.7
|
44
|
+
requirements: []
|
45
|
+
rubyforge_project:
|
46
|
+
rubygems_version: 1.8.24
|
47
|
+
signing_key:
|
48
|
+
specification_version: 2
|
49
|
+
summary: Distributed daemons that just will not die.
|
50
|
+
test_files: []
|