SciMed-bj 1.2.4

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.
@@ -0,0 +1,72 @@
1
+ class Bj
2
+ module ClassMethods
3
+ fattr("rails_root"){ Util.const_or_env("RAILS_ROOT"){ "." } }
4
+ fattr("rails_env"){ Util.const_or_env("RAILS_ENV"){ "development" } }
5
+ fattr("database_yml"){ File.join rails_root, "config", "database.yml" }
6
+ fattr("configurations"){ YAML::load(ERB.new(IO.read(database_yml)).result) }
7
+ fattr("tables"){ Table.list }
8
+ fattr("hostname"){ Socket.gethostname }
9
+ fattr("logger"){ Bj::Logger.off STDERR }
10
+ fattr("ruby"){ Util.which_ruby }
11
+ fattr("rake"){ Util.which_rake }
12
+ fattr("script"){ Util.find_script "bj" }
13
+ fattr("ttl"){ Integer(Bj::Table::Config["ttl"] || (twenty_four_hours = 24 * 60 * 60)) }
14
+ fattr("table"){ Table }
15
+ fattr("config"){ table.config }
16
+ fattr("util"){ Util }
17
+ fattr("runner"){ Runner }
18
+ fattr("joblist"){ Joblist }
19
+ fattr("default_path"){ %w'/bin /usr/bin /usr/local/bin /opt/local/bin'.join(File::PATH_SEPARATOR) }
20
+
21
+ def transaction options = {}, &block
22
+ options.to_options!
23
+
24
+ cur_rails_env = Bj.rails_env.to_s
25
+ new_rails_env = options[:rails_env].to_s
26
+
27
+ cur_spec = configurations[cur_rails_env]
28
+ table.establish_connection(cur_spec) unless table.connected?
29
+
30
+ if(new_rails_env.empty? or cur_rails_env == new_rails_env)
31
+ table.transaction{ block.call(table.connection) }
32
+ else
33
+ new_spec = configurations[new_rails_env]
34
+ table.establish_connection(new_spec)
35
+ Bj.rails_env = new_rails_env
36
+ begin
37
+ table.transaction{ block.call(table.connection) }
38
+ ensure
39
+ table.establish_connection(cur_spec)
40
+ Bj.rails_env = cur_rails_env
41
+ end
42
+ end
43
+ end
44
+
45
+ def chroot options = {}, &block
46
+ if defined? @chrooted and @chrooted
47
+ return(block ? block.call(@chrooted) : @chrooted)
48
+ end
49
+ if block
50
+ begin
51
+ chrooted = @chrooted
52
+ Dir.chdir(@chrooted = rails_root) do
53
+ raise RailsRoot, "<#{ Dir.pwd }> is not a rails root" unless Util.valid_rails_root?(Dir.pwd)
54
+ block.call(@chrooted)
55
+ end
56
+ ensure
57
+ @chrooted = chrooted
58
+ end
59
+ else
60
+ Dir.chdir(@chrooted = rails_root)
61
+ raise RailsRoot, "<#{ Dir.pwd }> is not a rails root" unless Util.valid_rails_root?(Dir.pwd)
62
+ @chrooted
63
+ end
64
+ end
65
+
66
+ def boot
67
+ load File.join(rails_root, "config", "boot.rb")
68
+ load File.join(rails_root, "config", "environment.rb")
69
+ end
70
+ end
71
+ send :extend, ClassMethods
72
+ end
@@ -0,0 +1,4 @@
1
+ class Bj
2
+ class Error < ::StandardError; end
3
+ class RailsRoot < Error; end
4
+ end
@@ -0,0 +1,112 @@
1
+ class Bj
2
+ class JobList < ::Array
3
+ module ClassMethods
4
+ def for jobs, options = {}
5
+ if Joblist === jobs
6
+ jobs.update options
7
+ return jobs
8
+ end
9
+ options.to_options!
10
+ jobs = [jobs].flatten.compact
11
+ list = []
12
+ jobs.each do |arg|
13
+ list.push(
14
+ case arg
15
+ when String
16
+ case arg
17
+ when %r/^\d+$/
18
+ job_from_id arg
19
+ else
20
+ job_from_string arg
21
+ end
22
+ when Hash
23
+ job_from_hash arg
24
+ when Io
25
+ jobs_from_io arg
26
+ when Fixnum, Bignum
27
+ job_from_number arg
28
+ else
29
+ job_from_string arg
30
+ end
31
+ )
32
+ end
33
+ list.flatten!
34
+ list.compact!
35
+ list.map!{|job| job.reverse_merge! options}
36
+ list
37
+ end
38
+
39
+ def job_from_hash arg
40
+ arg.to_hash.to_options!
41
+ end
42
+
43
+ def job_from_string arg
44
+ unless arg.strip.empty?
45
+ { :command => arg.to_s }
46
+ else
47
+ nil
48
+ end
49
+ end
50
+
51
+ def job_from_number arg
52
+ id = arg.to_i
53
+ Table::Job.find(id).to_hash
54
+ end
55
+
56
+ def jobs_from_io arg
57
+ if arg == "-"
58
+ load_from_io STDIN
59
+ else
60
+ if arg.respond_to? :read
61
+ load_from_io arg
62
+ else
63
+ open(arg, "r"){|fd| load_from_io fd}
64
+ end
65
+ end
66
+ end
67
+
68
+ def load_from_io io
69
+ list = []
70
+ io.each do |line|
71
+ line.strip!
72
+ next if line.empty?
73
+ list << job_from_string(line)
74
+ end
75
+ list
76
+ end
77
+
78
+ def jobs_from_yaml arg
79
+ object =
80
+ if arg == "-"
81
+ YAML.load STDIN
82
+ else
83
+ if arg.respond_to? :read
84
+ YAML.load arg
85
+ else
86
+ open(arg, "r"){|fd| YAML.load fd}
87
+ end
88
+ end
89
+ Joblist.for object
90
+ end
91
+ end
92
+ send :extend, ClassMethods
93
+
94
+ module InstanceMethods
95
+ def update options = {}
96
+ options.to_options!
97
+ each{|job| job.update options}
98
+ end
99
+
100
+ def push other
101
+ Joblist.for(other).each do |job|
102
+ super job
103
+ end
104
+ self
105
+ end
106
+ alias_method "<<", "push"
107
+ end
108
+ send :include, InstanceMethods
109
+ end
110
+
111
+ Joblist = JobList
112
+ end
@@ -0,0 +1,51 @@
1
+ require 'logger'
2
+ class Bj
3
+ class Logger < ::Logger
4
+ def self.new *a, &b
5
+ super(*a, &b).instance_eval{ @default_formatter = @formatter = Formatter.new; self }
6
+ end
7
+ def format_message(severity, datetime, progname, msg)
8
+ (@formatter || @default_formatter).call(severity, datetime, progname, msg)
9
+ end
10
+
11
+ def device
12
+ @logdev.instance_eval{ @dev }
13
+ end
14
+
15
+ def tty?
16
+ device.respond_to?('tty?') and device.tty?
17
+ end
18
+
19
+ def turn which
20
+ @logdev.extend OnOff unless OnOff === @logdev
21
+ @logdev.turn which
22
+ end
23
+
24
+ module OnOff
25
+ def turn which
26
+ @turned = which.to_s =~ %r/on/i ? :on : :off
27
+ end
28
+
29
+ def write message
30
+ return message.to_s.size if @turned == :off
31
+ super
32
+ end
33
+ end
34
+
35
+ def on
36
+ turn :on
37
+ end
38
+ alias_method "on!", "on"
39
+ def self.on *a, &b
40
+ new(*a, &b).instance_eval{ turn :on; self }
41
+ end
42
+
43
+ def off
44
+ turn :off
45
+ end
46
+ alias_method "off!", "off"
47
+ def self.off *a, &b
48
+ new(*a, &b).instance_eval{ turn :off; self }
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,369 @@
1
+ class Bj
2
+ class Runner
3
+ class Background
4
+ def self.for(*a, &b) new(*a, &b) end
5
+
6
+ fattr("command"){}
7
+ fattr("thread"){}
8
+ fattr("pid"){}
9
+
10
+ def initialize command
11
+ @command = command
12
+ @thread = new_thread
13
+ end
14
+
15
+ def inspect
16
+ {
17
+ "command" => command,
18
+ "pid" => pid,
19
+ }.inspect
20
+ end
21
+
22
+
23
+ # TODO - auto start runner?
24
+
25
+ def new_thread
26
+ this = self
27
+ Thread.new do
28
+ Thread.current.abort_on_exception = true
29
+ loop do
30
+ cleanup = lambda{}
31
+
32
+ IO.popen command, "r+" do |pipe|
33
+ this.pid = pid = pipe.pid
34
+ cleanup = lambda do
35
+ cleanup = lambda{}
36
+ begin; Process.kill(Runner.kill_signal, pid); rescue Exception; 42; end
37
+ end
38
+ at_exit &cleanup
39
+ Process.wait
40
+ end
41
+
42
+ Bj.logger.error{ "#{ command } failed with #{ $?.inspect }" } unless
43
+ [0, 42].include?($?.exitstatus)
44
+
45
+ cleanup.call
46
+
47
+ sleep 42
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ module ClassMethods
54
+ fattr("thread"){ Thread.current }
55
+ fattr("hup_signal"){ Signal.list.keys.index("HUP") ? "HUP" : "ABRT" }
56
+ fattr("hup_signaled"){ false }
57
+ fattr("kill_signal"){ "TERM" }
58
+ fattr("kill_signaled"){ false }
59
+
60
+ def tickle
61
+ return nil if Bj.config[Runner.no_tickle_key]
62
+ ping or start
63
+ end
64
+
65
+ def ping
66
+ begin
67
+ pid = nil
68
+ uri = nil
69
+ process = nil
70
+ Bj.transaction do
71
+ pid = Bj.config[Runner.key(Process.pid)] || Bj.config[Runner.key]
72
+ uri = Bj.config["#{ pid }.uri"]
73
+ process =
74
+ if uri
75
+ require "drb"
76
+ # DRb.start_service "druby://localhost:0"
77
+ DRbObject.new(nil, uri)
78
+ else
79
+ Process
80
+ end
81
+ end
82
+ return nil unless pid
83
+ pid = Integer pid
84
+ begin
85
+ process.kill Runner.hup_signal, pid
86
+ pid
87
+ rescue Exception => e
88
+ false
89
+ end
90
+ rescue Exception => e
91
+ false
92
+ end
93
+ end
94
+
95
+ def key ppid = 0
96
+ ppid ||= 0
97
+ #"#{ Bj.rails_env }.#{ ppid }.pid"
98
+ "runner.#{ ppid }.pid"
99
+ end
100
+
101
+ def no_tickle_key
102
+ # "#{ Bj.rails_env }.no_tickle"
103
+ "no_tickle"
104
+ end
105
+
106
+ def start options = {}
107
+ options.to_options!
108
+ background.delete Bj.rails_env if options[:force]
109
+ background[Bj.rails_env] ||= Background.for(command)
110
+ end
111
+
112
+ def background
113
+ @background ||= Hash.new
114
+ end
115
+
116
+ def background= value
117
+ @background ||= value
118
+ end
119
+
120
+ def command
121
+ "#{ Bj.ruby } " + %W[
122
+ #{ Bj.script }
123
+ run
124
+ --forever
125
+ --redirect=#{ log }
126
+ --ppid=#{ Process.pid }
127
+ --rails_env=#{ Bj.rails_env }
128
+ --rails_root=#{ Bj.rails_root }
129
+ ].map{|word| word.inspect}.join(" ")
130
+ end
131
+
132
+ def log
133
+ File.join logdir, "bj.#{ Bj.hostname }.#{ Bj.rails_env }.log"
134
+ end
135
+
136
+ def logdir
137
+ File.join File.expand_path(Bj.rails_root), 'log'
138
+ end
139
+
140
+ def run options = {}, &block
141
+ new(options, &block).run
142
+ end
143
+ end
144
+ send :extend, ClassMethods
145
+
146
+ module Instance_Methods
147
+ fattr("options"){}
148
+ fattr("block"){}
149
+
150
+ def initialize options = {}, &block
151
+ options.to_options!
152
+ @options, @block = options, block
153
+ end
154
+
155
+ def run
156
+ wait = options[:wait] || 42
157
+ limit = options[:limit]
158
+ forever = options[:forever]
159
+
160
+ limit = false if forever
161
+ wait = Integer wait
162
+ loopno = 0
163
+
164
+ Runner.thread = Thread.current
165
+ Bj.chroot
166
+
167
+ register or exit!(EXIT::WARNING)
168
+
169
+ Bj.logger.info{ "STARTED" }
170
+ at_exit{ Bj.logger.info{ "STOPPED" } }
171
+
172
+ fill_morgue
173
+ install_signal_handlers
174
+
175
+ loop do
176
+ ping_parent
177
+
178
+ loopno += 1
179
+ break if(limit and loopno > limit)
180
+
181
+ archive_jobs
182
+
183
+ catch :no_jobs do
184
+ loop do
185
+ job = thread = stdout = stderr = nil
186
+
187
+ Bj.transaction(options) do
188
+ now = Time.now
189
+
190
+ job = Bj::Table::Job.find :first,
191
+ :conditions => ["state = ? and submitted_at <= ?", "pending", now],
192
+ :order => "priority DESC, submitted_at ASC",
193
+ :limit => 1,
194
+ :lock => true
195
+ throw :no_jobs unless job
196
+
197
+ job.state = "running"
198
+ job.save!
199
+ job.reload
200
+
201
+ Bj.logger.info{ "#{ job.title } - starting..." }
202
+
203
+ command = job.command
204
+ env = job.env ? YAML.load(job.env) : {}
205
+ stdin = job.stdin || ''
206
+ stdout = job.stdout || ''
207
+ stderr = job.stderr || ''
208
+
209
+ started_at = Time.now
210
+
211
+ thread = Util.start command, :cwd=>Bj.rails_root, :env=>env, :stdin=>stdin, :stdout=>stdout, :stderr=>stderr
212
+
213
+ job.state = "running"
214
+ job.runner = Bj.hostname
215
+ job.pid = thread.pid
216
+ job.started_at = started_at
217
+ job.save!
218
+ job.reload
219
+ end
220
+
221
+ job.run!
222
+
223
+ =begin
224
+ exit_status = thread.value
225
+ finished_at = Time.now
226
+
227
+ Bj.transaction(options) do
228
+ job = Bj::Table::Job.find job.id
229
+ break unless job
230
+ job.state = "finished"
231
+ job.finished_at = finished_at
232
+ job.stdout = stdout
233
+ job.stderr = stderr
234
+ job.exit_status = exit_status
235
+ job.save!
236
+ job.reload
237
+ Bj.logger.info{ "#{ job.title } - exit_status=#{ job.exit_status }" }
238
+ end
239
+ =end
240
+
241
+ Bj.logger.info{ "#{ job.title } - exit_status=#{ job.exit_status }" }
242
+ end
243
+ end
244
+
245
+ Runner.hup_signaled false
246
+ wait.times do
247
+ break if Runner.hup_signaled?
248
+ break if Runner.kill_signaled?
249
+ sleep 1
250
+ end
251
+
252
+ break unless(limit or limit == false)
253
+ break if Runner.kill_signaled?
254
+ end
255
+ end
256
+
257
+ def ping_parent
258
+ ppid = options[:ppid]
259
+ return unless ppid
260
+ begin
261
+ Process.kill 0, Integer(ppid)
262
+ rescue Errno::ESRCH
263
+ Kernel.exit 42
264
+ rescue Exception
265
+ 42
266
+ end
267
+ end
268
+
269
+ def install_signal_handlers
270
+ Runner.hup_signaled false
271
+ hup_handler = nil
272
+ hup_handler =
273
+ trap Runner.hup_signal do |*a|
274
+ begin
275
+ Runner.hup_signaled true
276
+ rescue Exception => e
277
+ Bj.logger.error{ e } rescue nil
278
+ end
279
+ hup_handler.call *a rescue nil
280
+ end
281
+
282
+ Runner.kill_signaled false
283
+ kill_handler = nil
284
+ kill_handler =
285
+ trap Runner.kill_signal do |*a|
286
+ begin
287
+ Runner.kill_signaled true
288
+ rescue Exception => e
289
+ Bj.logger.error{ e } rescue nil
290
+ end
291
+ kill_handler.call *a rescue nil
292
+ end
293
+
294
+ begin
295
+ trap("INT"){ exit }
296
+ rescue Exception
297
+ end
298
+ end
299
+
300
+ def fill_morgue
301
+ Bj.transaction do
302
+ now = Time.now
303
+ jobs = Bj::Table::Job.find :all,
304
+ :conditions => ["state = 'running' and runner = ?", Bj.hostname]
305
+ jobs.each do |job|
306
+ if job.is_restartable?
307
+ Bj.logger.info{ "#{ job.title } - found dead and bloated but resubmitted" }
308
+ %w[ runner pid started_at finished_at stdout stderr exit_status ].each do |column|
309
+ job[column] = nil
310
+ end
311
+ job.state = 'pending'
312
+ else
313
+ Bj.logger.info{ "#{ job.title } - found dead and bloated" }
314
+ job.state = 'dead'
315
+ job.finished_at = now
316
+ end
317
+ job.save!
318
+ end
319
+ end
320
+ end
321
+
322
+ def archive_jobs
323
+ Bj.transaction do
324
+ now = Time.now
325
+ too_old = now - Bj.ttl
326
+ jobs = Bj::Table::Job.find :all,
327
+ :conditions => ["(state = 'finished' or state = 'dead') and submitted_at < ?", too_old]
328
+ jobs.each do |job|
329
+ Bj.logger.info{ "#{ job.title } - archived" }
330
+ hash = job.to_hash.update(:archived_at => now)
331
+ Bj::Table::JobArchive.create! hash
332
+ job.destroy
333
+ end
334
+ end
335
+ end
336
+
337
+ def register
338
+ Bj.transaction do
339
+ pid = Bj.config[key]
340
+ return false if Util.alive?(pid)
341
+ Bj.config[key] = Process.pid
342
+ unless Bj.util.ipc_signals_supported? # not winblows
343
+ require "drb"
344
+ DRb.start_service "druby://localhost:0", Process
345
+ Bj.config["#{ Process.pid }.uri"] = DRb.uri
346
+ end
347
+ end
348
+ at_exit{ unregister }
349
+ true
350
+ rescue Exception
351
+ false
352
+ end
353
+
354
+ def unregister
355
+ Bj.transaction do
356
+ Bj.config.delete key
357
+ end
358
+ true
359
+ rescue Exception
360
+ false
361
+ end
362
+
363
+ def key
364
+ @key ||= ( options[:ppid] ? Runner.key(options[:ppid]) : Runner.key )
365
+ end
366
+ end
367
+ send :include, Instance_Methods
368
+ end
369
+ end