SciMed-bj 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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