pigeon 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|