pigeon 0.2.0 → 0.3.0
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/.gitignore +1 -0
- data/VERSION +1 -1
- data/bin/launcher.example +3 -51
- data/lib/pigeon.rb +7 -2
- data/lib/pigeon/engine.rb +153 -76
- data/lib/pigeon/launcher.rb +79 -0
- data/lib/pigeon/option_accessor.rb +95 -0
- data/lib/pigeon/support.rb +15 -1
- data/pigeon.gemspec +8 -2
- data/test/helper.rb +8 -1
- data/test/test_pigeon_engine.rb +18 -9
- data/test/test_pigeon_launcher.rb +51 -0
- data/test/test_pigeon_option_accessor.rb +71 -0
- metadata +9 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/bin/launcher.example
CHANGED
@@ -1,54 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
$LOAD_PATH << File.expand_path(File.join(*%w[ .. lib ]), File.dirname(__FILE__))
|
4
|
+
require 'pigeon'
|
5
5
|
|
6
|
-
|
7
|
-
:dir => engine.pid_dir,
|
8
|
-
:debug => true, # ((ENV['RAILS_ENV'] == 'production') ? ENV['PINGITY_DEBUG'] : true),
|
9
|
-
:modules => [ ]
|
10
|
-
}
|
11
|
-
|
12
|
-
begin
|
13
|
-
case (command)
|
14
|
-
when 'start'
|
15
|
-
engine.start(options) do |pid|
|
16
|
-
puts "Pigeon Engine now running. [%d]" % pid
|
17
|
-
end
|
18
|
-
when 'stop'
|
19
|
-
engine.stop(options) do |pid|
|
20
|
-
if (pid)
|
21
|
-
puts "Pigeon Engine shut down. [%d]" % pid
|
22
|
-
else
|
23
|
-
puts "Pigeon Engine was not running."
|
24
|
-
end
|
25
|
-
end
|
26
|
-
when 'restart'
|
27
|
-
engine.restart(options) do |old_pid, new_pid|
|
28
|
-
if (old_pid)
|
29
|
-
puts "Pigeon Engine terminated. [%d]" % old_pid
|
30
|
-
end
|
31
|
-
puts "Pigeon Engine now running. [%d]" % new_pid
|
32
|
-
end
|
33
|
-
when 'status'
|
34
|
-
engine.status(options) do |pid|
|
35
|
-
if (pid)
|
36
|
-
puts "Pigeon Engine running. [%d]" % pid
|
37
|
-
else
|
38
|
-
puts "Pigeon Engine is not running."
|
39
|
-
end
|
40
|
-
end
|
41
|
-
when 'run'
|
42
|
-
options[:logger] = Pigeon::Logger.new(STDOUT)
|
43
|
-
|
44
|
-
engine.run(options) do |pid|
|
45
|
-
puts "Pigeon Engine now running. [%d]" % pid
|
46
|
-
puts "Use ^C to terminate."
|
47
|
-
end
|
48
|
-
else
|
49
|
-
puts "Usage: #{COMMAND_NAME} [start|stop|restart|status|run]"
|
50
|
-
end
|
51
|
-
rescue Interrupt
|
52
|
-
puts "Shutting down."
|
53
|
-
exit(0)
|
54
|
-
end
|
6
|
+
Pigeon::Launcher.new(Pigeon::Engine).handle_args(ARGV)
|
data/lib/pigeon.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
module Pigeon
|
2
|
+
# == Autoloads ============================================================
|
3
|
+
|
2
4
|
autoload(:Engine, 'pigeon/engine')
|
3
|
-
autoload(:
|
5
|
+
autoload(:Launcher, 'pigeon/launcher')
|
6
|
+
autoload(:OptionAccessor, 'pigeon/option_accessor')
|
4
7
|
autoload(:Pidfile, 'pigeon/pidfile')
|
8
|
+
autoload(:Queue, 'pigeon/queue')
|
5
9
|
autoload(:Support, 'pigeon/support')
|
6
|
-
autoload(:Logger, 'pigeon/logger')
|
7
10
|
end
|
11
|
+
|
12
|
+
require 'pigeon/logger'
|
data/lib/pigeon/engine.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'eventmachine'
|
2
2
|
require 'socket'
|
3
|
+
require 'digest/sha1'
|
3
4
|
|
4
5
|
class Pigeon::Engine
|
5
6
|
# == Submodules ===========================================================
|
@@ -7,9 +8,43 @@ class Pigeon::Engine
|
|
7
8
|
class RuntimeError < Exception
|
8
9
|
end
|
9
10
|
|
11
|
+
class ConfigurationError < Exception
|
12
|
+
end
|
13
|
+
|
14
|
+
# == Extensions ==========================================================
|
15
|
+
|
16
|
+
extend Pigeon::OptionAccessor
|
17
|
+
|
10
18
|
# == Properties ===========================================================
|
11
19
|
|
12
|
-
|
20
|
+
option_accessor :logger
|
21
|
+
|
22
|
+
option_accessor :name
|
23
|
+
option_accessor :pid_file_name
|
24
|
+
option_accessor :foreground,
|
25
|
+
:boolean => true
|
26
|
+
option_accessor :debug,
|
27
|
+
:boolean => true
|
28
|
+
|
29
|
+
option_accessor :engine_log_name,
|
30
|
+
:default => 'engine.log'
|
31
|
+
option_accessor :engine_logger
|
32
|
+
|
33
|
+
option_accessor :query_log_name,
|
34
|
+
:default => 'query.log'
|
35
|
+
option_accessor :query_logger
|
36
|
+
|
37
|
+
option_accessor :try_pid_dirs,
|
38
|
+
:default => %w[
|
39
|
+
/var/run
|
40
|
+
/tmp
|
41
|
+
].freeze
|
42
|
+
|
43
|
+
option_accessor :try_log_dirs,
|
44
|
+
:default => %w[
|
45
|
+
/var/log
|
46
|
+
/tmp
|
47
|
+
].freeze
|
13
48
|
|
14
49
|
# == Constants ============================================================
|
15
50
|
|
@@ -20,33 +55,45 @@ class Pigeon::Engine
|
|
20
55
|
before_stop
|
21
56
|
after_stop
|
22
57
|
].collect(&:to_sym).freeze
|
23
|
-
|
24
|
-
PID_DIR = [
|
25
|
-
File.expand_path(File.join(*%w[ .. .. .. .. shared run ]), File.dirname(__FILE__)),
|
26
|
-
'/var/run',
|
27
|
-
'/tmp'
|
28
|
-
].find { |path| File.exist?(path) and File.writable?(path) }
|
29
|
-
|
30
|
-
LOG_DIR = [
|
31
|
-
File.expand_path(File.join(*%w[ .. .. .. .. shared log ] ), File.dirname(__FILE__)),
|
32
|
-
File.expand_path(File.join(*%w[ .. .. log ]), File.dirname(__FILE__)),
|
33
|
-
'/tmp'
|
34
|
-
].find { |path| File.exist?(path) and File.writable?(path) }
|
35
|
-
|
36
|
-
DEFAULT_OPTIONS = {
|
37
|
-
:pid_file => File.expand_path('pigeon-engine.pid', PID_DIR)
|
38
|
-
}.freeze
|
39
58
|
|
40
59
|
# == Class Methods ========================================================
|
41
60
|
|
42
|
-
|
43
|
-
|
61
|
+
# Returns the human-readable name of this engine. Defaults to the name
|
62
|
+
# of the engine class, but can be replaced to customize a subclass.
|
63
|
+
def self.name
|
64
|
+
@name or self.to_s.gsub(/::/, ' ')
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns the name of the PID file to use. The full path to the file
|
68
|
+
# is specified elsewhere.
|
69
|
+
def self.pid_file_name
|
70
|
+
@pid_file_name or self.name.downcase.gsub(/ /, '-') + '.pid'
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns the full path to the PID file that should be used to track
|
74
|
+
# the running status of this engine.
|
75
|
+
def self.pid_file_path
|
76
|
+
@pid_file_path ||= begin
|
77
|
+
if (path = Pigeon::Support.find_writable_directory(self.try_pid_dirs))
|
78
|
+
File.expand_path(self.pid_file_name, path)
|
79
|
+
else
|
80
|
+
raise ConfigurationError, "Could not find a writable directory for the PID file in: #{self.try_pid_dirs.join(' ')}"
|
81
|
+
end
|
82
|
+
end
|
44
83
|
end
|
45
84
|
|
46
|
-
|
85
|
+
# Returns the full path to the directory used to store logs.
|
86
|
+
def self.log_dir
|
87
|
+
@log_file_path ||= Pigeon::Support.find_writable_directory(self.try_log_dirs)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Launches the engine with the specified options
|
91
|
+
def self.launch(options = nil)
|
47
92
|
EventMachine.run do
|
48
|
-
engine = new(
|
49
|
-
|
93
|
+
engine = new(options)
|
94
|
+
|
95
|
+
yield(engine) if (block_given?)
|
96
|
+
|
50
97
|
Signal.trap('INT') do
|
51
98
|
engine.terminate
|
52
99
|
end
|
@@ -55,35 +102,30 @@ class Pigeon::Engine
|
|
55
102
|
end
|
56
103
|
end
|
57
104
|
|
58
|
-
def self.
|
59
|
-
|
60
|
-
end
|
61
|
-
|
62
|
-
def self.pid_file(options = nil)
|
63
|
-
Pigeon::Pidfile.new(options_with_defaults(options)[:pid_file])
|
105
|
+
def self.pid_file
|
106
|
+
@pid_file ||= Pigeon::Pidfile.new(self.pid_file_path)
|
64
107
|
end
|
65
108
|
|
66
109
|
def self.start(options = nil)
|
67
110
|
pid = Pigeon::Support.daemonize do
|
68
|
-
|
111
|
+
launch(options)
|
69
112
|
end
|
70
113
|
|
71
|
-
pid_file
|
114
|
+
pid_file.create!(pid)
|
72
115
|
|
73
116
|
yield(pid) if (block_given?)
|
74
117
|
|
75
118
|
pid
|
76
119
|
end
|
77
120
|
|
78
|
-
def self.run
|
121
|
+
def self.run
|
79
122
|
yield($$) if (block_given?)
|
80
123
|
|
81
|
-
|
124
|
+
launch(:foreground => true)
|
82
125
|
end
|
83
126
|
|
84
|
-
def self.stop
|
85
|
-
|
86
|
-
pid = pf.running
|
127
|
+
def self.stop
|
128
|
+
pid = self.pid_file.running
|
87
129
|
|
88
130
|
if (pid)
|
89
131
|
begin
|
@@ -92,7 +134,8 @@ class Pigeon::Engine
|
|
92
134
|
# No such process exception
|
93
135
|
pid = nil
|
94
136
|
end
|
95
|
-
|
137
|
+
|
138
|
+
pid_file.remove!
|
96
139
|
end
|
97
140
|
|
98
141
|
pid = pid.to_i if (pid)
|
@@ -102,28 +145,41 @@ class Pigeon::Engine
|
|
102
145
|
pid
|
103
146
|
end
|
104
147
|
|
105
|
-
def self.restart
|
106
|
-
self.stop
|
107
|
-
self.start
|
148
|
+
def self.restart
|
149
|
+
self.stop
|
150
|
+
self.start
|
108
151
|
end
|
109
152
|
|
110
|
-
def self.
|
111
|
-
|
153
|
+
def self.running?
|
154
|
+
pid_file.running
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.status
|
158
|
+
pid = pid_file.running
|
112
159
|
|
113
160
|
yield(pid) if (block_given?)
|
114
161
|
|
115
162
|
pid
|
116
163
|
end
|
164
|
+
|
165
|
+
# Returns a default logger for the engine.
|
166
|
+
def self.engine_logger
|
167
|
+
@engine_logger ||= begin
|
168
|
+
f = File.open(File.expand_path(self.engine_log_name, self.log_dir), 'w+')
|
169
|
+
f.sync = true
|
117
170
|
|
118
|
-
|
119
|
-
|
120
|
-
f.sync = true
|
121
|
-
|
122
|
-
Pigeon::Logger.new(f)
|
171
|
+
Pigeon::Logger.new(f)
|
172
|
+
end
|
123
173
|
end
|
124
|
-
|
125
|
-
|
126
|
-
|
174
|
+
|
175
|
+
# Returns a default logger for queries.
|
176
|
+
def self.query_logger
|
177
|
+
@query_logger ||= begin
|
178
|
+
f = File.open(File.expand_path(self.query_log_name, self.log_dir), 'w+')
|
179
|
+
f.sync = true
|
180
|
+
|
181
|
+
Pigeon::Logger.new(f)
|
182
|
+
end
|
127
183
|
end
|
128
184
|
|
129
185
|
# == Instance Methods =====================================================
|
@@ -131,35 +187,47 @@ class Pigeon::Engine
|
|
131
187
|
def initialize(options = nil)
|
132
188
|
@options = options || { }
|
133
189
|
|
134
|
-
@task_lock =
|
190
|
+
@task_lock = Mutex.new
|
191
|
+
@task_locks = { }
|
135
192
|
|
136
|
-
|
137
|
-
|
138
|
-
@logger.level = Pigeon::Logger::DEBUG if (@options[:debug])
|
193
|
+
self.logger ||= self.engine_logger
|
194
|
+
self.logger.level = Pigeon::Logger::DEBUG if (self.debug?)
|
139
195
|
|
140
196
|
@queue = { }
|
141
197
|
|
142
198
|
run_chain(:after_initialize)
|
143
199
|
end
|
144
200
|
|
201
|
+
# Returns the hostname of the system this engine is running on.
|
202
|
+
def host
|
203
|
+
Socket.gethostname
|
204
|
+
end
|
205
|
+
|
206
|
+
# Returns a unique 160-bit identifier for this engine expressed as a 40
|
207
|
+
# character hexadecimal string. The first 32-bit sequence is a timestamp
|
208
|
+
# so these numbers increase over time and can be used to identify when
|
209
|
+
# a particular instance was launched.
|
210
|
+
def id
|
211
|
+
@id ||= '%8x%s' % [
|
212
|
+
Time.now.to_i,
|
213
|
+
Digest::SHA1.hexdigest(
|
214
|
+
'%.8f%8x' % [ Time.now.to_f, rand(1 << 32) ]
|
215
|
+
)[0, 32]
|
216
|
+
]
|
217
|
+
end
|
218
|
+
|
219
|
+
# Handles the run phase of the engine, triggers the before_start and
|
220
|
+
# after_start events accordingly.
|
145
221
|
def run
|
146
222
|
run_chain(:before_start)
|
147
223
|
|
148
224
|
STDOUT.sync = true
|
149
225
|
|
150
|
-
|
226
|
+
logger.info("Engine \##{id} Running")
|
151
227
|
|
152
228
|
run_chain(:after_start)
|
153
229
|
end
|
154
230
|
|
155
|
-
def host
|
156
|
-
Socket.gethostname
|
157
|
-
end
|
158
|
-
|
159
|
-
def id
|
160
|
-
@id ||= '%8x%8x' % [ Time.now.to_i, rand(1 << 32) ]
|
161
|
-
end
|
162
|
-
|
163
231
|
# Used to periodically execute a task or block. When giving a task name,
|
164
232
|
# a method by that name is called, otherwise a block must be supplied.
|
165
233
|
# An interval can be specified in seconds, or will default to 1.
|
@@ -177,19 +245,20 @@ class Pigeon::Engine
|
|
177
245
|
end
|
178
246
|
end
|
179
247
|
|
248
|
+
# This is a somewhat naive locking mechanism that may break down
|
249
|
+
# when two requests are fired off within a nearly identical period.
|
250
|
+
# For now, this achieves a general purpose solution that should work
|
251
|
+
# under most circumstances. Refactor later to improve.
|
180
252
|
def task_lock(task_name)
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
# under most circumstances. Refactor later to improve.
|
185
|
-
|
186
|
-
return if (@task_lock[task_name])
|
187
|
-
|
188
|
-
@task_lock[task_name] = true
|
253
|
+
@task_lock.synchronize do
|
254
|
+
@task_locks[task_name] ||= Mutex.new
|
255
|
+
end
|
189
256
|
|
190
|
-
|
257
|
+
return if (@task_locks[task_name].locked?)
|
191
258
|
|
192
|
-
@task_lock[task_name]
|
259
|
+
@task_lock[task_name].synchronize do
|
260
|
+
yield if (block_given?)
|
261
|
+
end
|
193
262
|
end
|
194
263
|
|
195
264
|
def timer(interval, &block)
|
@@ -202,21 +271,27 @@ class Pigeon::Engine
|
|
202
271
|
EventMachine::PeriodicTimer.new(interval, &block)
|
203
272
|
end
|
204
273
|
|
205
|
-
# Used to defer a block of work for near-immediate execution.
|
206
|
-
# EventMachine#defer
|
274
|
+
# Used to defer a block of work for near-immediate execution. Is a
|
275
|
+
# wrapper around EventMachine#defer and does not perform as well as using
|
276
|
+
# the alternate queue method.
|
207
277
|
def defer(&block)
|
208
278
|
EventMachine.defer(&block)
|
209
279
|
end
|
210
280
|
|
211
|
-
# Shuts down the engine.
|
281
|
+
# Shuts down the engine. Will also trigger the before_stop and after_stop
|
282
|
+
# events.
|
212
283
|
def terminate
|
213
284
|
run_chain(:before_stop)
|
285
|
+
|
214
286
|
EventMachine.stop_event_loop
|
287
|
+
|
215
288
|
run_chain(:after_stop)
|
216
289
|
end
|
217
290
|
|
218
291
|
# Used to queue a block for immediate processing on a background thread.
|
219
|
-
# An optional queue name can be used to sequence tasks properly.
|
292
|
+
# An optional queue name can be used to sequence tasks properly. The main
|
293
|
+
# queue has a large number of threads, while the named queues default
|
294
|
+
# to only one so they can be processed sequentially.
|
220
295
|
def queue(name = :default, &block)
|
221
296
|
target_queue = @queue[name] ||= Pigeon::Queue.new(name == :default ? nil : 1)
|
222
297
|
|
@@ -251,10 +326,12 @@ class Pigeon::Engine
|
|
251
326
|
end
|
252
327
|
end
|
253
328
|
|
329
|
+
# Returns true if the debug option was set, false otherwise.
|
254
330
|
def debug?
|
255
331
|
!!@options[:debug]
|
256
332
|
end
|
257
333
|
|
334
|
+
# Returns true if running in the foreground, false otherwise.
|
258
335
|
def foreground?
|
259
336
|
!!@options[:foreground]
|
260
337
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
class Pigeon::Launcher
|
2
|
+
# == Class Methods ========================================================
|
3
|
+
|
4
|
+
def self.launch(engine, options)
|
5
|
+
end
|
6
|
+
|
7
|
+
# == Instance Methods =====================================================
|
8
|
+
|
9
|
+
def initialize(with_engine = Pigeon::Engine)
|
10
|
+
@engine = with_engine
|
11
|
+
end
|
12
|
+
|
13
|
+
def handle_args(*args)
|
14
|
+
command = args.flatten.first
|
15
|
+
|
16
|
+
begin
|
17
|
+
case (command)
|
18
|
+
when 'start'
|
19
|
+
@engine.start(&method(:start))
|
20
|
+
when 'stop'
|
21
|
+
@engine.stop(&method(:stop))
|
22
|
+
when 'restart'
|
23
|
+
@engine.restart(&method(:restart))
|
24
|
+
when 'status'
|
25
|
+
@engine.status(&method(:status))
|
26
|
+
when 'run'
|
27
|
+
@engine.logger = Pigeon::Logger.new(STDOUT)
|
28
|
+
|
29
|
+
@engine.run(&method(:run))
|
30
|
+
else
|
31
|
+
usage
|
32
|
+
end
|
33
|
+
rescue Interrupt
|
34
|
+
shutdown(pid)
|
35
|
+
exit(0)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def run(pid)
|
40
|
+
puts "#{@engine.name} now running. [%d]" % pid
|
41
|
+
puts "Use ^C to terminate."
|
42
|
+
end
|
43
|
+
|
44
|
+
def start(pid)
|
45
|
+
puts "#{@engine.name} now running. [%d]" % pid
|
46
|
+
end
|
47
|
+
|
48
|
+
def stop(pid)
|
49
|
+
if (pid)
|
50
|
+
puts "#{@engine.name} shut down. [%d]" % pid
|
51
|
+
else
|
52
|
+
puts "#{@engine.name} was not running."
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def status(pid)
|
57
|
+
if (pid)
|
58
|
+
puts "#{@engine.name} running. [%d]" % pid
|
59
|
+
else
|
60
|
+
puts "#{@engine.name} is not running."
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def restart(pid, old_pid)
|
65
|
+
if (old_pid)
|
66
|
+
puts "#{@engine.name} terminated. [%d]" % old_pid
|
67
|
+
end
|
68
|
+
|
69
|
+
puts "#{@engine.name} now running. [%d]" % pid
|
70
|
+
end
|
71
|
+
|
72
|
+
def shutdown(pid)
|
73
|
+
puts "Shutting down."
|
74
|
+
end
|
75
|
+
|
76
|
+
def usage
|
77
|
+
puts "Usage: #{File.basename($0)} [start|stop|restart|status|run]"
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Pigeon::OptionAccessor
|
2
|
+
# Given a list of names, this declares an option accessor which works like
|
3
|
+
# a combination of cattr_accessor and attr_accessor, except that defaults
|
4
|
+
# defined for a class will propagate down to the instances and subclasses,
|
5
|
+
# but these defaults can be over-ridden in subclasses and instances
|
6
|
+
# without interference. Optional hash at end of list can be used to set:
|
7
|
+
# * :default => Assigns a default value which is otherwise nil
|
8
|
+
def option_accessor(*args)
|
9
|
+
option_reader(*args)
|
10
|
+
option_writer(*args)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Given a list of names, this declares an option reader which works like
|
14
|
+
# a combination of cattr_reader and attr_reader, except that defaults
|
15
|
+
# defined for a class will propagate down to the instances and subclasses,
|
16
|
+
# but these defaults can be over-ridden in subclasses and instances
|
17
|
+
# without interference. Optional hash at end of list can be used to set:
|
18
|
+
# * :default => Assigns a default value which is otherwise nil
|
19
|
+
def option_reader(*names)
|
20
|
+
names = [ names ].flatten.compact
|
21
|
+
options = names.last.is_a?(Hash) ? names.pop : { }
|
22
|
+
|
23
|
+
names.each do |name|
|
24
|
+
iv = :"@#{name}"
|
25
|
+
|
26
|
+
(class << self; self; end).class_eval do
|
27
|
+
if (options[:boolean])
|
28
|
+
define_method(:"#{name}?") do
|
29
|
+
iv_value = instance_variable_get(iv)
|
30
|
+
|
31
|
+
!!(iv_value.nil? ? (self.superclass.respond_to?(name) ? self.superclass.send(name) : nil) : iv_value)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
define_method(name) do
|
36
|
+
iv_value = instance_variable_get(iv)
|
37
|
+
|
38
|
+
iv_value.nil? ? (self.superclass.respond_to?(name) ? self.superclass.send(name) : nil) : iv_value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
define_method(name) do
|
43
|
+
iv_value = instance_variable_get(iv)
|
44
|
+
|
45
|
+
iv_value.nil? ? self.class.send(name) : iv_value
|
46
|
+
end
|
47
|
+
|
48
|
+
if (options[:boolean])
|
49
|
+
define_method(:"#{name}?") do
|
50
|
+
iv_value = instance_variable_get(iv)
|
51
|
+
|
52
|
+
!!(iv_value.nil? ? self.class.send(name) : iv_value)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
instance_variable_set(iv, options[:default])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Given a list of names, this declares an option writer which works like
|
61
|
+
# a combination of cattr_writer and attr_writer, except that defaults
|
62
|
+
# defined for a class will propagate down to the instances and subclasses,
|
63
|
+
# but these defaults can be over-ridden in subclasses and instances
|
64
|
+
# without interference.
|
65
|
+
def option_writer(*names)
|
66
|
+
names = [ names ].flatten.compact
|
67
|
+
options = names.last.is_a?(Hash) ? names.pop : { }
|
68
|
+
|
69
|
+
names.each do |name|
|
70
|
+
iv = :"@#{name}"
|
71
|
+
|
72
|
+
(class << self; self; end).class_eval do
|
73
|
+
if (options[:boolean])
|
74
|
+
define_method(:"#{name}=") do |value|
|
75
|
+
instance_variable_set(iv, value.nil? ? nil : !!value)
|
76
|
+
end
|
77
|
+
else
|
78
|
+
define_method(:"#{name}=") do |value|
|
79
|
+
instance_variable_set(iv, value)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
if (options[:boolean])
|
85
|
+
define_method(:"#{name}=") do |value|
|
86
|
+
instance_variable_set(iv, value.nil? ? nil : !!value)
|
87
|
+
end
|
88
|
+
else
|
89
|
+
define_method(:"#{name}=") do |value|
|
90
|
+
instance_variable_set(iv, value)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/pigeon/support.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
module Pigeon::Support
|
2
|
-
|
2
|
+
# Uses the double-fork method to create a fully detached background
|
3
|
+
# process. Returns the process ID of the created process. May throw an
|
4
|
+
# exception if these processes could not be created.
|
5
|
+
def daemonize
|
3
6
|
rfd, wfd = IO.pipe
|
4
7
|
|
5
8
|
forked_pid = fork do
|
@@ -18,4 +21,15 @@ module Pigeon::Support
|
|
18
21
|
|
19
22
|
daemon_pid.to_i
|
20
23
|
end
|
24
|
+
|
25
|
+
# Finds the first directory in the given list that exists and is
|
26
|
+
# writable. Returns nil if none match.
|
27
|
+
def find_writable_directory(*list)
|
28
|
+
list.flatten.compact.find do |dir|
|
29
|
+
File.exist?(dir) and File.writable?(dir)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Make all methods callable directly without having to include it
|
34
|
+
extend self
|
21
35
|
end
|
data/pigeon.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{pigeon}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.3.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["tadman"]
|
12
|
-
s.date = %q{2010-10-
|
12
|
+
s.date = %q{2010-10-19}
|
13
13
|
s.default_executable = %q{launcher.example}
|
14
14
|
s.description = %q{Pigeon is a simple way to get started building an EventMachine engine that's intended to run as a background job.}
|
15
15
|
s.email = %q{github@tadman.ca}
|
@@ -28,7 +28,9 @@ Gem::Specification.new do |s|
|
|
28
28
|
"bin/launcher.example",
|
29
29
|
"lib/pigeon.rb",
|
30
30
|
"lib/pigeon/engine.rb",
|
31
|
+
"lib/pigeon/launcher.rb",
|
31
32
|
"lib/pigeon/logger.rb",
|
33
|
+
"lib/pigeon/option_accessor.rb",
|
32
34
|
"lib/pigeon/pidfile.rb",
|
33
35
|
"lib/pigeon/queue.rb",
|
34
36
|
"lib/pigeon/support.rb",
|
@@ -36,6 +38,8 @@ Gem::Specification.new do |s|
|
|
36
38
|
"test/helper.rb",
|
37
39
|
"test/test_pigeon.rb",
|
38
40
|
"test/test_pigeon_engine.rb",
|
41
|
+
"test/test_pigeon_launcher.rb",
|
42
|
+
"test/test_pigeon_option_accessor.rb",
|
39
43
|
"test/test_pigeon_queue.rb"
|
40
44
|
]
|
41
45
|
s.homepage = %q{http://github.com/tadman/pigeon}
|
@@ -47,6 +51,8 @@ Gem::Specification.new do |s|
|
|
47
51
|
"test/helper.rb",
|
48
52
|
"test/test_pigeon.rb",
|
49
53
|
"test/test_pigeon_engine.rb",
|
54
|
+
"test/test_pigeon_launcher.rb",
|
55
|
+
"test/test_pigeon_option_accessor.rb",
|
50
56
|
"test/test_pigeon_queue.rb"
|
51
57
|
]
|
52
58
|
|
data/test/helper.rb
CHANGED
@@ -4,8 +4,15 @@ require 'test/unit'
|
|
4
4
|
$LOAD_PATH.unshift(File.expand_path(*%w[ .. lib ]), File.dirname(__FILE__))
|
5
5
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
6
6
|
|
7
|
+
require 'rubygems'
|
8
|
+
|
9
|
+
if (Gem.available?('eventmachine'))
|
10
|
+
gem 'eventmachine'
|
11
|
+
else
|
12
|
+
raise "EventMachine gem is not installed."
|
13
|
+
end
|
14
|
+
|
7
15
|
require 'pigeon'
|
8
16
|
|
9
17
|
class Test::Unit::TestCase
|
10
|
-
# ...
|
11
18
|
end
|
data/test/test_pigeon_engine.rb
CHANGED
@@ -28,35 +28,39 @@ end
|
|
28
28
|
|
29
29
|
class CallbackTestEngine < Pigeon::Engine
|
30
30
|
after_initialize do
|
31
|
-
|
31
|
+
track(:after_initialize)
|
32
32
|
end
|
33
33
|
|
34
34
|
before_start do
|
35
|
-
|
35
|
+
track(:before_start)
|
36
36
|
end
|
37
37
|
|
38
38
|
after_start do
|
39
|
-
|
39
|
+
track(:after_start)
|
40
40
|
end
|
41
41
|
|
42
42
|
before_stop do
|
43
|
-
|
43
|
+
track(:before_stop)
|
44
44
|
end
|
45
45
|
|
46
46
|
after_stop do
|
47
|
-
|
47
|
+
track(:after_stop)
|
48
|
+
|
48
49
|
@options[:pipe].close
|
49
50
|
end
|
50
51
|
|
51
52
|
def track(callback)
|
52
|
-
|
53
|
-
|
54
|
-
pipe.puts(callback.to_s)
|
55
|
-
pipe.flush
|
53
|
+
@options[:pipe].puts(callback.to_s)
|
54
|
+
@options[:pipe].flush
|
56
55
|
end
|
57
56
|
end
|
58
57
|
|
59
58
|
class TestPigeonEngine < Test::Unit::TestCase
|
59
|
+
def test_default_options
|
60
|
+
assert TestEngine.engine_logger
|
61
|
+
assert TestEngine.engine_logger.is_a?(Logger)
|
62
|
+
end
|
63
|
+
|
60
64
|
def test_example_subclass
|
61
65
|
engine_pid = nil
|
62
66
|
|
@@ -122,10 +126,15 @@ class TestPigeonEngine < Test::Unit::TestCase
|
|
122
126
|
|
123
127
|
write_fd.close
|
124
128
|
|
129
|
+
reported_status = false
|
130
|
+
|
125
131
|
CallbackTestEngine.status do |pid|
|
126
132
|
assert_equal engine_pid, pid
|
133
|
+
reported_status = true
|
127
134
|
end
|
128
135
|
|
136
|
+
assert reported_status
|
137
|
+
|
129
138
|
CallbackTestEngine.stop do |pid|
|
130
139
|
assert_equal engine_pid, pid
|
131
140
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require File.expand_path(File.join(*%w[ helper ]), File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class PigeonLauncherTest < Test::Unit::TestCase
|
4
|
+
def test_default_launcher
|
5
|
+
pid = Pigeon::Launcher.launch
|
6
|
+
|
7
|
+
assert pid
|
8
|
+
assert Pigeon::Engine.running?
|
9
|
+
|
10
|
+
Pigeon::Launcher.stop
|
11
|
+
|
12
|
+
assert !Pigeon::Engine.running?
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_triggers
|
16
|
+
triggered = Hash.new do |h, k|
|
17
|
+
h[k] = 0
|
18
|
+
end
|
19
|
+
|
20
|
+
Pigeon::Launcher.new(Pigeon::Engine).handle_args('start') do
|
21
|
+
start do
|
22
|
+
triggered[:start] += 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Pigeon::Launcher.new(Pigeon::Engine).handle_args('restart') do
|
27
|
+
restart do
|
28
|
+
triggered[:restart] += 1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
Pigeon::Launcher.new(Pigeon::Engine).handle_args('status') do
|
33
|
+
status do
|
34
|
+
triggered[:status] += 1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
Pigeon::Launcher.new(Pigeon::Engine).handle_args('stop') do
|
39
|
+
stop do
|
40
|
+
triggered[:stop] += 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# FIX: Test `run`
|
45
|
+
|
46
|
+
assert_equal 1, triggered[:start]
|
47
|
+
assert_equal 1, triggered[:restart]
|
48
|
+
assert_equal 1, triggered[:status]
|
49
|
+
assert_equal 1, triggered[:stop]
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require File.expand_path(File.join(*%w[ helper ]), File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class OptionClass
|
4
|
+
extend Pigeon::OptionAccessor
|
5
|
+
|
6
|
+
option_accessor :single_option
|
7
|
+
option_accessor :multi1, :multi2
|
8
|
+
option_accessor :option_with_default,
|
9
|
+
:default => :example_default
|
10
|
+
|
11
|
+
option_accessor :boolean_option,
|
12
|
+
:boolean => true
|
13
|
+
|
14
|
+
self.single_option = :single_option_default
|
15
|
+
end
|
16
|
+
|
17
|
+
class OptionSubclass < OptionClass
|
18
|
+
end
|
19
|
+
|
20
|
+
class PigeonOptionAccessorTest < Test::Unit::TestCase
|
21
|
+
def test_class_and_instance_chaining
|
22
|
+
assert_equal :single_option_default, OptionClass.single_option
|
23
|
+
|
24
|
+
instance = OptionClass.new
|
25
|
+
|
26
|
+
assert_equal :single_option_default, instance.single_option
|
27
|
+
|
28
|
+
OptionClass.single_option = :new_default
|
29
|
+
|
30
|
+
assert_equal :new_default, instance.single_option
|
31
|
+
|
32
|
+
instance.single_option = :override
|
33
|
+
|
34
|
+
assert_equal :override, instance.single_option
|
35
|
+
assert_equal :new_default, OptionClass.single_option
|
36
|
+
|
37
|
+
# Reset to defaults for next test
|
38
|
+
OptionClass.single_option = :single_option_default
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_subclass_inheritance
|
42
|
+
assert_equal :single_option_default, OptionSubclass.single_option
|
43
|
+
|
44
|
+
OptionSubclass.single_option = :subclass_default
|
45
|
+
|
46
|
+
assert_equal :subclass_default, OptionSubclass.single_option
|
47
|
+
assert_equal :single_option_default, OptionClass.single_option
|
48
|
+
|
49
|
+
class_instance = OptionClass.new
|
50
|
+
subclass_instance = OptionSubclass.new
|
51
|
+
|
52
|
+
assert_equal :subclass_default, subclass_instance.single_option
|
53
|
+
assert_equal :single_option_default, class_instance.single_option
|
54
|
+
|
55
|
+
# Reset to defaults for next test
|
56
|
+
OptionSubclass.single_option = nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_boolean_option
|
60
|
+
assert_equal nil, OptionClass.multi1
|
61
|
+
|
62
|
+
instance = OptionClass.new
|
63
|
+
|
64
|
+
instance.multi1 = false
|
65
|
+
|
66
|
+
assert_equal false, instance.multi1
|
67
|
+
assert_equal nil, OptionClass.multi1
|
68
|
+
|
69
|
+
assert_equal nil, instance.multi2
|
70
|
+
end
|
71
|
+
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
7
|
+
- 3
|
8
8
|
- 0
|
9
|
-
version: 0.
|
9
|
+
version: 0.3.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- tadman
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-10-
|
17
|
+
date: 2010-10-19 00:00:00 -04:00
|
18
18
|
default_executable: launcher.example
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -49,7 +49,9 @@ files:
|
|
49
49
|
- bin/launcher.example
|
50
50
|
- lib/pigeon.rb
|
51
51
|
- lib/pigeon/engine.rb
|
52
|
+
- lib/pigeon/launcher.rb
|
52
53
|
- lib/pigeon/logger.rb
|
54
|
+
- lib/pigeon/option_accessor.rb
|
53
55
|
- lib/pigeon/pidfile.rb
|
54
56
|
- lib/pigeon/queue.rb
|
55
57
|
- lib/pigeon/support.rb
|
@@ -57,6 +59,8 @@ files:
|
|
57
59
|
- test/helper.rb
|
58
60
|
- test/test_pigeon.rb
|
59
61
|
- test/test_pigeon_engine.rb
|
62
|
+
- test/test_pigeon_launcher.rb
|
63
|
+
- test/test_pigeon_option_accessor.rb
|
60
64
|
- test/test_pigeon_queue.rb
|
61
65
|
has_rdoc: true
|
62
66
|
homepage: http://github.com/tadman/pigeon
|
@@ -94,4 +98,6 @@ test_files:
|
|
94
98
|
- test/helper.rb
|
95
99
|
- test/test_pigeon.rb
|
96
100
|
- test/test_pigeon_engine.rb
|
101
|
+
- test/test_pigeon_launcher.rb
|
102
|
+
- test/test_pigeon_option_accessor.rb
|
97
103
|
- test/test_pigeon_queue.rb
|