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 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