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/HISTORY +64 -0
- data/README +308 -0
- data/TODO +40 -0
- data/bin/bj +680 -0
- data/bj-1.0.2.gem +0 -0
- data/gemspec.rb +30 -0
- data/install.rb +210 -0
- data/lib/bj/api.rb +161 -0
- data/lib/bj/attributes.rb +120 -0
- data/lib/bj/bj.rb +72 -0
- data/lib/bj/errors.rb +4 -0
- data/lib/bj/joblist.rb +112 -0
- data/lib/bj/logger.rb +50 -0
- data/lib/bj/runner.rb +357 -0
- data/lib/bj/stdext.rb +86 -0
- data/lib/bj/table.rb +384 -0
- data/lib/bj/util.rb +111 -0
- data/lib/bj.rb +88 -0
- metadata +131 -0
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
|