bj_fixed_for_rails3 1.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/bj/logger.rb ADDED
@@ -0,0 +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
data/lib/bj/runner.rb ADDED
@@ -0,0 +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
+ ActiveRecord::Base.default_timezone = :utc
152
+ end
153
+
154
+ def run
155
+ wait = options[:wait] || 42
156
+ limit = options[:limit]
157
+ forever = options[:forever]
158
+
159
+ limit = false if forever
160
+ wait = Integer wait
161
+ loopno = 0
162
+
163
+ Runner.thread = Thread.current
164
+ Bj.chroot
165
+
166
+ register or exit!(EXIT::WARNING)
167
+
168
+ Bj.logger.info{ "STARTED" }
169
+ at_exit{ Bj.logger.info{ "STOPPED" } }
170
+
171
+ fill_morgue
172
+ install_signal_handlers
173
+
174
+ loop do
175
+ ping_parent
176
+
177
+ loopno += 1
178
+ break if(limit and loopno > limit)
179
+
180
+ archive_jobs
181
+
182
+ catch :no_jobs do
183
+ loop do
184
+ job = thread = stdout = stderr = nil
185
+
186
+ Bj.transaction(options) do
187
+ now = Time.now
188
+ job = Bj::Table::Job.find :first,
189
+ :conditions => ["state = 'pending'"],
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 = Time.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 = Time.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 = Time.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 = Time.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
data/lib/bj/stdext.rb ADDED
@@ -0,0 +1,86 @@
1
+ class Hash
2
+ begin
3
+ method "to_options"
4
+ rescue
5
+ def to_options
6
+ inject(Hash.new){|h, kv| h.update kv.first.to_s.to_sym => kv.last}
7
+ end
8
+ def to_options!
9
+ replace to_options
10
+ end
11
+ end
12
+
13
+ begin
14
+ method "to_string_options"
15
+ rescue
16
+ def to_string_options
17
+ inject(Hash.new){|h, kv| h.update kv.first.to_s => kv.last}
18
+ end
19
+ def to_string_options!
20
+ replace to_string_options
21
+ end
22
+ end
23
+
24
+ begin
25
+ method "reverse_merge"
26
+ rescue
27
+ def reverse_merge other
28
+ other.merge self
29
+ end
30
+ def reverse_merge! other
31
+ replace reverse_merge(other)
32
+ end
33
+ end
34
+
35
+ begin
36
+ method "slice"
37
+ rescue
38
+ def slice(*keys)
39
+ allowed = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
40
+ reject { |key,| !allowed.include?(key) }
41
+ end
42
+ end
43
+
44
+ begin
45
+ method "slice!"
46
+ rescue
47
+ def slice!(*keys)
48
+ replace(slice(*keys))
49
+ end
50
+ end
51
+ end
52
+
53
+ class Object
54
+ begin
55
+ method "returning"
56
+ rescue
57
+ def returning value, &block
58
+ block.call value
59
+ value
60
+ end
61
+ end
62
+ end
63
+
64
+ class Object
65
+ def singleton_class &block
66
+ @singleton_class ||=
67
+ class << self
68
+ self
69
+ end
70
+ block ? @singleton_class.module_eval(&block) : @singleton_class
71
+ end
72
+ end
73
+
74
+ class String
75
+ begin
76
+ method 'underscore'
77
+ rescue
78
+ def underscore
79
+ gsub(/::/, '/').
80
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
81
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
82
+ tr("-", "_").
83
+ downcase
84
+ end
85
+ end
86
+ end