bj 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY +39 -0
- data/TODO +35 -3
- data/bin/bj +54 -30
- data/lib/bj.rb +8 -2
- data/lib/bj/api.rb +15 -2
- data/lib/bj/bj.rb +31 -52
- data/lib/bj/runner.rb +149 -130
- data/lib/bj/stdext.rb +17 -11
- data/lib/bj/table.rb +49 -60
- data/lib/bj/util.rb +20 -14
- metadata +2 -2
data/HISTORY
CHANGED
@@ -1,2 +1,41 @@
|
|
1
|
+
0.0.3:
|
2
|
+
- *many* small bug fixes
|
3
|
+
|
4
|
+
- plugin install should now pick up dependancies from plugin dir, last
|
5
|
+
release had LOAD_PATH/gem issues and was picking up globally installed
|
6
|
+
gems
|
7
|
+
|
8
|
+
- automatic management of the background processing can be turned off if you
|
9
|
+
want to manage your own processes
|
10
|
+
|
11
|
+
- all jobs are automatically restartable unless submitted with
|
12
|
+
|
13
|
+
:restartable => false
|
14
|
+
|
15
|
+
this means that, should a runner ever die, upon restart any jobs that were
|
16
|
+
mid-process will automatically be restarted
|
17
|
+
|
18
|
+
- signal based parent lifeline move out of thread and into even loop
|
19
|
+
|
20
|
+
- :lock => true added to a few AR finds to support true write serializable
|
21
|
+
transaction isolation when the db supports it
|
22
|
+
|
23
|
+
- all migrations now use :force => true and
|
24
|
+
|
25
|
+
- running 'bj setup' will always generate a new migration, even if you've
|
26
|
+
already run it before. this allows easy version upgrades.
|
27
|
+
|
28
|
+
- a few command would blow up on windows because they weren't prefixed with
|
29
|
+
'ruby'. gotta love the lack of #shebang line on windoze...
|
30
|
+
|
31
|
+
- ./script/bj is searched first before system path env var
|
32
|
+
|
33
|
+
- a default PATH is provided for whacky systems without one
|
34
|
+
|
35
|
+
- database.yml is filtered through ERB ala rails
|
36
|
+
|
37
|
+
0.0.2:
|
38
|
+
- path bug fixes
|
39
|
+
|
1
40
|
0.0.1:
|
2
41
|
- initial release
|
data/TODO
CHANGED
@@ -1,3 +1,35 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
|
2
|
+
- need a new signal, or no signal on windows
|
3
|
+
|
4
|
+
- ./script/console submission hangs on windows
|
5
|
+
|
6
|
+
- ttl will be added. maxing it out will cause auto-resubmission (Steve Midgley)
|
7
|
+
|
8
|
+
- is having the runner thread try forever to start the process the best thing?
|
9
|
+
|
10
|
+
- allow easy way to run ruby code. perhaps ./script/runner 'eval STDIN.read'
|
11
|
+
is good enough
|
12
|
+
|
13
|
+
- allow easy way to run ruby code that persists
|
14
|
+
|
15
|
+
- allow specification of runner on submit (--runner)
|
16
|
+
|
17
|
+
- allow specification of tags a runner will consume (--tag)
|
18
|
+
|
19
|
+
- flesh out the cli interface - it's a test only at this point
|
20
|
+
|
21
|
+
- test in windows
|
22
|
+
|
23
|
+
================================================================================
|
24
|
+
|
25
|
+
X default PATH setting
|
26
|
+
X install issues for dave? - gem_path...
|
27
|
+
X main only loaded for (bin|script)/bj
|
28
|
+
X make it possible to declare externally managed runners
|
29
|
+
X restartable will be added. true by default (Steve Midgley)
|
30
|
+
X do the lifeline inline with the loop
|
31
|
+
X need to address the serialzable writer issue (:lock => true ??)
|
32
|
+
X migrations use --force
|
33
|
+
X i forget to add "#{ Bj.ruby } ... " to the generate command
|
34
|
+
X ./script/bj must be found in path before c:/.....bin/bj
|
35
|
+
X make sure database.yml is loaded via YAML::load(ERB.new(File.read * "config/database.yml").result)
|
data/bin/bj
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
#! /usr/bin/env ruby
|
2
2
|
|
3
|
+
require "main"
|
3
4
|
require "bj"
|
4
5
|
|
5
6
|
Main {
|
@@ -178,30 +179,55 @@ Main {
|
|
178
179
|
description "generate a migration and migrate"
|
179
180
|
|
180
181
|
def run
|
182
|
+
set_rails_env(argv.first) if argv.first
|
181
183
|
Bj.setup
|
182
184
|
end
|
183
185
|
end
|
184
186
|
|
187
|
+
mode "plugin" do
|
188
|
+
description "dump the plugin into rails_root"
|
189
|
+
|
190
|
+
def run
|
191
|
+
Bj.plugin
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
185
195
|
mode "run" do
|
186
196
|
description "start a job runnner, possibly as a daemon"
|
187
197
|
|
188
198
|
option("--forever"){}
|
189
|
-
option("--daemon"){}
|
190
199
|
option("--ppid"){
|
191
200
|
argument :required
|
192
201
|
cast :integer
|
193
202
|
}
|
203
|
+
option("--wait"){
|
204
|
+
argument :required
|
205
|
+
cast :integer
|
206
|
+
}
|
207
|
+
option("--limit"){
|
208
|
+
argument :required
|
209
|
+
cast :integer
|
210
|
+
}
|
194
211
|
option("--redirect"){
|
195
212
|
argument :required
|
196
213
|
}
|
214
|
+
option("--daemon"){}
|
197
215
|
|
198
216
|
def run
|
199
217
|
options = {}
|
200
218
|
|
201
|
-
|
219
|
+
=begin
|
220
|
+
%w[ forever ].each do |key|
|
202
221
|
options[key.to_sym] = true if param[key].given?
|
203
222
|
end
|
223
|
+
=end
|
204
224
|
|
225
|
+
%w[ forever ppid wait limit ].each do |key|
|
226
|
+
options[key.to_sym] = param[key].value if param[key].given?
|
227
|
+
end
|
228
|
+
|
229
|
+
#p options
|
230
|
+
#exit
|
205
231
|
if param["redirect"].given?
|
206
232
|
open(param["redirect"].value, "a+") do |fd|
|
207
233
|
STDERR.reopen fd
|
@@ -318,23 +344,23 @@ Main {
|
|
318
344
|
def before_run
|
319
345
|
self.logger = param["log"].value
|
320
346
|
Bj.logger = logger
|
347
|
+
set_rails_root(param["rails_root"].value) if param["rails_root"].given?
|
348
|
+
set_rails_env(param["rails_env"].value) if param["rails_env"].given?
|
349
|
+
end
|
321
350
|
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
const_set :RAILS_ROOT, rails_root
|
328
|
-
end
|
351
|
+
def set_rails_root rails_root
|
352
|
+
ENV["RAILS_ROOT"] = rails_root
|
353
|
+
::Object.instance_eval do
|
354
|
+
remove_const :RAILS_ROOT
|
355
|
+
const_set :RAILS_ROOT, rails_root
|
329
356
|
end
|
357
|
+
end
|
330
358
|
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
const_set :RAILS_ENV, rails_env
|
337
|
-
end
|
359
|
+
def set_rails_env rails_env
|
360
|
+
ENV["RAILS_ENV"] = rails_env
|
361
|
+
::Object.instance_eval do
|
362
|
+
remove_const :RAILS_ENV
|
363
|
+
const_set :RAILS_ENV, rails_env
|
338
364
|
end
|
339
365
|
end
|
340
366
|
|
@@ -392,19 +418,18 @@ BEGIN {
|
|
392
418
|
#
|
393
419
|
# see if we're running out of RAILS_ROOT/script/
|
394
420
|
#
|
395
|
-
unless defined?
|
421
|
+
unless defined?(BJ_IS_SCRIPT)
|
396
422
|
BJ_IS_SCRIPT =
|
397
423
|
if %w[ script config app ].map{|d| test ?d, "#{ File.dirname __FILE__ }/../#{ d }"}.all?
|
398
424
|
__FILE__
|
399
425
|
else
|
400
426
|
nil
|
401
427
|
end
|
402
|
-
end
|
403
|
-
|
428
|
+
end
|
404
429
|
#
|
405
430
|
# setup RAILS_ROOT
|
406
431
|
#
|
407
|
-
unless defined?
|
432
|
+
unless defined?(RAILS_ROOT)
|
408
433
|
### grab env var first
|
409
434
|
rails_root = ENV["RAILS_ROOT"]
|
410
435
|
|
@@ -429,12 +454,11 @@ unless defined? RAILS_ROOT #{{{
|
|
429
454
|
|
430
455
|
### bootstrap
|
431
456
|
RAILS_ROOT = rails_root
|
432
|
-
end
|
433
|
-
|
457
|
+
end
|
434
458
|
#
|
435
459
|
# setup RAILS_ENV
|
436
460
|
#
|
437
|
-
unless defined?
|
461
|
+
unless defined?(RAILS_ENV)
|
438
462
|
### grab env var first
|
439
463
|
rails_env = ENV["RAILS_ENV"]
|
440
464
|
|
@@ -450,18 +474,19 @@ unless defined? RAILS_ENV #{{{
|
|
450
474
|
|
451
475
|
### bootstrap
|
452
476
|
RAILS_ENV = rails_env
|
453
|
-
end
|
454
|
-
|
477
|
+
end
|
455
478
|
#
|
456
479
|
# setup $LOAD_PATH to detect plugin - iff it is installed and running out of
|
457
480
|
# RAILS_ROOT/script
|
458
481
|
#
|
459
|
-
if RAILS_ROOT
|
460
|
-
if
|
482
|
+
if RAILS_ROOT and BJ_IS_SCRIPT
|
483
|
+
if test(?d, "#{ RAILS_ROOT }/vendor/plugins/bj/lib")
|
461
484
|
$LOAD_PATH.unshift "#{ RAILS_ROOT }/vendor/plugins/bj/lib"
|
462
485
|
end
|
463
|
-
|
464
|
-
|
486
|
+
if test(?s, "#{ RAILS_ROOT }/vendor/plugins/bj/init.rb")
|
487
|
+
load "#{ RAILS_ROOT }/vendor/plugins/bj/init.rb"
|
488
|
+
end
|
489
|
+
end
|
465
490
|
#
|
466
491
|
# ensure that rubygems is loaded
|
467
492
|
#
|
@@ -470,7 +495,6 @@ end #}}}
|
|
470
495
|
rescue
|
471
496
|
42
|
472
497
|
end
|
473
|
-
|
474
498
|
#
|
475
499
|
# hack to_s of STDERR/STDOUT for nice help messages
|
476
500
|
#
|
data/lib/bj.rb
CHANGED
@@ -29,6 +29,8 @@ unless defined? Bj
|
|
29
29
|
require "yaml"
|
30
30
|
require "thread"
|
31
31
|
require "rbconfig"
|
32
|
+
require "set"
|
33
|
+
require "erb"
|
32
34
|
#
|
33
35
|
# bootstrap rubygems
|
34
36
|
#
|
@@ -44,7 +46,7 @@ unless defined? Bj
|
|
44
46
|
#
|
45
47
|
# rubyforge/remote or local/lib
|
46
48
|
#
|
47
|
-
%w[ attributes
|
49
|
+
%w[ attributes systemu orderedhash ].each do |lib|
|
48
50
|
begin
|
49
51
|
require lib
|
50
52
|
rescue
|
@@ -67,11 +69,15 @@ unless defined? Bj
|
|
67
69
|
# an imperfect reloading hook - because neither rails' plugins nor gems provide one, sigh...
|
68
70
|
#
|
69
71
|
def self.reload!
|
72
|
+
background = nil
|
70
73
|
::Object.module_eval do
|
74
|
+
background = Bj.runner.background
|
71
75
|
remove_const :Bj rescue nil
|
72
76
|
remove_const :BackgroundJob rescue nil
|
73
77
|
end
|
74
|
-
load __FILE__ rescue nil
|
78
|
+
returned = load __FILE__ rescue nil
|
79
|
+
Bj.runner.background = background if background
|
80
|
+
returned
|
75
81
|
end
|
76
82
|
end
|
77
83
|
|
data/lib/bj/api.rb
CHANGED
@@ -88,7 +88,9 @@ class Bj
|
|
88
88
|
# list simply returns a list of all jobs in the job table
|
89
89
|
#
|
90
90
|
def list options = {}, &block
|
91
|
+
options.to_options!
|
91
92
|
Bj.transaction(options) do
|
93
|
+
options.delete :rails_env
|
92
94
|
table.job.find(:all, options)
|
93
95
|
end
|
94
96
|
end
|
@@ -118,7 +120,9 @@ class Bj
|
|
118
120
|
options.to_options!
|
119
121
|
chroot do
|
120
122
|
before = Dir.glob "./db/migrate/*"
|
121
|
-
|
123
|
+
n = Dir.glob("./db/migrate/*_bj_*").size
|
124
|
+
classname = "BjMigration#{ n }"
|
125
|
+
util.spawn "#{ Bj.ruby } ./script/generate migration #{ classname }", options rescue nil
|
122
126
|
after = Dir.glob "./db/migrate/*"
|
123
127
|
candidates = after - before
|
124
128
|
case candidates.size
|
@@ -126,7 +130,7 @@ class Bj
|
|
126
130
|
false
|
127
131
|
when 1
|
128
132
|
generated = candidates.first
|
129
|
-
open(generated, "w"){|fd| fd.puts Bj.table.migration_code}
|
133
|
+
open(generated, "w"){|fd| fd.puts Bj.table.migration_code(classname)}
|
130
134
|
Bj.logger.info{ "generated <#{ generated }>" }
|
131
135
|
generated
|
132
136
|
else
|
@@ -143,6 +147,15 @@ class Bj
|
|
143
147
|
util.spawn "rake RAILS_ENV=#{ Bj.rails_env } db:migrate", options
|
144
148
|
end
|
145
149
|
end
|
150
|
+
#
|
151
|
+
# install plugin into this rails app
|
152
|
+
#
|
153
|
+
def plugin options = {}
|
154
|
+
options.to_options!
|
155
|
+
chroot do
|
156
|
+
util.spawn "./script/plugin install http://codeforpeople.rubyforge.org/svn/rails/plugins/bj", options
|
157
|
+
end
|
158
|
+
end
|
146
159
|
end
|
147
160
|
send :extend, API
|
148
161
|
end
|
data/lib/bj/bj.rb
CHANGED
@@ -3,7 +3,7 @@ class Bj
|
|
3
3
|
attribute("rails_root"){ Util.const_or_env("RAILS_ROOT"){ "." } }
|
4
4
|
attribute("rails_env"){ Util.const_or_env("RAILS_ENV"){ "development" } }
|
5
5
|
attribute("database_yml"){ File.join rails_root, "config", "database.yml" }
|
6
|
-
attribute("configurations"){ YAML.
|
6
|
+
attribute("configurations"){ YAML::load(ERB.new(IO.read(database_yml)).result) }
|
7
7
|
attribute("tables"){ Table.list }
|
8
8
|
attribute("hostname"){ Socket.gethostname }
|
9
9
|
attribute("logger"){ Bj::Logger.off STDERR }
|
@@ -15,71 +15,50 @@ class Bj
|
|
15
15
|
attribute('util'){ Util }
|
16
16
|
attribute('runner'){ Runner }
|
17
17
|
attribute('joblist'){ Joblist }
|
18
|
+
attribute('default_path'){ %w'/bin /usr/bin /usr/local/bin /opt/local/bin'.join(File::PATH_SEPARATOR) }
|
18
19
|
|
19
20
|
def transaction options = {}, &block
|
20
21
|
options.to_options!
|
21
|
-
options.reverse_merge! :rails_env => Bj.rails_env
|
22
|
-
bj_rails_env = Bj.rails_env
|
23
|
-
begin
|
24
|
-
Bj.rails_env = options[:rails_env].to_s
|
25
|
-
connecting(options) do
|
26
|
-
ActiveRecord::Base.transaction do
|
27
|
-
block.call ActiveRecord::Base.connection
|
28
|
-
end
|
29
|
-
end
|
30
|
-
ensure
|
31
|
-
Bj.rails_env = bj_rails_env
|
32
|
-
end
|
33
|
-
end
|
34
22
|
|
35
|
-
|
36
|
-
options.
|
37
|
-
rails_env = (options[:rails_env] || Bj.rails_env).to_s
|
38
|
-
previous = ar_connection
|
39
|
-
ActiveRecord::Base.connection = connections[rails_env] unless ActiveRecord::Base.connection == connections[rails_env]
|
40
|
-
begin
|
41
|
-
block.call ActiveRecord::Base.connection
|
42
|
-
ensure
|
43
|
-
ActiveRecord::Base.connection = previous unless ActiveRecord::Base.connection == previous
|
44
|
-
end
|
45
|
-
end
|
23
|
+
cur_rails_env = Bj.rails_env.to_s
|
24
|
+
new_rails_env = options[:rails_env].to_s
|
46
25
|
|
47
|
-
|
48
|
-
|
49
|
-
rails_env = rails_env.to_s
|
50
|
-
ActiveRecord::Base.establish_connection configurations[rails_env]
|
51
|
-
hash[rails_env] = ActiveRecord::Base.connection
|
52
|
-
end.merge(ar_connections)
|
53
|
-
@connections
|
54
|
-
end
|
26
|
+
cur_spec = configurations[cur_rails_env]
|
27
|
+
table.establish_connection(cur_spec) unless table.connected?
|
55
28
|
|
56
|
-
|
57
|
-
|
58
|
-
{ RAILS_ENV => ar_connection }
|
29
|
+
if(new_rails_env.empty? or cur_rails_env == new_rails_env)
|
30
|
+
table.transaction{ block.call(table.connection) }
|
59
31
|
else
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
32
|
+
new_spec = configurations[new_rails_env]
|
33
|
+
table.establish_connection(new_spec)
|
34
|
+
Bj.rails_env = new_rails_env
|
35
|
+
begin
|
36
|
+
table.transaction{ block.call(table.connection) }
|
37
|
+
ensure
|
38
|
+
table.establish_connection(cur_spec)
|
39
|
+
Bj.rails_env = cur_rails_env
|
40
|
+
end
|
69
41
|
end
|
70
42
|
end
|
71
43
|
|
72
44
|
def chroot options = {}, &block
|
73
45
|
if defined? @chrooted and @chrooted
|
74
|
-
return block.call(@chrooted)
|
46
|
+
return(block ? block.call(@chrooted) : @chrooted)
|
75
47
|
end
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
48
|
+
if block
|
49
|
+
begin
|
50
|
+
chrooted = @chrooted
|
51
|
+
Dir.chdir(@chrooted = rails_root) do
|
52
|
+
raise RailsRoot, "<#{ Dir.pwd }> is not a rails root" unless Util.valid_rails_root?(Dir.pwd)
|
53
|
+
block.call(@chrooted)
|
54
|
+
end
|
55
|
+
ensure
|
56
|
+
@chrooted = chrooted
|
80
57
|
end
|
81
|
-
|
82
|
-
@chrooted =
|
58
|
+
else
|
59
|
+
Dir.chdir(@chrooted = rails_root)
|
60
|
+
raise RailsRoot, "<#{ Dir.pwd }> is not a rails root" unless Util.valid_rails_root?(Dir.pwd)
|
61
|
+
@chrooted
|
83
62
|
end
|
84
63
|
end
|
85
64
|
|
data/lib/bj/runner.rb
CHANGED
@@ -33,7 +33,7 @@ class Bj
|
|
33
33
|
begin; Process.kill('SIGTERM', pid); rescue Exception; 42; end
|
34
34
|
end
|
35
35
|
at_exit &cleanup
|
36
|
-
|
36
|
+
Process.wait
|
37
37
|
end
|
38
38
|
|
39
39
|
Bj.logger.error{ "#{ command } failed with #{ $?.inspect }" } unless
|
@@ -48,19 +48,35 @@ class Bj
|
|
48
48
|
end
|
49
49
|
|
50
50
|
module ClassMethods
|
51
|
+
attribute("thread"){ Thread.current }
|
52
|
+
attribute("signal"){ Signal.list.keys.index("HUP") ? "HUP" : "INT" }
|
53
|
+
attribute("signaled"){ true }
|
54
|
+
|
51
55
|
def tickle
|
56
|
+
return nil if Bj.config[Runner.no_tickle_key]
|
52
57
|
ping or start
|
53
58
|
end
|
54
59
|
|
55
60
|
def ping
|
56
61
|
pid = Bj.config[Runner.key]
|
57
|
-
|
62
|
+
return nil unless pid
|
63
|
+
pid = Integer pid
|
64
|
+
begin
|
65
|
+
Process.kill Runner.signal, pid
|
66
|
+
pid
|
67
|
+
rescue Exception
|
68
|
+
false
|
69
|
+
end
|
58
70
|
end
|
59
71
|
|
60
72
|
def key
|
61
73
|
"#{ Bj.rails_env }.pid"
|
62
74
|
end
|
63
75
|
|
76
|
+
def no_tickle_key
|
77
|
+
"#{ Bj.rails_env }.no_tickle"
|
78
|
+
end
|
79
|
+
|
64
80
|
def start options = {}
|
65
81
|
options.to_options!
|
66
82
|
background.delete Bj.rails_env if options[:force]
|
@@ -71,8 +87,19 @@ class Bj
|
|
71
87
|
@background ||= Hash.new
|
72
88
|
end
|
73
89
|
|
90
|
+
def background= value
|
91
|
+
@background ||= value
|
92
|
+
end
|
93
|
+
|
74
94
|
def command
|
75
|
-
|
95
|
+
%W[
|
96
|
+
#{ Bj.ruby } #{ Bj.script } run
|
97
|
+
--forever
|
98
|
+
--redirect=#{ log }
|
99
|
+
--ppid=#{ Process.pid }
|
100
|
+
--rails_env=#{ Bj.rails_env }
|
101
|
+
--rails_root=#{ Bj.rails_root }
|
102
|
+
].join(" ")
|
76
103
|
end
|
77
104
|
|
78
105
|
def log
|
@@ -102,113 +129,148 @@ class Bj
|
|
102
129
|
wait = options[:wait] || 42
|
103
130
|
limit = options[:limit]
|
104
131
|
forever = options[:forever]
|
105
|
-
ppid = options[:ppid]
|
106
132
|
|
107
133
|
limit = false if forever
|
108
134
|
wait = Integer wait
|
109
135
|
loopno = 0
|
110
|
-
thread = new_lifeline(ppid) if ppid
|
111
136
|
|
112
|
-
|
113
|
-
|
114
|
-
Bj.logger.info{ "START" }
|
115
|
-
fill_morgue
|
116
|
-
install_signal_handlers
|
137
|
+
Runner.thread = Thread.current
|
138
|
+
Bj.chroot
|
117
139
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
140
|
+
register or exit!(EXIT::WARNING)
|
141
|
+
|
142
|
+
Bj.logger.info{ "STARTED" }
|
143
|
+
at_exit{ Bj.logger.info{ "STOPPED" } }
|
144
|
+
|
145
|
+
fill_morgue
|
146
|
+
install_signal_handlers
|
147
|
+
|
148
|
+
loop do
|
149
|
+
ping_parent
|
150
|
+
|
151
|
+
loopno += 1
|
152
|
+
break if(limit and loopno > limit)
|
153
|
+
|
154
|
+
archive_jobs
|
155
|
+
|
156
|
+
catch :no_jobs do
|
157
|
+
loop do
|
158
|
+
job = thread = stdout = stderr = nil
|
159
|
+
|
160
|
+
Bj.transaction(options) do
|
161
|
+
now = Time.now
|
162
|
+
|
163
|
+
job = Bj::Table::Job.find :first,
|
164
|
+
:conditions => ["state = ? and submitted_at <= ?", "pending", now],
|
165
|
+
:order => "priority DESC, submitted_at ASC",
|
166
|
+
:limit => 1,
|
167
|
+
:lock => true
|
168
|
+
throw :no_jobs unless job
|
169
|
+
|
170
|
+
|
171
|
+
Bj.logger.info{ "#{ job.title } - started" }
|
172
|
+
|
173
|
+
command = job.command
|
174
|
+
env = job.env || {}
|
175
|
+
stdin = job.stdin || ''
|
176
|
+
stdout = job.stdout || ''
|
177
|
+
stderr = job.stderr || ''
|
178
|
+
started_at = Time.now
|
179
|
+
|
180
|
+
thread = Util.start command, :cwd=>Bj.rails_root, :env=>env, :stdin=>stdin, :stdout=>stdout, :stderr=>stderr
|
181
|
+
|
182
|
+
job.state = "running"
|
183
|
+
job.runner = Bj.hostname
|
184
|
+
job.pid = thread.pid
|
185
|
+
job.started_at = started_at
|
186
|
+
job.save!
|
187
|
+
job.reload
|
188
|
+
end
|
189
|
+
|
190
|
+
exit_status = thread.value
|
191
|
+
finished_at = Time.now
|
192
|
+
|
193
|
+
Bj.transaction(options) do
|
194
|
+
job = Bj::Table::Job.find job.id
|
195
|
+
break unless job
|
196
|
+
job.state = "finished"
|
197
|
+
job.finished_at = finished_at
|
198
|
+
job.stdout = stdout
|
199
|
+
job.stderr = stderr
|
200
|
+
job.exit_status = exit_status
|
201
|
+
job.save!
|
202
|
+
job.reload
|
203
|
+
Bj.logger.info{ "#{ job.title } - exit_status=#{ job.exit_status }" }
|
178
204
|
end
|
179
205
|
end
|
206
|
+
end
|
180
207
|
|
181
|
-
|
208
|
+
Runner.signaled false
|
209
|
+
wait.times do
|
210
|
+
sleep 1
|
211
|
+
break if Runner.signaled?
|
212
|
+
end
|
213
|
+
|
214
|
+
break unless(limit or limit == false)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def ping_parent
|
219
|
+
ppid = options[:ppid]
|
220
|
+
return unless ppid
|
221
|
+
begin
|
222
|
+
Process.kill 0, Integer(ppid)
|
223
|
+
rescue Errno::ESRCH
|
224
|
+
Kernel.exit 42
|
225
|
+
rescue Exception
|
226
|
+
42
|
227
|
+
end
|
228
|
+
end
|
182
229
|
|
183
|
-
|
230
|
+
def install_signal_handlers
|
231
|
+
trap(Runner.signal) do
|
232
|
+
begin
|
233
|
+
Runner.signaled!
|
234
|
+
rescue Exception => e
|
235
|
+
Bj.logger.error{ e }
|
184
236
|
end
|
185
237
|
end
|
238
|
+
|
239
|
+
trap("INT"){ exit } unless Runner.signal == "INT"
|
240
|
+
rescue Exception
|
241
|
+
42
|
186
242
|
end
|
187
243
|
|
188
244
|
def fill_morgue options = {}
|
189
|
-
Bj.logger.debug{ "filling the morgue..." }
|
190
245
|
Bj.transaction(options) do
|
191
246
|
now = Time.now
|
192
247
|
jobs = Bj::Table::Job.find :all,
|
193
248
|
:conditions => ["state = 'running' and runner = ?", Bj.hostname]
|
194
249
|
jobs.each do |job|
|
195
|
-
|
196
|
-
|
197
|
-
|
250
|
+
if job.is_restartable?
|
251
|
+
Bj.logger.info{ "#{ job.title } - found dead and bloated but resubmitted" }
|
252
|
+
%w[ runner pid started_at finished_at stdout stderr exit_status ].each do |column|
|
253
|
+
job[column] = nil
|
254
|
+
end
|
255
|
+
job.state = 'pending'
|
256
|
+
else
|
257
|
+
Bj.logger.info{ "#{ job.title } - found dead and bloated" }
|
258
|
+
job.state = 'dead'
|
259
|
+
job.finished_at = now
|
260
|
+
end
|
198
261
|
job.save!
|
199
262
|
end
|
200
263
|
end
|
201
264
|
end
|
202
265
|
|
203
|
-
def
|
204
|
-
Bj.logger.debug{ "sweeping..." }
|
266
|
+
def archive_jobs options = {}
|
205
267
|
Bj.transaction(options) do
|
206
268
|
now = Time.now
|
207
269
|
too_old = now - Bj.ttl
|
208
270
|
jobs = Bj::Table::Job.find :all,
|
209
271
|
:conditions => ["(state = 'finished' or state = 'dead') and submitted_at < ?", too_old]
|
210
272
|
jobs.each do |job|
|
211
|
-
Bj.logger.info{ "
|
273
|
+
Bj.logger.info{ "#{ job.title } - archived" }
|
212
274
|
hash = job.to_hash.update(:archived_at => now)
|
213
275
|
Bj::Table::JobArchive.create! hash
|
214
276
|
job.destroy
|
@@ -216,68 +278,25 @@ class Bj
|
|
216
278
|
end
|
217
279
|
end
|
218
280
|
|
219
|
-
def new_lifeline ppid
|
220
|
-
Thread.new do
|
221
|
-
loop do
|
222
|
-
begin
|
223
|
-
Process.kill 0, ppid
|
224
|
-
rescue Errno::ESRCH
|
225
|
-
STDERR.puts "parent #{ ppid } died"
|
226
|
-
Kernel.exit 42
|
227
|
-
end
|
228
|
-
sleep 42
|
229
|
-
end
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
attribute 'sighup' => false
|
234
|
-
|
235
|
-
def install_signal_handlers
|
236
|
-
trap('SIGHUP') do
|
237
|
-
if sleeping?
|
238
|
-
Bj.logger.debug{ "woke up!" }
|
239
|
-
throw :wake_up
|
240
|
-
else
|
241
|
-
sighup true
|
242
|
-
end
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
def sleep seconds
|
247
|
-
if sighup
|
248
|
-
sighup false
|
249
|
-
return
|
250
|
-
end
|
251
|
-
Bj.logger.debug{ "sleeping #{ seconds }..." }
|
252
|
-
catch :wake_up do
|
253
|
-
begin
|
254
|
-
@sleeping = true
|
255
|
-
Kernel.sleep seconds
|
256
|
-
ensure
|
257
|
-
@sleeping = false
|
258
|
-
end
|
259
|
-
end
|
260
|
-
end
|
261
|
-
|
262
|
-
def sleeping?
|
263
|
-
@sleeping
|
264
|
-
end
|
265
|
-
|
266
281
|
def register options = {}
|
267
282
|
Bj.transaction(options) do
|
268
283
|
pid = Bj.config[Runner.key]
|
269
|
-
if Util.alive?(pid)
|
270
|
-
return false
|
271
|
-
end
|
284
|
+
return false if Util.alive?(pid)
|
272
285
|
Bj.config[Runner.key] = Process.pid
|
273
|
-
at_exit{ unregister }
|
274
286
|
end
|
287
|
+
at_exit{ unregister }
|
288
|
+
true
|
289
|
+
rescue Exception
|
290
|
+
false
|
275
291
|
end
|
276
292
|
|
277
293
|
def unregister options = {}
|
278
294
|
Bj.transaction(options) do
|
279
295
|
Bj.config.delete Runner.key
|
280
296
|
end
|
297
|
+
true
|
298
|
+
rescue Exception
|
299
|
+
false
|
281
300
|
end
|
282
301
|
end
|
283
302
|
send :include, Instance_Methods
|
data/lib/bj/stdext.rb
CHANGED
@@ -31,6 +31,23 @@ class Hash
|
|
31
31
|
replace reverse_merge(other)
|
32
32
|
end
|
33
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
|
34
51
|
end
|
35
52
|
|
36
53
|
class Object
|
@@ -54,17 +71,6 @@ class Object
|
|
54
71
|
end
|
55
72
|
end
|
56
73
|
|
57
|
-
class Module
|
58
|
-
Instance_Methods = instance_method "instance_methods" unless defined? Instance_Methods
|
59
|
-
def instance_methods &block
|
60
|
-
block ? module_eval(&block) : Instance_Methods.bind(self).call
|
61
|
-
end
|
62
|
-
def module_methods &block
|
63
|
-
block ? singleton_class(&block) : singleton_methods
|
64
|
-
end
|
65
|
-
alias_method "class_methods", "module_methods"
|
66
|
-
end
|
67
|
-
|
68
74
|
class String
|
69
75
|
begin
|
70
76
|
method 'underscore'
|
data/lib/bj/table.rb
CHANGED
@@ -7,26 +7,9 @@ class Bj
|
|
7
7
|
attribute("list"){ Array.new }
|
8
8
|
attribute("migration"){}
|
9
9
|
|
10
|
-
def
|
11
|
-
before = Dir.glob "./db/migrate/*"
|
12
|
-
Util.spawn "./script/generate migration BjMigration"
|
13
|
-
after = Dir.glob "./db/migrate/*"
|
14
|
-
candidates = after - before
|
15
|
-
case candidates.size
|
16
|
-
when 0
|
17
|
-
false
|
18
|
-
when 1
|
19
|
-
generated = candidates.first
|
20
|
-
open(generated, "w"){|fd| fd.puts migration_code}
|
21
|
-
generated
|
22
|
-
else
|
23
|
-
raise "ambiguous migration <#{ candidates.inspect }>"
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def migration_code
|
10
|
+
def migration_code classname = "BjMigration"
|
28
11
|
<<-code
|
29
|
-
class
|
12
|
+
class #{ classname } < ActiveRecord::Migration
|
30
13
|
def self.up
|
31
14
|
Bj::Table.each{|table| table.up}
|
32
15
|
end
|
@@ -113,25 +96,26 @@ class Bj
|
|
113
96
|
migration {
|
114
97
|
define_method :up do
|
115
98
|
create_table table.table_name, :primary_key => table.primary_key, :force => true do |t|
|
116
|
-
t.column "command"
|
117
|
-
|
118
|
-
t.column "state"
|
119
|
-
t.column "priority"
|
120
|
-
t.column "tag"
|
121
|
-
|
122
|
-
|
123
|
-
t.column "
|
124
|
-
t.column "
|
125
|
-
|
126
|
-
|
127
|
-
t.column "
|
128
|
-
t.column "
|
129
|
-
|
130
|
-
|
131
|
-
t.column "
|
132
|
-
t.column "
|
133
|
-
t.column "
|
134
|
-
t.column "
|
99
|
+
t.column "command" , :text
|
100
|
+
|
101
|
+
t.column "state" , :text
|
102
|
+
t.column "priority" , :integer
|
103
|
+
t.column "tag" , :text
|
104
|
+
t.column "is_restartable" , :integer
|
105
|
+
|
106
|
+
t.column "submitter" , :text
|
107
|
+
t.column "runner" , :text
|
108
|
+
t.column "pid" , :integer
|
109
|
+
|
110
|
+
t.column "submitted_at" , :datetime
|
111
|
+
t.column "started_at" , :datetime
|
112
|
+
t.column "finished_at" , :datetime
|
113
|
+
|
114
|
+
t.column "env" , :text
|
115
|
+
t.column "stdin" , :text
|
116
|
+
t.column "stdout" , :text
|
117
|
+
t.column "stderr" , :text
|
118
|
+
t.column "exit_status" , :integer
|
135
119
|
end
|
136
120
|
end
|
137
121
|
|
@@ -159,6 +143,7 @@ class Bj
|
|
159
143
|
:state => "pending",
|
160
144
|
:priority => 0,
|
161
145
|
:tag => "",
|
146
|
+
:is_restartable => true,
|
162
147
|
:submitter => Bj.hostname,
|
163
148
|
:submitted_at => Time.now,
|
164
149
|
}
|
@@ -167,6 +152,9 @@ class Bj
|
|
167
152
|
send :extend, ClassMethods
|
168
153
|
|
169
154
|
module InstanceMethods
|
155
|
+
def title
|
156
|
+
"job[#{ id }](#{ command })"
|
157
|
+
end
|
170
158
|
def finished
|
171
159
|
reload
|
172
160
|
exit_status
|
@@ -183,26 +171,27 @@ class Bj
|
|
183
171
|
migration {
|
184
172
|
define_method(:up) do
|
185
173
|
create_table table.table_name, :primary_key => table.primary_key, :force => true do |t|
|
186
|
-
t.column "command"
|
187
|
-
|
188
|
-
t.column "state"
|
189
|
-
t.column "priority"
|
190
|
-
t.column "tag"
|
191
|
-
|
192
|
-
|
193
|
-
t.column "
|
194
|
-
t.column "
|
195
|
-
|
196
|
-
|
197
|
-
t.column "
|
198
|
-
t.column "
|
199
|
-
t.column "
|
200
|
-
|
201
|
-
|
202
|
-
t.column "
|
203
|
-
t.column "
|
204
|
-
t.column "
|
205
|
-
t.column "
|
174
|
+
t.column "command" , :text
|
175
|
+
|
176
|
+
t.column "state" , :text
|
177
|
+
t.column "priority" , :integer
|
178
|
+
t.column "tag" , :text
|
179
|
+
t.column "is_restartable" , :integer
|
180
|
+
|
181
|
+
t.column "submitter" , :text
|
182
|
+
t.column "runner" , :text
|
183
|
+
t.column "pid" , :integer
|
184
|
+
|
185
|
+
t.column "submitted_at" , :datetime
|
186
|
+
t.column "started_at" , :datetime
|
187
|
+
t.column "finished_at" , :datetime
|
188
|
+
t.column "archived_at" , :datetime
|
189
|
+
|
190
|
+
t.column "env" , :text
|
191
|
+
t.column "stdin" , :text
|
192
|
+
t.column "stdout" , :text
|
193
|
+
t.column "stderr" , :text
|
194
|
+
t.column "exit_status" , :integer
|
206
195
|
end
|
207
196
|
end
|
208
197
|
|
@@ -277,7 +266,7 @@ class Bj
|
|
277
266
|
transaction do
|
278
267
|
options.to_options!
|
279
268
|
hostname = options[:hostname] || Bj.hostname
|
280
|
-
record = find :first, :conditions => conditions(:key => key, :hostname => hostname)
|
269
|
+
record = find :first, :conditions => conditions(:key => key, :hostname => hostname), :lock => true
|
281
270
|
cast = options[:cast] || cast_for(value)
|
282
271
|
key = key.to_s
|
283
272
|
value = value.to_s
|
@@ -294,7 +283,7 @@ class Bj
|
|
294
283
|
|
295
284
|
def delete key
|
296
285
|
transaction do
|
297
|
-
record = find :first, :conditions => conditions(:key => key)
|
286
|
+
record = find :first, :conditions => conditions(:key => key), :lock => true
|
298
287
|
if record
|
299
288
|
record.destroy
|
300
289
|
record
|
data/lib/bj/util.rb
CHANGED
@@ -29,6 +29,17 @@ class Bj
|
|
29
29
|
[ stdout, stderr ]
|
30
30
|
end
|
31
31
|
|
32
|
+
def start *a
|
33
|
+
q = Queue.new
|
34
|
+
thread = Thread.new do
|
35
|
+
Thread.current.abort_on_exception = true
|
36
|
+
systemu(*a){|pid| q << pid}
|
37
|
+
end
|
38
|
+
pid = q.pop
|
39
|
+
thread.singleton_class{ define_method(:pid){ pid } }
|
40
|
+
thread
|
41
|
+
end
|
42
|
+
|
32
43
|
def alive pid
|
33
44
|
return false unless pid
|
34
45
|
pid = Integer pid.to_s
|
@@ -39,18 +50,6 @@ class Bj
|
|
39
50
|
end
|
40
51
|
alias_method "alive?", "alive"
|
41
52
|
|
42
|
-
def ping pid, options = {}
|
43
|
-
return false unless pid
|
44
|
-
pid = Integer pid
|
45
|
-
signal = options[:signal] || 'SIGHUP'
|
46
|
-
begin
|
47
|
-
Process::kill signal, pid
|
48
|
-
pid
|
49
|
-
rescue Exception
|
50
|
-
false
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
53
|
def which_ruby
|
55
54
|
c = ::Config::CONFIG
|
56
55
|
ruby = File::join(c['bindir'], c['ruby_install_name']) << c['EXEEXT']
|
@@ -59,10 +58,10 @@ class Bj
|
|
59
58
|
end
|
60
59
|
|
61
60
|
def find_script basename
|
62
|
-
path = ENV["PATH"] || ENV["path"]
|
61
|
+
path = ENV["PATH"] || ENV["path"] || Bj.default_path
|
63
62
|
raise "no env PATH" unless path
|
64
63
|
path = path.split File::PATH_SEPARATOR
|
65
|
-
path.
|
64
|
+
path.unshift File.join(Bj.rails_root, "script")
|
66
65
|
path.each do |directory|
|
67
66
|
script = File.join directory, basename
|
68
67
|
return File.expand_path(script) if(test(?s, script) and test(?r, script))
|
@@ -75,6 +74,13 @@ class Bj
|
|
75
74
|
directories.all?{|dir| test(?d, File.join(root, dir))}
|
76
75
|
end
|
77
76
|
alias_method "valid_rails_root?", "valid_rails_root"
|
77
|
+
|
78
|
+
def emsg e
|
79
|
+
m = e.message rescue ""
|
80
|
+
c = e.class rescue Exception
|
81
|
+
b = e.backtrace.join("\n") rescue ""
|
82
|
+
"#{ m }(#{ c })\n#{ b }"
|
83
|
+
end
|
78
84
|
end
|
79
85
|
send :extend, ModuleMethods
|
80
86
|
end
|