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 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
- * flesh out the cli interface - it's a test only at this point
2
- * test in windows
3
- * make sure database.yml is loaded via YAML::load(ERB.new(File.read * "config/database.yml").result)
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
- %w[ forever daemon ppid ].each do |key|
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
- if param["rails_root"].given?
323
- rails_root = param["rails_root"].value
324
- ENV["RAILS_ROOT"] = rails_root
325
- ::Object.instance_eval do
326
- remove_const :RAILS_ROOT
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
- if param["rails_env"].given?
332
- rails_env = param["rails_env"].value
333
- ENV["RAILS_ENV"] = rails_env
334
- ::Object.instance_eval do
335
- remove_const :RAILS_ENV
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? BJ_IS_SCRIPT #{{{
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? RAILS_ROOT #{{{
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? RAILS_ENV #{{{
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 BJ_IS_SCRIPT and test(?d, "#{ RAILS_ROOT }/vendor/plugins/bj/lib")
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
- end #}}}
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 main systemu orderedhash ].each do |lib|
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
 
@@ -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
- util.spawn "./script/generate migration BjMigration", options rescue nil
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
@@ -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.load IO.read(database_yml) }
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
- def connecting options = {}, &block
36
- options.to_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
- def connections
48
- @connections ||= Hash.new do |hash, rails_env|
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
- def ar_connections
57
- if defined?(RAILS_ENV)
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
- end
62
- end
63
-
64
- def ar_connection
65
- begin
66
- ActiveRecord::Base.connection
67
- rescue ActiveRecord::ConnectionNotEstablished
68
- ActiveRecord::Base.establish_connection configurations[rails_env]
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
- begin
77
- Dir.chdir @chrooted = rails_root do |pwd|
78
- raise RailsRoot, "<#{ pwd }> is not a rails root" unless Util.valid_rails_root?(pwd)
79
- block.call(@chrooted)
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
- ensure
82
- @chrooted = nil
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
 
@@ -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
- pipe.each{|line|}
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
- pid ? Util.ping(pid) : nil
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
- "#{ Bj.ruby } #{ Bj.script } run --forever --redirect=#{ log } --rails_env=#{ Bj.rails_env } --ppid=#{ Process.pid }"
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
- Bj.chroot do
113
- register or exit!(EXIT::WARNING)
114
- Bj.logger.info{ "START" }
115
- fill_morgue
116
- install_signal_handlers
137
+ Runner.thread = Thread.current
138
+ Bj.chroot
117
139
 
118
- loop do
119
- loopno += 1
120
- break if(limit and n > limit)
121
-
122
- Bj.logger.debug{ "loopno #{ loopno }" }
123
-
124
- sweep
125
-
126
- catch :no_jobs do
127
- loop do
128
- job = thread = stdout = stderr = nil
129
-
130
- Bj.transaction(options) do
131
- job = Bj::Table::Job.find :first,
132
- :conditions => "state = 'pending'",
133
- :order => "priority DESC, submitted_at ASC",
134
- :limit => 1
135
- throw :no_jobs unless job
136
-
137
- Bj.logger.info{ "running #{ job.id } (#{ job.command })" }
138
-
139
- command = job.command
140
- env = job.env || {}
141
- stdin = job.stdin || ''
142
- stdout = job.stdout || ''
143
- stderr = job.stderr || ''
144
- started_at = Time.now
145
-
146
- q = Queue.new
147
- thread = Thread.new do
148
- Thread.current.abort_on_exception = true
149
- systemu command, :cwd=>Bj.rails_root, :env=>env, :stdin=>stdin, :stdout=>stdout, :stderr=>stderr do |pid|
150
- q << pid
151
- end
152
- end
153
- pid = q.pop
154
-
155
- job.state = "running"
156
- job.runner = Bj.hostname
157
- job.pid = pid
158
- job.started_at = started_at
159
- job.save!
160
- job.update
161
- end
162
-
163
- exit_status = thread.value
164
- finished_at = Time.now
165
-
166
- Bj.transaction(options) do
167
- job = Bj::Table::Job.find job.id
168
- break unless job
169
- job.state = "finished"
170
- job.finished_at = finished_at
171
- job.stdout = stdout
172
- job.stderr = stderr
173
- job.exit_status = exit_status
174
- job.save!
175
- job.update
176
- Bj.logger.info{ "exit_status #{ job.exit_status }" }
177
- end
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
- sleep wait
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
- break unless(limit or limit == false)
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
- Bj.logger.info{ "marking #{ job.id } (#{ job.command }) as dead." }
196
- job.state = 'dead'
197
- job.finished_at = now
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 sweep options = {}
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{ "archiving #{ job.id } (#{ job.command })." }
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
@@ -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'
@@ -7,26 +7,9 @@ class Bj
7
7
  attribute("list"){ Array.new }
8
8
  attribute("migration"){}
9
9
 
10
- def generate_migration
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 BjMigration < ActiveRecord::Migration
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" , :text
117
-
118
- t.column "state" , :text
119
- t.column "priority" , :integer
120
- t.column "tag" , :text
121
-
122
- t.column "submitter" , :text
123
- t.column "runner" , :text
124
- t.column "pid" , :integer
125
-
126
- t.column "submitted_at" , :datetime
127
- t.column "started_at" , :datetime
128
- t.column "finished_at" , :datetime
129
-
130
- t.column "env" , :text
131
- t.column "stdin" , :text
132
- t.column "stdout" , :text
133
- t.column "stderr" , :text
134
- t.column "exit_status" , :integer
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" , :text
187
-
188
- t.column "state" , :text
189
- t.column "priority" , :integer
190
- t.column "tag" , :text
191
-
192
- t.column "submitter" , :text
193
- t.column "runner" , :text
194
- t.column "pid" , :integer
195
-
196
- t.column "submitted_at" , :datetime
197
- t.column "started_at" , :datetime
198
- t.column "finished_at" , :datetime
199
- t.column "archived_at" , :datetime
200
-
201
- t.column "env" , :text
202
- t.column "stdin" , :text
203
- t.column "stdout" , :text
204
- t.column "stderr" , :text
205
- t.column "exit_status" , :integer
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
@@ -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.push File.join(Bj.rails_root, "script")
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
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
3
3
  specification_version: 1
4
4
  name: bj
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.0.2
7
- date: 2007-12-12 00:00:00 -07:00
6
+ version: 0.0.3
7
+ date: 2007-12-22 00:00:00 -07:00
8
8
  summary: bj
9
9
  require_paths:
10
10
  - lib