bj_fixed_for_rails3 1.0.2

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