bj 0.0.2 → 0.0.3
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 +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
|