bricooke-bj 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY ADDED
@@ -0,0 +1,74 @@
1
+ 1.0.3
2
+ - env wasn't properly unpacked by runner, added YAML.load(job.env), thanks
3
+ Chris Wanstrath
4
+ - made operations that generate migrations, etc, verbose - they dump
5
+ stdout and stderr to console when running
6
+
7
+ 1.0.2:
8
+ - Bj now (should) auto detect the correct rake command on windows as
9
+ "rake.bat" not "rake". see Bj.which_rake and Bj.rake for impl.
10
+
11
+ 1.0.1:
12
+ - fixed name collision with 'record.attributes = hash' ar mass
13
+ assignment method (thx jon guymon)
14
+ - added new sponsor: http://igicom.com/
15
+
16
+ 0.0.5:
17
+ - use full path to ruby for plugin mode
18
+ - plugin correctly installs bin -->> script
19
+ - plugin install uses --force
20
+ - properly quote paths in windows (spaces)
21
+ - switch win signal to ABRT (was INT)
22
+ - background job regrestration now uses ppid to pin the subprocess to a
23
+ parent
24
+ - use ppid to detect parent death and exit in event loop
25
+ - don't use gem dependanices in plugin as they are broken when loading from
26
+ muliple gem repos
27
+ - added a small amount of drb magic that allows signals to work across
28
+ processes even on windows (see http://drawohara.com/post/22540307)
29
+
30
+ 0.0.4:
31
+ - basic functionality in windows
32
+ - several small bug fixes
33
+
34
+ 0.0.3:
35
+ - *many* small bug fixes
36
+
37
+ - plugin install should now pick up dependancies from plugin dir, last
38
+ release had LOAD_PATH/gem issues and was picking up globally installed
39
+ gems
40
+
41
+ - automatic management of the background processing can be turned off if you
42
+ want to manage your own processes
43
+
44
+ - all jobs are automatically restartable unless submitted with
45
+
46
+ :restartable => false
47
+
48
+ this means that, should a runner ever die, upon restart any jobs that were
49
+ mid-process will automatically be restarted
50
+
51
+ - signal based parent lifeline move out of thread and into even loop
52
+
53
+ - :lock => true added to a few AR finds to support true write serializable
54
+ transaction isolation when the db supports it
55
+
56
+ - all migrations now use :force => true and
57
+
58
+ - running 'bj setup' will always generate a new migration, even if you've
59
+ already run it before. this allows easy version upgrades.
60
+
61
+ - a few command would blow up on windows because they weren't prefixed with
62
+ 'ruby'. gotta love the lack of #shebang line on windoze...
63
+
64
+ - ./script/bj is searched first before system path env var
65
+
66
+ - a default PATH is provided for whacky systems without one
67
+
68
+ - database.yml is filtered through ERB ala rails
69
+
70
+ 0.0.2:
71
+ - path bug fixes
72
+
73
+ 0.0.1:
74
+ - initial release
data/README ADDED
@@ -0,0 +1,308 @@
1
+ NAME
2
+ bj
3
+
4
+ SYNOPSIS
5
+ bj (migration_code|generate_migration|migrate|setup|plugin|run|submit|list|set|config|pid) [options]+
6
+
7
+ DESCRIPTION
8
+ ________________________________
9
+ Overview
10
+ --------------------------------
11
+
12
+ Backgroundjob (Bj) is a brain dead simple zero admin background priority queue
13
+ for Rails. Bj is robust, platform independent (including windows), and
14
+ supports internal or external manangement of the background runner process.
15
+
16
+ Jobs can be submitted to the queue directly using the api or from the command
17
+ line using the ./script/bj:
18
+
19
+ api:
20
+ Bj.submit 'cat /etc/password'
21
+
22
+ command line:
23
+ bj submit cat /etc/password
24
+
25
+ Bj's priority queue lives in the database and is therefore durable - your jobs
26
+ will live across an app crash or machine reboot. The job management is
27
+ comprehensive capturing stdout, stderr, exit_status, and temporal statistics
28
+ about each job:
29
+
30
+ jobs = Bj.submit array_of_commands, :priority => 42
31
+
32
+ ...
33
+
34
+ jobs.each do |job|
35
+ if job.finished?
36
+ p job.stdout
37
+ p job.stderr
38
+ p job.exit_status
39
+ p job.started_at
40
+ p job.finished_at
41
+ end
42
+ end
43
+
44
+ In addition the background runner process logs all commands run and their
45
+ exit_status to a log named using the following convention:
46
+
47
+ rails_root/log/bj.#{ HOSTNAME }.#{ RAILS_ENV }.log
48
+
49
+ Bj allows you to submit jobs to multiple databases; for instance, if your
50
+ application is running in development mode you may do:
51
+
52
+ Bj.in :production do
53
+ Bj.submit 'my_job.exe'
54
+ end
55
+
56
+ Bj manages the ever growing list of jobs ran by automatically archiving them
57
+ into another table (by default jobs > 24 hrs old are archived) to prevent the
58
+ jobs table from becoming bloated and huge.
59
+
60
+ All Bj's tables are namespaced and accessible via the Bj module:
61
+
62
+ Bj.table.job.find(:all) # jobs table
63
+ Bj.table.job_archive.find(:all) # archived jobs
64
+ Bj.table.config.find(:all) # configuration and runner state
65
+
66
+ Bj always arranges for submitted jobs to run with a current working directory
67
+ of RAILS_ROOT and with the correct RAILS_ENV setting. For example, if you
68
+ submit a job in production it will have ENV['RAILS_ENV'] == 'production'.
69
+
70
+ When Bj manages the background runner it will never outlive the rails
71
+ application - it is started and stopped on demand as the rails app is started
72
+ and stopped. This is also true for ./script/console - Bj will automatically
73
+ fire off the background runner to process jobs submitted using the console.
74
+
75
+ Bj ensures that only one background process is running for your application -
76
+ firing up three mongrels or fcgi processes will result in only one background
77
+ runner being started. Note that the number of background runners does not
78
+ determine throughput - that is determined primarily by the nature of the jobs
79
+ themselves and how much work they perform per process.
80
+
81
+
82
+ ________________________________
83
+ Architecture
84
+ --------------------------------
85
+
86
+ If one ignores platform specific details the design of Bj is quite simple: the
87
+ main Rails application submits jobs to table, stored in the database. The act
88
+ of submitting triggers exactly one of two things to occur:
89
+
90
+ 1) a new long running background runner to be started
91
+
92
+ 2) an existing background runner to be signaled
93
+
94
+ The background runner refuses to run two copies of itself for a given
95
+ hostname/rails_env combination. For example you may only have one background
96
+ runner processing jobs on localhost in development mode.
97
+
98
+ The background runner, under normal circumstances, is managed by Bj itself -
99
+ you need do nothing to start, monitor, or stop it - it just works. However,
100
+ some people will prefer manage their own background process, see 'External
101
+ Runner' section below for more on this.
102
+
103
+ The runner simply processes each job in a highest priority oldest-in fashion,
104
+ capturing stdout, stderr, exit_status, etc. and storing the information back
105
+ into the database while logging it's actions. When there are no jobs to run
106
+ the runner goes to sleep for 42 seconds; however this sleep is interuptable,
107
+ such as when the runner is signaled that a new job has been submitted so,
108
+ under normal circumstances there will be zero lag between job submission and
109
+ job running for an empty queue.
110
+
111
+
112
+ ________________________________
113
+ External Runner / Clustering
114
+ --------------------------------
115
+
116
+ For the paranoid control freaks out there (myself included) it is quite
117
+ possible to manage and monitor the runner process manually. This can be
118
+ desirable in production setups where monitoring software may kill leaking
119
+ rails apps periodically.
120
+
121
+ Recalling that Bj will only allow one copy of itself to process jobs per
122
+ hostname/rails_env pair we can simply do something like this in cron
123
+
124
+ cmd = bj run --forever \
125
+ --rails_env=development \
126
+ --rails_root=/Users/ahoward/rails_root
127
+
128
+ */15 * * * * $cmd
129
+
130
+ this will simply attempt the start the background runner every 15 minutes if,
131
+ and only if, it's not *already* running.
132
+
133
+ In addtion to this you'll want to tell Bj not to manage the runner itself
134
+ using
135
+
136
+ Bj.config["production.no_tickle"] = true
137
+
138
+ Note that, for clusting setups, it's as simple as adding a crontab and config
139
+ entry like this for each host. Because Bj throttles background runners per
140
+ hostname this will allow one runner per hostname - making it quite simple to
141
+ cluster three nodes behind a besieged rails application.
142
+
143
+
144
+ ________________________________
145
+ Designing Jobs
146
+ --------------------------------
147
+
148
+ Bj runs it's jobs as command line applications. It ensures that all jobs run
149
+ in RAILS_ROOT so it's quite natural to apply a pattern such as
150
+
151
+ mkdir ./jobs
152
+ edit ./jobs/background_job_to_run
153
+
154
+ ...
155
+
156
+ Bj.submit "./jobs/background_job_to_run"
157
+
158
+ If you need to run you jobs under an entire rails environment you'll need to
159
+ do this:
160
+
161
+ Bj.submit "./script/runner ./jobs/background_job_to_run"
162
+
163
+ Obviously "./script/runner" loads the rails environment for you. It's worth
164
+ noting that this happens for each job and that this is by design: the reason
165
+ is that most rails applications leak memory like a sieve so, if one were to
166
+ spawn a long running process that used the application code base you'd have a
167
+ lovely doubling of memory usage on you app servers. Although loading the
168
+ rails environment for each background job requires a little time, a little
169
+ cpu, and a lot less memory. A future version of Bj will provide a way to load
170
+ the rails environment once and to process background jobs in this environment,
171
+ but anyone wanting to use this in production will be required to duct tape
172
+ their entire chest and have a team of oxen rip off the tape without screaming
173
+ to prove steelyness of spirit and profound understanding of the other side.
174
+
175
+ Don't forget that you can submit jobs with command line arguments:
176
+
177
+ Bj.submit "./jobs/a.rb 1 foobar --force"
178
+
179
+ and that you can do powerful things by passing stdin to a job that powers
180
+ through a list of work. For instance, assume a "./jobs/bulkmail" job
181
+ resembling
182
+
183
+ STDIN.each do |line|
184
+ address = line.strip
185
+ mail_message_to address
186
+ end
187
+
188
+ then you could
189
+
190
+ stdin = [
191
+ "foo@bar.com",
192
+ "bar@foo.com",
193
+ "ara.t.howard@codeforpeople.com",
194
+ ]
195
+
196
+ Bj.submit "./script/runner ./jobs/bulkmail", :stdin => stdin
197
+
198
+ and all those emails would be sent in the background.
199
+
200
+ Bj's power is putting jobs in the background in a simple and robust fashion.
201
+ It's your task to build intelligent jobs that leverage batch processing, and
202
+ other, possibilities. The upshot of building tasks this way is that they are
203
+ quite easy to test before submitting them from inside your application.
204
+
205
+
206
+ ________________________________
207
+ Install
208
+ --------------------------------
209
+
210
+ Bj can be installed two ways: as a plugin or via rubygems
211
+
212
+ plugin:
213
+ 1) ./script/plugin install http://codeforpeople.rubyforge.org/svn/rails/plugins/bj
214
+ 2) ./script/bj setup
215
+
216
+ gem:
217
+ 1) $sudo gem install bj
218
+ 2) add "require 'bj'" to config/environment.rb
219
+ 3) bj setup
220
+
221
+ ________________________________
222
+ Api
223
+ --------------------------------
224
+
225
+ submit jobs for background processing. 'jobs' can be a string or array of
226
+ strings. options are applied to each job in the 'jobs', and the list of
227
+ submitted jobs is always returned. options (string or symbol) can be
228
+
229
+ :rails_env => production|development|key_in_database_yml
230
+ when given this keyword causes bj to submit jobs to the
231
+ specified database. default is RAILS_ENV.
232
+
233
+ :priority => any number, including negative ones. default is zero.
234
+
235
+ :tag => a tag added to the job. simply makes searching easier.
236
+
237
+ :env => a hash specifying any additional environment vars the background
238
+ process should have.
239
+
240
+ :stdin => any stdin the background process should have. must respond_to
241
+ to_s
242
+
243
+ eg:
244
+
245
+ jobs = Bj.submit 'echo foobar', :tag => 'simple job'
246
+
247
+ jobs = Bj.submit '/bin/cat', :stdin => 'in the hat', :priority => 42
248
+
249
+ jobs = Bj.submit './script/runner ./scripts/a.rb', :rails_env => 'production'
250
+
251
+ jobs = Bj.submit './script/runner /dev/stdin',
252
+ :stdin => 'p RAILS_ENV',
253
+ :tag => 'dynamic ruby code'
254
+
255
+ jobs Bj.submit array_of_commands, :priority => 451
256
+
257
+ when jobs are run, they are run in RAILS_ROOT. various attributes are
258
+ available *only* once the job has finished. you can check whether or not a
259
+ job is finished by using the #finished method, which simple does a reload and
260
+ checks to see if the exit_status is non-nil.
261
+
262
+ eg:
263
+
264
+ jobs = Bj.submit list_of_jobs, :tag => 'important'
265
+ ...
266
+
267
+ jobs.each do |job|
268
+ if job.finished?
269
+ p job.exit_status
270
+ p job.stdout
271
+ p job.stderr
272
+ end
273
+ end
274
+
275
+ See lib/bj/api.rb for more details.
276
+
277
+ ________________________________
278
+ Sponsors
279
+ --------------------------------
280
+ http://quintess.com/
281
+ http://www.engineyard.com/
282
+ http://igicom.com/
283
+ http://eparklabs.com/
284
+
285
+ http://your_company.com/ <<-- (targeted marketing aimed at *you*)
286
+
287
+ ________________________________
288
+ Version
289
+ --------------------------------
290
+ 1.0.1
291
+
292
+ PARAMETERS
293
+ --rails_root=rails_root, -R (0 ~> rails_root=)
294
+ the rails_root will be guessed unless you set this
295
+ --rails_env=rails_env, -E (0 ~> rails_env=development)
296
+ set the rails_env
297
+ --log=log, -l (0 ~> log=STDERR)
298
+ set the logfile
299
+ --help, -h
300
+
301
+ AUTHOR
302
+ ara.t.howard@gmail.com
303
+
304
+ URIS
305
+ http://codeforpeople.com/lib/ruby/
306
+ http://rubyforge.org/projects/codeforpeople/
307
+ http://codeforpeople.rubyforge.org/svn/rails/plugins/
308
+
data/TODO ADDED
@@ -0,0 +1,40 @@
1
+
2
+ ? the whole gem_path thing is still fubar
3
+
4
+ ? commands need quoting, esp for windows, "c:\Documents And..." etc
5
+
6
+ - signals not operating properly on windows , non critical error tho...
7
+
8
+ - need to figure out how to cache connections for Bj.in(...)
9
+
10
+ - ttl will be added. maxing it out will cause auto-resubmission (Steve Midgley)
11
+
12
+ - is having the runner thread try forever to start the process the best thing?
13
+
14
+ - allow easy way to run ruby code. perhaps ./script/runner 'eval STDIN.read'
15
+ is good enough
16
+
17
+ - allow easy way to run ruby code that persists
18
+
19
+ - allow specification of runner on submit (--runner)
20
+
21
+ - allow specification of tags a runner will consume (--tag)
22
+
23
+ - flesh out the cli interface - it's a test only at this point
24
+
25
+ - test in windows
26
+
27
+ ================================================================================
28
+
29
+ X ./script/console submission hangs on windows
30
+ X default PATH setting
31
+ X install issues for dave? - gem_path...
32
+ X main only loaded for (bin|script)/bj
33
+ X make it possible to declare externally managed runners
34
+ X restartable will be added. true by default (Steve Midgley)
35
+ X do the lifeline inline with the loop
36
+ X need to address the serialzable writer issue (:lock => true ??)
37
+ X migrations use --force
38
+ X i forget to add "#{ Bj.ruby } ... " to the generate command
39
+ X ./script/bj must be found in path before c:/.....bin/bj
40
+ X make sure database.yml is loaded via YAML::load(ERB.new(File.read * "config/database.yml").result)
data/bin/bj ADDED
@@ -0,0 +1,687 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require "bj"
4
+ require "main"
5
+
6
+ Main {
7
+ usage["description"] = <<-txt
8
+ ________________________________
9
+ Overview
10
+ --------------------------------
11
+
12
+ Backgroundjob (Bj) is a brain dead simple zero admin background priority queue
13
+ for Rails. Bj is robust, platform independent (including windows), and
14
+ supports internal or external manangement of the background runner process.
15
+
16
+ Jobs can be submitted to the queue directly using the api or from the command
17
+ line using the ./script/bj:
18
+
19
+ api:
20
+ Bj.submit 'cat /etc/password'
21
+
22
+ command line:
23
+ bj submit cat /etc/password
24
+
25
+ Bj's priority queue lives in the database and is therefore durable - your jobs
26
+ will live across an app crash or machine reboot. The job management is
27
+ comprehensive capturing stdout, stderr, exit_status, and temporal statistics
28
+ about each job:
29
+
30
+ jobs = Bj.submit array_of_commands, :priority => 42
31
+
32
+ ...
33
+
34
+ jobs.each do |job|
35
+ if job.finished?
36
+ p job.stdout
37
+ p job.stderr
38
+ p job.exit_status
39
+ p job.started_at
40
+ p job.finished_at
41
+ end
42
+ end
43
+
44
+ In addition the background runner process logs all commands run and their
45
+ exit_status to a log named using the following convention:
46
+
47
+ rails_root/log/bj.\#{ HOSTNAME }.\#{ RAILS_ENV }.log
48
+
49
+ Bj allows you to submit jobs to multiple databases; for instance, if your
50
+ application is running in development mode you may do:
51
+
52
+ Bj.in :production do
53
+ Bj.submit 'my_job.exe'
54
+ end
55
+
56
+ Bj manages the ever growing list of jobs ran by automatically archiving them
57
+ into another table (by default jobs > 24 hrs old are archived) to prevent the
58
+ jobs table from becoming bloated and huge.
59
+
60
+ All Bj's tables are namespaced and accessible via the Bj module:
61
+
62
+ Bj.table.job.find(:all) # jobs table
63
+ Bj.table.job_archive.find(:all) # archived jobs
64
+ Bj.table.config.find(:all) # configuration and runner state
65
+
66
+ Bj always arranges for submitted jobs to run with a current working directory
67
+ of RAILS_ROOT and with the correct RAILS_ENV setting. For example, if you
68
+ submit a job in production it will have ENV['RAILS_ENV'] == 'production'.
69
+
70
+ When Bj manages the background runner it will never outlive the rails
71
+ application - it is started and stopped on demand as the rails app is started
72
+ and stopped. This is also true for ./script/console - Bj will automatically
73
+ fire off the background runner to process jobs submitted using the console.
74
+
75
+ Bj ensures that only one background process is running for your application -
76
+ firing up three mongrels or fcgi processes will result in only one background
77
+ runner being started. Note that the number of background runners does not
78
+ determine throughput - that is determined primarily by the nature of the jobs
79
+ themselves and how much work they perform per process.
80
+
81
+
82
+ ________________________________
83
+ Architecture
84
+ --------------------------------
85
+
86
+ If one ignores platform specific details the design of Bj is quite simple: the
87
+ main Rails application submits jobs to table, stored in the database. The act
88
+ of submitting triggers exactly one of two things to occur:
89
+
90
+ 1) a new long running background runner to be started
91
+
92
+ 2) an existing background runner to be signaled
93
+
94
+ The background runner refuses to run two copies of itself for a given
95
+ hostname/rails_env combination. For example you may only have one background
96
+ runner processing jobs on localhost in development mode.
97
+
98
+ The background runner, under normal circumstances, is managed by Bj itself -
99
+ you need do nothing to start, monitor, or stop it - it just works. However,
100
+ some people will prefer manage their own background process, see 'External
101
+ Runner' section below for more on this.
102
+
103
+ The runner simply processes each job in a highest priority oldest-in fashion,
104
+ capturing stdout, stderr, exit_status, etc. and storing the information back
105
+ into the database while logging it's actions. When there are no jobs to run
106
+ the runner goes to sleep for 42 seconds; however this sleep is interuptable,
107
+ such as when the runner is signaled that a new job has been submitted so,
108
+ under normal circumstances there will be zero lag between job submission and
109
+ job running for an empty queue.
110
+
111
+
112
+ ________________________________
113
+ External Runner / Clustering
114
+ --------------------------------
115
+
116
+ For the paranoid control freaks out there (myself included) it is quite
117
+ possible to manage and monitor the runner process manually. This can be
118
+ desirable in production setups where monitoring software may kill leaking
119
+ rails apps periodically.
120
+
121
+ Recalling that Bj will only allow one copy of itself to process jobs per
122
+ hostname/rails_env pair we can simply do something like this in cron
123
+
124
+ cmd = bj run --forever \\
125
+ --rails_env=development \\
126
+ --rails_root=/Users/ahoward/rails_root
127
+
128
+ */15 * * * * $cmd
129
+
130
+ this will simply attempt the start the background runner every 15 minutes if,
131
+ and only if, it's not *already* running.
132
+
133
+ In addtion to this you'll want to tell Bj not to manage the runner itself
134
+ using
135
+
136
+ Bj.config["production.no_tickle"] = true
137
+
138
+ Note that, for clusting setups, it's as simple as adding a crontab and config
139
+ entry like this for each host. Because Bj throttles background runners per
140
+ hostname this will allow one runner per hostname - making it quite simple to
141
+ cluster three nodes behind a besieged rails application.
142
+
143
+
144
+ ________________________________
145
+ Designing Jobs
146
+ --------------------------------
147
+
148
+ Bj runs it's jobs as command line applications. It ensures that all jobs run
149
+ in RAILS_ROOT so it's quite natural to apply a pattern such as
150
+
151
+ mkdir ./jobs
152
+ edit ./jobs/background_job_to_run
153
+
154
+ ...
155
+
156
+ Bj.submit "./jobs/background_job_to_run"
157
+
158
+ If you need to run you jobs under an entire rails environment you'll need to
159
+ do this:
160
+
161
+ Bj.submit "./script/runner ./jobs/background_job_to_run"
162
+
163
+ Obviously "./script/runner" loads the rails environment for you. It's worth
164
+ noting that this happens for each job and that this is by design: the reason
165
+ is that most rails applications leak memory like a sieve so, if one were to
166
+ spawn a long running process that used the application code base you'd have a
167
+ lovely doubling of memory usage on you app servers. Although loading the
168
+ rails environment for each background job requires a little time, a little
169
+ cpu, and a lot less memory. A future version of Bj will provide a way to load
170
+ the rails environment once and to process background jobs in this environment,
171
+ but anyone wanting to use this in production will be required to duct tape
172
+ their entire chest and have a team of oxen rip off the tape without screaming
173
+ to prove steelyness of spirit and profound understanding of the other side.
174
+
175
+ Don't forget that you can submit jobs with command line arguments:
176
+
177
+ Bj.submit "./jobs/a.rb 1 foobar --force"
178
+
179
+ and that you can do powerful things by passing stdin to a job that powers
180
+ through a list of work. For instance, assume a "./jobs/bulkmail" job
181
+ resembling
182
+
183
+ STDIN.each do |line|
184
+ address = line.strip
185
+ mail_message_to address
186
+ end
187
+
188
+ then you could
189
+
190
+ stdin = [
191
+ "foo@bar.com",
192
+ "bar@foo.com",
193
+ "ara.t.howard@codeforpeople.com",
194
+ ]
195
+
196
+ Bj.submit "./script/runner ./jobs/bulkmail", :stdin => stdin
197
+
198
+ and all those emails would be sent in the background.
199
+
200
+ Bj's power is putting jobs in the background in a simple and robust fashion.
201
+ It's your task to build intelligent jobs that leverage batch processing, and
202
+ other, possibilities. The upshot of building tasks this way is that they are
203
+ quite easy to test before submitting them from inside your application.
204
+
205
+
206
+ ________________________________
207
+ Install
208
+ --------------------------------
209
+
210
+ Bj can be installed two ways: as a plugin or via rubygems
211
+
212
+ plugin:
213
+ 1) ./script/plugin install http://codeforpeople.rubyforge.org/svn/rails/plugins/bj
214
+ 2) ./script/bj setup
215
+
216
+ gem:
217
+ 1) $sudo gem install bj
218
+ 2) add "require 'bj'" to config/environment.rb
219
+ 3) bj setup
220
+
221
+ ________________________________
222
+ Api
223
+ --------------------------------
224
+
225
+ submit jobs for background processing. 'jobs' can be a string or array of
226
+ strings. options are applied to each job in the 'jobs', and the list of
227
+ submitted jobs is always returned. options (string or symbol) can be
228
+
229
+ :rails_env => production|development|key_in_database_yml
230
+ when given this keyword causes bj to submit jobs to the
231
+ specified database. default is RAILS_ENV.
232
+
233
+ :priority => any number, including negative ones. default is zero.
234
+
235
+ :tag => a tag added to the job. simply makes searching easier.
236
+
237
+ :env => a hash specifying any additional environment vars the background
238
+ process should have.
239
+
240
+ :stdin => any stdin the background process should have. must respond_to
241
+ to_s
242
+
243
+ eg:
244
+
245
+ jobs = Bj.submit 'echo foobar', :tag => 'simple job'
246
+
247
+ jobs = Bj.submit '/bin/cat', :stdin => 'in the hat', :priority => 42
248
+
249
+ jobs = Bj.submit './script/runner ./scripts/a.rb', :rails_env => 'production'
250
+
251
+ jobs = Bj.submit './script/runner /dev/stdin',
252
+ :stdin => 'p RAILS_ENV',
253
+ :tag => 'dynamic ruby code'
254
+
255
+ jobs Bj.submit array_of_commands, :priority => 451
256
+
257
+ when jobs are run, they are run in RAILS_ROOT. various attributes are
258
+ available *only* once the job has finished. you can check whether or not a
259
+ job is finished by using the #finished method, which simple does a reload and
260
+ checks to see if the exit_status is non-nil.
261
+
262
+ eg:
263
+
264
+ jobs = Bj.submit list_of_jobs, :tag => 'important'
265
+ ...
266
+
267
+ jobs.each do |job|
268
+ if job.finished?
269
+ p job.exit_status
270
+ p job.stdout
271
+ p job.stderr
272
+ end
273
+ end
274
+
275
+ See lib/bj/api.rb for more details.
276
+
277
+ ________________________________
278
+ Sponsors
279
+ --------------------------------
280
+ http://quintess.com/
281
+ http://www.engineyard.com/
282
+ http://igicom.com/
283
+ http://eparklabs.com/
284
+
285
+ http://your_company.com/ <<-- (targeted marketing aimed at *you*)
286
+
287
+ ________________________________
288
+ Version
289
+ --------------------------------
290
+ #{ Bj.version }
291
+ txt
292
+
293
+ usage["uris"] = <<-txt
294
+ http://codeforpeople.com/lib/ruby/
295
+ http://rubyforge.org/projects/codeforpeople/
296
+ http://codeforpeople.rubyforge.org/svn/rails/plugins/
297
+ txt
298
+
299
+ author "ara.t.howard@gmail.com"
300
+
301
+ option("rails_root", "R"){
302
+ description "the rails_root will be guessed unless you set this"
303
+ argument_required
304
+ default RAILS_ROOT
305
+ }
306
+
307
+ option("rails_env", "E"){
308
+ description "set the rails_env"
309
+ argument_required
310
+ default RAILS_ENV
311
+ }
312
+
313
+ option("log", "l"){
314
+ description "set the logfile"
315
+ argument_required
316
+ default STDERR
317
+ }
318
+
319
+
320
+ mode "migration_code" do
321
+ description "dump migration code on stdout"
322
+
323
+ def run
324
+ puts Bj.table.migration_code
325
+ end
326
+ end
327
+
328
+ mode "generate_migration" do
329
+ description "generate a migration"
330
+
331
+ def run
332
+ Bj.generate_migration
333
+ end
334
+ end
335
+
336
+ mode "migrate" do
337
+ description "migrate the db"
338
+
339
+ def run
340
+ Bj.migrate
341
+ end
342
+ end
343
+
344
+ mode "setup" do
345
+ description "generate a migration and migrate"
346
+
347
+ def run
348
+ set_rails_env(argv.first) if argv.first
349
+ Bj.setup
350
+ end
351
+ end
352
+
353
+ mode "plugin" do
354
+ description "dump the plugin into rails_root"
355
+
356
+ def run
357
+ Bj.plugin
358
+ end
359
+ end
360
+
361
+ mode "run" do
362
+ description "start a job runnner, possibly as a daemon"
363
+
364
+ option("--forever"){}
365
+ option("--ppid"){
366
+ argument :required
367
+ cast :integer
368
+ }
369
+ option("--wait"){
370
+ argument :required
371
+ cast :integer
372
+ }
373
+ option("--instance"){
374
+ argument :required
375
+ cast :string
376
+ }
377
+ option("--limit"){
378
+ argument :required
379
+ cast :integer
380
+ }
381
+ option("--redirect"){
382
+ argument :required
383
+ }
384
+ option("--daemon"){}
385
+
386
+ def run
387
+ options = {}
388
+
389
+ =begin
390
+ %w[ forever ].each do |key|
391
+ options[key.to_sym] = true if param[key].given?
392
+ end
393
+ =end
394
+
395
+ %w[ forever ppid wait limit instance].each do |key|
396
+ options[key.to_sym] = param[key].value if param[key].given?
397
+ end
398
+
399
+ #p options
400
+ #exit
401
+ if param["redirect"].given?
402
+ open(param["redirect"].value, "a+") do |fd|
403
+ STDERR.reopen fd
404
+ STDOUT.reopen fd
405
+ end
406
+ STDERR.sync = true
407
+ STDOUT.sync = true
408
+ end
409
+
410
+ trap("SIGTERM"){
411
+ info{ "SIGTERM" }
412
+ exit
413
+ }
414
+
415
+ if param["instance"].given?
416
+ Bj.hostname = Bj.hostname + "." + param["instance"].value
417
+ end
418
+
419
+ if param["daemon"].given?
420
+ daemon{ Bj.run options }
421
+ else
422
+ Bj.run options
423
+ end
424
+ end
425
+ end
426
+
427
+ mode "submit" do
428
+ keyword("file"){
429
+ argument :required
430
+ attr
431
+ }
432
+
433
+ def run
434
+ joblist = Bj.joblist.for argv.join(' ')
435
+
436
+ case file
437
+ when "-"
438
+ joblist.push(Bj.joblist.jobs_from_io(STDIN))
439
+ when "--", "---"
440
+ joblist.push(Bj.joblist.jobs_from_yaml(STDIN))
441
+ else
442
+ open(file){|io| joblist.push(Bj.joblist.jobs_from_io(io)) }
443
+ end
444
+
445
+ jobs = Bj.submit joblist, :no_tickle => true
446
+
447
+ oh = lambda{|job| OrderedHash["id", job.id, "command", job.command]}
448
+
449
+ y jobs.map{|job| oh[job]}
450
+ end
451
+ end
452
+
453
+ mode "list" do
454
+ def run
455
+ Bj.transaction do
456
+ y Bj::Table::Job.find(:all).map(&:to_hash)
457
+ end
458
+ end
459
+ end
460
+
461
+ mode "set" do
462
+ argument("key"){ attr }
463
+
464
+ argument("value"){ attr }
465
+
466
+ option("hostname", "H"){
467
+ argument :required
468
+ default Bj.hostname
469
+ attr
470
+ }
471
+
472
+ option("cast", "c"){
473
+ argument :required
474
+ default "to_s"
475
+ attr
476
+ }
477
+
478
+ def run
479
+ Bj.transaction do
480
+ Bj.config.set(key, value, :hostname => hostname, :cast => cast)
481
+ y Bj.table.config.for(:hostname => hostname)
482
+ end
483
+ end
484
+ end
485
+
486
+ mode "config" do
487
+ option("hostname", "H"){
488
+ argument :required
489
+ default Bj.hostname
490
+ }
491
+
492
+ def run
493
+ Bj.transaction do
494
+ y Bj.table.config.for(:hostname => param["hostname"].value)
495
+ end
496
+ end
497
+ end
498
+
499
+ mode "pid" do
500
+ option("hostname", "H"){
501
+ argument :required
502
+ default Bj.hostname
503
+ }
504
+
505
+ def run
506
+ Bj.transaction do
507
+ config = Bj.table.config.for(:hostname => param["hostname"].value)
508
+ puts config[ "#{ RAILS_ENV }.pid" ] if config
509
+ end
510
+ end
511
+ end
512
+
513
+
514
+ def run
515
+ help!
516
+ end
517
+
518
+ def before_run
519
+ self.logger = param["log"].value
520
+ Bj.logger = logger
521
+ set_rails_root(param["rails_root"].value) if param["rails_root"].given?
522
+ set_rails_env(param["rails_env"].value) if param["rails_env"].given?
523
+ end
524
+
525
+ def set_rails_root rails_root
526
+ ENV["RAILS_ROOT"] = rails_root
527
+ ::Object.instance_eval do
528
+ remove_const :RAILS_ROOT
529
+ const_set :RAILS_ROOT, rails_root
530
+ end
531
+ end
532
+
533
+ def set_rails_env rails_env
534
+ ENV["RAILS_ENV"] = rails_env
535
+ ::Object.instance_eval do
536
+ remove_const :RAILS_ENV
537
+ const_set :RAILS_ENV, rails_env
538
+ end
539
+ end
540
+
541
+ def daemon
542
+ ra, wa = IO.pipe
543
+ rb, wb = IO.pipe
544
+ if fork
545
+ at_exit{ exit! }
546
+ wa.close
547
+ r = ra
548
+ rb.close
549
+ w = wb
550
+ pid = r.gets
551
+ w.puts pid
552
+ Integer pid.strip
553
+ else
554
+ ra.close
555
+ w = wa
556
+ wb.close
557
+ r = rb
558
+ open("/dev/null", "r+") do |fd|
559
+ STDIN.reopen fd
560
+ STDOUT.reopen fd
561
+ STDERR.reopen fd
562
+ end
563
+ Process::setsid rescue nil
564
+ pid =
565
+ fork do
566
+ Dir::chdir RAILS_ROOT
567
+ File::umask 0
568
+ $DAEMON = true
569
+ yield
570
+ exit!
571
+ end
572
+ w.puts pid
573
+ r.gets
574
+ exit!
575
+ end
576
+ end
577
+ }
578
+
579
+
580
+
581
+
582
+
583
+ #
584
+ # we setup a few things so the script works regardless of whether it was
585
+ # called out of /usr/local/bin, ./script, or wherever. note that the script
586
+ # does *not* require the entire rails application to be loaded into memory!
587
+ # we could just load boot.rb and environment.rb, but this method let's
588
+ # submitting and running jobs be infinitely more lightweight.
589
+ #
590
+
591
+ BEGIN {
592
+ #
593
+ # see if we're running out of RAILS_ROOT/script/
594
+ #
595
+ unless defined?(BJ_SCRIPT)
596
+ BJ_SCRIPT =
597
+ if %w[ script config app ].map{|d| test ?d, "#{ File.dirname __FILE__ }/../#{ d }"}.all?
598
+ __FILE__
599
+ else
600
+ nil
601
+ end
602
+ end
603
+ #
604
+ # setup RAILS_ROOT
605
+ #
606
+ unless defined?(RAILS_ROOT)
607
+ ### grab env var first
608
+ rails_root = ENV["RAILS_ROOT"]
609
+
610
+ ### commandline usage clobbers
611
+ kv = nil
612
+ ARGV.delete_if{|arg| arg =~ %r/^RAILS_ROOT=/ and kv = arg}
613
+ rails_root = kv.split(%r/=/,2).last if kv
614
+
615
+ ### we know the rails_root if we are in RAILS_ROOT/script/
616
+ unless rails_root
617
+ if BJ_SCRIPT
618
+ rails_root = File.expand_path "#{ File.dirname __FILE__ }/.."
619
+ end
620
+ end
621
+
622
+ ### perhaps the current directory is a rails_root?
623
+ unless rails_root
624
+ if %w[ script config app ].map{|d| test(?d, d)}.all?
625
+ rails_root = File.expand_path "."
626
+ end
627
+ end
628
+
629
+ ### bootstrap
630
+ RAILS_ROOT = rails_root
631
+ end
632
+ #
633
+ # setup RAILS_ENV
634
+ #
635
+ unless defined?(RAILS_ENV)
636
+ ### grab env var first
637
+ rails_env = ENV["RAILS_ENV"]
638
+
639
+ ### commandline usage clobbers
640
+ kv = nil
641
+ ARGV.delete_if{|arg| arg =~ %r/^RAILS_ENV=/ and kv = arg}
642
+ rails_env = kv.split(%r/=/,2).last if kv
643
+
644
+ ### fallback to development
645
+ unless rails_env
646
+ rails_env = "development"
647
+ end
648
+
649
+ ### bootstrap
650
+ RAILS_ENV = rails_env
651
+ end
652
+ #
653
+ # ensure that rubygems is loaded
654
+ #
655
+ begin
656
+ require "rubygems"
657
+ rescue
658
+ 42
659
+ end
660
+ #
661
+ # load gems from plugin dir iff installed as plugin - otherwise load normally
662
+ #
663
+ if RAILS_ROOT and BJ_SCRIPT
664
+ =begin
665
+ dir = Gem.dir
666
+ path = Gem.path
667
+ gem_home = File.join RAILS_ROOT, "vendor", "plugins", "bj", "gem_home"
668
+ gem_path = [gem_home]
669
+ Gem.send :use_paths, gem_home, gem_path
670
+ gem "bj"
671
+ require "bj"
672
+ gem "main"
673
+ require "main"
674
+ =end
675
+ libdir = File.join(RAILS_ROOT, "vendor", "plugins", "bj", "lib")
676
+ $LOAD_PATH.unshift libdir
677
+ end
678
+ #
679
+ # hack of #to_s of STDERR/STDOUT for nice help messages
680
+ #
681
+ class << STDERR
682
+ def to_s() 'STDERR' end
683
+ end
684
+ class << STDOUT
685
+ def to_s() 'STDOUT' end
686
+ end
687
+ }