ThiagoLelis-backgroundjob 1.0.4 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/lib/bj/logger.rb CHANGED
@@ -1,50 +1,50 @@
1
- class Bj
2
- class Logger < ::Logger
3
- def self.new *a, &b
4
- super(*a, &b).instance_eval{ @default_formatter = @formatter = Formatter.new; self }
5
- end
6
- def format_message(severity, datetime, progname, msg)
7
- (@formatter || @default_formatter).call(severity, datetime, progname, msg)
8
- end
9
-
10
- def device
11
- @logdev.instance_eval{ @dev }
12
- end
13
-
14
- def tty?
15
- device.respond_to?('tty?') and device.tty?
16
- end
17
-
18
- def turn which
19
- @logdev.extend OnOff unless OnOff === @logdev
20
- @logdev.turn which
21
- end
22
-
23
- module OnOff
24
- def turn which
25
- @turned = which.to_s =~ %r/on/i ? :on : :off
26
- end
27
-
28
- def write message
29
- return message.to_s.size if @turned == :off
30
- super
31
- end
32
- end
33
-
34
- def on
35
- turn :on
36
- end
37
- alias_method "on!", "on"
38
- def self.on *a, &b
39
- new(*a, &b).instance_eval{ turn :on; self }
40
- end
41
-
42
- def off
43
- turn :off
44
- end
45
- alias_method "off!", "off"
46
- def self.off *a, &b
47
- new(*a, &b).instance_eval{ turn :off; self }
48
- end
49
- end
50
- end
1
+ class Bj
2
+ class Logger < ::Logger
3
+ def self.new *a, &b
4
+ super(*a, &b).instance_eval{ @default_formatter = @formatter = Formatter.new; self }
5
+ end
6
+ def format_message(severity, datetime, progname, msg)
7
+ (@formatter || @default_formatter).call(severity, datetime, progname, msg)
8
+ end
9
+
10
+ def device
11
+ @logdev.instance_eval{ @dev }
12
+ end
13
+
14
+ def tty?
15
+ device.respond_to?('tty?') and device.tty?
16
+ end
17
+
18
+ def turn which
19
+ @logdev.extend OnOff unless OnOff === @logdev
20
+ @logdev.turn which
21
+ end
22
+
23
+ module OnOff
24
+ def turn which
25
+ @turned = which.to_s =~ %r/on/i ? :on : :off
26
+ end
27
+
28
+ def write message
29
+ return message.to_s.size if @turned == :off
30
+ super
31
+ end
32
+ end
33
+
34
+ def on
35
+ turn :on
36
+ end
37
+ alias_method "on!", "on"
38
+ def self.on *a, &b
39
+ new(*a, &b).instance_eval{ turn :on; self }
40
+ end
41
+
42
+ def off
43
+ turn :off
44
+ end
45
+ alias_method "off!", "off"
46
+ def self.off *a, &b
47
+ new(*a, &b).instance_eval{ turn :off; self }
48
+ end
49
+ end
50
+ end
data/lib/bj/runner.rb CHANGED
@@ -1,357 +1,357 @@
1
- class Bj
2
- class Runner
3
- class Background
4
- def self.for(*a, &b) new(*a, &b) end
5
-
6
- attribute "command"
7
- attribute "thread"
8
- attribute "pid"
9
-
10
- def initialize command
11
- @command = command
12
- @thread = new_thread
13
- end
14
-
15
- def inspect
16
- {
17
- "command" => command,
18
- "pid" => pid,
19
- }.inspect
20
- end
21
-
22
-
23
- # TODO - auto start runner?
24
-
25
- def new_thread
26
- this = self
27
- Thread.new do
28
- Thread.current.abort_on_exception = true
29
- loop do
30
- cleanup = lambda{}
31
-
32
- IO.popen command, "r+" do |pipe|
33
- this.pid = pid = pipe.pid
34
- cleanup = lambda do
35
- cleanup = lambda{}
36
- begin; Process.kill(Runner.kill_signal, pid); rescue Exception; 42; end
37
- end
38
- at_exit &cleanup
39
- Process.wait
40
- end
41
-
42
- Bj.logger.error{ "#{ command } failed with #{ $?.inspect }" } unless
43
- [0, 42].include?($?.exitstatus)
44
-
45
- cleanup.call
46
-
47
- sleep 42
48
- end
49
- end
50
- end
51
- end
52
-
53
- module ClassMethods
54
- attribute("thread"){ Thread.current }
55
- attribute("hup_signal"){ Signal.list.keys.index("HUP") ? "HUP" : "ABRT" }
56
- attribute("hup_signaled"){ false }
57
- attribute("kill_signal"){ "TERM" }
58
- attribute("kill_signaled"){ false }
59
-
60
- def tickle
61
- return nil if Bj.config[Runner.no_tickle_key]
62
- ping or start
63
- end
64
-
65
- def ping
66
- begin
67
- pid = nil
68
- uri = nil
69
- process = nil
70
- Bj.transaction do
71
- pid = Bj.config[Runner.key(Process.pid)] || Bj.config[Runner.key]
72
- uri = Bj.config["#{ pid }.uri"]
73
- process =
74
- if uri
75
- require "drb"
76
- # DRb.start_service "druby://localhost:0"
77
- DRbObject.new(nil, uri)
78
- else
79
- Process
80
- end
81
- end
82
- return nil unless pid
83
- pid = Integer pid
84
- begin
85
- process.kill Runner.hup_signal, pid
86
- pid
87
- rescue Exception => e
88
- false
89
- end
90
- rescue Exception => e
91
- false
92
- end
93
- end
94
-
95
- def key ppid = 0
96
- ppid ||= 0
97
- "#{ Bj.rails_env }.#{ ppid }.pid"
98
- end
99
-
100
- def no_tickle_key
101
- "#{ Bj.rails_env }.no_tickle"
102
- end
103
-
104
- def start options = {}
105
- options.to_options!
106
- background.delete Bj.rails_env if options[:force]
107
- background[Bj.rails_env] ||= Background.for(command)
108
- end
109
-
110
- def background
111
- @background ||= Hash.new
112
- end
113
-
114
- def background= value
115
- @background ||= value
116
- end
117
-
118
- def command
119
- "#{ Bj.ruby } " + %W[
120
- #{ Bj.script }
121
- run
122
- --forever
123
- --redirect=#{ log }
124
- --ppid=#{ Process.pid }
125
- --rails_env=#{ Bj.rails_env }
126
- --rails_root=#{ Bj.rails_root }
127
- ].map{|word| word.inspect}.join(" ")
128
- end
129
-
130
- def log
131
- File.join logdir, "bj.#{ Bj.hostname }.#{ Bj.rails_env }.log"
132
- end
133
-
134
- def logdir
135
- File.join File.expand_path(Bj.rails_root), 'log'
136
- end
137
-
138
- def run options = {}, &block
139
- new(options, &block).run
140
- end
141
- end
142
- send :extend, ClassMethods
143
-
144
- module Instance_Methods
145
- attribute "options"
146
- attribute "block"
147
-
148
- def initialize options = {}, &block
149
- options.to_options!
150
- @options, @block = options, block
151
- end
152
-
153
- def run
154
- wait = options[:wait] || 42
155
- limit = options[:limit]
156
- forever = options[:forever]
157
-
158
- limit = false if forever
159
- wait = Integer wait
160
- loopno = 0
161
-
162
- Runner.thread = Thread.current
163
- Bj.chroot
164
-
165
- register or exit!(EXIT::WARNING)
166
-
167
- Bj.logger.info{ "STARTED" }
168
- at_exit{ Bj.logger.info{ "STOPPED" } }
169
-
170
- fill_morgue
171
- install_signal_handlers
172
-
173
- loop do
174
- ping_parent
175
-
176
- loopno += 1
177
- break if(limit and loopno > limit)
178
-
179
- archive_jobs
180
-
181
- catch :no_jobs do
182
- loop do
183
- job = thread = stdout = stderr = nil
184
-
185
- Bj.transaction(options) do
186
- now = Util.now
187
-
188
- job = Bj::Table::Job.find_first_and_lock(
189
- :conditions => ["state = ? and submitted_at <= ?", "pending", now],
190
- :order => "priority DESC, submitted_at ASC",
191
- :limit => 1,
192
- :lock => true)
193
- throw :no_jobs unless job
194
-
195
-
196
- Bj.logger.info{ "#{ job.title } - started" }
197
-
198
- command = job.command
199
- env = job.env || {}
200
- stdin = job.stdin || ''
201
- stdout = job.stdout || ''
202
- stderr = job.stderr || ''
203
- started_at = Util.now
204
-
205
- thread = Util.start command, :cwd=>Bj.rails_root, :env=>env, :stdin=>stdin, :stdout=>stdout, :stderr=>stderr
206
-
207
- job.state = "running"
208
- job.runner = Bj.hostname
209
- job.pid = thread.pid
210
- job.started_at = started_at
211
- job.save!
212
- job.reload
213
- end
214
-
215
- exit_status = thread.value
216
- finished_at = Util.now
217
-
218
- Bj.transaction(options) do
219
- job = Bj::Table::Job.find job.id
220
- break unless job
221
- job.state = "finished"
222
- job.finished_at = finished_at
223
- job.stdout = stdout
224
- job.stderr = stderr
225
- job.exit_status = exit_status
226
- job.save!
227
- job.reload
228
- Bj.logger.info{ "#{ job.title } - exit_status=#{ job.exit_status }" }
229
- end
230
- end
231
- end
232
-
233
- Runner.hup_signaled false
234
- wait.times do
235
- break if Runner.hup_signaled?
236
- break if Runner.kill_signaled?
237
- sleep 1
238
- end
239
-
240
- break unless(limit or limit == false)
241
- break if Runner.kill_signaled?
242
- end
243
- end
244
-
245
- def ping_parent
246
- ppid = options[:ppid]
247
- return unless ppid
248
- begin
249
- Process.kill 0, Integer(ppid)
250
- rescue Errno::ESRCH
251
- Kernel.exit 42
252
- rescue Exception
253
- 42
254
- end
255
- end
256
-
257
- def install_signal_handlers
258
- Runner.hup_signaled false
259
- hup_handler = nil
260
- hup_handler =
261
- trap Runner.hup_signal do |*a|
262
- begin
263
- Runner.hup_signaled true
264
- rescue Exception => e
265
- Bj.logger.error{ e } rescue nil
266
- end
267
- hup_handler.call *a rescue nil
268
- end
269
-
270
- Runner.kill_signaled false
271
- kill_handler = nil
272
- kill_handler =
273
- trap Runner.kill_signal do |*a|
274
- begin
275
- Runner.kill_signaled true
276
- rescue Exception => e
277
- Bj.logger.error{ e } rescue nil
278
- end
279
- kill_handler.call *a rescue nil
280
- end
281
-
282
- begin
283
- trap("INT"){ exit }
284
- rescue Exception
285
- end
286
- end
287
-
288
- def fill_morgue
289
- Bj.transaction do
290
- now = Util.now
291
- jobs = Bj::Table::Job.find :all,
292
- :conditions => ["state = 'running' and runner = ?", Bj.hostname]
293
- jobs.each do |job|
294
- if job.is_restartable?
295
- Bj.logger.info{ "#{ job.title } - found dead and bloated but resubmitted" }
296
- %w[ runner pid started_at finished_at stdout stderr exit_status ].each do |column|
297
- job[column] = nil
298
- end
299
- job.state = 'pending'
300
- else
301
- Bj.logger.info{ "#{ job.title } - found dead and bloated" }
302
- job.state = 'dead'
303
- job.finished_at = now
304
- end
305
- job.save!
306
- end
307
- end
308
- end
309
-
310
- def archive_jobs
311
- Bj.transaction do
312
- now = Util.now
313
- too_old = now - Bj.ttl
314
- jobs = Bj::Table::Job.find :all,
315
- :conditions => ["(state = 'finished' or state = 'dead') and submitted_at < ?", too_old]
316
- jobs.each do |job|
317
- Bj.logger.info{ "#{ job.title } - archived" }
318
- hash = job.to_hash.update(:archived_at => now)
319
- Bj::Table::JobArchive.create! hash
320
- job.destroy
321
- end
322
- end
323
- end
324
-
325
- def register
326
- Bj.transaction do
327
- pid = Bj.config[key]
328
- return false if Util.alive?(pid)
329
- Bj.config[key] = Process.pid
330
- unless Bj.util.ipc_signals_supported? # not winblows
331
- require "drb"
332
- DRb.start_service "druby://localhost:0", Process
333
- Bj.config["#{ Process.pid }.uri"] = DRb.uri
334
- end
335
- end
336
- at_exit{ unregister }
337
- true
338
- rescue Exception
339
- false
340
- end
341
-
342
- def unregister
343
- Bj.transaction do
344
- Bj.config.delete key
345
- end
346
- true
347
- rescue Exception
348
- false
349
- end
350
-
351
- def key
352
- @key ||= ( options[:ppid] ? Runner.key(options[:ppid]) : Runner.key )
353
- end
354
- end
355
- send :include, Instance_Methods
356
- end
357
- end
1
+ class Bj
2
+ class Runner
3
+ class Background
4
+ def self.for(*a, &b) new(*a, &b) end
5
+
6
+ attribute "command"
7
+ attribute "thread"
8
+ attribute "pid"
9
+
10
+ def initialize command
11
+ @command = command
12
+ @thread = new_thread
13
+ end
14
+
15
+ def inspect
16
+ {
17
+ "command" => command,
18
+ "pid" => pid,
19
+ }.inspect
20
+ end
21
+
22
+
23
+ # TODO - auto start runner?
24
+
25
+ def new_thread
26
+ this = self
27
+ Thread.new do
28
+ Thread.current.abort_on_exception = true
29
+ loop do
30
+ cleanup = lambda{}
31
+
32
+ IO.popen command, "r+" do |pipe|
33
+ this.pid = pid = pipe.pid
34
+ cleanup = lambda do
35
+ cleanup = lambda{}
36
+ begin; Process.kill(Runner.kill_signal, pid); rescue Exception; 42; end
37
+ end
38
+ at_exit &cleanup
39
+ Process.wait
40
+ end
41
+
42
+ Bj.logger.error{ "#{ command } failed with #{ $?.inspect }" } unless
43
+ [0, 42].include?($?.exitstatus)
44
+
45
+ cleanup.call
46
+
47
+ sleep 42
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ module ClassMethods
54
+ attribute("thread"){ Thread.current }
55
+ attribute("hup_signal"){ Signal.list.keys.index("HUP") ? "HUP" : "ABRT" }
56
+ attribute("hup_signaled"){ false }
57
+ attribute("kill_signal"){ "TERM" }
58
+ attribute("kill_signaled"){ false }
59
+
60
+ def tickle
61
+ return nil if Bj.config[Runner.no_tickle_key]
62
+ ping or start
63
+ end
64
+
65
+ def ping
66
+ begin
67
+ pid = nil
68
+ uri = nil
69
+ process = nil
70
+ Bj.transaction do
71
+ pid = Bj.config[Runner.key(Process.pid)] || Bj.config[Runner.key]
72
+ uri = Bj.config["#{ pid }.uri"]
73
+ process =
74
+ if uri
75
+ require "drb"
76
+ # DRb.start_service "druby://localhost:0"
77
+ DRbObject.new(nil, uri)
78
+ else
79
+ Process
80
+ end
81
+ end
82
+ return nil unless pid
83
+ pid = Integer pid
84
+ begin
85
+ process.kill Runner.hup_signal, pid
86
+ pid
87
+ rescue Exception => e
88
+ false
89
+ end
90
+ rescue Exception => e
91
+ false
92
+ end
93
+ end
94
+
95
+ def key ppid = 0
96
+ ppid ||= 0
97
+ "#{ Bj.rails_env }.#{ ppid }.pid"
98
+ end
99
+
100
+ def no_tickle_key
101
+ "#{ Bj.rails_env }.no_tickle"
102
+ end
103
+
104
+ def start options = {}
105
+ options.to_options!
106
+ background.delete Bj.rails_env if options[:force]
107
+ background[Bj.rails_env] ||= Background.for(command)
108
+ end
109
+
110
+ def background
111
+ @background ||= Hash.new
112
+ end
113
+
114
+ def background= value
115
+ @background ||= value
116
+ end
117
+
118
+ def command
119
+ "#{ Bj.ruby } " + %W[
120
+ #{ Bj.script }
121
+ run
122
+ --forever
123
+ --redirect=#{ log }
124
+ --ppid=#{ Process.pid }
125
+ --rails_env=#{ Bj.rails_env }
126
+ --rails_root=#{ Bj.rails_root }
127
+ ].map{|word| word.inspect}.join(" ")
128
+ end
129
+
130
+ def log
131
+ File.join logdir, "bj.#{ Bj.hostname }.#{ Bj.rails_env }.log"
132
+ end
133
+
134
+ def logdir
135
+ File.join File.expand_path(Bj.rails_root), 'log'
136
+ end
137
+
138
+ def run options = {}, &block
139
+ new(options, &block).run
140
+ end
141
+ end
142
+ send :extend, ClassMethods
143
+
144
+ module Instance_Methods
145
+ attribute "options"
146
+ attribute "block"
147
+
148
+ def initialize options = {}, &block
149
+ options.to_options!
150
+ @options, @block = options, block
151
+ end
152
+
153
+ def run
154
+ wait = options[:wait] || 42
155
+ limit = options[:limit]
156
+ forever = options[:forever]
157
+
158
+ limit = false if forever
159
+ wait = Integer wait
160
+ loopno = 0
161
+
162
+ Runner.thread = Thread.current
163
+ Bj.chroot
164
+
165
+ register or exit!(EXIT::WARNING)
166
+
167
+ Bj.logger.info{ "STARTED" }
168
+ at_exit{ Bj.logger.info{ "STOPPED" } }
169
+
170
+ fill_morgue
171
+ install_signal_handlers
172
+
173
+ loop do
174
+ ping_parent
175
+
176
+ loopno += 1
177
+ break if(limit and loopno > limit)
178
+
179
+ archive_jobs
180
+
181
+ catch :no_jobs do
182
+ loop do
183
+ job = thread = stdout = stderr = nil
184
+
185
+ Bj.transaction(options) do
186
+ now = Util.now
187
+
188
+ job = Bj::Table::Job.find_first_and_lock(
189
+ :conditions => ["state = ? and submitted_at <= ?", "pending", now],
190
+ :order => "priority DESC, submitted_at ASC",
191
+ :limit => 1,
192
+ :lock => true)
193
+ throw :no_jobs unless job
194
+
195
+
196
+ Bj.logger.info{ "#{ job.title } - started" }
197
+
198
+ command = job.command
199
+ env = job.env || {}
200
+ stdin = job.stdin || ''
201
+ stdout = job.stdout || ''
202
+ stderr = job.stderr || ''
203
+ started_at = Util.now
204
+
205
+ thread = Util.start command, :cwd=>Bj.rails_root, :env=>env, :stdin=>stdin, :stdout=>stdout, :stderr=>stderr
206
+
207
+ job.state = "running"
208
+ job.runner = Bj.hostname
209
+ job.pid = thread.pid
210
+ job.started_at = started_at
211
+ job.save!
212
+ job.reload
213
+ end
214
+
215
+ exit_status = thread.value
216
+ finished_at = Util.now
217
+
218
+ Bj.transaction(options) do
219
+ job = Bj::Table::Job.find job.id
220
+ break unless job
221
+ job.state = "finished"
222
+ job.finished_at = finished_at
223
+ job.stdout = stdout
224
+ job.stderr = stderr
225
+ job.exit_status = exit_status
226
+ job.save!
227
+ job.reload
228
+ Bj.logger.info{ "#{ job.title } - exit_status=#{ job.exit_status }" }
229
+ end
230
+ end
231
+ end
232
+
233
+ Runner.hup_signaled false
234
+ wait.times do
235
+ break if Runner.hup_signaled?
236
+ break if Runner.kill_signaled?
237
+ sleep 1
238
+ end
239
+
240
+ break unless(limit or limit == false)
241
+ break if Runner.kill_signaled?
242
+ end
243
+ end
244
+
245
+ def ping_parent
246
+ ppid = options[:ppid]
247
+ return unless ppid
248
+ begin
249
+ Process.kill 0, Integer(ppid)
250
+ rescue Errno::ESRCH
251
+ Kernel.exit 42
252
+ rescue Exception
253
+ 42
254
+ end
255
+ end
256
+
257
+ def install_signal_handlers
258
+ Runner.hup_signaled false
259
+ hup_handler = nil
260
+ hup_handler =
261
+ trap Runner.hup_signal do |*a|
262
+ begin
263
+ Runner.hup_signaled true
264
+ rescue Exception => e
265
+ Bj.logger.error{ e } rescue nil
266
+ end
267
+ hup_handler.call *a rescue nil
268
+ end
269
+
270
+ Runner.kill_signaled false
271
+ kill_handler = nil
272
+ kill_handler =
273
+ trap Runner.kill_signal do |*a|
274
+ begin
275
+ Runner.kill_signaled true
276
+ rescue Exception => e
277
+ Bj.logger.error{ e } rescue nil
278
+ end
279
+ kill_handler.call *a rescue nil
280
+ end
281
+
282
+ begin
283
+ trap("INT"){ exit }
284
+ rescue Exception
285
+ end
286
+ end
287
+
288
+ def fill_morgue
289
+ Bj.transaction do
290
+ now = Util.now
291
+ jobs = Bj::Table::Job.find :all,
292
+ :conditions => ["state = 'running' and runner = ?", Bj.hostname]
293
+ jobs.each do |job|
294
+ if job.is_restartable?
295
+ Bj.logger.info{ "#{ job.title } - found dead and bloated but resubmitted" }
296
+ %w[ runner pid started_at finished_at stdout stderr exit_status ].each do |column|
297
+ job[column] = nil
298
+ end
299
+ job.state = 'pending'
300
+ else
301
+ Bj.logger.info{ "#{ job.title } - found dead and bloated" }
302
+ job.state = 'dead'
303
+ job.finished_at = now
304
+ end
305
+ job.save!
306
+ end
307
+ end
308
+ end
309
+
310
+ def archive_jobs
311
+ Bj.transaction do
312
+ now = Util.now
313
+ too_old = now - Bj.ttl
314
+ jobs = Bj::Table::Job.find :all,
315
+ :conditions => ["(state = 'finished' or state = 'dead') and submitted_at < ?", too_old]
316
+ jobs.each do |job|
317
+ Bj.logger.info{ "#{ job.title } - archived" }
318
+ hash = job.to_hash.update(:archived_at => now)
319
+ Bj::Table::JobArchive.create! hash
320
+ job.destroy
321
+ end
322
+ end
323
+ end
324
+
325
+ def register
326
+ Bj.transaction do
327
+ pid = Bj.config[key]
328
+ return false if Util.alive?(pid)
329
+ Bj.config[key] = Process.pid
330
+ unless Bj.util.ipc_signals_supported? # not winblows
331
+ require "drb"
332
+ DRb.start_service "druby://localhost:0", Process
333
+ Bj.config["#{ Process.pid }.uri"] = DRb.uri
334
+ end
335
+ end
336
+ at_exit{ unregister }
337
+ true
338
+ rescue Exception
339
+ false
340
+ end
341
+
342
+ def unregister
343
+ Bj.transaction do
344
+ Bj.config.delete key
345
+ end
346
+ true
347
+ rescue Exception
348
+ false
349
+ end
350
+
351
+ def key
352
+ @key ||= ( options[:ppid] ? Runner.key(options[:ppid]) : Runner.key )
353
+ end
354
+ end
355
+ send :include, Instance_Methods
356
+ end
357
+ end