Commit e227e0e3d4402574e317db2fd372483a07e8c2ea
1 parent
d568d8b7
Exists in
master
and in
29 other branches
rails3: upgrading delayed_job
Showing
111 changed files
with
3703 additions
and
2006 deletions
Show diff stats
config/initializers/delayed_job_config.rb
db/schema.rb
1 | -# This file is auto-generated from the current state of the database. Instead of editing this file, | ||
2 | -# please use the migrations feature of Active Record to incrementally modify your database, and | ||
3 | -# then regenerate this schema definition. | 1 | +# encoding: UTF-8 |
2 | +# This file is auto-generated from the current state of the database. Instead | ||
3 | +# of editing this file, please use the migrations feature of Active Record to | ||
4 | +# incrementally modify your database, and then regenerate this schema definition. | ||
4 | # | 5 | # |
5 | -# Note that this schema.rb definition is the authoritative source for your database schema. If you need | ||
6 | -# to create the application database on another system, you should be using db:schema:load, not running | ||
7 | -# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations | 6 | +# Note that this schema.rb definition is the authoritative source for your |
7 | +# database schema. If you need to create the application database on another | ||
8 | +# system, you should be using db:schema:load, not running all the migrations | ||
9 | +# from scratch. The latter is a flawed and unsustainable approach (the more migrations | ||
8 | # you'll amass, the slower it'll run and the greater likelihood for issues). | 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). |
9 | # | 11 | # |
10 | # It's strongly recommended to check this file into your version control system. | 12 | # It's strongly recommended to check this file into your version control system. |
11 | 13 | ||
12 | -ActiveRecord::Schema.define(:version => 20130606110602) do | 14 | +ActiveRecord::Schema.define(:version => 20130924152827) do |
13 | 15 | ||
14 | create_table "abuse_reports", :force => true do |t| | 16 | create_table "abuse_reports", :force => true do |t| |
15 | t.integer "reporter_id" | 17 | t.integer "reporter_id" |
@@ -245,6 +247,7 @@ ActiveRecord::Schema.define(:version => 20130606110602) do | @@ -245,6 +247,7 @@ ActiveRecord::Schema.define(:version => 20130606110602) do | ||
245 | t.string "locked_by" | 247 | t.string "locked_by" |
246 | t.datetime "created_at" | 248 | t.datetime "created_at" |
247 | t.datetime "updated_at" | 249 | t.datetime "updated_at" |
250 | + t.string "queue" | ||
248 | end | 251 | end |
249 | 252 | ||
250 | add_index "delayed_jobs", ["priority", "run_at"], :name => "delayed_jobs_priority" | 253 | add_index "delayed_jobs", ["priority", "run_at"], :name => "delayed_jobs_priority" |
script/delayed_job
@@ -7,7 +7,5 @@ | @@ -7,7 +7,5 @@ | ||
7 | # etc. The actual feed update logic is DelayedJob plugin. | 7 | # etc. The actual feed update logic is DelayedJob plugin. |
8 | 8 | ||
9 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment')) | 9 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment')) |
10 | -require 'daemons' | ||
11 | require 'delayed/command' | 10 | require 'delayed/command' |
12 | - | ||
13 | Delayed::Command.new(ARGV).daemonize | 11 | Delayed::Command.new(ARGV).daemonize |
@@ -0,0 +1,22 @@ | @@ -0,0 +1,22 @@ | ||
1 | +language: ruby | ||
2 | +only: | ||
3 | + - master | ||
4 | +rvm: | ||
5 | + - jruby-19mode | ||
6 | + - rbx-19mode | ||
7 | + - 1.9.2 | ||
8 | + - 1.9.3 | ||
9 | + - 2.0.0 | ||
10 | +env: | ||
11 | + matrix: | ||
12 | + - "RAILS_VERSION=\"~> 3.0.0\"" | ||
13 | + - "RAILS_VERSION=\"~> 3.1.0\"" | ||
14 | + - "RAILS_VERSION=\"~> 3.2.0\"" | ||
15 | + - "RAILS_VERSION=\"~> 4.0.0\"" | ||
16 | +matrix: | ||
17 | + exclude: | ||
18 | + - rvm: 1.9.2 | ||
19 | + env: "RAILS_VERSION=\"~> 4.0.0\"" | ||
20 | + allow_failures: | ||
21 | + - rvm: jruby-19mode | ||
22 | + - rvm: rbx-19mode | ||
0 | \ No newline at end of file | 23 | \ No newline at end of file |
@@ -0,0 +1,170 @@ | @@ -0,0 +1,170 @@ | ||
1 | +4.0.0 - 2013-07-30 | ||
2 | +================== | ||
3 | +* Rails 4 compatibility | ||
4 | +* Reverted threaded startup due to daemons incompatibilities | ||
5 | +* Attempt to recover from job reservation errors | ||
6 | + | ||
7 | +4.0.0.beta2 - 2013-05-28 | ||
8 | +======================== | ||
9 | +* Rails 4 compatibility | ||
10 | +* Threaded startup script for faster multi-worker startup | ||
11 | +* YAML compatibility changes | ||
12 | +* Added jobs:check rake task | ||
13 | + | ||
14 | +4.0.0.beta1 - 2013-03-02 | ||
15 | +======================== | ||
16 | +* Rails 4 compatibility | ||
17 | + | ||
18 | +3.0.5 - 2013-01-28 | ||
19 | +================== | ||
20 | +* Better job timeout error logging | ||
21 | +* psych support for delayed_job_data_mapper deserialization | ||
22 | +* User can configure the worker to raise a SignalException on TERM and/or INT | ||
23 | +* Add the ability to run all available jobs and exit when complete | ||
24 | + | ||
25 | +3.0.4 - 2012-11-09 | ||
26 | +================== | ||
27 | +* Allow the app to specify a default queue name | ||
28 | +* Capistrano script now allows user to specify the DJ command, allowing the user to add "bundle exec" if necessary | ||
29 | +* Persisted record check is now more general | ||
30 | + | ||
31 | +3.0.3 - 2012-05-25 | ||
32 | +================== | ||
33 | +* Fix a bug where the worker would not respect the exit condition | ||
34 | +* Properly handle sleep delay command line argument | ||
35 | + | ||
36 | +3.0.2 - 2012-04-02 | ||
37 | +================== | ||
38 | +* Fix deprecation warnings | ||
39 | +* Raise ArgumentError if attempting to enqueue a performable method on an object that hasn't been persisted yet | ||
40 | +* Allow the number of jobs read at a time to be configured from the command line using --read-ahead | ||
41 | +* Allow custom logger to be configured through Delayed::Worker.logger | ||
42 | +* Various documentation improvements | ||
43 | + | ||
44 | +3.0.1 - 2012-01-24 | ||
45 | +================== | ||
46 | +* Added RecordNotFound message to deserialization error | ||
47 | +* Direct JRuby's yecht parser to syck extensions | ||
48 | +* Updated psych extensions for better compatibility with ruby 1.9.2 | ||
49 | +* Updated syck extension for increased compatibility with class methods | ||
50 | +* Test grooming | ||
51 | + | ||
52 | +3.0.0 - 2011-12-30 | ||
53 | +================== | ||
54 | +* New: Named queues | ||
55 | +* New: Job/Worker lifecycle callbacks | ||
56 | +* Change: daemons is no longer a runtime dependency | ||
57 | +* Change: Active Record backend support is provided by a separate gem | ||
58 | +* Change: Enqueue hook is called before jobs are saved so that they may be modified | ||
59 | +* Fix problem deserializing models that use a custom primary key column | ||
60 | +* Fix deserializing AR models when the object isn't in the default scope | ||
61 | +* Fix hooks not getting called when delay_jobs is false | ||
62 | + | ||
63 | +2.1.4 - 2011-02-11 | ||
64 | +================== | ||
65 | +* Working around issues when psych is loaded, fixes issues with bundler 1.0.10 and Rails 3.0.4 | ||
66 | +* Added -p/--prefix option to help differentiate multiple delayed job workers on the same host. | ||
67 | + | ||
68 | +2.1.3 - 2011-01-20 | ||
69 | +================== | ||
70 | +* Revert worker contention fix due to regressions | ||
71 | +* Added Delayed::Worker.delay_jobs flag to support running jobs immediately | ||
72 | + | ||
73 | +2.1.2 - 2010-12-01 | ||
74 | +================== | ||
75 | +* Remove contention between multiple workers by performing an update to lock a job before fetching it | ||
76 | +* Job payloads may implement #max_attempts to control how many times it should be retried | ||
77 | +* Fix for loading ActionMailer extension | ||
78 | +* Added 'delayed_job_server_role' Capistrano variable to allow delayed_job to run on its own worker server | ||
79 | + set :delayed_job_server_role, :worker | ||
80 | +* Fix `rake jobs:work` so it outputs to the console | ||
81 | + | ||
82 | +2.1.1 - 2010-11-14 | ||
83 | +================== | ||
84 | +* Fix issue with worker name not getting properly set when locking a job | ||
85 | +* Fixes for YAML serialization | ||
86 | + | ||
87 | +2.1.0 - 2010-11-14 | ||
88 | +================== | ||
89 | +* Added enqueue, before, after, success, error, and failure. See the README | ||
90 | +* Remove Merb support | ||
91 | +* Remove all non Active Record backends into separate gems. See https://github.com/collectiveidea/delayed_job/wiki/Backends | ||
92 | +* remove rails 2 support. delayed_job 2.1 will only support Rails 3 | ||
93 | +* New pure-YAML serialization | ||
94 | +* Added Rails 3 railtie and generator | ||
95 | +* Changed @@sleep_delay to self.class.sleep_delay to be consistent with other class variable usage | ||
96 | +* Added --sleep-delay command line option | ||
97 | + | ||
98 | +2.0.8 - Unreleased | ||
99 | +================== | ||
100 | +* Backport fix for deserialization errors that bring down the daemon | ||
101 | + | ||
102 | +2.0.7 - 2011-02-10 | ||
103 | +================== | ||
104 | +* Fixed missing generators and recipes for Rails 2.x | ||
105 | + | ||
106 | +2.0.6 - 2011-01-20 | ||
107 | +================== | ||
108 | +* Revert worker contention fix due to regressions | ||
109 | + | ||
110 | +2.0.5 - 2010-12-01 | ||
111 | +================== | ||
112 | +* Added #reschedule_at hook on payload to determine when the job should be rescheduled [backported from 2.1] | ||
113 | +* Added --sleep-delay command line option [backported from 2.1] | ||
114 | +* Added 'delayed_job_server_role' Capistrano variable to allow delayed_job to run on its own worker server | ||
115 | + set :delayed_job_server_role, :worker | ||
116 | +* Changed AR backend to reserve jobs using an UPDATE query to reduce worker contention [backported from 2.1] | ||
117 | + | ||
118 | +2.0.4 - 2010-11-14 | ||
119 | +================== | ||
120 | +* Fix issue where dirty tracking prevented job from being properly unlocked | ||
121 | +* Add delayed_job_args variable for Capistrano recipe to allow configuration of started workers (e.g. "-n 2 --max-priority 10") | ||
122 | +* Added options to handle_asynchronously | ||
123 | +* Added Delayed::Worker.default_priority | ||
124 | +* Allow private methods to be delayed | ||
125 | +* Fixes for Ruby 1.9 | ||
126 | +* Added -m command line option to start a monitor process | ||
127 | +* normalize logging in worker | ||
128 | +* Deprecate #send_later and #send_at in favor of new #delay method | ||
129 | +* Added @#delay@ to Object that allows you to delay any method and pass options: | ||
130 | + options = {:priority => 19, :run_at => 5.minutes.from_now} | ||
131 | + UserMailer.delay(options).deliver_confirmation(@user) | ||
132 | + | ||
133 | +2.0.3 - 2010-04-16 | ||
134 | +================== | ||
135 | +* Fix initialization for Rails 2.x | ||
136 | + | ||
137 | +2.0.2 - 2010-04-08 | ||
138 | +================== | ||
139 | +* Fixes to Mongo Mapper backend [ "14be7a24":http://github.com/collectiveidea/delayed_job/commit/14be7a24, "dafd5f46":http://github.com/collectiveidea/delayed_job/commit/dafd5f46, "54d40913":http://github.com/collectiveidea/delayed_job/commit/54d40913 ] | ||
140 | +* DataMapper backend performance improvements [ "93833cce":http://github.com/collectiveidea/delayed_job/commit/93833cce, "e9b1573e":http://github.com/collectiveidea/delayed_job/commit/e9b1573e, "37a16d11":http://github.com/collectiveidea/delayed_job/commit/37a16d11, "803f2bfa":http://github.com/collectiveidea/delayed_job/commit/803f2bfa ] | ||
141 | +* Fixed Delayed::Command to create tmp/pids directory [ "8ec8ca41":http://github.com/collectiveidea/delayed_job/commit/8ec8ca41 ] | ||
142 | +* Railtie to perform Rails 3 initialization [ "3e0fc41f":http://github.com/collectiveidea/delayed_job/commit/3e0fc41f ] | ||
143 | +* Added on_permanent_failure hook [ "d2f14cd6":http://github.com/collectiveidea/delayed_job/commit/d2f14cd6 ] | ||
144 | + | ||
145 | +2.0.1 - 2010-04-03 | ||
146 | +================== | ||
147 | +* Bug fix for using ActiveRecord backend with daemon [martinbtt] | ||
148 | + | ||
149 | +2.0.0 - 2010-04-03 | ||
150 | +================== | ||
151 | +* Multiple backend support (See README for more details) | ||
152 | +* Added MongoMapper backend [zbelzer, moneypools] | ||
153 | +* Added DataMapper backend [lpetre] | ||
154 | +* Reverse priority so the jobs table can be indexed. Lower numbers have higher priority. The default priority is 0, so increase it for jobs that are not important. | ||
155 | +* Move most of the heavy lifting from Job to Worker (#work_off, #reschedule, #run, #min_priority, #max_priority, #max_run_time, #max_attempts, #worker_name) [albus522] | ||
156 | +* Remove EvaledJob. Implement your own if you need this functionality. | ||
157 | +* Only use Time.zone if it is set. Closes #20 | ||
158 | +* Fix for last_error recording when destroy_failed_jobs = false, max_attempts = 1 | ||
159 | +* Implemented worker name_prefix to maintain dynamic nature of pid detection | ||
160 | +* Some Rails 3 compatibility fixes [fredwu] | ||
161 | + | ||
162 | +1.8.5 - 2010-03-15 | ||
163 | +================== | ||
164 | +* Set auto_flushing=true on Rails logger to fix logging in production | ||
165 | +* Fix error message when trying to send_later on a method that doesn't exist | ||
166 | +* Don't use rails_env in capistrano if it's not set. closes #22 | ||
167 | +* Delayed job should append to delayed_job.log not overwrite | ||
168 | +* Version bump to 1.8.5 | ||
169 | +* fixing Time.now to be Time.zone.now if set to honor the app set local TimeZone | ||
170 | +* Replaced @Worker::SLEEP@, @Job::MAX_ATTEMPTS@, and @Job::MAX_RUN_TIME@ with class methods that can be overridden. |
@@ -0,0 +1,27 @@ | @@ -0,0 +1,27 @@ | ||
1 | +How to contribute | ||
2 | +================= | ||
3 | + | ||
4 | +If you find what looks like a bug: | ||
5 | + | ||
6 | +* Search the "mailing list":http://groups.google.com/group/delayed_job to see | ||
7 | + if anyone else had the same issue. | ||
8 | +* Check the "GitHub issue tracker":http://github.com/collectiveidea/delayed_job/issues/ | ||
9 | + to see if anyone else has reported issue. | ||
10 | +* Make sure you are using the latest version of delayed_job | ||
11 | +  | ||
12 | +* Make sure you are using the latest backend gem for delayed_job | ||
13 | + * Active Record  | ||
14 | + * Mongoid  | ||
15 | +* If you are still having an issue, create an issue including: | ||
16 | + * Ruby version | ||
17 | + * Gemfile.lock contents or at least major gem versions, such as Rails version | ||
18 | + * Steps to reproduce the issue | ||
19 | + * Full backtrace for any errors encountered | ||
20 | + | ||
21 | +If you want to contribute an enhancement or a fix: | ||
22 | + | ||
23 | +* Fork the project on GitHub. | ||
24 | +* Make your changes with tests. | ||
25 | +* Commit the changes without making changes to the Rakefile or any other files | ||
26 | + that aren't related to your enhancement or fix. | ||
27 | +* Send a pull request. |
@@ -0,0 +1,22 @@ | @@ -0,0 +1,22 @@ | ||
1 | +source 'https://rubygems.org' | ||
2 | + | ||
3 | +gem 'rake' | ||
4 | + | ||
5 | +platforms :ruby do | ||
6 | + gem 'sqlite3' | ||
7 | +end | ||
8 | + | ||
9 | +platforms :jruby do | ||
10 | + gem 'jruby-openssl' | ||
11 | + gem 'activerecord-jdbcsqlite3-adapter' | ||
12 | +end | ||
13 | + | ||
14 | +group :test do | ||
15 | + gem 'activerecord', (ENV['RAILS_VERSION'] || ['>= 3.0', '< 4.1']) | ||
16 | + gem 'actionmailer', (ENV['RAILS_VERSION'] || ['>= 3.0', '< 4.1']) | ||
17 | + gem 'coveralls', :require => false | ||
18 | + gem 'rspec', '>= 2.11' | ||
19 | + gem 'simplecov', :require => false | ||
20 | +end | ||
21 | + | ||
22 | +gemspec |
@@ -0,0 +1,20 @@ | @@ -0,0 +1,20 @@ | ||
1 | +Copyright (c) 2005 Tobias Lütke | ||
2 | + | ||
3 | +Permission is hereby granted, free of charge, to any person obtaining | ||
4 | +a copy of this software and associated documentation files (the | ||
5 | +"Software"), to deal in the Software without restriction, including | ||
6 | +without limitation the rights to use, copy, modify, merge, publish, | ||
7 | +distribute, sublicense, and/or sell copies of the Software, and to | ||
8 | +permit persons to whom the Software is furnished to do so, subject to | ||
9 | +the following conditions: | ||
10 | + | ||
11 | +The above copyright notice and this permission notice shall be | ||
12 | +included in all copies or substantial portions of the Software. | ||
13 | + | ||
14 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
15 | +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
16 | +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOa AND | ||
17 | +NONINFRINGEMENT. IN NO EVENT SaALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
18 | +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
19 | +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
20 | +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
vendor/plugins/delayed_job/MIT-LICENSE
@@ -1,20 +0,0 @@ | @@ -1,20 +0,0 @@ | ||
1 | -Copyright (c) 2005 Tobias Luetke | ||
2 | - | ||
3 | -Permission is hereby granted, free of charge, to any person obtaining | ||
4 | -a copy of this software and associated documentation files (the | ||
5 | -"Software"), to deal in the Software without restriction, including | ||
6 | -without limitation the rights to use, copy, modify, merge, publish, | ||
7 | -distribute, sublicense, and/or sell copies of the Software, and to | ||
8 | -permit persons to whom the Software is furnished to do so, subject to | ||
9 | -the following conditions: | ||
10 | - | ||
11 | -The above copyright notice and this permission notice shall be | ||
12 | -included in all copies or substantial portions of the Software. | ||
13 | - | ||
14 | -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
15 | -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
16 | -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOa AND | ||
17 | -NONINFRINGEMENT. IN NO EVENT SaALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
18 | -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
19 | -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
20 | -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
21 | \ No newline at end of file | 0 | \ No newline at end of file |
@@ -0,0 +1,346 @@ | @@ -0,0 +1,346 @@ | ||
1 | +Delayed::Job | ||
2 | +============ | ||
3 | +[][gem] | ||
4 | +[][travis] | ||
5 | +[][gemnasium] | ||
6 | +[][codeclimate] | ||
7 | +[][coveralls] | ||
8 | + | ||
9 | +[gem]: https://rubygems.org/gems/delayed_job | ||
10 | +[travis]: http://travis-ci.org/collectiveidea/delayed_job | ||
11 | +[gemnasium]: https://gemnasium.com/collectiveidea/delayed_job | ||
12 | +[codeclimate]: https://codeclimate.com/github/collectiveidea/delayed_job | ||
13 | +[coveralls]: https://coveralls.io/r/collectiveidea/delayed_job | ||
14 | + | ||
15 | +Delayed::Job (or DJ) encapsulates the common pattern of asynchronously executing | ||
16 | +longer tasks in the background. | ||
17 | + | ||
18 | +It is a direct extraction from Shopify where the job table is responsible for a | ||
19 | +multitude of core tasks. Amongst those tasks are: | ||
20 | + | ||
21 | +* sending massive newsletters | ||
22 | +* image resizing | ||
23 | +* http downloads | ||
24 | +* updating smart collections | ||
25 | +* updating solr, our search server, after product changes | ||
26 | +* batch imports | ||
27 | +* spam checks | ||
28 | + | ||
29 | +[Follow us on Twitter][twitter] to get updates and notices about new releases. | ||
30 | + | ||
31 | +[twitter]: https://twitter.com/delayedjob | ||
32 | + | ||
33 | +Installation | ||
34 | +============ | ||
35 | +delayed_job 3.0.0 only supports Rails 3.0+. See the [2.0 | ||
36 | +branch](https://github.com/collectiveidea/delayed_job/tree/v2.0) for Rails 2. | ||
37 | + | ||
38 | +delayed_job supports multiple backends for storing the job queue. [See the wiki | ||
39 | +for other backends](http://wiki.github.com/collectiveidea/delayed_job/backends). | ||
40 | + | ||
41 | +If you plan to use delayed_job with Active Record, add `delayed_job_active_record` to your `Gemfile`. | ||
42 | + | ||
43 | +```ruby | ||
44 | +gem 'delayed_job_active_record' | ||
45 | +``` | ||
46 | + | ||
47 | +If you plan to use delayed_job with Mongoid, add `delayed_job_mongoid` to your `Gemfile`. | ||
48 | + | ||
49 | +```ruby | ||
50 | +gem 'delayed_job_mongoid' | ||
51 | +``` | ||
52 | + | ||
53 | +Run `bundle install` to install the backend and delayed_job gems. | ||
54 | + | ||
55 | +The Active Record backend requires a jobs table. You can create that table by | ||
56 | +running the following command: | ||
57 | + | ||
58 | + rails generate delayed_job:active_record | ||
59 | + rake db:migrate | ||
60 | + | ||
61 | +Rails 4 | ||
62 | +======= | ||
63 | +If you are using the protected_attributes gem, it must appear before delayed_job in your gemfile. | ||
64 | + | ||
65 | +Upgrading from 2.x to 3.0.0 on Active Record | ||
66 | +============================================ | ||
67 | +Delayed Job 3.0.0 introduces a new column to the delayed_jobs table. | ||
68 | + | ||
69 | +If you're upgrading from Delayed Job 2.x, run the upgrade generator to create a migration to add the column. | ||
70 | + | ||
71 | + rails generate delayed_job:upgrade | ||
72 | + rake db:migrate | ||
73 | + | ||
74 | +Queuing Jobs | ||
75 | +============ | ||
76 | +Call `.delay.method(params)` on any object and it will be processed in the background. | ||
77 | + | ||
78 | +```ruby | ||
79 | +# without delayed_job | ||
80 | +@user.activate!(@device) | ||
81 | + | ||
82 | +# with delayed_job | ||
83 | +@user.delay.activate!(@device) | ||
84 | +``` | ||
85 | + | ||
86 | +If a method should always be run in the background, you can call | ||
87 | +`#handle_asynchronously` after the method declaration: | ||
88 | + | ||
89 | +```ruby | ||
90 | +class Device | ||
91 | + def deliver | ||
92 | + # long running method | ||
93 | + end | ||
94 | + handle_asynchronously :deliver | ||
95 | +end | ||
96 | + | ||
97 | +device = Device.new | ||
98 | +device.deliver | ||
99 | +``` | ||
100 | + | ||
101 | +handle_asynchronously can take as options anything you can pass to delay. In | ||
102 | +addition, the values can be Proc objects allowing call time evaluation of the | ||
103 | +value. For some examples: | ||
104 | + | ||
105 | +```ruby | ||
106 | +class LongTasks | ||
107 | + def send_mailer | ||
108 | + # Some other code | ||
109 | + end | ||
110 | + handle_asynchronously :send_mailer, :priority => 20 | ||
111 | + | ||
112 | + def in_the_future | ||
113 | + # Some other code | ||
114 | + end | ||
115 | + # 5.minutes.from_now will be evaluated when in_the_future is called | ||
116 | + handle_asynchronously :in_the_future, :run_at => Proc.new { 5.minutes.from_now } | ||
117 | + | ||
118 | + def self.when_to_run | ||
119 | + 2.hours.from_now | ||
120 | + end | ||
121 | + | ||
122 | + def call_a_class_method | ||
123 | + # Some other code | ||
124 | + end | ||
125 | + handle_asynchronously :call_a_class_method, :run_at => Proc.new { when_to_run } | ||
126 | + | ||
127 | + attr_reader :how_important | ||
128 | + | ||
129 | + def call_an_instance_method | ||
130 | + # Some other code | ||
131 | + end | ||
132 | + handle_asynchronously :call_an_instance_method, :priority => Proc.new {|i| i.how_important } | ||
133 | +end | ||
134 | +``` | ||
135 | + | ||
136 | +If you ever want to call a `handle_asynchronously`'d method without Delayed Job, for instance while debugging something at the console, just add `_without_delay` to the method name. For instance, if your original method was `foo`, then call `foo_without_delay`. | ||
137 | + | ||
138 | +Rails 3 Mailers | ||
139 | +=============== | ||
140 | +Due to how mailers are implemented in Rails 3, we had to do a little work around to get delayed_job to work. | ||
141 | + | ||
142 | +```ruby | ||
143 | +# without delayed_job | ||
144 | +Notifier.signup(@user).deliver | ||
145 | + | ||
146 | +# with delayed_job | ||
147 | +Notifier.delay.signup(@user) | ||
148 | +``` | ||
149 | + | ||
150 | +Remove the `.deliver` method to make it work. It's not ideal, but it's the best | ||
151 | +we could do for now. | ||
152 | + | ||
153 | +Named Queues | ||
154 | +============ | ||
155 | +DJ 3 introduces Resque-style named queues while still retaining DJ-style | ||
156 | +priority. The goal is to provide a system for grouping tasks to be worked by | ||
157 | +separate pools of workers, which may be scaled and controlled individually. | ||
158 | + | ||
159 | +Jobs can be assigned to a queue by setting the `queue` option: | ||
160 | + | ||
161 | +```ruby | ||
162 | +object.delay(:queue => 'tracking').method | ||
163 | + | ||
164 | +Delayed::Job.enqueue job, :queue => 'tracking' | ||
165 | + | ||
166 | +handle_asynchronously :tweet_later, :queue => 'tweets' | ||
167 | +``` | ||
168 | + | ||
169 | +Running Jobs | ||
170 | +============ | ||
171 | +`script/delayed_job` can be used to manage a background process which will | ||
172 | +start working off jobs. | ||
173 | + | ||
174 | +To do so, add `gem "daemons"` to your `Gemfile` and make sure you've run `rails | ||
175 | +generate delayed_job`. | ||
176 | + | ||
177 | +You can then do the following: | ||
178 | + | ||
179 | + RAILS_ENV=production script/delayed_job start | ||
180 | + RAILS_ENV=production script/delayed_job stop | ||
181 | + | ||
182 | + # Runs two workers in separate processes. | ||
183 | + RAILS_ENV=production script/delayed_job -n 2 start | ||
184 | + RAILS_ENV=production script/delayed_job stop | ||
185 | + | ||
186 | + # Set the --queue or --queues option to work from a particular queue. | ||
187 | + RAILS_ENV=production script/delayed_job --queue=tracking start | ||
188 | + RAILS_ENV=production script/delayed_job --queues=mailers,tasks start | ||
189 | + | ||
190 | + # Runs all available jobs and then exits | ||
191 | + RAILS_ENV=production script/delayed_job start --exit-on-complete | ||
192 | + # or to run in the foreground | ||
193 | + RAILS_ENV=production script/delayed_job run --exit-on-complete | ||
194 | + | ||
195 | +**Rails 4:** *replace script/delayed_job with bin/delayed_job* | ||
196 | + | ||
197 | +Workers can be running on any computer, as long as they have access to the | ||
198 | +database and their clock is in sync. Keep in mind that each worker will check | ||
199 | +the database at least every 5 seconds. | ||
200 | + | ||
201 | +You can also invoke `rake jobs:work` which will start working off jobs. You can | ||
202 | +cancel the rake task with `CTRL-C`. | ||
203 | + | ||
204 | +If you want to just run all available jobs and exit you can use `rake jobs:workoff` | ||
205 | + | ||
206 | +Work off queues by setting the `QUEUE` or `QUEUES` environment variable. | ||
207 | + | ||
208 | + QUEUE=tracking rake jobs:work | ||
209 | + QUEUES=mailers,tasks rake jobs:work | ||
210 | + | ||
211 | +Restarting delayed_job | ||
212 | +====================== | ||
213 | + | ||
214 | +The following syntax will restart delayed jobs: | ||
215 | + | ||
216 | + RAILS_ENV=production script/delayed_job restart | ||
217 | + | ||
218 | +To restart multiple delayed_job workers: | ||
219 | + | ||
220 | + RAILS_ENV=production script/delayed_job -n2 restart | ||
221 | + | ||
222 | +**Rails 4:** *replace script/delayed_job with bin/delayed_job* | ||
223 | + | ||
224 | + | ||
225 | + | ||
226 | +Custom Jobs | ||
227 | +=========== | ||
228 | +Jobs are simple ruby objects with a method called perform. Any object which responds to perform can be stuffed into the jobs table. Job objects are serialized to yaml so that they can later be resurrected by the job runner. | ||
229 | + | ||
230 | +```ruby | ||
231 | +class NewsletterJob < Struct.new(:text, :emails) | ||
232 | + def perform | ||
233 | + emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) } | ||
234 | + end | ||
235 | +end | ||
236 | + | ||
237 | +Delayed::Job.enqueue NewsletterJob.new('lorem ipsum...', Customers.find(:all).collect(&:email)) | ||
238 | +``` | ||
239 | +To set a per-job max attempts that overrides the Delayed::Worker.max_attempts you can define a max_attempts method on the job | ||
240 | +```ruby | ||
241 | +class NewsletterJob < Struct.new(:text, :emails) | ||
242 | + def perform | ||
243 | + emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) } | ||
244 | + end | ||
245 | + | ||
246 | + def max_attempts | ||
247 | + return 3 | ||
248 | + end | ||
249 | +end | ||
250 | +```` | ||
251 | + | ||
252 | + | ||
253 | +Hooks | ||
254 | +===== | ||
255 | +You can define hooks on your job that will be called at different stages in the process: | ||
256 | + | ||
257 | +```ruby | ||
258 | +class ParanoidNewsletterJob < NewsletterJob | ||
259 | + def enqueue(job) | ||
260 | + record_stat 'newsletter_job/enqueue' | ||
261 | + end | ||
262 | + | ||
263 | + def perform | ||
264 | + emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) } | ||
265 | + end | ||
266 | + | ||
267 | + def before(job) | ||
268 | + record_stat 'newsletter_job/start' | ||
269 | + end | ||
270 | + | ||
271 | + def after(job) | ||
272 | + record_stat 'newsletter_job/after' | ||
273 | + end | ||
274 | + | ||
275 | + def success(job) | ||
276 | + record_stat 'newsletter_job/success' | ||
277 | + end | ||
278 | + | ||
279 | + def error(job, exception) | ||
280 | + Airbrake.notify(exception) | ||
281 | + end | ||
282 | + | ||
283 | + def failure(job) | ||
284 | + page_sysadmin_in_the_middle_of_the_night | ||
285 | + end | ||
286 | +end | ||
287 | +``` | ||
288 | + | ||
289 | +Gory Details | ||
290 | +============ | ||
291 | +The library revolves around a delayed_jobs table which looks as follows: | ||
292 | + | ||
293 | +```ruby | ||
294 | +create_table :delayed_jobs, :force => true do |table| | ||
295 | + table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue | ||
296 | + table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually. | ||
297 | + table.text :handler # YAML-encoded string of the object that will do work | ||
298 | + table.text :last_error # reason for last failure (See Note below) | ||
299 | + table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future. | ||
300 | + table.datetime :locked_at # Set when a client is working on this object | ||
301 | + table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead) | ||
302 | + table.string :locked_by # Who is working on this object (if locked) | ||
303 | + table.string :queue # The name of the queue this job is in | ||
304 | + table.timestamps | ||
305 | +end | ||
306 | +``` | ||
307 | + | ||
308 | +On failure, the job is scheduled again in 5 seconds + N ** 4, where N is the number of retries. | ||
309 | + | ||
310 | +The default Worker.max_attempts is 25. After this, the job either deleted (default), or left in the database with "failed_at" set. | ||
311 | +With the default of 25 attempts, the last retry will be 20 days later, with the last interval being almost 100 hours. | ||
312 | + | ||
313 | +The default Worker.max_run_time is 4.hours. If your job takes longer than that, another computer could pick it up. It's up to you to | ||
314 | +make sure your job doesn't exceed this time. You should set this to the longest time you think the job could take. | ||
315 | + | ||
316 | +By default, it will delete failed jobs (and it always deletes successful jobs). If you want to keep failed jobs, set | ||
317 | +Delayed::Worker.destroy_failed_jobs = false. The failed jobs will be marked with non-null failed_at. | ||
318 | + | ||
319 | +By default all jobs are scheduled with priority = 0, which is top priority. You can change this by setting Delayed::Worker.default_priority to something else. Lower numbers have higher priority. | ||
320 | + | ||
321 | +The default behavior is to read 5 jobs from the queue when finding an available job. You can configure this by setting Delayed::Worker.read_ahead. | ||
322 | + | ||
323 | +By default all jobs will be queued without a named queue. A default named queue can be specified by using Delayed::Worker.default_queue_name. | ||
324 | + | ||
325 | +It is possible to disable delayed jobs for testing purposes. Set Delayed::Worker.delay_jobs = false to execute all jobs realtime. | ||
326 | + | ||
327 | +Here is an example of changing job parameters in Rails: | ||
328 | + | ||
329 | +```ruby | ||
330 | +# config/initializers/delayed_job_config.rb | ||
331 | +Delayed::Worker.destroy_failed_jobs = false | ||
332 | +Delayed::Worker.sleep_delay = 60 | ||
333 | +Delayed::Worker.max_attempts = 3 | ||
334 | +Delayed::Worker.max_run_time = 5.minutes | ||
335 | +Delayed::Worker.read_ahead = 10 | ||
336 | +Delayed::Worker.default_queue_name = 'default' | ||
337 | +Delayed::Worker.delay_jobs = !Rails.env.test? | ||
338 | +``` | ||
339 | + | ||
340 | +Cleaning up | ||
341 | +=========== | ||
342 | +You can invoke `rake jobs:clear` to delete all jobs in the queue. | ||
343 | + | ||
344 | +Mailing List | ||
345 | +============ | ||
346 | +Join us on the [mailing list](http://groups.google.com/group/delayed_job) |
vendor/plugins/delayed_job/README.textile
@@ -1,209 +0,0 @@ | @@ -1,209 +0,0 @@ | ||
1 | -h1. Delayed::Job | ||
2 | - | ||
3 | -Delated_job (or DJ) encapsulates the common pattern of asynchronously executing longer tasks in the background. | ||
4 | - | ||
5 | -It is a direct extraction from Shopify where the job table is responsible for a multitude of core tasks. Amongst those tasks are: | ||
6 | - | ||
7 | -* sending massive newsletters | ||
8 | -* image resizing | ||
9 | -* http downloads | ||
10 | -* updating smart collections | ||
11 | -* batch imports | ||
12 | -* spam checks | ||
13 | - | ||
14 | -h2. Installation | ||
15 | - | ||
16 | -To install as a gem, add the following to @config/environment.rb@: | ||
17 | - | ||
18 | -<pre> | ||
19 | -config.gem 'delayed_job' | ||
20 | -</pre> | ||
21 | - | ||
22 | -Rake tasks are not automatically loaded from gems, so you'll need to add the following to your Rakefile: | ||
23 | - | ||
24 | -<pre> | ||
25 | -begin | ||
26 | - require 'delayed/tasks' | ||
27 | -rescue LoadError | ||
28 | - STDERR.puts "Run `rake gems:install` to install delayed_job" | ||
29 | -end | ||
30 | -</pre> | ||
31 | - | ||
32 | -To install as a plugin: | ||
33 | - | ||
34 | -<pre> | ||
35 | -script/plugin install git://github.com/collectiveidea/delayed_job.git | ||
36 | -</pre> | ||
37 | - | ||
38 | -After delayed_job is installed, you will need to setup the backend. | ||
39 | - | ||
40 | -h2. Backends | ||
41 | - | ||
42 | -delayed_job supports multiple backends for storing the job queue. There are currently implementations for Active Record, MongoMapper, and DataMapper. | ||
43 | - | ||
44 | -h3. Active Record | ||
45 | - | ||
46 | -The default is Active Record, which requires a jobs table. | ||
47 | - | ||
48 | -<pre> | ||
49 | -$ script/generate delayed_job | ||
50 | -$ rake db:migrate | ||
51 | -</pre> | ||
52 | - | ||
53 | -h3. MongoMapper | ||
54 | - | ||
55 | -You must use @MongoMapper.setup@ in the initializer: | ||
56 | - | ||
57 | -<pre> | ||
58 | -config = YAML::load(File.read(Rails.root.join('config/mongo.yml'))) | ||
59 | -MongoMapper.setup(config, Rails.env) | ||
60 | - | ||
61 | -Delayed::Worker.backend = :mongo_mapper | ||
62 | -</pre> | ||
63 | - | ||
64 | -h3. DataMapper | ||
65 | - | ||
66 | -<pre> | ||
67 | -# config/initializers/delayed_job.rb | ||
68 | -Delayed::Worker.backend = :data_mapper | ||
69 | -Delayed::Worker.backend.auto_upgrade! | ||
70 | -</pre> | ||
71 | - | ||
72 | -h2. Queuing Jobs | ||
73 | - | ||
74 | -Call @.delay.method(params)@ on any object and it will be processed in the background. | ||
75 | - | ||
76 | -<pre> | ||
77 | -# without delayed_job | ||
78 | -Notifier.deliver_signup(@user) | ||
79 | - | ||
80 | -# with delayed_job | ||
81 | -Notifier.delay.deliver_signup @user | ||
82 | -</pre> | ||
83 | - | ||
84 | -If a method should always be run in the background, you can call @#handle_asynchronously@ after the method declaration: | ||
85 | - | ||
86 | -<pre> | ||
87 | -class Device | ||
88 | - def deliver | ||
89 | - # long running method | ||
90 | - end | ||
91 | - handle_asynchronously :deliver | ||
92 | -end | ||
93 | - | ||
94 | -device = Device.new | ||
95 | -device.deliver | ||
96 | -</pre> | ||
97 | - | ||
98 | -h2. Running Jobs | ||
99 | - | ||
100 | -@script/delayed_job@ can be used to manage a background process which will start working off jobs. Make sure you've run `script/generate delayed_job`. | ||
101 | - | ||
102 | -<pre> | ||
103 | -$ RAILS_ENV=production script/delayed_job start | ||
104 | -$ RAILS_ENV=production script/delayed_job stop | ||
105 | - | ||
106 | -# Runs two workers in separate processes. | ||
107 | -$ RAILS_ENV=production script/delayed_job -n 2 start | ||
108 | -$ RAILS_ENV=production script/delayed_job stop | ||
109 | -</pre> | ||
110 | - | ||
111 | -Workers can be running on any computer, as long as they have access to the database and their clock is in sync. Keep in mind that each worker will check the database at least every 5 seconds. | ||
112 | - | ||
113 | -You can also invoke @rake jobs:work@ which will start working off jobs. You can cancel the rake task with @CTRL-C@. | ||
114 | - | ||
115 | -h2. Custom Jobs | ||
116 | - | ||
117 | -Jobs are simple ruby objects with a method called perform. Any object which responds to perform can be stuffed into the jobs table. Job objects are serialized to yaml so that they can later be resurrected by the job runner. | ||
118 | - | ||
119 | -<pre> | ||
120 | -class NewsletterJob < Struct.new(:text, :emails) | ||
121 | - def perform | ||
122 | - emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) } | ||
123 | - end | ||
124 | -end | ||
125 | - | ||
126 | -Delayed::Job.enqueue NewsletterJob.new('lorem ipsum...', Customers.find(:all).collect(&:email)) | ||
127 | -</pre> | ||
128 | - | ||
129 | -You can also add an optional on_permanent_failure method which will run if the job has failed too many times to be retried: | ||
130 | - | ||
131 | -<pre> | ||
132 | -class ParanoidNewsletterJob < NewsletterJob | ||
133 | - def perform | ||
134 | - emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) } | ||
135 | - end | ||
136 | - | ||
137 | - def on_permanent_failure | ||
138 | - page_sysadmin_in_the_middle_of_the_night | ||
139 | - end | ||
140 | -end | ||
141 | -</pre> | ||
142 | - | ||
143 | -h2. Gory Details | ||
144 | - | ||
145 | -The library evolves around a delayed_jobs table which looks as follows: | ||
146 | - | ||
147 | -<pre> | ||
148 | -create_table :delayed_jobs, :force => true do |table| | ||
149 | - table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue | ||
150 | - table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually. | ||
151 | - table.text :handler # YAML-encoded string of the object that will do work | ||
152 | - table.text :last_error # reason for last failure (See Note below) | ||
153 | - table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future. | ||
154 | - table.datetime :locked_at # Set when a client is working on this object | ||
155 | - table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead) | ||
156 | - table.string :locked_by # Who is working on this object (if locked) | ||
157 | - table.timestamps | ||
158 | -end | ||
159 | -</pre> | ||
160 | - | ||
161 | -On failure, the job is scheduled again in 5 seconds + N ** 4, where N is the number of retries. | ||
162 | - | ||
163 | -The default Worker.max_attempts is 25. After this, the job either deleted (default), or left in the database with "failed_at" set. | ||
164 | -With the default of 25 attempts, the last retry will be 20 days later, with the last interval being almost 100 hours. | ||
165 | - | ||
166 | -The default Worker.max_run_time is 4.hours. If your job takes longer than that, another computer could pick it up. It's up to you to | ||
167 | -make sure your job doesn't exceed this time. You should set this to the longest time you think the job could take. | ||
168 | - | ||
169 | -By default, it will delete failed jobs (and it always deletes successful jobs). If you want to keep failed jobs, set | ||
170 | -Delayed::Worker.destroy_failed_jobs = false. The failed jobs will be marked with non-null failed_at. | ||
171 | - | ||
172 | -Here is an example of changing job parameters in Rails: | ||
173 | - | ||
174 | -<pre> | ||
175 | -# config/initializers/delayed_job_config.rb | ||
176 | -Delayed::Worker.destroy_failed_jobs = false | ||
177 | -Delayed::Worker.sleep_delay = 60 | ||
178 | -Delayed::Worker.max_attempts = 3 | ||
179 | -Delayed::Worker.max_run_time = 5.minutes | ||
180 | -</pre> | ||
181 | - | ||
182 | -h3. Cleaning up | ||
183 | - | ||
184 | -You can invoke @rake jobs:clear@ to delete all jobs in the queue. | ||
185 | - | ||
186 | -h2. Mailing List | ||
187 | - | ||
188 | -Join us on the mailing list at http://groups.google.com/group/delayed_job | ||
189 | - | ||
190 | -h2. How to contribute | ||
191 | - | ||
192 | -If you find what looks like a bug: | ||
193 | - | ||
194 | -# Check the GitHub issue tracker to see if anyone else has had the same issue. | ||
195 | - http://github.com/collectiveidea/delayed_job/issues/ | ||
196 | -# If you don't see anything, create an issue with information on how to reproduce it. | ||
197 | - | ||
198 | -If you want to contribute an enhancement or a fix: | ||
199 | - | ||
200 | -# Fork the project on github. | ||
201 | - http://github.com/collectiveidea/delayed_job/ | ||
202 | -# Make your changes with tests. | ||
203 | -# Commit the changes without making changes to the Rakefile, VERSION, or any other files that aren't related to your enhancement or fix | ||
204 | -# Send a pull request. | ||
205 | - | ||
206 | -h3. Changelog | ||
207 | - | ||
208 | -See http://wiki.github.com/collectiveidea/delayed_job/changelog for a list of changes. | ||
209 | - |
vendor/plugins/delayed_job/Rakefile
1 | # -*- encoding: utf-8 -*- | 1 | # -*- encoding: utf-8 -*- |
2 | -begin | ||
3 | - require 'jeweler' | ||
4 | -rescue LoadError | ||
5 | - puts "Jeweler not available. Install it with: sudo gem install jeweler" | ||
6 | - exit 1 | ||
7 | -end | ||
8 | - | ||
9 | -Jeweler::Tasks.new do |s| | ||
10 | - s.name = "delayed_job" | ||
11 | - s.summary = "Database-backed asynchronous priority queue system -- Extracted from Shopify" | ||
12 | - s.email = "tobi@leetsoft.com" | ||
13 | - s.homepage = "http://github.com/collectiveidea/delayed_job" | ||
14 | - s.description = "Delayed_job (or DJ) encapsulates the common pattern of asynchronously executing longer tasks in the background. It is a direct extraction from Shopify where the job table is responsible for a multitude of core tasks.\n\nThis gem is collectiveidea's fork (http://github.com/collectiveidea/delayed_job)." | ||
15 | - s.authors = ["Brandon Keepers", "Tobias Lütke"] | ||
16 | - | ||
17 | - s.has_rdoc = true | ||
18 | - s.rdoc_options = ["--main", "README.textile", "--inline-source", "--line-numbers"] | ||
19 | - s.extra_rdoc_files = ["README.textile"] | ||
20 | - | ||
21 | - s.test_files = Dir['spec/*_spec.rb'] | ||
22 | - | ||
23 | - s.add_dependency "daemons" | ||
24 | - s.add_development_dependency "rspec" | ||
25 | - s.add_development_dependency "sqlite3-ruby" | ||
26 | - s.add_development_dependency "activerecord" | ||
27 | - s.add_development_dependency "mongo_mapper" | ||
28 | - s.add_development_dependency "dm-core" | ||
29 | - s.add_development_dependency "dm-observer" | ||
30 | - s.add_development_dependency "dm-aggregates" | ||
31 | - s.add_development_dependency "dm-validations" | ||
32 | - s.add_development_dependency "do_sqlite3" | ||
33 | - s.add_development_dependency "couchrest" | ||
34 | -end | ||
35 | - | ||
36 | -require 'spec/rake/spectask' | ||
37 | - | ||
38 | - | ||
39 | -task :default do | ||
40 | - %w(2.3.5 3.0.0.beta3).each do |version| | ||
41 | - puts "Running specs with Rails #{version}" | ||
42 | - system("RAILS_VERSION=#{version} rake -s spec;") | ||
43 | - end | ||
44 | -end | 2 | +require 'bundler/setup' |
3 | +Bundler::GemHelper.install_tasks | ||
45 | 4 | ||
5 | +require 'rspec/core/rake_task' | ||
46 | desc 'Run the specs' | 6 | desc 'Run the specs' |
47 | -Spec::Rake::SpecTask.new(:spec) do |t| | ||
48 | - t.libs << 'lib' | ||
49 | - t.pattern = 'spec/*_spec.rb' | ||
50 | - t.verbose = true | 7 | +RSpec::Core::RakeTask.new do |r| |
8 | + r.verbose = false | ||
51 | end | 9 | end |
52 | -task :spec => :check_dependencies | ||
53 | 10 | ||
11 | +task :default => :spec |
vendor/plugins/delayed_job/VERSION
@@ -1 +0,0 @@ | @@ -1 +0,0 @@ | ||
1 | -2.1.0.pre |
vendor/plugins/delayed_job/benchmarks.rb
1 | -$:.unshift(File.dirname(__FILE__) + '/lib') | ||
2 | -require 'rubygems' | 1 | +require 'spec/helper' |
3 | require 'logger' | 2 | require 'logger' |
4 | -require 'delayed_job' | ||
5 | require 'benchmark' | 3 | require 'benchmark' |
6 | 4 | ||
7 | -RAILS_ENV = 'test' | ||
8 | - | ||
9 | -Delayed::Worker.logger = Logger.new('/dev/null') | ||
10 | - | ||
11 | -BACKENDS = [] | ||
12 | -Dir.glob("#{File.dirname(__FILE__)}/spec/setup/*.rb") do |backend| | ||
13 | - begin | ||
14 | - backend = File.basename(backend, '.rb') | ||
15 | - require "spec/setup/#{backend}" | ||
16 | - BACKENDS << backend.to_sym | ||
17 | - rescue LoadError | ||
18 | - puts "Unable to load #{backend} backend! #{$!}" | ||
19 | - end | ||
20 | -end | ||
21 | - | 5 | +# Delayed::Worker.logger = Logger.new('/dev/null') |
22 | 6 | ||
23 | Benchmark.bm(10) do |x| | 7 | Benchmark.bm(10) do |x| |
24 | - BACKENDS.each do |backend| | ||
25 | - require "spec/setup/#{backend}" | ||
26 | - Delayed::Worker.backend = backend | ||
27 | - | ||
28 | - n = 10000 | ||
29 | - n.times { "foo".delay.length } | 8 | + Delayed::Job.delete_all |
9 | + n = 10000 | ||
10 | + n.times { "foo".delay.length } | ||
30 | 11 | ||
31 | - x.report(backend.to_s) { Delayed::Worker.new(:quiet => true).work_off(n) } | ||
32 | - end | 12 | + x.report { Delayed::Worker.new(:quiet => true).work_off(n) } |
33 | end | 13 | end |
vendor/plugins/delayed_job/contrib/delayed_job.monitrc
@@ -11,4 +11,4 @@ | @@ -11,4 +11,4 @@ | ||
11 | check process delayed_job | 11 | check process delayed_job |
12 | with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.pid | 12 | with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.pid |
13 | start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job start" | 13 | start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job start" |
14 | - stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job stop" | ||
15 | \ No newline at end of file | 14 | \ No newline at end of file |
15 | + stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job stop" |
vendor/plugins/delayed_job/contrib/delayed_job_multiple.monitrc
@@ -3,21 +3,32 @@ | @@ -3,21 +3,32 @@ | ||
3 | # To use: | 3 | # To use: |
4 | # 1. copy to /var/www/apps/{app_name}/shared/delayed_job.monitrc | 4 | # 1. copy to /var/www/apps/{app_name}/shared/delayed_job.monitrc |
5 | # 2. replace {app_name} as appropriate | 5 | # 2. replace {app_name} as appropriate |
6 | +# you might also need to change the program strings to | ||
7 | +# "/bin/su - {username} -c '/usr/bin/env ...'" | ||
8 | +# to load your shell environment. | ||
9 | +# | ||
6 | # 3. add this to your /etc/monit/monitrc | 10 | # 3. add this to your /etc/monit/monitrc |
7 | # | 11 | # |
8 | # include /var/www/apps/{app_name}/shared/delayed_job.monitrc | 12 | # include /var/www/apps/{app_name}/shared/delayed_job.monitrc |
13 | +# | ||
14 | +# The processes are grouped so that monit can act on them as a whole, e.g. | ||
15 | +# | ||
16 | +# monit -g delayed_job restart | ||
9 | 17 | ||
10 | check process delayed_job_0 | 18 | check process delayed_job_0 |
11 | with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.0.pid | 19 | with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.0.pid |
12 | start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job start -i 0" | 20 | start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job start -i 0" |
13 | stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job stop -i 0" | 21 | stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job stop -i 0" |
14 | - | 22 | + group delayed_job |
23 | + | ||
15 | check process delayed_job_1 | 24 | check process delayed_job_1 |
16 | with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.1.pid | 25 | with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.1.pid |
17 | start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job start -i 1" | 26 | start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job start -i 1" |
18 | stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job stop -i 1" | 27 | stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job stop -i 1" |
19 | - | 28 | + group delayed_job |
29 | + | ||
20 | check process delayed_job_2 | 30 | check process delayed_job_2 |
21 | with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.2.pid | 31 | with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.2.pid |
22 | start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job start -i 2" | 32 | start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job start -i 2" |
23 | - stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job stop -i 2" | ||
24 | \ No newline at end of file | 33 | \ No newline at end of file |
34 | + stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/script/delayed_job stop -i 2" | ||
35 | + group delayed_job |
vendor/plugins/delayed_job/contrib/delayed_job_rails_4.monitrc
0 → 100644
@@ -0,0 +1,14 @@ | @@ -0,0 +1,14 @@ | ||
1 | +# an example Monit configuration file for delayed_job | ||
2 | +# See: http://stackoverflow.com/questions/1226302/how-to-monitor-delayedjob-with-monit/1285611 | ||
3 | +# | ||
4 | +# To use: | ||
5 | +# 1. copy to /var/www/apps/{app_name}/shared/delayed_job.monitrc | ||
6 | +# 2. replace {app_name} as appropriate | ||
7 | +# 3. add this to your /etc/monit/monitrc | ||
8 | +# | ||
9 | +# include /var/www/apps/{app_name}/shared/delayed_job.monitrc | ||
10 | + | ||
11 | +check process delayed_job | ||
12 | + with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.pid | ||
13 | + start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/bin/delayed_job start" | ||
14 | + stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/bin/delayed_job stop" |
vendor/plugins/delayed_job/contrib/delayed_job_rails_4_multiple.monitrc
0 → 100644
@@ -0,0 +1,34 @@ | @@ -0,0 +1,34 @@ | ||
1 | +# an example Monit configuration file for delayed_job running multiple processes | ||
2 | +# | ||
3 | +# To use: | ||
4 | +# 1. copy to /var/www/apps/{app_name}/shared/delayed_job.monitrc | ||
5 | +# 2. replace {app_name} as appropriate | ||
6 | +# you might also need to change the program strings to | ||
7 | +# "/bin/su - {username} -c '/usr/bin/env ...'" | ||
8 | +# to load your shell environment. | ||
9 | +# | ||
10 | +# 3. add this to your /etc/monit/monitrc | ||
11 | +# | ||
12 | +# include /var/www/apps/{app_name}/shared/delayed_job.monitrc | ||
13 | +# | ||
14 | +# The processes are grouped so that monit can act on them as a whole, e.g. | ||
15 | +# | ||
16 | +# monit -g delayed_job restart | ||
17 | + | ||
18 | +check process delayed_job_0 | ||
19 | + with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.0.pid | ||
20 | + start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/bin/delayed_job start -i 0" | ||
21 | + stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/bin/delayed_job stop -i 0" | ||
22 | + group delayed_job | ||
23 | + | ||
24 | +check process delayed_job_1 | ||
25 | + with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.1.pid | ||
26 | + start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/bin/delayed_job start -i 1" | ||
27 | + stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/bin/delayed_job stop -i 1" | ||
28 | + group delayed_job | ||
29 | + | ||
30 | +check process delayed_job_2 | ||
31 | + with pidfile /var/www/apps/{app_name}/shared/pids/delayed_job.2.pid | ||
32 | + start program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/bin/delayed_job start -i 2" | ||
33 | + stop program = "/usr/bin/env RAILS_ENV=production /var/www/apps/{app_name}/current/bin/delayed_job stop -i 2" | ||
34 | + group delayed_job |
vendor/plugins/delayed_job/delayed_job.gemspec
1 | -# Generated by jeweler | ||
2 | -# DO NOT EDIT THIS FILE DIRECTLY | ||
3 | -# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command | ||
4 | # -*- encoding: utf-8 -*- | 1 | # -*- encoding: utf-8 -*- |
5 | 2 | ||
6 | -Gem::Specification.new do |s| | ||
7 | - s.name = %q{delayed_job} | ||
8 | - s.version = "2.1.0.pre" | ||
9 | - | ||
10 | - s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version= | ||
11 | - s.authors = ["Brandon Keepers", "Tobias L\303\274tke"] | ||
12 | - s.date = %q{2010-05-21} | ||
13 | - s.description = %q{Delayed_job (or DJ) encapsulates the common pattern of asynchronously executing longer tasks in the background. It is a direct extraction from Shopify where the job table is responsible for a multitude of core tasks. | ||
14 | - | ||
15 | -This gem is collectiveidea's fork (http://github.com/collectiveidea/delayed_job).} | ||
16 | - s.email = %q{tobi@leetsoft.com} | ||
17 | - s.extra_rdoc_files = [ | ||
18 | - "README.textile" | ||
19 | - ] | ||
20 | - s.files = [ | ||
21 | - ".gitignore", | ||
22 | - "MIT-LICENSE", | ||
23 | - "README.textile", | ||
24 | - "Rakefile", | ||
25 | - "VERSION", | ||
26 | - "benchmarks.rb", | ||
27 | - "contrib/delayed_job.monitrc", | ||
28 | - "contrib/delayed_job_multiple.monitrc", | ||
29 | - "delayed_job.gemspec", | ||
30 | - "generators/delayed_job/delayed_job_generator.rb", | ||
31 | - "generators/delayed_job/templates/migration.rb", | ||
32 | - "generators/delayed_job/templates/script", | ||
33 | - "init.rb", | ||
34 | - "lib/delayed/backend/active_record.rb", | ||
35 | - "lib/delayed/backend/base.rb", | ||
36 | - "lib/delayed/backend/couch_rest.rb", | ||
37 | - "lib/delayed/backend/data_mapper.rb", | ||
38 | - "lib/delayed/backend/mongo_mapper.rb", | ||
39 | - "lib/delayed/command.rb", | ||
40 | - "lib/delayed/message_sending.rb", | ||
41 | - "lib/delayed/performable_method.rb", | ||
42 | - "lib/delayed/railtie.rb", | ||
43 | - "lib/delayed/recipes.rb", | ||
44 | - "lib/delayed/tasks.rb", | ||
45 | - "lib/delayed/worker.rb", | ||
46 | - "lib/delayed/yaml_ext.rb", | ||
47 | - "lib/delayed_job.rb", | ||
48 | - "lib/generators/delayed_job/delayed_job_generator.rb", | ||
49 | - "lib/generators/delayed_job/templates/migration.rb", | ||
50 | - "lib/generators/delayed_job/templates/script", | ||
51 | - "rails/init.rb", | ||
52 | - "recipes/delayed_job.rb", | ||
53 | - "spec/autoloaded/clazz.rb", | ||
54 | - "spec/autoloaded/struct.rb", | ||
55 | - "spec/backend/active_record_job_spec.rb", | ||
56 | - "spec/backend/couch_rest_job_spec.rb", | ||
57 | - "spec/backend/data_mapper_job_spec.rb", | ||
58 | - "spec/backend/mongo_mapper_job_spec.rb", | ||
59 | - "spec/backend/shared_backend_spec.rb", | ||
60 | - "spec/message_sending_spec.rb", | ||
61 | - "spec/performable_method_spec.rb", | ||
62 | - "spec/sample_jobs.rb", | ||
63 | - "spec/setup/active_record.rb", | ||
64 | - "spec/setup/couch_rest.rb", | ||
65 | - "spec/setup/data_mapper.rb", | ||
66 | - "spec/setup/mongo_mapper.rb", | ||
67 | - "spec/spec_helper.rb", | ||
68 | - "spec/worker_spec.rb", | ||
69 | - "tasks/jobs.rake" | ||
70 | - ] | ||
71 | - s.homepage = %q{http://github.com/collectiveidea/delayed_job} | ||
72 | - s.rdoc_options = ["--main", "README.textile", "--inline-source", "--line-numbers"] | ||
73 | - s.require_paths = ["lib"] | ||
74 | - s.rubygems_version = %q{1.3.6} | ||
75 | - s.summary = %q{Database-backed asynchronous priority queue system -- Extracted from Shopify} | ||
76 | - s.test_files = [ | ||
77 | - "spec/message_sending_spec.rb", | ||
78 | - "spec/performable_method_spec.rb", | ||
79 | - "spec/worker_spec.rb" | ||
80 | - ] | ||
81 | - | ||
82 | - if s.respond_to? :specification_version then | ||
83 | - current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION | ||
84 | - s.specification_version = 3 | ||
85 | - | ||
86 | - if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then | ||
87 | - s.add_runtime_dependency(%q<daemons>, [">= 0"]) | ||
88 | - s.add_development_dependency(%q<rspec>, [">= 0"]) | ||
89 | - s.add_development_dependency(%q<sqlite3-ruby>, [">= 0"]) | ||
90 | - s.add_development_dependency(%q<activerecord>, [">= 0"]) | ||
91 | - s.add_development_dependency(%q<mongo_mapper>, [">= 0"]) | ||
92 | - s.add_development_dependency(%q<dm-core>, [">= 0"]) | ||
93 | - s.add_development_dependency(%q<dm-observer>, [">= 0"]) | ||
94 | - s.add_development_dependency(%q<dm-aggregates>, [">= 0"]) | ||
95 | - s.add_development_dependency(%q<dm-validations>, [">= 0"]) | ||
96 | - s.add_development_dependency(%q<do_sqlite3>, [">= 0"]) | ||
97 | - s.add_development_dependency(%q<couchrest>, [">= 0"]) | ||
98 | - else | ||
99 | - s.add_dependency(%q<daemons>, [">= 0"]) | ||
100 | - s.add_dependency(%q<rspec>, [">= 0"]) | ||
101 | - s.add_dependency(%q<sqlite3-ruby>, [">= 0"]) | ||
102 | - s.add_dependency(%q<activerecord>, [">= 0"]) | ||
103 | - s.add_dependency(%q<mongo_mapper>, [">= 0"]) | ||
104 | - s.add_dependency(%q<dm-core>, [">= 0"]) | ||
105 | - s.add_dependency(%q<dm-observer>, [">= 0"]) | ||
106 | - s.add_dependency(%q<dm-aggregates>, [">= 0"]) | ||
107 | - s.add_dependency(%q<dm-validations>, [">= 0"]) | ||
108 | - s.add_dependency(%q<do_sqlite3>, [">= 0"]) | ||
109 | - s.add_dependency(%q<couchrest>, [">= 0"]) | ||
110 | - end | ||
111 | - else | ||
112 | - s.add_dependency(%q<daemons>, [">= 0"]) | ||
113 | - s.add_dependency(%q<rspec>, [">= 0"]) | ||
114 | - s.add_dependency(%q<sqlite3-ruby>, [">= 0"]) | ||
115 | - s.add_dependency(%q<activerecord>, [">= 0"]) | ||
116 | - s.add_dependency(%q<mongo_mapper>, [">= 0"]) | ||
117 | - s.add_dependency(%q<dm-core>, [">= 0"]) | ||
118 | - s.add_dependency(%q<dm-observer>, [">= 0"]) | ||
119 | - s.add_dependency(%q<dm-aggregates>, [">= 0"]) | ||
120 | - s.add_dependency(%q<dm-validations>, [">= 0"]) | ||
121 | - s.add_dependency(%q<do_sqlite3>, [">= 0"]) | ||
122 | - s.add_dependency(%q<couchrest>, [">= 0"]) | ||
123 | - end | 3 | +Gem::Specification.new do |spec| |
4 | + spec.add_dependency 'activesupport', ['>= 3.0', '< 4.1'] | ||
5 | + spec.authors = ["Brandon Keepers", "Brian Ryckbost", "Chris Gaffney", "David Genord II", "Erik Michaels-Ober", "Matt Griffin", "Steve Richert", "Tobias Lütke"] | ||
6 | + spec.description = "Delayed_job (or DJ) encapsulates the common pattern of asynchronously executing longer tasks in the background. It is a direct extraction from Shopify where the job table is responsible for a multitude of core tasks." | ||
7 | + spec.email = ['brian@collectiveidea.com'] | ||
8 | + spec.files = %w(CHANGELOG.md CONTRIBUTING.md LICENSE.md README.md Rakefile delayed_job.gemspec) | ||
9 | + spec.files += Dir.glob('{contrib,lib,recipes,spec}/**/*') | ||
10 | + spec.homepage = 'http://github.com/collectiveidea/delayed_job' | ||
11 | + spec.licenses = ['MIT'] | ||
12 | + spec.name = 'delayed_job' | ||
13 | + spec.require_paths = ['lib'] | ||
14 | + spec.summary = 'Database-backed asynchronous priority queue system -- Extracted from Shopify' | ||
15 | + spec.test_files = Dir.glob('spec/**/*') | ||
16 | + spec.version = '4.0.0' | ||
124 | end | 17 | end |
125 | - |
vendor/plugins/delayed_job/generators/delayed_job/delayed_job_generator.rb
@@ -1,22 +0,0 @@ | @@ -1,22 +0,0 @@ | ||
1 | -class DelayedJobGenerator < Rails::Generator::Base | ||
2 | - default_options :skip_migration => false | ||
3 | - | ||
4 | - def manifest | ||
5 | - record do |m| | ||
6 | - m.template 'script', 'script/delayed_job', :chmod => 0755 | ||
7 | - if !options[:skip_migration] && defined?(ActiveRecord) | ||
8 | - m.migration_template "migration.rb", 'db/migrate', | ||
9 | - :migration_file_name => "create_delayed_jobs" | ||
10 | - end | ||
11 | - end | ||
12 | - end | ||
13 | - | ||
14 | -protected | ||
15 | - | ||
16 | - def add_options!(opt) | ||
17 | - opt.separator '' | ||
18 | - opt.separator 'Options:' | ||
19 | - opt.on("--skip-migration", "Don't generate a migration") { |v| options[:skip_migration] = v } | ||
20 | - end | ||
21 | - | ||
22 | -end |
vendor/plugins/delayed_job/generators/delayed_job/templates/migration.rb
@@ -1,21 +0,0 @@ | @@ -1,21 +0,0 @@ | ||
1 | -class CreateDelayedJobs < ActiveRecord::Migration | ||
2 | - def self.up | ||
3 | - create_table :delayed_jobs, :force => true do |table| | ||
4 | - table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue | ||
5 | - table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually. | ||
6 | - table.text :handler # YAML-encoded string of the object that will do work | ||
7 | - table.text :last_error # reason for last failure (See Note below) | ||
8 | - table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future. | ||
9 | - table.datetime :locked_at # Set when a client is working on this object | ||
10 | - table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead) | ||
11 | - table.string :locked_by # Who is working on this object (if locked) | ||
12 | - table.timestamps | ||
13 | - end | ||
14 | - | ||
15 | - add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority' | ||
16 | - end | ||
17 | - | ||
18 | - def self.down | ||
19 | - drop_table :delayed_jobs | ||
20 | - end | ||
21 | -end | ||
22 | \ No newline at end of file | 0 | \ No newline at end of file |
vendor/plugins/delayed_job/init.rb
vendor/plugins/delayed_job/lib/delayed/backend/active_record.rb
@@ -1,101 +0,0 @@ | @@ -1,101 +0,0 @@ | ||
1 | -require 'active_record' | ||
2 | -require 'active_record/version' | ||
3 | - | ||
4 | -class ActiveRecord::Base | ||
5 | - yaml_as "tag:ruby.yaml.org,2002:ActiveRecord" | ||
6 | - | ||
7 | - def self.yaml_new(klass, tag, val) | ||
8 | - klass.find(val['attributes']['id']) | ||
9 | - rescue ActiveRecord::RecordNotFound | ||
10 | - nil | ||
11 | - end | ||
12 | - | ||
13 | - def to_yaml_properties | ||
14 | - ['@attributes'] | ||
15 | - end | ||
16 | -end | ||
17 | - | ||
18 | -module Delayed | ||
19 | - module Backend | ||
20 | - module ActiveRecord | ||
21 | - # A job object that is persisted to the database. | ||
22 | - # Contains the work object as a YAML field. | ||
23 | - class Job < ::ActiveRecord::Base | ||
24 | - include Delayed::Backend::Base | ||
25 | - | ||
26 | - attr_accessible :payload_object, :priority, :run_at | ||
27 | - | ||
28 | - set_table_name :delayed_jobs | ||
29 | - | ||
30 | - before_save :set_default_run_at | ||
31 | - | ||
32 | - if ::ActiveRecord::VERSION::MAJOR >= 3 | ||
33 | - scope :ready_to_run, lambda {|worker_name, max_run_time| | ||
34 | - where(['(run_at <= ? AND (locked_at IS NULL OR locked_at < ?) OR locked_by = ?) AND failed_at IS NULL', db_time_now, db_time_now - max_run_time, worker_name]) | ||
35 | - } | ||
36 | - scope :by_priority, order('priority ASC, run_at ASC') | ||
37 | - else | ||
38 | - scope :ready_to_run, lambda {|worker_name, max_run_time| | ||
39 | - {:conditions => ['(run_at <= ? AND (locked_at IS NULL OR locked_at < ?) OR locked_by = ?) AND failed_at IS NULL', db_time_now, db_time_now - max_run_time, worker_name]} | ||
40 | - } | ||
41 | - scope :by_priority, :order => 'priority ASC, run_at ASC' | ||
42 | - end | ||
43 | - | ||
44 | - def self.after_fork | ||
45 | - ::ActiveRecord::Base.connection.reconnect! | ||
46 | - end | ||
47 | - | ||
48 | - # When a worker is exiting, make sure we don't have any locked jobs. | ||
49 | - def self.clear_locks!(worker_name) | ||
50 | - update_all("locked_by = null, locked_at = null", ["locked_by = ?", worker_name]) | ||
51 | - end | ||
52 | - | ||
53 | - # Find a few candidate jobs to run (in case some immediately get locked by others). | ||
54 | - def self.find_available(worker_name, limit = 5, max_run_time = Worker.max_run_time) | ||
55 | - scope = self.ready_to_run(worker_name, max_run_time) | ||
56 | - scope = scope.scoped(:conditions => ['priority >= ?', Worker.min_priority]) if Worker.min_priority | ||
57 | - scope = scope.scoped(:conditions => ['priority <= ?', Worker.max_priority]) if Worker.max_priority | ||
58 | - | ||
59 | - ::ActiveRecord::Base.silence do | ||
60 | - scope.by_priority.all(:limit => limit) | ||
61 | - end | ||
62 | - end | ||
63 | - | ||
64 | - # Lock this job for this worker. | ||
65 | - # Returns true if we have the lock, false otherwise. | ||
66 | - def lock_exclusively!(max_run_time, worker) | ||
67 | - now = self.class.db_time_now | ||
68 | - affected_rows = if locked_by != worker | ||
69 | - # We don't own this job so we will update the locked_by name and the locked_at | ||
70 | - self.class.update_all(["locked_at = ?, locked_by = ?", now, worker], ["id = ? and (locked_at is null or locked_at < ?) and (run_at <= ?)", id, (now - max_run_time.to_i), now]) | ||
71 | - else | ||
72 | - # We already own this job, this may happen if the job queue crashes. | ||
73 | - # Simply resume and update the locked_at | ||
74 | - self.class.update_all(["locked_at = ?", now], ["id = ? and locked_by = ?", id, worker]) | ||
75 | - end | ||
76 | - if affected_rows == 1 | ||
77 | - self.locked_at = now | ||
78 | - self.locked_by = worker | ||
79 | - return true | ||
80 | - else | ||
81 | - return false | ||
82 | - end | ||
83 | - end | ||
84 | - | ||
85 | - # Get the current time (GMT or local depending on DB) | ||
86 | - # Note: This does not ping the DB to get the time, so all your clients | ||
87 | - # must have syncronized clocks. | ||
88 | - def self.db_time_now | ||
89 | - if Time.zone | ||
90 | - Time.zone.now | ||
91 | - elsif ::ActiveRecord::Base.default_timezone == :utc | ||
92 | - Time.now.utc | ||
93 | - else | ||
94 | - Time.now | ||
95 | - end | ||
96 | - end | ||
97 | - | ||
98 | - end | ||
99 | - end | ||
100 | - end | ||
101 | -end |
vendor/plugins/delayed_job/lib/delayed/backend/base.rb
1 | module Delayed | 1 | module Delayed |
2 | module Backend | 2 | module Backend |
3 | - class DeserializationError < StandardError | ||
4 | - end | ||
5 | - | ||
6 | module Base | 3 | module Base |
7 | def self.included(base) | 4 | def self.included(base) |
8 | base.extend ClassMethods | 5 | base.extend ClassMethods |
9 | end | 6 | end |
10 | - | 7 | + |
11 | module ClassMethods | 8 | module ClassMethods |
12 | # Add a job to the queue | 9 | # Add a job to the queue |
13 | def enqueue(*args) | 10 | def enqueue(*args) |
14 | - object = args.shift | ||
15 | - unless object.respond_to?(:perform) | 11 | + options = { |
12 | + :priority => Delayed::Worker.default_priority, | ||
13 | + :queue => Delayed::Worker.default_queue_name | ||
14 | + }.merge!(args.extract_options!) | ||
15 | + | ||
16 | + options[:payload_object] ||= args.shift | ||
17 | + | ||
18 | + if args.size > 0 | ||
19 | + warn "[DEPRECATION] Passing multiple arguments to `#enqueue` is deprecated. Pass a hash with :priority and :run_at." | ||
20 | + options[:priority] = args.first || options[:priority] | ||
21 | + options[:run_at] = args[1] | ||
22 | + end | ||
23 | + | ||
24 | + unless options[:payload_object].respond_to?(:perform) | ||
16 | raise ArgumentError, 'Cannot enqueue items which do not respond to perform' | 25 | raise ArgumentError, 'Cannot enqueue items which do not respond to perform' |
17 | end | 26 | end |
18 | - | ||
19 | - priority = args.first || Delayed::Worker.default_priority | ||
20 | - run_at = args[1] | ||
21 | - self.create(:payload_object => object, :priority => priority.to_i, :run_at => run_at) | 27 | + |
28 | + if Delayed::Worker.delay_jobs | ||
29 | + self.new(options).tap do |job| | ||
30 | + Delayed::Worker.lifecycle.run_callbacks(:enqueue, job) do | ||
31 | + job.hook(:enqueue) | ||
32 | + job.save | ||
33 | + end | ||
34 | + end | ||
35 | + else | ||
36 | + Delayed::Job.new(:payload_object => options[:payload_object]).tap do |job| | ||
37 | + job.invoke_job | ||
38 | + end | ||
39 | + end | ||
22 | end | 40 | end |
23 | - | 41 | + |
42 | + def reserve(worker, max_run_time = Worker.max_run_time) | ||
43 | + # We get up to 5 jobs from the db. In case we cannot get exclusive access to a job we try the next. | ||
44 | + # this leads to a more even distribution of jobs across the worker processes | ||
45 | + find_available(worker.name, worker.read_ahead, max_run_time).detect do |job| | ||
46 | + job.lock_exclusively!(max_run_time, worker.name) | ||
47 | + end | ||
48 | + end | ||
49 | + | ||
50 | + # Allow the backend to attempt recovery from reserve errors | ||
51 | + def recover_from(error) | ||
52 | + end | ||
53 | + | ||
24 | # Hook method that is called before a new worker is forked | 54 | # Hook method that is called before a new worker is forked |
25 | def before_fork | 55 | def before_fork |
26 | end | 56 | end |
27 | - | 57 | + |
28 | # Hook method that is called after a new worker is forked | 58 | # Hook method that is called after a new worker is forked |
29 | def after_fork | 59 | def after_fork |
30 | end | 60 | end |
31 | - | 61 | + |
32 | def work_off(num = 100) | 62 | def work_off(num = 100) |
33 | warn "[DEPRECATION] `Delayed::Job.work_off` is deprecated. Use `Delayed::Worker.new.work_off instead." | 63 | warn "[DEPRECATION] `Delayed::Job.work_off` is deprecated. Use `Delayed::Worker.new.work_off instead." |
34 | Delayed::Worker.new.work_off(num) | 64 | Delayed::Worker.new.work_off(num) |
35 | end | 65 | end |
36 | end | 66 | end |
37 | - | ||
38 | - ParseObjectFromYaml = /\!ruby\/\w+\:([^\s]+)/ | ||
39 | 67 | ||
40 | def failed? | 68 | def failed? |
41 | - failed_at | 69 | + !!failed_at |
42 | end | 70 | end |
43 | alias_method :failed, :failed? | 71 | alias_method :failed, :failed? |
44 | 72 | ||
73 | + ParseObjectFromYaml = /\!ruby\/\w+\:([^\s]+)/ | ||
74 | + | ||
45 | def name | 75 | def name |
46 | - @name ||= begin | ||
47 | - payload = payload_object | ||
48 | - payload.respond_to?(:display_name) ? payload.display_name : payload.class.name | ||
49 | - end | 76 | + @name ||= payload_object.respond_to?(:display_name) ? |
77 | + payload_object.display_name : | ||
78 | + payload_object.class.name | ||
79 | + rescue DeserializationError | ||
80 | + ParseObjectFromYaml.match(handler)[1] | ||
50 | end | 81 | end |
51 | 82 | ||
52 | def payload_object=(object) | 83 | def payload_object=(object) |
84 | + @payload_object = object | ||
53 | self.handler = object.to_yaml | 85 | self.handler = object.to_yaml |
54 | end | 86 | end |
55 | - | 87 | + |
56 | def payload_object | 88 | def payload_object |
57 | - @payload_object ||= YAML.load(self.handler) | ||
58 | - rescue TypeError, LoadError, NameError => e | ||
59 | - raise DeserializationError, | ||
60 | - "Job failed to load: #{e.message}. Try to manually require the required file. Handler: #{handler.inspect}" | 89 | + if YAML.respond_to?(:unsafe_load) |
90 | + #See https://github.com/dtao/safe_yaml | ||
91 | + #When the method is there, we need to load our YAML like this... | ||
92 | + @payload_object ||= YAML.load(self.handler, :safe => false) | ||
93 | + else | ||
94 | + @payload_object ||= YAML.load(self.handler) | ||
95 | + end | ||
96 | + rescue TypeError, LoadError, NameError, ArgumentError => e | ||
97 | + raise DeserializationError, | ||
98 | + "Job failed to load: #{e.message}. Handler: #{handler.inspect}" | ||
61 | end | 99 | end |
62 | 100 | ||
63 | - # Moved into its own method so that new_relic can trace it. | ||
64 | def invoke_job | 101 | def invoke_job |
65 | - payload_object.perform | 102 | + Delayed::Worker.lifecycle.run_callbacks(:invoke_job, self) do |
103 | + begin | ||
104 | + hook :before | ||
105 | + payload_object.perform | ||
106 | + hook :success | ||
107 | + rescue Exception => e | ||
108 | + hook :error, e | ||
109 | + raise e | ||
110 | + ensure | ||
111 | + hook :after | ||
112 | + end | ||
113 | + end | ||
66 | end | 114 | end |
67 | - | 115 | + |
68 | # Unlock this job (note: not saved to DB) | 116 | # Unlock this job (note: not saved to DB) |
69 | def unlock | 117 | def unlock |
70 | self.locked_at = nil | 118 | self.locked_at = nil |
71 | self.locked_by = nil | 119 | self.locked_by = nil |
72 | end | 120 | end |
73 | - | 121 | + |
122 | + def hook(name, *args) | ||
123 | + if payload_object.respond_to?(name) | ||
124 | + method = payload_object.method(name) | ||
125 | + method.arity == 0 ? method.call : method.call(self, *args) | ||
126 | + end | ||
127 | + rescue DeserializationError | ||
128 | + # do nothing | ||
129 | + end | ||
130 | + | ||
131 | + def reschedule_at | ||
132 | + payload_object.respond_to?(:reschedule_at) ? | ||
133 | + payload_object.reschedule_at(self.class.db_time_now, attempts) : | ||
134 | + self.class.db_time_now + (attempts ** 4) + 5 | ||
135 | + end | ||
136 | + | ||
137 | + def max_attempts | ||
138 | + payload_object.max_attempts if payload_object.respond_to?(:max_attempts) | ||
139 | + end | ||
140 | + | ||
141 | + def fail! | ||
142 | + update_attributes(:failed_at => self.class.db_time_now) | ||
143 | + end | ||
144 | + | ||
74 | protected | 145 | protected |
75 | 146 | ||
76 | def set_default_run_at | 147 | def set_default_run_at |
77 | self.run_at ||= self.class.db_time_now | 148 | self.run_at ||= self.class.db_time_now |
78 | end | 149 | end |
79 | - | 150 | + |
151 | + # Call during reload operation to clear out internal state | ||
152 | + def reset | ||
153 | + @payload_object = nil | ||
154 | + end | ||
80 | end | 155 | end |
81 | end | 156 | end |
82 | end | 157 | end |
vendor/plugins/delayed_job/lib/delayed/backend/couch_rest.rb
@@ -1,109 +0,0 @@ | @@ -1,109 +0,0 @@ | ||
1 | -require 'couchrest' | ||
2 | - | ||
3 | -#extent couchrest to handle delayed_job serialization. | ||
4 | -class CouchRest::ExtendedDocument | ||
5 | - yaml_as "tag:ruby.yaml.org,2002:CouchRest" | ||
6 | - | ||
7 | - def reload | ||
8 | - job = self.class.get self['_id'] | ||
9 | - job.each {|k,v| self[k] = v} | ||
10 | - end | ||
11 | - def self.find(id) | ||
12 | - get id | ||
13 | - end | ||
14 | - def self.yaml_new(klass, tag, val) | ||
15 | - klass.get(val['_id']) | ||
16 | - end | ||
17 | - def ==(other) | ||
18 | - if other.is_a? ::CouchRest::ExtendedDocument | ||
19 | - self['_id'] == other['_id'] | ||
20 | - else | ||
21 | - super | ||
22 | - end | ||
23 | - end | ||
24 | -end | ||
25 | - | ||
26 | -#couchrest adapter | ||
27 | -module Delayed | ||
28 | - module Backend | ||
29 | - module CouchRest | ||
30 | - class Job < ::CouchRest::ExtendedDocument | ||
31 | - include Delayed::Backend::Base | ||
32 | - use_database ::CouchRest::Server.new.database('delayed_job') | ||
33 | - | ||
34 | - property :handler | ||
35 | - property :last_error | ||
36 | - property :locked_by | ||
37 | - property :priority, :default => 0 | ||
38 | - property :attempts, :default => 0 | ||
39 | - property :run_at, :cast_as => 'Time' | ||
40 | - property :locked_at, :cast_as => 'Time' | ||
41 | - property :failed_at, :cast_as => 'Time' | ||
42 | - timestamps! | ||
43 | - | ||
44 | - set_callback :save, :before, :set_default_run_at | ||
45 | - | ||
46 | - view_by(:failed_at, :locked_by, :run_at, | ||
47 | - :map => "function(doc){" + | ||
48 | - " if(doc['couchrest-type'] == 'Delayed::Backend::CouchRest::Job') {" + | ||
49 | - " emit([doc.failed_at || null, doc.locked_by || null, doc.run_at || null], null);}" + | ||
50 | - " }") | ||
51 | - view_by(:failed_at, :locked_at, :run_at, | ||
52 | - :map => "function(doc){" + | ||
53 | - " if(doc['couchrest-type'] == 'Delayed::Backend::CouchRest::Job') {" + | ||
54 | - " emit([doc.failed_at || null, doc.locked_at || null, doc.run_at || null], null);}" + | ||
55 | - " }") | ||
56 | - | ||
57 | - def self.db_time_now; Time.now; end | ||
58 | - def self.find_available(worker_name, limit = 5, max_run_time = ::Delayed::Worker.max_run_time) | ||
59 | - ready = ready_jobs | ||
60 | - mine = my_jobs worker_name | ||
61 | - expire = expired_jobs max_run_time | ||
62 | - jobs = (ready + mine + expire)[0..limit-1].sort_by { |j| j.priority } | ||
63 | - jobs = jobs.find_all { |j| j.priority >= Worker.min_priority } if Worker.min_priority | ||
64 | - jobs = jobs.find_all { |j| j.priority <= Worker.max_priority } if Worker.max_priority | ||
65 | - jobs | ||
66 | - end | ||
67 | - def self.clear_locks!(worker_name) | ||
68 | - jobs = my_jobs worker_name | ||
69 | - jobs.each { |j| j.locked_by, j.locked_at = nil, nil; } | ||
70 | - database.bulk_save jobs | ||
71 | - end | ||
72 | - def self.delete_all | ||
73 | - database.bulk_save all.each { |doc| doc['_deleted'] = true } | ||
74 | - end | ||
75 | - | ||
76 | - def lock_exclusively!(max_run_time, worker = worker_name) | ||
77 | - return false if locked_by_other?(worker) and not expired?(max_run_time) | ||
78 | - case | ||
79 | - when locked_by_me?(worker) | ||
80 | - self.locked_at = self.class.db_time_now | ||
81 | - when (unlocked? or (locked_by_other?(worker) and expired?(max_run_time))) | ||
82 | - self.locked_at, self.locked_by = self.class.db_time_now, worker | ||
83 | - end | ||
84 | - save | ||
85 | - rescue RestClient::Conflict | ||
86 | - false | ||
87 | - end | ||
88 | - | ||
89 | - private | ||
90 | - def self.ready_jobs | ||
91 | - options = {:startkey => [nil, nil], :endkey => [nil, nil, db_time_now]} | ||
92 | - by_failed_at_and_locked_by_and_run_at options | ||
93 | - end | ||
94 | - def self.my_jobs(worker_name) | ||
95 | - options = {:startkey => [nil, worker_name], :endkey => [nil, worker_name, {}]} | ||
96 | - by_failed_at_and_locked_by_and_run_at options | ||
97 | - end | ||
98 | - def self.expired_jobs(max_run_time) | ||
99 | - options = {:startkey => [nil,'0'], :endkey => [nil, db_time_now - max_run_time, db_time_now]} | ||
100 | - by_failed_at_and_locked_at_and_run_at options | ||
101 | - end | ||
102 | - def unlocked?; locked_by.nil?; end | ||
103 | - def expired?(time); locked_at < self.class.db_time_now - time; end | ||
104 | - def locked_by_me?(worker); not locked_by.nil? and locked_by == worker; end | ||
105 | - def locked_by_other?(worker); not locked_by.nil? and locked_by != worker; end | ||
106 | - end | ||
107 | - end | ||
108 | - end | ||
109 | -end |
vendor/plugins/delayed_job/lib/delayed/backend/data_mapper.rb
@@ -1,121 +0,0 @@ | @@ -1,121 +0,0 @@ | ||
1 | -require 'dm-core' | ||
2 | -require 'dm-observer' | ||
3 | -require 'dm-aggregates' | ||
4 | - | ||
5 | -DataMapper::Resource.class_eval do | ||
6 | - yaml_as "tag:ruby.yaml.org,2002:DataMapper" | ||
7 | - | ||
8 | - def self.yaml_new(klass, tag, val) | ||
9 | - klass.find(val['id']) | ||
10 | - end | ||
11 | - | ||
12 | - def to_yaml_properties | ||
13 | - ['@id'] | ||
14 | - end | ||
15 | -end | ||
16 | - | ||
17 | -module Delayed | ||
18 | - module Backend | ||
19 | - module DataMapper | ||
20 | - class Job | ||
21 | - include ::DataMapper::Resource | ||
22 | - include Delayed::Backend::Base | ||
23 | - | ||
24 | - storage_names[:default] = 'delayed_jobs' | ||
25 | - | ||
26 | - property :id, Serial | ||
27 | - property :priority, Integer, :default => 0, :index => :run_at_priority | ||
28 | - property :attempts, Integer, :default => 0 | ||
29 | - property :handler, Text, :lazy => false | ||
30 | - property :run_at, Time, :index => :run_at_priority | ||
31 | - property :locked_at, Time, :index => true | ||
32 | - property :locked_by, String | ||
33 | - property :failed_at, Time | ||
34 | - property :last_error, Text | ||
35 | - | ||
36 | - def self.db_time_now | ||
37 | - Time.now | ||
38 | - end | ||
39 | - | ||
40 | - def self.find_available(worker_name, limit = 5, max_run_time = Worker.max_run_time) | ||
41 | - | ||
42 | - simple_conditions = { :run_at.lte => db_time_now, :limit => limit, :failed_at => nil, :order => [:priority.asc, :run_at.asc] } | ||
43 | - | ||
44 | - # respect priorities | ||
45 | - simple_conditions[:priority.gte] = Worker.min_priority if Worker.min_priority | ||
46 | - simple_conditions[:priority.lte] = Worker.max_priority if Worker.max_priority | ||
47 | - | ||
48 | - # lockable | ||
49 | - lockable = ( | ||
50 | - # not locked or past the max time | ||
51 | - ( all(:locked_at => nil ) | all(:locked_at.lt => db_time_now - max_run_time)) | | ||
52 | - | ||
53 | - # OR locked by our worker | ||
54 | - all(:locked_by => worker_name)) | ||
55 | - | ||
56 | - # plus some other boring junk | ||
57 | - (lockable).all( simple_conditions ) | ||
58 | - end | ||
59 | - | ||
60 | - # When a worker is exiting, make sure we don't have any locked jobs. | ||
61 | - def self.clear_locks!(worker_name) | ||
62 | - all(:locked_by => worker_name).update(:locked_at => nil, :locked_by => nil) | ||
63 | - end | ||
64 | - | ||
65 | - # Lock this job for this worker. | ||
66 | - # Returns true if we have the lock, false otherwise. | ||
67 | - def lock_exclusively!(max_run_time, worker = worker_name) | ||
68 | - | ||
69 | - now = self.class.db_time_now | ||
70 | - overtime = now - max_run_time | ||
71 | - | ||
72 | - # FIXME - this is a bit gross | ||
73 | - # DM doesn't give us the number of rows affected by a collection update | ||
74 | - # so we have to circumvent some niceness in DM::Collection here | ||
75 | - collection = locked_by != worker ? | ||
76 | - (self.class.all(:id => id, :run_at.lte => now) & ( self.class.all(:locked_at => nil) | self.class.all(:locked_at.lt => overtime) ) ) : | ||
77 | - self.class.all(:id => id, :locked_by => worker) | ||
78 | - | ||
79 | - attributes = collection.model.new(:locked_at => now, :locked_by => worker).dirty_attributes | ||
80 | - affected_rows = self.repository.update(attributes, collection) | ||
81 | - | ||
82 | - if affected_rows == 1 | ||
83 | - self.locked_at = now | ||
84 | - self.locked_by = worker | ||
85 | - return true | ||
86 | - else | ||
87 | - return false | ||
88 | - end | ||
89 | - end | ||
90 | - | ||
91 | - # these are common to the other backends, so we provide an implementation | ||
92 | - def self.delete_all | ||
93 | - Delayed::Job.auto_migrate! | ||
94 | - end | ||
95 | - | ||
96 | - def self.find id | ||
97 | - get id | ||
98 | - end | ||
99 | - | ||
100 | - def update_attributes(attributes) | ||
101 | - attributes.each do |k,v| | ||
102 | - self[k] = v | ||
103 | - end | ||
104 | - self.save | ||
105 | - end | ||
106 | - | ||
107 | - | ||
108 | - end | ||
109 | - | ||
110 | - class JobObserver | ||
111 | - include ::DataMapper::Observer | ||
112 | - | ||
113 | - observe Job | ||
114 | - | ||
115 | - before :save do | ||
116 | - self.run_at ||= self.class.db_time_now | ||
117 | - end | ||
118 | - end | ||
119 | - end | ||
120 | - end | ||
121 | -end |
vendor/plugins/delayed_job/lib/delayed/backend/mongo_mapper.rb
@@ -1,106 +0,0 @@ | @@ -1,106 +0,0 @@ | ||
1 | -require 'mongo_mapper' | ||
2 | - | ||
3 | -MongoMapper::Document.class_eval do | ||
4 | - yaml_as "tag:ruby.yaml.org,2002:MongoMapper" | ||
5 | - | ||
6 | - def self.yaml_new(klass, tag, val) | ||
7 | - klass.find(val['_id']) | ||
8 | - end | ||
9 | - | ||
10 | - def to_yaml_properties | ||
11 | - ['@_id'] | ||
12 | - end | ||
13 | -end | ||
14 | - | ||
15 | -module Delayed | ||
16 | - module Backend | ||
17 | - module MongoMapper | ||
18 | - class Job | ||
19 | - include ::MongoMapper::Document | ||
20 | - include Delayed::Backend::Base | ||
21 | - set_collection_name 'delayed_jobs' | ||
22 | - | ||
23 | - key :priority, Integer, :default => 0 | ||
24 | - key :attempts, Integer, :default => 0 | ||
25 | - key :handler, String | ||
26 | - key :run_at, Time | ||
27 | - key :locked_at, Time | ||
28 | - key :locked_by, String, :index => true | ||
29 | - key :failed_at, Time | ||
30 | - key :last_error, String | ||
31 | - timestamps! | ||
32 | - | ||
33 | - before_save :set_default_run_at | ||
34 | - | ||
35 | - ensure_index [[:priority, 1], [:run_at, 1]] | ||
36 | - | ||
37 | - def self.before_fork | ||
38 | - ::MongoMapper.connection.close | ||
39 | - end | ||
40 | - | ||
41 | - def self.after_fork | ||
42 | - ::MongoMapper.connect(RAILS_ENV) | ||
43 | - end | ||
44 | - | ||
45 | - def self.db_time_now | ||
46 | - Time.now.utc | ||
47 | - end | ||
48 | - | ||
49 | - def self.find_available(worker_name, limit = 5, max_run_time = Worker.max_run_time) | ||
50 | - right_now = db_time_now | ||
51 | - | ||
52 | - conditions = { | ||
53 | - :run_at => {"$lte" => right_now}, | ||
54 | - :limit => -limit, # In mongo, positive limits are 'soft' and negative are 'hard' | ||
55 | - :failed_at => nil, | ||
56 | - :sort => [['priority', 1], ['run_at', 1]] | ||
57 | - } | ||
58 | - | ||
59 | - where = "this.locked_at == null || this.locked_at < #{make_date(right_now - max_run_time)}" | ||
60 | - | ||
61 | - (conditions[:priority] ||= {})['$gte'] = Worker.min_priority.to_i if Worker.min_priority | ||
62 | - (conditions[:priority] ||= {})['$lte'] = Worker.max_priority.to_i if Worker.max_priority | ||
63 | - | ||
64 | - results = all(conditions.merge(:locked_by => worker_name)) | ||
65 | - results += all(conditions.merge('$where' => where)) if results.size < limit | ||
66 | - results | ||
67 | - end | ||
68 | - | ||
69 | - # When a worker is exiting, make sure we don't have any locked jobs. | ||
70 | - def self.clear_locks!(worker_name) | ||
71 | - collection.update({:locked_by => worker_name}, {"$set" => {:locked_at => nil, :locked_by => nil}}, :multi => true) | ||
72 | - end | ||
73 | - | ||
74 | - # Lock this job for this worker. | ||
75 | - # Returns true if we have the lock, false otherwise. | ||
76 | - def lock_exclusively!(max_run_time, worker = worker_name) | ||
77 | - right_now = self.class.db_time_now | ||
78 | - overtime = right_now - max_run_time.to_i | ||
79 | - | ||
80 | - query = "this.locked_at == null || this.locked_at < #{make_date(overtime)} || this.locked_by == #{worker.to_json}" | ||
81 | - conditions = {:_id => id, :run_at => {"$lte" => right_now}, "$where" => query} | ||
82 | - | ||
83 | - collection.update(conditions, {"$set" => {:locked_at => right_now, :locked_by => worker}}) | ||
84 | - affected_rows = collection.find({:_id => id, :locked_by => worker}).count | ||
85 | - if affected_rows == 1 | ||
86 | - self.locked_at = right_now | ||
87 | - self.locked_by = worker | ||
88 | - return true | ||
89 | - else | ||
90 | - return false | ||
91 | - end | ||
92 | - end | ||
93 | - | ||
94 | - private | ||
95 | - | ||
96 | - def self.make_date(date_or_seconds) | ||
97 | - "new Date(#{date_or_seconds.to_f * 1000})" | ||
98 | - end | ||
99 | - | ||
100 | - def make_date(date) | ||
101 | - self.class.make_date(date) | ||
102 | - end | ||
103 | - end | ||
104 | - end | ||
105 | - end | ||
106 | -end |
vendor/plugins/delayed_job/lib/delayed/backend/shared_spec.rb
0 → 100644
@@ -0,0 +1,594 @@ | @@ -0,0 +1,594 @@ | ||
1 | +require File.expand_path('../../../../spec/sample_jobs', __FILE__) | ||
2 | + | ||
3 | +require 'active_support/core_ext' | ||
4 | + | ||
5 | +shared_examples_for "a delayed_job backend" do | ||
6 | + let(:worker) { Delayed::Worker.new } | ||
7 | + | ||
8 | + def create_job(opts = {}) | ||
9 | + described_class.create(opts.merge(:payload_object => SimpleJob.new)) | ||
10 | + end | ||
11 | + | ||
12 | + before do | ||
13 | + Delayed::Worker.max_priority = nil | ||
14 | + Delayed::Worker.min_priority = nil | ||
15 | + Delayed::Worker.default_priority = 99 | ||
16 | + Delayed::Worker.delay_jobs = true | ||
17 | + SimpleJob.runs = 0 | ||
18 | + described_class.delete_all | ||
19 | + end | ||
20 | + | ||
21 | + after do | ||
22 | + Delayed::Worker.reset | ||
23 | + end | ||
24 | + | ||
25 | + it "sets run_at automatically if not set" do | ||
26 | + expect(described_class.create(:payload_object => ErrorJob.new ).run_at).not_to be_nil | ||
27 | + end | ||
28 | + | ||
29 | + it "does not set run_at automatically if already set" do | ||
30 | + later = described_class.db_time_now + 5.minutes | ||
31 | + job = described_class.create(:payload_object => ErrorJob.new, :run_at => later) | ||
32 | + expect(job.run_at).to be_within(1).of(later) | ||
33 | + end | ||
34 | + | ||
35 | + describe "#reload" do | ||
36 | + it "reloads the payload" do | ||
37 | + job = described_class.enqueue :payload_object => SimpleJob.new | ||
38 | + expect(job.payload_object.object_id).not_to eq(job.reload.payload_object.object_id) | ||
39 | + end | ||
40 | + end | ||
41 | + | ||
42 | + describe "enqueue" do | ||
43 | + context "with a hash" do | ||
44 | + it "raises ArgumentError when handler doesn't respond_to :perform" do | ||
45 | + expect{described_class.enqueue(:payload_object => Object.new)}.to raise_error(ArgumentError) | ||
46 | + end | ||
47 | + | ||
48 | + it "is able to set priority" do | ||
49 | + job = described_class.enqueue :payload_object => SimpleJob.new, :priority => 5 | ||
50 | + expect(job.priority).to eq(5) | ||
51 | + end | ||
52 | + | ||
53 | + it "uses default priority" do | ||
54 | + job = described_class.enqueue :payload_object => SimpleJob.new | ||
55 | + expect(job.priority).to eq(99) | ||
56 | + end | ||
57 | + | ||
58 | + it "is able to set run_at" do | ||
59 | + later = described_class.db_time_now + 5.minutes | ||
60 | + job = described_class.enqueue :payload_object => SimpleJob.new, :run_at => later | ||
61 | + expect(job.run_at).to be_within(1).of(later) | ||
62 | + end | ||
63 | + | ||
64 | + it "is able to set queue" do | ||
65 | + job = described_class.enqueue :payload_object => SimpleJob.new, :queue => 'tracking' | ||
66 | + expect(job.queue).to eq('tracking') | ||
67 | + end | ||
68 | + end | ||
69 | + | ||
70 | + context "with multiple arguments" do | ||
71 | + it "raises ArgumentError when handler doesn't respond_to :perform" do | ||
72 | + expect{described_class.enqueue(Object.new)}.to raise_error(ArgumentError) | ||
73 | + end | ||
74 | + | ||
75 | + it "increases count after enqueuing items" do | ||
76 | + described_class.enqueue SimpleJob.new | ||
77 | + expect(described_class.count).to eq(1) | ||
78 | + end | ||
79 | + | ||
80 | + it "is able to set priority [DEPRECATED]" do | ||
81 | + silence_warnings do | ||
82 | + job = described_class.enqueue SimpleJob.new, 5 | ||
83 | + expect(job.priority).to eq(5) | ||
84 | + end | ||
85 | + end | ||
86 | + | ||
87 | + it "uses default priority when it is not set" do | ||
88 | + @job = described_class.enqueue SimpleJob.new | ||
89 | + expect(@job.priority).to eq(99) | ||
90 | + end | ||
91 | + | ||
92 | + it "is able to set run_at [DEPRECATED]" do | ||
93 | + silence_warnings do | ||
94 | + later = described_class.db_time_now + 5.minutes | ||
95 | + @job = described_class.enqueue SimpleJob.new, 5, later | ||
96 | + expect(@job.run_at).to be_within(1).of(later) | ||
97 | + end | ||
98 | + end | ||
99 | + | ||
100 | + it "works with jobs in modules" do | ||
101 | + M::ModuleJob.runs = 0 | ||
102 | + job = described_class.enqueue M::ModuleJob.new | ||
103 | + expect{job.invoke_job}.to change { M::ModuleJob.runs }.from(0).to(1) | ||
104 | + end | ||
105 | + end | ||
106 | + | ||
107 | + context "with delay_jobs = false" do | ||
108 | + before(:each) do | ||
109 | + Delayed::Worker.delay_jobs = false | ||
110 | + end | ||
111 | + | ||
112 | + it "does not increase count after enqueuing items" do | ||
113 | + described_class.enqueue SimpleJob.new | ||
114 | + expect(described_class.count).to eq(0) | ||
115 | + end | ||
116 | + | ||
117 | + it "invokes the enqueued job" do | ||
118 | + job = SimpleJob.new | ||
119 | + job.should_receive(:perform) | ||
120 | + described_class.enqueue job | ||
121 | + end | ||
122 | + | ||
123 | + it "returns a job, not the result of invocation" do | ||
124 | + expect(described_class.enqueue(SimpleJob.new)).to be_instance_of(described_class) | ||
125 | + end | ||
126 | + end | ||
127 | + end | ||
128 | + | ||
129 | + describe "callbacks" do | ||
130 | + before(:each) do | ||
131 | + CallbackJob.messages = [] | ||
132 | + end | ||
133 | + | ||
134 | + %w(before success after).each do |callback| | ||
135 | + it "calls #{callback} with job" do | ||
136 | + job = described_class.enqueue(CallbackJob.new) | ||
137 | + job.payload_object.should_receive(callback).with(job) | ||
138 | + job.invoke_job | ||
139 | + end | ||
140 | + end | ||
141 | + | ||
142 | + it "calls before and after callbacks" do | ||
143 | + job = described_class.enqueue(CallbackJob.new) | ||
144 | + expect(CallbackJob.messages).to eq(["enqueue"]) | ||
145 | + job.invoke_job | ||
146 | + expect(CallbackJob.messages).to eq(["enqueue", "before", "perform", "success", "after"]) | ||
147 | + end | ||
148 | + | ||
149 | + it "calls the after callback with an error" do | ||
150 | + job = described_class.enqueue(CallbackJob.new) | ||
151 | + job.payload_object.should_receive(:perform).and_raise(RuntimeError.new("fail")) | ||
152 | + | ||
153 | + expect{job.invoke_job}.to raise_error | ||
154 | + expect(CallbackJob.messages).to eq(["enqueue", "before", "error: RuntimeError", "after"]) | ||
155 | + end | ||
156 | + | ||
157 | + it "calls error when before raises an error" do | ||
158 | + job = described_class.enqueue(CallbackJob.new) | ||
159 | + job.payload_object.should_receive(:before).and_raise(RuntimeError.new("fail")) | ||
160 | + expect{job.invoke_job}.to raise_error(RuntimeError) | ||
161 | + expect(CallbackJob.messages).to eq(["enqueue", "error: RuntimeError", "after"]) | ||
162 | + end | ||
163 | + end | ||
164 | + | ||
165 | + describe "payload_object" do | ||
166 | + it "raises a DeserializationError when the job class is totally unknown" do | ||
167 | + job = described_class.new :handler => "--- !ruby/object:JobThatDoesNotExist {}" | ||
168 | + expect{job.payload_object}.to raise_error(Delayed::DeserializationError) | ||
169 | + end | ||
170 | + | ||
171 | + it "raises a DeserializationError when the job struct is totally unknown" do | ||
172 | + job = described_class.new :handler => "--- !ruby/struct:StructThatDoesNotExist {}" | ||
173 | + expect{job.payload_object}.to raise_error(Delayed::DeserializationError) | ||
174 | + end | ||
175 | + | ||
176 | + it "raises a DeserializationError when the YAML.load raises argument error" do | ||
177 | + job = described_class.new :handler => "--- !ruby/struct:GoingToRaiseArgError {}" | ||
178 | + YAML.should_receive(:load).and_raise(ArgumentError) | ||
179 | + expect{job.payload_object}.to raise_error(Delayed::DeserializationError) | ||
180 | + end | ||
181 | + end | ||
182 | + | ||
183 | + describe "reserve" do | ||
184 | + before do | ||
185 | + Delayed::Worker.max_run_time = 2.minutes | ||
186 | + end | ||
187 | + | ||
188 | + after do | ||
189 | + Time.zone = nil | ||
190 | + end | ||
191 | + | ||
192 | + it "does not reserve failed jobs" do | ||
193 | + create_job :attempts => 50, :failed_at => described_class.db_time_now | ||
194 | + expect(described_class.reserve(worker)).to be_nil | ||
195 | + end | ||
196 | + | ||
197 | + it "does not reserve jobs scheduled for the future" do | ||
198 | + create_job :run_at => described_class.db_time_now + 1.minute | ||
199 | + expect(described_class.reserve(worker)).to be_nil | ||
200 | + end | ||
201 | + | ||
202 | + it "reserves jobs scheduled for the past" do | ||
203 | + job = create_job :run_at => described_class.db_time_now - 1.minute | ||
204 | + expect(described_class.reserve(worker)).to eq(job) | ||
205 | + end | ||
206 | + | ||
207 | + it "reserves jobs scheduled for the past when time zones are involved" do | ||
208 | + Time.zone = 'US/Eastern' | ||
209 | + job = create_job :run_at => described_class.db_time_now - 1.minute | ||
210 | + expect(described_class.reserve(worker)).to eq(job) | ||
211 | + end | ||
212 | + | ||
213 | + it "does not reserve jobs locked by other workers" do | ||
214 | + job = create_job | ||
215 | + other_worker = Delayed::Worker.new | ||
216 | + other_worker.name = 'other_worker' | ||
217 | + expect(described_class.reserve(other_worker)).to eq(job) | ||
218 | + expect(described_class.reserve(worker)).to be_nil | ||
219 | + end | ||
220 | + | ||
221 | + it "reserves open jobs" do | ||
222 | + job = create_job | ||
223 | + expect(described_class.reserve(worker)).to eq(job) | ||
224 | + end | ||
225 | + | ||
226 | + it "reserves expired jobs" do | ||
227 | + job = create_job(:locked_by => 'some other worker', :locked_at => described_class.db_time_now - Delayed::Worker.max_run_time - 1.minute) | ||
228 | + expect(described_class.reserve(worker)).to eq(job) | ||
229 | + end | ||
230 | + | ||
231 | + it "reserves own jobs" do | ||
232 | + job = create_job(:locked_by => worker.name, :locked_at => (described_class.db_time_now - 1.minutes)) | ||
233 | + expect(described_class.reserve(worker)).to eq(job) | ||
234 | + end | ||
235 | + end | ||
236 | + | ||
237 | + context "#name" do | ||
238 | + it "is the class name of the job that was enqueued" do | ||
239 | + expect(described_class.create(:payload_object => ErrorJob.new ).name).to eq('ErrorJob') | ||
240 | + end | ||
241 | + | ||
242 | + it "is the method that will be called if its a performable method object" do | ||
243 | + job = described_class.new(:payload_object => NamedJob.new) | ||
244 | + expect(job.name).to eq('named_job') | ||
245 | + end | ||
246 | + | ||
247 | + it "is the instance method that will be called if its a performable method object" do | ||
248 | + job = Story.create(:text => "...").delay.save | ||
249 | + expect(job.name).to eq('Story#save') | ||
250 | + end | ||
251 | + | ||
252 | + it "parses from handler on deserialization error" do | ||
253 | + job = Story.create(:text => "...").delay.text | ||
254 | + job.payload_object.object.destroy | ||
255 | + expect(job.reload.name).to eq('Delayed::PerformableMethod') | ||
256 | + end | ||
257 | + end | ||
258 | + | ||
259 | + context "worker prioritization" do | ||
260 | + after do | ||
261 | + Delayed::Worker.max_priority = nil | ||
262 | + Delayed::Worker.min_priority = nil | ||
263 | + end | ||
264 | + | ||
265 | + it "fetches jobs ordered by priority" do | ||
266 | + 10.times { described_class.enqueue SimpleJob.new, :priority => rand(10) } | ||
267 | + jobs = [] | ||
268 | + 10.times { jobs << described_class.reserve(worker) } | ||
269 | + expect(jobs.size).to eq(10) | ||
270 | + jobs.each_cons(2) do |a, b| | ||
271 | + expect(a.priority).to be <= b.priority | ||
272 | + end | ||
273 | + end | ||
274 | + | ||
275 | + it "only finds jobs greater than or equal to min priority" do | ||
276 | + min = 5 | ||
277 | + Delayed::Worker.min_priority = min | ||
278 | + [4,5,6].sort_by {|i| rand }.each {|i| create_job :priority => i } | ||
279 | + 2.times do | ||
280 | + job = described_class.reserve(worker) | ||
281 | + expect(job.priority).to be >= min | ||
282 | + job.destroy | ||
283 | + end | ||
284 | + expect(described_class.reserve(worker)).to be_nil | ||
285 | + end | ||
286 | + | ||
287 | + it "only finds jobs less than or equal to max priority" do | ||
288 | + max = 5 | ||
289 | + Delayed::Worker.max_priority = max | ||
290 | + [4,5,6].sort_by {|i| rand }.each {|i| create_job :priority => i } | ||
291 | + 2.times do | ||
292 | + job = described_class.reserve(worker) | ||
293 | + expect(job.priority).to be <= max | ||
294 | + job.destroy | ||
295 | + end | ||
296 | + expect(described_class.reserve(worker)).to be_nil | ||
297 | + end | ||
298 | + end | ||
299 | + | ||
300 | + context "clear_locks!" do | ||
301 | + before do | ||
302 | + @job = create_job(:locked_by => 'worker1', :locked_at => described_class.db_time_now) | ||
303 | + end | ||
304 | + | ||
305 | + it "clears locks for the given worker" do | ||
306 | + described_class.clear_locks!('worker1') | ||
307 | + expect(described_class.reserve(worker)).to eq(@job) | ||
308 | + end | ||
309 | + | ||
310 | + it "does not clear locks for other workers" do | ||
311 | + described_class.clear_locks!('different_worker') | ||
312 | + expect(described_class.reserve(worker)).not_to eq(@job) | ||
313 | + end | ||
314 | + end | ||
315 | + | ||
316 | + context "unlock" do | ||
317 | + before do | ||
318 | + @job = create_job(:locked_by => 'worker', :locked_at => described_class.db_time_now) | ||
319 | + end | ||
320 | + | ||
321 | + it "clears locks" do | ||
322 | + @job.unlock | ||
323 | + expect(@job.locked_by).to be_nil | ||
324 | + expect(@job.locked_at).to be_nil | ||
325 | + end | ||
326 | + end | ||
327 | + | ||
328 | + context "large handler" do | ||
329 | + before do | ||
330 | + text = "Lorem ipsum dolor sit amet. " * 1000 | ||
331 | + @job = described_class.enqueue Delayed::PerformableMethod.new(text, :length, {}) | ||
332 | + end | ||
333 | + | ||
334 | + it "has an id" do | ||
335 | + expect(@job.id).not_to be_nil | ||
336 | + end | ||
337 | + end | ||
338 | + | ||
339 | + context "named queues" do | ||
340 | + context "when worker has one queue set" do | ||
341 | + before(:each) do | ||
342 | + worker.queues = ['large'] | ||
343 | + end | ||
344 | + | ||
345 | + it "only works off jobs which are from its queue" do | ||
346 | + expect(SimpleJob.runs).to eq(0) | ||
347 | + | ||
348 | + create_job(:queue => "large") | ||
349 | + create_job(:queue => "small") | ||
350 | + worker.work_off | ||
351 | + | ||
352 | + expect(SimpleJob.runs).to eq(1) | ||
353 | + end | ||
354 | + end | ||
355 | + | ||
356 | + context "when worker has two queue set" do | ||
357 | + before(:each) do | ||
358 | + worker.queues = ['large', 'small'] | ||
359 | + end | ||
360 | + | ||
361 | + it "only works off jobs which are from its queue" do | ||
362 | + expect(SimpleJob.runs).to eq(0) | ||
363 | + | ||
364 | + create_job(:queue => "large") | ||
365 | + create_job(:queue => "small") | ||
366 | + create_job(:queue => "medium") | ||
367 | + create_job | ||
368 | + worker.work_off | ||
369 | + | ||
370 | + expect(SimpleJob.runs).to eq(2) | ||
371 | + end | ||
372 | + end | ||
373 | + | ||
374 | + context "when worker does not have queue set" do | ||
375 | + before(:each) do | ||
376 | + worker.queues = [] | ||
377 | + end | ||
378 | + | ||
379 | + it "works off all jobs" do | ||
380 | + expect(SimpleJob.runs).to eq(0) | ||
381 | + | ||
382 | + create_job(:queue => "one") | ||
383 | + create_job(:queue => "two") | ||
384 | + create_job | ||
385 | + worker.work_off | ||
386 | + | ||
387 | + expect(SimpleJob.runs).to eq(3) | ||
388 | + end | ||
389 | + end | ||
390 | + end | ||
391 | + | ||
392 | + context "max_attempts" do | ||
393 | + before(:each) do | ||
394 | + @job = described_class.enqueue SimpleJob.new | ||
395 | + end | ||
396 | + | ||
397 | + it "is not defined" do | ||
398 | + expect(@job.max_attempts).to be_nil | ||
399 | + end | ||
400 | + | ||
401 | + it "uses the max_retries value on the payload when defined" do | ||
402 | + @job.payload_object.stub(:max_attempts).and_return(99) | ||
403 | + expect(@job.max_attempts).to eq(99) | ||
404 | + end | ||
405 | + end | ||
406 | + | ||
407 | + describe "yaml serialization" do | ||
408 | + it "reloads changed attributes" do | ||
409 | + story = Story.create(:text => 'hello') | ||
410 | + job = story.delay.tell | ||
411 | + story.update_attributes :text => 'goodbye' | ||
412 | + expect(job.reload.payload_object.object.text).to eq('goodbye') | ||
413 | + end | ||
414 | + | ||
415 | + it "raises error ArgumentError the record is not persisted" do | ||
416 | + story = Story.new(:text => 'hello') | ||
417 | + if story.respond_to?(:new_record?) | ||
418 | + expect { | ||
419 | + story.delay.tell | ||
420 | + }.to raise_error(ArgumentError, "Jobs cannot be created for records before they've been persisted") | ||
421 | + end | ||
422 | + end | ||
423 | + | ||
424 | + it "raises deserialization error for destroyed records" do | ||
425 | + story = Story.create(:text => 'hello') | ||
426 | + job = story.delay.tell | ||
427 | + story.destroy | ||
428 | + expect { | ||
429 | + job.reload.payload_object | ||
430 | + }.to raise_error(Delayed::DeserializationError) | ||
431 | + end | ||
432 | + end | ||
433 | + | ||
434 | + describe "worker integration" do | ||
435 | + before do | ||
436 | + Delayed::Job.delete_all | ||
437 | + SimpleJob.runs = 0 | ||
438 | + end | ||
439 | + | ||
440 | + describe "running a job" do | ||
441 | + it "fails after Worker.max_run_time" do | ||
442 | + Delayed::Worker.max_run_time = 1.second | ||
443 | + job = Delayed::Job.create :payload_object => LongRunningJob.new | ||
444 | + worker.run(job) | ||
445 | + expect(job.reload.last_error).to match(/expired/) | ||
446 | + expect(job.reload.last_error).to match(/Delayed::Worker.max_run_time is only 1 second/) | ||
447 | + expect(job.attempts).to eq(1) | ||
448 | + end | ||
449 | + | ||
450 | + context "when the job raises a deserialization error" do | ||
451 | + after do | ||
452 | + Delayed::Worker.destroy_failed_jobs = true | ||
453 | + end | ||
454 | + | ||
455 | + it "marks the job as failed" do | ||
456 | + Delayed::Worker.destroy_failed_jobs = false | ||
457 | + job = described_class.create! :handler => "--- !ruby/object:JobThatDoesNotExist {}" | ||
458 | + worker.work_off | ||
459 | + job.reload | ||
460 | + expect(job).to be_failed | ||
461 | + end | ||
462 | + end | ||
463 | + end | ||
464 | + | ||
465 | + describe "failed jobs" do | ||
466 | + before do | ||
467 | + @job = Delayed::Job.enqueue(ErrorJob.new, :run_at => described_class.db_time_now - 1) | ||
468 | + end | ||
469 | + | ||
470 | + after do | ||
471 | + # reset default | ||
472 | + Delayed::Worker.destroy_failed_jobs = true | ||
473 | + end | ||
474 | + | ||
475 | + it "records last_error when destroy_failed_jobs = false, max_attempts = 1" do | ||
476 | + Delayed::Worker.destroy_failed_jobs = false | ||
477 | + Delayed::Worker.max_attempts = 1 | ||
478 | + worker.run(@job) | ||
479 | + @job.reload | ||
480 | + expect(@job.last_error).to match(/did not work/) | ||
481 | + expect(@job.attempts).to eq(1) | ||
482 | + expect(@job).to be_failed | ||
483 | + end | ||
484 | + | ||
485 | + it "re-schedules jobs after failing" do | ||
486 | + worker.work_off | ||
487 | + @job.reload | ||
488 | + expect(@job.last_error).to match(/did not work/) | ||
489 | + expect(@job.last_error).to match(/sample_jobs.rb:\d+:in `perform'/) | ||
490 | + expect(@job.attempts).to eq(1) | ||
491 | + expect(@job.run_at).to be > Delayed::Job.db_time_now - 10.minutes | ||
492 | + expect(@job.run_at).to be < Delayed::Job.db_time_now + 10.minutes | ||
493 | + expect(@job.locked_by).to be_nil | ||
494 | + expect(@job.locked_at).to be_nil | ||
495 | + end | ||
496 | + | ||
497 | + it "re-schedules jobs with handler provided time if present" do | ||
498 | + job = Delayed::Job.enqueue(CustomRescheduleJob.new(99.minutes)) | ||
499 | + worker.run(job) | ||
500 | + job.reload | ||
501 | + | ||
502 | + expect((Delayed::Job.db_time_now + 99.minutes - job.run_at).abs).to be < 1 | ||
503 | + end | ||
504 | + | ||
505 | + it "does not fail when the triggered error doesn't have a message" do | ||
506 | + error_with_nil_message = StandardError.new | ||
507 | + error_with_nil_message.stub(:message).and_return nil | ||
508 | + @job.stub(:invoke_job).and_raise error_with_nil_message | ||
509 | + expect{worker.run(@job)}.not_to raise_error | ||
510 | + end | ||
511 | + end | ||
512 | + | ||
513 | + context "reschedule" do | ||
514 | + before do | ||
515 | + @job = Delayed::Job.create :payload_object => SimpleJob.new | ||
516 | + end | ||
517 | + | ||
518 | + share_examples_for "any failure more than Worker.max_attempts times" do | ||
519 | + context "when the job's payload has a #failure hook" do | ||
520 | + before do | ||
521 | + @job = Delayed::Job.create :payload_object => OnPermanentFailureJob.new | ||
522 | + expect(@job.payload_object).to respond_to :failure | ||
523 | + end | ||
524 | + | ||
525 | + it "runs that hook" do | ||
526 | + @job.payload_object.should_receive :failure | ||
527 | + worker.reschedule(@job) | ||
528 | + end | ||
529 | + end | ||
530 | + | ||
531 | + context "when the job's payload has no #failure hook" do | ||
532 | + # It's a little tricky to test this in a straightforward way, | ||
533 | + # because putting a should_not_receive expectation on | ||
534 | + # @job.payload_object.failure makes that object | ||
535 | + # incorrectly return true to | ||
536 | + # payload_object.respond_to? :failure, which is what | ||
537 | + # reschedule uses to decide whether to call failure. | ||
538 | + # So instead, we just make sure that the payload_object as it | ||
539 | + # already stands doesn't respond_to? failure, then | ||
540 | + # shove it through the iterated reschedule loop and make sure we | ||
541 | + # don't get a NoMethodError (caused by calling that nonexistent | ||
542 | + # failure method). | ||
543 | + | ||
544 | + before do | ||
545 | + expect(@job.payload_object).not_to respond_to(:failure) | ||
546 | + end | ||
547 | + | ||
548 | + it "does not try to run that hook" do | ||
549 | + expect { | ||
550 | + Delayed::Worker.max_attempts.times { worker.reschedule(@job) } | ||
551 | + }.not_to raise_exception | ||
552 | + end | ||
553 | + end | ||
554 | + end | ||
555 | + | ||
556 | + context "and we want to destroy jobs" do | ||
557 | + it_should_behave_like "any failure more than Worker.max_attempts times" | ||
558 | + | ||
559 | + it "is destroyed if it failed more than Worker.max_attempts times" do | ||
560 | + @job.should_receive(:destroy) | ||
561 | + Delayed::Worker.max_attempts.times { worker.reschedule(@job) } | ||
562 | + end | ||
563 | + | ||
564 | + it "is not destroyed if failed fewer than Worker.max_attempts times" do | ||
565 | + @job.should_not_receive(:destroy) | ||
566 | + (Delayed::Worker.max_attempts - 1).times { worker.reschedule(@job) } | ||
567 | + end | ||
568 | + end | ||
569 | + | ||
570 | + context "and we don't want to destroy jobs" do | ||
571 | + before do | ||
572 | + Delayed::Worker.destroy_failed_jobs = false | ||
573 | + end | ||
574 | + | ||
575 | + after do | ||
576 | + Delayed::Worker.destroy_failed_jobs = true | ||
577 | + end | ||
578 | + | ||
579 | + it_should_behave_like "any failure more than Worker.max_attempts times" | ||
580 | + | ||
581 | + it "is failed if it failed more than Worker.max_attempts times" do | ||
582 | + expect(@job.reload).not_to be_failed | ||
583 | + Delayed::Worker.max_attempts.times { worker.reschedule(@job) } | ||
584 | + expect(@job.reload).to be_failed | ||
585 | + end | ||
586 | + | ||
587 | + it "is not failed if it failed fewer than Worker.max_attempts times" do | ||
588 | + (Delayed::Worker.max_attempts - 1).times { worker.reschedule(@job) } | ||
589 | + expect(@job.reload).not_to be_failed | ||
590 | + end | ||
591 | + end | ||
592 | + end | ||
593 | + end | ||
594 | +end |
vendor/plugins/delayed_job/lib/delayed/command.rb
1 | -require 'rubygems' | ||
2 | -require 'daemons' | 1 | +begin |
2 | + require 'daemons' | ||
3 | +rescue LoadError | ||
4 | + raise "You need to add gem 'daemons' to your Gemfile if you wish to use it." | ||
5 | +end | ||
3 | require 'optparse' | 6 | require 'optparse' |
4 | 7 | ||
5 | module Delayed | 8 | module Delayed |
6 | class Command | 9 | class Command |
7 | attr_accessor :worker_count | 10 | attr_accessor :worker_count |
8 | - | 11 | + |
9 | def initialize(args) | 12 | def initialize(args) |
10 | - @files_to_reopen = [] | ||
11 | @options = { | 13 | @options = { |
12 | :quiet => true, | 14 | :quiet => true, |
13 | :pid_dir => "#{Rails.root}/tmp/pids" | 15 | :pid_dir => "#{Rails.root}/tmp/pids" |
14 | } | 16 | } |
15 | - | 17 | + |
16 | @worker_count = 1 | 18 | @worker_count = 1 |
17 | @monitor = false | 19 | @monitor = false |
18 | - | 20 | + |
19 | opts = OptionParser.new do |opts| | 21 | opts = OptionParser.new do |opts| |
20 | opts.banner = "Usage: #{File.basename($0)} [options] start|stop|restart|run" | 22 | opts.banner = "Usage: #{File.basename($0)} [options] start|stop|restart|run" |
21 | 23 | ||
@@ -44,22 +46,32 @@ module Delayed | @@ -44,22 +46,32 @@ module Delayed | ||
44 | opts.on('-m', '--monitor', 'Start monitor process.') do | 46 | opts.on('-m', '--monitor', 'Start monitor process.') do |
45 | @monitor = true | 47 | @monitor = true |
46 | end | 48 | end |
47 | - | ||
48 | - | 49 | + opts.on('--sleep-delay N', "Amount of time to sleep when no jobs are found") do |n| |
50 | + @options[:sleep_delay] = n.to_i | ||
51 | + end | ||
52 | + opts.on('--read-ahead N', "Number of jobs from the queue to consider") do |n| | ||
53 | + @options[:read_ahead] = n | ||
54 | + end | ||
55 | + opts.on('-p', '--prefix NAME', "String to be prefixed to worker process names") do |prefix| | ||
56 | + @options[:prefix] = prefix | ||
57 | + end | ||
58 | + opts.on('--queues=queues', "Specify which queue DJ must look up for jobs") do |queues| | ||
59 | + @options[:queues] = queues.split(',') | ||
60 | + end | ||
61 | + opts.on('--queue=queue', "Specify which queue DJ must look up for jobs") do |queue| | ||
62 | + @options[:queues] = queue.split(',') | ||
63 | + end | ||
64 | + opts.on('--exit-on-complete', "Exit when no more jobs are available to run. This will exit if all jobs are scheduled to run in the future.") do | ||
65 | + @options[:exit_on_complete] = true | ||
66 | + end | ||
49 | end | 67 | end |
50 | @args = opts.parse!(args) | 68 | @args = opts.parse!(args) |
51 | end | 69 | end |
52 | - | ||
53 | - def daemonize | ||
54 | - Delayed::Worker.backend.before_fork | ||
55 | 70 | ||
56 | - ObjectSpace.each_object(File) do |file| | ||
57 | - @files_to_reopen << file unless file.closed? | ||
58 | - end | ||
59 | - | 71 | + def daemonize |
60 | dir = @options[:pid_dir] | 72 | dir = @options[:pid_dir] |
61 | Dir.mkdir(dir) unless File.exists?(dir) | 73 | Dir.mkdir(dir) unless File.exists?(dir) |
62 | - | 74 | + |
63 | if @worker_count > 1 && @options[:identifier] | 75 | if @worker_count > 1 && @options[:identifier] |
64 | raise ArgumentError, 'Cannot specify both --number-of-workers and --identifier' | 76 | raise ArgumentError, 'Cannot specify both --number-of-workers and --identifier' |
65 | elsif @worker_count == 1 && @options[:identifier] | 77 | elsif @worker_count == 1 && @options[:identifier] |
@@ -72,28 +84,21 @@ module Delayed | @@ -72,28 +84,21 @@ module Delayed | ||
72 | end | 84 | end |
73 | end | 85 | end |
74 | end | 86 | end |
75 | - | 87 | + |
76 | def run_process(process_name, dir) | 88 | def run_process(process_name, dir) |
89 | + Delayed::Worker.before_fork | ||
77 | Daemons.run_proc(process_name, :dir => dir, :dir_mode => :normal, :monitor => @monitor, :ARGV => @args) do |*args| | 90 | Daemons.run_proc(process_name, :dir => dir, :dir_mode => :normal, :monitor => @monitor, :ARGV => @args) do |*args| |
91 | + $0 = File.join(@options[:prefix], process_name) if @options[:prefix] | ||
78 | run process_name | 92 | run process_name |
79 | end | 93 | end |
80 | end | 94 | end |
81 | - | 95 | + |
82 | def run(worker_name = nil) | 96 | def run(worker_name = nil) |
83 | Dir.chdir(Rails.root) | 97 | Dir.chdir(Rails.root) |
84 | - | ||
85 | - # Re-open file handles | ||
86 | - @files_to_reopen.each do |file| | ||
87 | - begin | ||
88 | - file.reopen file.path, "a+" | ||
89 | - file.sync = true | ||
90 | - rescue ::Exception | ||
91 | - end | ||
92 | - end | ||
93 | - | ||
94 | - Delayed::Worker.logger = Logger.new(File.join(Rails.root, 'log', 'delayed_job.log')) | ||
95 | - Delayed::Worker.backend.after_fork | ||
96 | - | 98 | + |
99 | + Delayed::Worker.after_fork | ||
100 | + Delayed::Worker.logger ||= Logger.new(File.join(Rails.root, 'log', 'delayed_job.log')) | ||
101 | + | ||
97 | worker = Delayed::Worker.new(@options) | 102 | worker = Delayed::Worker.new(@options) |
98 | worker.name_prefix = "#{worker_name} " | 103 | worker.name_prefix = "#{worker_name} " |
99 | worker.start | 104 | worker.start |
@@ -102,6 +107,5 @@ module Delayed | @@ -102,6 +107,5 @@ module Delayed | ||
102 | STDERR.puts e.message | 107 | STDERR.puts e.message |
103 | exit 1 | 108 | exit 1 |
104 | end | 109 | end |
105 | - | ||
106 | end | 110 | end |
107 | end | 111 | end |
@@ -0,0 +1,27 @@ | @@ -0,0 +1,27 @@ | ||
1 | +require 'active_support/version' | ||
2 | + | ||
3 | +module Delayed | ||
4 | + module Compatibility | ||
5 | + if ActiveSupport::VERSION::MAJOR >= 4 | ||
6 | + require 'active_support/proxy_object' | ||
7 | + | ||
8 | + def self.executable_prefix | ||
9 | + 'bin' | ||
10 | + end | ||
11 | + | ||
12 | + def self.proxy_object_class | ||
13 | + ActiveSupport::ProxyObject | ||
14 | + end | ||
15 | + else | ||
16 | + require 'active_support/basic_object' | ||
17 | + | ||
18 | + def self.executable_prefix | ||
19 | + 'script' | ||
20 | + end | ||
21 | + | ||
22 | + def self.proxy_object_class | ||
23 | + ActiveSupport::BasicObject | ||
24 | + end | ||
25 | + end | ||
26 | + end | ||
27 | +end |
vendor/plugins/delayed_job/lib/delayed/deserialization_error.rb
0 → 100644
@@ -0,0 +1,84 @@ | @@ -0,0 +1,84 @@ | ||
1 | +module Delayed | ||
2 | + class InvalidCallback < Exception; end | ||
3 | + | ||
4 | + class Lifecycle | ||
5 | + EVENTS = { | ||
6 | + :enqueue => [:job], | ||
7 | + :execute => [:worker], | ||
8 | + :loop => [:worker], | ||
9 | + :perform => [:worker, :job], | ||
10 | + :error => [:worker, :job], | ||
11 | + :failure => [:worker, :job], | ||
12 | + :invoke_job => [:job] | ||
13 | + } | ||
14 | + | ||
15 | + def initialize | ||
16 | + @callbacks = EVENTS.keys.inject({}) { |hash, e| hash[e] = Callback.new; hash } | ||
17 | + end | ||
18 | + | ||
19 | + def before(event, &block) | ||
20 | + add(:before, event, &block) | ||
21 | + end | ||
22 | + | ||
23 | + def after(event, &block) | ||
24 | + add(:after, event, &block) | ||
25 | + end | ||
26 | + | ||
27 | + def around(event, &block) | ||
28 | + add(:around, event, &block) | ||
29 | + end | ||
30 | + | ||
31 | + def run_callbacks(event, *args, &block) | ||
32 | + missing_callback(event) unless @callbacks.has_key?(event) | ||
33 | + | ||
34 | + unless EVENTS[event].size == args.size | ||
35 | + raise ArgumentError, "Callback #{event} expects #{EVENTS[event].size} parameter(s): #{EVENTS[event].join(', ')}" | ||
36 | + end | ||
37 | + | ||
38 | + @callbacks[event].execute(*args, &block) | ||
39 | + end | ||
40 | + | ||
41 | + private | ||
42 | + | ||
43 | + def add(type, event, &block) | ||
44 | + missing_callback(event) unless @callbacks.has_key?(event) | ||
45 | + | ||
46 | + @callbacks[event].add(type, &block) | ||
47 | + end | ||
48 | + | ||
49 | + def missing_callback(event) | ||
50 | + raise InvalidCallback, "Unknown callback event: #{event}" | ||
51 | + end | ||
52 | + end | ||
53 | + | ||
54 | + class Callback | ||
55 | + def initialize | ||
56 | + @before = [] | ||
57 | + @after = [] | ||
58 | + | ||
59 | + # Identity proc. Avoids special cases when there is no existing around chain. | ||
60 | + @around = lambda { |*args, &block| block.call(*args) } | ||
61 | + end | ||
62 | + | ||
63 | + def execute(*args, &block) | ||
64 | + @before.each { |c| c.call(*args) } | ||
65 | + result = @around.call(*args, &block) | ||
66 | + @after.each { |c| c.call(*args) } | ||
67 | + result | ||
68 | + end | ||
69 | + | ||
70 | + def add(type, &callback) | ||
71 | + case type | ||
72 | + when :before | ||
73 | + @before << callback | ||
74 | + when :after | ||
75 | + @after << callback | ||
76 | + when :around | ||
77 | + chain = @around # use a local variable so that the current chain is closed over in the following lambda | ||
78 | + @around = lambda { |*a, &block| chain.call(*a) { |*b| callback.call(*b, &block) } } | ||
79 | + else | ||
80 | + raise InvalidCallback, "Invalid callback type: #{type}" | ||
81 | + end | ||
82 | + end | ||
83 | + end | ||
84 | +end |
vendor/plugins/delayed_job/lib/delayed/message_sending.rb
1 | -require 'active_support/basic_object' | 1 | +require 'active_support/core_ext/module/aliasing' |
2 | 2 | ||
3 | module Delayed | 3 | module Delayed |
4 | - class DelayProxy < ActiveSupport::BasicObject | ||
5 | - def initialize(target, options) | 4 | + class DelayProxy < Delayed::Compatibility.proxy_object_class |
5 | + def initialize(payload_class, target, options) | ||
6 | + @payload_class = payload_class | ||
6 | @target = target | 7 | @target = target |
7 | @options = options | 8 | @options = options |
8 | end | 9 | end |
9 | 10 | ||
10 | def method_missing(method, *args) | 11 | def method_missing(method, *args) |
11 | - if (Rails.env == "test" or Rails.env == "cucumber" and !$DISABLE_DELAYED_JOB_TEST_ENV_RUN) | ||
12 | - @target.send method, *args | ||
13 | - return | ||
14 | - end | ||
15 | - Job.create({ | ||
16 | - :payload_object => PerformableMethod.new(@target, method.to_sym, args), | ||
17 | - :priority => ::Delayed::Worker.default_priority | ||
18 | - }.merge(@options)) | 12 | + Job.enqueue({:payload_object => @payload_class.new(@target, method.to_sym, args)}.merge(@options)) |
19 | end | 13 | end |
20 | end | 14 | end |
21 | 15 | ||
22 | module MessageSending | 16 | module MessageSending |
23 | def delay(options = {}) | 17 | def delay(options = {}) |
24 | - DelayProxy.new(self, options) | 18 | + DelayProxy.new(PerformableMethod, self, options) |
25 | end | 19 | end |
26 | alias __delay__ delay | 20 | alias __delay__ delay |
27 | - | 21 | + |
28 | def send_later(method, *args) | 22 | def send_later(method, *args) |
29 | warn "[DEPRECATION] `object.send_later(:method)` is deprecated. Use `object.delay.method" | 23 | warn "[DEPRECATION] `object.send_later(:method)` is deprecated. Use `object.delay.method" |
30 | __delay__.__send__(method, *args) | 24 | __delay__.__send__(method, *args) |
@@ -34,17 +28,26 @@ module Delayed | @@ -34,17 +28,26 @@ module Delayed | ||
34 | warn "[DEPRECATION] `object.send_at(time, :method)` is deprecated. Use `object.delay(:run_at => time).method" | 28 | warn "[DEPRECATION] `object.send_at(time, :method)` is deprecated. Use `object.delay(:run_at => time).method" |
35 | __delay__(:run_at => time).__send__(method, *args) | 29 | __delay__(:run_at => time).__send__(method, *args) |
36 | end | 30 | end |
37 | - | 31 | + |
38 | module ClassMethods | 32 | module ClassMethods |
39 | - def handle_asynchronously(method) | ||
40 | - return if (Rails.env == "test" or Rails.env == "cucumber") | 33 | + def handle_asynchronously(method, opts = {}) |
41 | aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1 | 34 | aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1 |
42 | with_method, without_method = "#{aliased_method}_with_delay#{punctuation}", "#{aliased_method}_without_delay#{punctuation}" | 35 | with_method, without_method = "#{aliased_method}_with_delay#{punctuation}", "#{aliased_method}_without_delay#{punctuation}" |
43 | define_method(with_method) do |*args| | 36 | define_method(with_method) do |*args| |
44 | - delay.__send__(without_method, *args) | 37 | + curr_opts = opts.clone |
38 | + curr_opts.each_key do |key| | ||
39 | + if (val = curr_opts[key]).is_a?(Proc) | ||
40 | + curr_opts[key] = if val.arity == 1 | ||
41 | + val.call(self) | ||
42 | + else | ||
43 | + val.call | ||
44 | + end | ||
45 | + end | ||
46 | + end | ||
47 | + delay(curr_opts).__send__(without_method, *args) | ||
45 | end | 48 | end |
46 | alias_method_chain method, :delay | 49 | alias_method_chain method, :delay |
47 | end | 50 | end |
48 | end | 51 | end |
49 | - end | 52 | + end |
50 | end | 53 | end |
vendor/plugins/delayed_job/lib/delayed/performable_mailer.rb
0 → 100644
@@ -0,0 +1,21 @@ | @@ -0,0 +1,21 @@ | ||
1 | +require 'mail' | ||
2 | + | ||
3 | +module Delayed | ||
4 | + class PerformableMailer < PerformableMethod | ||
5 | + def perform | ||
6 | + object.send(method_name, *args).deliver | ||
7 | + end | ||
8 | + end | ||
9 | + | ||
10 | + module DelayMail | ||
11 | + def delay(options = {}) | ||
12 | + DelayProxy.new(PerformableMailer, self, options) | ||
13 | + end | ||
14 | + end | ||
15 | +end | ||
16 | + | ||
17 | +Mail::Message.class_eval do | ||
18 | + def delay(*args) | ||
19 | + raise RuntimeError, "Use MyMailer.delay.mailer_action(args) to delay sending of emails." | ||
20 | + end | ||
21 | +end |
vendor/plugins/delayed_job/lib/delayed/performable_method.rb
1 | +require 'active_support/core_ext/module/delegation' | ||
2 | + | ||
1 | module Delayed | 3 | module Delayed |
2 | - class PerformableMethod < Struct.new(:object, :method, :args) | ||
3 | - def initialize(object, method, args) | ||
4 | - raise NoMethodError, "undefined method `#{method}' for #{object.inspect}" unless object.respond_to?(method, true) | 4 | + class PerformableMethod |
5 | + attr_accessor :object, :method_name, :args | ||
6 | + | ||
7 | + delegate :method, :to => :object | ||
8 | + | ||
9 | + def initialize(object, method_name, args) | ||
10 | + raise NoMethodError, "undefined method `#{method_name}' for #{object.inspect}" unless object.respond_to?(method_name, true) | ||
5 | 11 | ||
6 | - self.object = object | ||
7 | - self.args = args | ||
8 | - self.method = method.to_sym | 12 | + if object.respond_to?(:new_record?) && object.new_record? |
13 | + raise(ArgumentError, 'Jobs cannot be created for records before they\'ve been persisted') | ||
14 | + end | ||
15 | + | ||
16 | + self.object = object | ||
17 | + self.args = args | ||
18 | + self.method_name = method_name.to_sym | ||
9 | end | 19 | end |
10 | - | 20 | + |
11 | def display_name | 21 | def display_name |
12 | - "#{object.class}##{method}" | 22 | + "#{object.class}##{method_name}" |
13 | end | 23 | end |
14 | - | 24 | + |
15 | def perform | 25 | def perform |
16 | - object.send(method, *args) if object | 26 | + object.send(method_name, *args) if object |
17 | end | 27 | end |
18 | - | 28 | + |
19 | def method_missing(symbol, *args) | 29 | def method_missing(symbol, *args) |
20 | - object.respond_to?(symbol) ? object.send(symbol, *args) : super | 30 | + object.send(symbol, *args) |
21 | end | 31 | end |
22 | - | 32 | + |
23 | def respond_to?(symbol, include_private=false) | 33 | def respond_to?(symbol, include_private=false) |
24 | - object.respond_to?(symbol, include_private) || super | ||
25 | - end | 34 | + super || object.respond_to?(symbol, include_private) |
35 | + end | ||
26 | end | 36 | end |
27 | end | 37 | end |
@@ -0,0 +1,15 @@ | @@ -0,0 +1,15 @@ | ||
1 | +require 'active_support/core_ext/class/attribute' | ||
2 | + | ||
3 | +module Delayed | ||
4 | + class Plugin | ||
5 | + class_attribute :callback_block | ||
6 | + | ||
7 | + def self.callbacks(&block) | ||
8 | + self.callback_block = block | ||
9 | + end | ||
10 | + | ||
11 | + def initialize | ||
12 | + self.class.callback_block.call(Delayed::Worker.lifecycle) if self.class.callback_block | ||
13 | + end | ||
14 | + end | ||
15 | +end |
vendor/plugins/delayed_job/lib/delayed/plugins/clear_locks.rb
0 → 100644
@@ -0,0 +1,15 @@ | @@ -0,0 +1,15 @@ | ||
1 | +module Delayed | ||
2 | + module Plugins | ||
3 | + class ClearLocks < Plugin | ||
4 | + callbacks do |lifecycle| | ||
5 | + lifecycle.around(:execute) do |worker, &block| | ||
6 | + begin | ||
7 | + block.call(worker) | ||
8 | + ensure | ||
9 | + Delayed::Job.clear_locks!(worker.name) | ||
10 | + end | ||
11 | + end | ||
12 | + end | ||
13 | + end | ||
14 | + end | ||
15 | +end |
@@ -0,0 +1,146 @@ | @@ -0,0 +1,146 @@ | ||
1 | +if defined?(ActiveRecord) | ||
2 | + ActiveRecord::Base.class_eval do | ||
3 | + if instance_methods.include?(:encode_with) | ||
4 | + def encode_with_override(coder) | ||
5 | + encode_with_without_override(coder) | ||
6 | + coder.tag = "!ruby/ActiveRecord:#{self.class.name}" | ||
7 | + end | ||
8 | + alias_method :encode_with_without_override, :encode_with | ||
9 | + alias_method :encode_with, :encode_with_override | ||
10 | + else | ||
11 | + def encode_with(coder) | ||
12 | + coder["attributes"] = attributes | ||
13 | + coder.tag = "!ruby/ActiveRecord:#{self.class.name}" | ||
14 | + end | ||
15 | + end | ||
16 | + end | ||
17 | +end | ||
18 | + | ||
19 | +class Delayed::PerformableMethod | ||
20 | + # serialize to YAML | ||
21 | + def encode_with(coder) | ||
22 | + coder.map = { | ||
23 | + "object" => object, | ||
24 | + "method_name" => method_name, | ||
25 | + "args" => args | ||
26 | + } | ||
27 | + end | ||
28 | +end | ||
29 | + | ||
30 | +module Psych | ||
31 | + module Visitors | ||
32 | + class YAMLTree | ||
33 | + def visit_Class(klass) | ||
34 | + @emitter.scalar klass.name, nil, '!ruby/class', false, false, Nodes::Scalar::SINGLE_QUOTED | ||
35 | + end | ||
36 | + end | ||
37 | + | ||
38 | + class ToRuby | ||
39 | + def visit_Psych_Nodes_Scalar(o) | ||
40 | + @st[o.anchor] = o.value if o.anchor | ||
41 | + | ||
42 | + if klass = Psych.load_tags[o.tag] | ||
43 | + instance = klass.allocate | ||
44 | + | ||
45 | + if instance.respond_to?(:init_with) | ||
46 | + coder = Psych::Coder.new(o.tag) | ||
47 | + coder.scalar = o.value | ||
48 | + instance.init_with coder | ||
49 | + end | ||
50 | + | ||
51 | + return instance | ||
52 | + end | ||
53 | + | ||
54 | + return o.value if o.quoted | ||
55 | + return @ss.tokenize(o.value) unless o.tag | ||
56 | + | ||
57 | + case o.tag | ||
58 | + when '!binary', 'tag:yaml.org,2002:binary' | ||
59 | + o.value.unpack('m').first | ||
60 | + when '!str', 'tag:yaml.org,2002:str' | ||
61 | + o.value | ||
62 | + when "!ruby/object:DateTime" | ||
63 | + require 'date' | ||
64 | + @ss.parse_time(o.value).to_datetime | ||
65 | + when "!ruby/object:Complex" | ||
66 | + Complex(o.value) | ||
67 | + when "!ruby/object:Rational" | ||
68 | + Rational(o.value) | ||
69 | + when "!ruby/class", "!ruby/module" | ||
70 | + resolve_class o.value | ||
71 | + when "tag:yaml.org,2002:float", "!float" | ||
72 | + Float(@ss.tokenize(o.value)) | ||
73 | + when "!ruby/regexp" | ||
74 | + o.value =~ /^\/(.*)\/([mixn]*)$/ | ||
75 | + source = $1 | ||
76 | + options = 0 | ||
77 | + lang = nil | ||
78 | + ($2 || '').split('').each do |option| | ||
79 | + case option | ||
80 | + when 'x' then options |= Regexp::EXTENDED | ||
81 | + when 'i' then options |= Regexp::IGNORECASE | ||
82 | + when 'm' then options |= Regexp::MULTILINE | ||
83 | + when 'n' then options |= Regexp::NOENCODING | ||
84 | + else lang = option | ||
85 | + end | ||
86 | + end | ||
87 | + Regexp.new(*[source, options, lang].compact) | ||
88 | + when "!ruby/range" | ||
89 | + args = o.value.split(/([.]{2,3})/, 2).map { |s| | ||
90 | + accept Nodes::Scalar.new(s) | ||
91 | + } | ||
92 | + args.push(args.delete_at(1) == '...') | ||
93 | + Range.new(*args) | ||
94 | + when /^!ruby\/sym(bol)?:?(.*)?$/ | ||
95 | + o.value.to_sym | ||
96 | + else | ||
97 | + @ss.tokenize o.value | ||
98 | + end | ||
99 | + end | ||
100 | + | ||
101 | + def visit_Psych_Nodes_Mapping_with_class(object) | ||
102 | + return revive(Psych.load_tags[object.tag], object) if Psych.load_tags[object.tag] | ||
103 | + | ||
104 | + case object.tag | ||
105 | + when /^!ruby\/ActiveRecord:(.+)$/ | ||
106 | + klass = resolve_class($1) | ||
107 | + payload = Hash[*object.children.map { |c| accept c }] | ||
108 | + id = payload["attributes"][klass.primary_key] | ||
109 | + begin | ||
110 | + klass.unscoped.find(id) | ||
111 | + rescue ActiveRecord::RecordNotFound | ||
112 | + raise Delayed::DeserializationError | ||
113 | + end | ||
114 | + when /^!ruby\/Mongoid:(.+)$/ | ||
115 | + klass = resolve_class($1) | ||
116 | + payload = Hash[*object.children.map { |c| accept c }] | ||
117 | + begin | ||
118 | + klass.find(payload["attributes"]["_id"]) | ||
119 | + rescue Mongoid::Errors::DocumentNotFound | ||
120 | + raise Delayed::DeserializationError | ||
121 | + end | ||
122 | + when /^!ruby\/DataMapper:(.+)$/ | ||
123 | + klass = resolve_class($1) | ||
124 | + payload = Hash[*object.children.map { |c| accept c }] | ||
125 | + begin | ||
126 | + primary_keys = klass.properties.select { |p| p.key? } | ||
127 | + key_names = primary_keys.map { |p| p.name.to_s } | ||
128 | + klass.get!(*key_names.map { |k| payload["attributes"][k] }) | ||
129 | + rescue DataMapper::ObjectNotFoundError | ||
130 | + raise Delayed::DeserializationError | ||
131 | + end | ||
132 | + else | ||
133 | + visit_Psych_Nodes_Mapping_without_class(object) | ||
134 | + end | ||
135 | + end | ||
136 | + alias_method_chain :visit_Psych_Nodes_Mapping, :class | ||
137 | + | ||
138 | + def resolve_class_with_constantize(klass_name) | ||
139 | + klass_name.constantize | ||
140 | + rescue | ||
141 | + resolve_class_without_constantize(klass_name) | ||
142 | + end | ||
143 | + alias_method_chain :resolve_class, :constantize | ||
144 | + end | ||
145 | + end | ||
146 | +end |
vendor/plugins/delayed_job/lib/delayed/railtie.rb
@@ -4,7 +4,9 @@ require 'rails' | @@ -4,7 +4,9 @@ require 'rails' | ||
4 | module Delayed | 4 | module Delayed |
5 | class Railtie < Rails::Railtie | 5 | class Railtie < Rails::Railtie |
6 | initializer :after_initialize do | 6 | initializer :after_initialize do |
7 | - Delayed::Worker.guess_backend | 7 | + ActiveSupport.on_load(:action_mailer) do |
8 | + ActionMailer::Base.send(:extend, Delayed::DelayMail) | ||
9 | + end | ||
8 | end | 10 | end |
9 | 11 | ||
10 | rake_tasks do | 12 | rake_tasks do |
vendor/plugins/delayed_job/lib/delayed/recipes.rb
@@ -6,26 +6,49 @@ | @@ -6,26 +6,49 @@ | ||
6 | # after "deploy:stop", "delayed_job:stop" | 6 | # after "deploy:stop", "delayed_job:stop" |
7 | # after "deploy:start", "delayed_job:start" | 7 | # after "deploy:start", "delayed_job:start" |
8 | # after "deploy:restart", "delayed_job:restart" | 8 | # after "deploy:restart", "delayed_job:restart" |
9 | +# | ||
10 | +# If you want to use command line options, for example to start multiple workers, | ||
11 | +# define a Capistrano variable delayed_job_args: | ||
12 | +# | ||
13 | +# set :delayed_job_args, "-n 2" | ||
14 | +# | ||
15 | +# If you've got delayed_job workers running on a servers, you can also specify | ||
16 | +# which servers have delayed_job running and should be restarted after deploy. | ||
17 | +# | ||
18 | +# set :delayed_job_server_role, :worker | ||
19 | +# | ||
9 | 20 | ||
10 | Capistrano::Configuration.instance.load do | 21 | Capistrano::Configuration.instance.load do |
11 | namespace :delayed_job do | 22 | namespace :delayed_job do |
12 | def rails_env | 23 | def rails_env |
13 | fetch(:rails_env, false) ? "RAILS_ENV=#{fetch(:rails_env)}" : '' | 24 | fetch(:rails_env, false) ? "RAILS_ENV=#{fetch(:rails_env)}" : '' |
14 | end | 25 | end |
15 | - | 26 | + |
27 | + def args | ||
28 | + fetch(:delayed_job_args, "") | ||
29 | + end | ||
30 | + | ||
31 | + def roles | ||
32 | + fetch(:delayed_job_server_role, :app) | ||
33 | + end | ||
34 | + | ||
35 | + def delayed_job_command | ||
36 | + fetch(:delayed_job_command, "script/delayed_job") | ||
37 | + end | ||
38 | + | ||
16 | desc "Stop the delayed_job process" | 39 | desc "Stop the delayed_job process" |
17 | - task :stop, :roles => :app do | ||
18 | - run "cd #{current_path};#{rails_env} script/delayed_job stop" | 40 | + task :stop, :roles => lambda { roles } do |
41 | + run "cd #{current_path};#{rails_env} #{delayed_job_command} stop" | ||
19 | end | 42 | end |
20 | 43 | ||
21 | desc "Start the delayed_job process" | 44 | desc "Start the delayed_job process" |
22 | - task :start, :roles => :app do | ||
23 | - run "cd #{current_path};#{rails_env} script/delayed_job start" | 45 | + task :start, :roles => lambda { roles } do |
46 | + run "cd #{current_path};#{rails_env} #{delayed_job_command} start #{args}" | ||
24 | end | 47 | end |
25 | 48 | ||
26 | desc "Restart the delayed_job process" | 49 | desc "Restart the delayed_job process" |
27 | - task :restart, :roles => :app do | ||
28 | - run "cd #{current_path};#{rails_env} script/delayed_job restart" | 50 | + task :restart, :roles => lambda { roles } do |
51 | + run "cd #{current_path};#{rails_env} #{delayed_job_command} restart #{args}" | ||
29 | end | 52 | end |
30 | end | 53 | end |
31 | -end | ||
32 | \ No newline at end of file | 54 | \ No newline at end of file |
55 | +end |
vendor/plugins/delayed_job/lib/delayed/serialization/active_record.rb
0 → 100644
@@ -0,0 +1,15 @@ | @@ -0,0 +1,15 @@ | ||
1 | +if defined?(ActiveRecord) | ||
2 | + class ActiveRecord::Base | ||
3 | + yaml_as "tag:ruby.yaml.org,2002:ActiveRecord" | ||
4 | + | ||
5 | + def self.yaml_new(klass, tag, val) | ||
6 | + klass.unscoped.find(val['attributes'][klass.primary_key]) | ||
7 | + rescue ActiveRecord::RecordNotFound | ||
8 | + raise Delayed::DeserializationError, "ActiveRecord::RecordNotFound, class: #{klass} , primary key: #{val['attributes'][klass.primary_key]} " | ||
9 | + end | ||
10 | + | ||
11 | + def to_yaml_properties | ||
12 | + ['@attributes'] | ||
13 | + end | ||
14 | + end | ||
15 | +end |
@@ -0,0 +1,34 @@ | @@ -0,0 +1,34 @@ | ||
1 | +class Module | ||
2 | + yaml_as "tag:ruby.yaml.org,2002:module" | ||
3 | + | ||
4 | + def self.yaml_new(klass, tag, val) | ||
5 | + val.constantize | ||
6 | + end | ||
7 | + | ||
8 | + def to_yaml(options = {}) | ||
9 | + YAML.quick_emit(nil, options) do |out| | ||
10 | + out.scalar(taguri, name, :plain) | ||
11 | + end | ||
12 | + end | ||
13 | + | ||
14 | + def yaml_tag_read_class(name) | ||
15 | + # Constantize the object so that ActiveSupport can attempt | ||
16 | + # its auto loading magic. Will raise LoadError if not successful. | ||
17 | + name.constantize | ||
18 | + name | ||
19 | + end | ||
20 | +end | ||
21 | + | ||
22 | +class Class | ||
23 | + yaml_as "tag:ruby.yaml.org,2002:class" | ||
24 | + remove_method :to_yaml if respond_to?(:to_yaml) && method(:to_yaml).owner == Class # use Module's to_yaml | ||
25 | +end | ||
26 | + | ||
27 | +class Struct | ||
28 | + def self.yaml_tag_read_class(name) | ||
29 | + # Constantize the object so that ActiveSupport can attempt | ||
30 | + # its auto loading magic. Will raise LoadError if not successful. | ||
31 | + name.constantize | ||
32 | + "Struct::#{ name }" | ||
33 | + end | ||
34 | +end |
vendor/plugins/delayed_job/lib/delayed/tasks.rb
1 | -# Re-definitions are appended to existing tasks | ||
2 | -task :environment | ||
3 | -task :merb_env | ||
4 | - | ||
5 | namespace :jobs do | 1 | namespace :jobs do |
6 | desc "Clear the delayed_job queue." | 2 | desc "Clear the delayed_job queue." |
7 | - task :clear => [:merb_env, :environment] do | 3 | + task :clear => :environment do |
8 | Delayed::Job.delete_all | 4 | Delayed::Job.delete_all |
9 | end | 5 | end |
10 | 6 | ||
11 | desc "Start a delayed_job worker." | 7 | desc "Start a delayed_job worker." |
12 | - task :work => [:merb_env, :environment] do | ||
13 | - Delayed::Worker.new(:min_priority => ENV['MIN_PRIORITY'], :max_priority => ENV['MAX_PRIORITY']).start | 8 | + task :work => :environment_options do |
9 | + Delayed::Worker.new(@worker_options).start | ||
10 | + end | ||
11 | + | ||
12 | + desc "Start a delayed_job worker and exit when all available jobs are complete." | ||
13 | + task :workoff => :environment_options do | ||
14 | + Delayed::Worker.new(@worker_options.merge({:exit_on_complete => true})).start | ||
14 | end | 15 | end |
16 | + | ||
17 | + task :environment_options => :environment do | ||
18 | + @worker_options = { | ||
19 | + :min_priority => ENV['MIN_PRIORITY'], | ||
20 | + :max_priority => ENV['MAX_PRIORITY'], | ||
21 | + :queues => (ENV['QUEUES'] || ENV['QUEUE'] || '').split(','), | ||
22 | + :quiet => false | ||
23 | + } | ||
24 | + end | ||
25 | + | ||
26 | + desc "Exit with error status if any jobs older than max_age seconds haven't been attempted yet." | ||
27 | + task :check, [:max_age] => :environment do |_, args| | ||
28 | + args.with_defaults(:max_age => 300) | ||
29 | + | ||
30 | + unprocessed_jobs = Delayed::Job.where('attempts = 0 AND created_at < ?', Time.now - args[:max_age].to_i).count | ||
31 | + | ||
32 | + if unprocessed_jobs > 0 | ||
33 | + fail "#{unprocessed_jobs} jobs older than #{args[:max_age]} seconds have not been processed yet" | ||
34 | + end | ||
35 | + | ||
36 | + end | ||
37 | + | ||
15 | end | 38 | end |
vendor/plugins/delayed_job/lib/delayed/worker.rb
@@ -2,59 +2,122 @@ require 'timeout' | @@ -2,59 +2,122 @@ require 'timeout' | ||
2 | require 'active_support/core_ext/numeric/time' | 2 | require 'active_support/core_ext/numeric/time' |
3 | require 'active_support/core_ext/class/attribute_accessors' | 3 | require 'active_support/core_ext/class/attribute_accessors' |
4 | require 'active_support/core_ext/kernel' | 4 | require 'active_support/core_ext/kernel' |
5 | +require 'active_support/core_ext/enumerable' | ||
6 | +require 'logger' | ||
7 | +require 'benchmark' | ||
5 | 8 | ||
6 | module Delayed | 9 | module Delayed |
7 | class Worker | 10 | class Worker |
8 | - cattr_accessor :min_priority, :max_priority, :max_attempts, :max_run_time, :default_priority, :sleep_delay, :logger | ||
9 | - self.sleep_delay = 5 | ||
10 | - self.max_attempts = 25 | ||
11 | - self.max_run_time = 4.hours | ||
12 | - self.default_priority = 0 | ||
13 | - | 11 | + DEFAULT_LOG_LEVEL = Logger::INFO |
12 | + DEFAULT_SLEEP_DELAY = 5 | ||
13 | + DEFAULT_MAX_ATTEMPTS = 25 | ||
14 | + DEFAULT_MAX_RUN_TIME = 4.hours | ||
15 | + DEFAULT_DEFAULT_PRIORITY = 0 | ||
16 | + DEFAULT_DELAY_JOBS = true | ||
17 | + DEFAULT_QUEUES = [] | ||
18 | + DEFAULT_READ_AHEAD = 5 | ||
19 | + | ||
20 | + cattr_accessor :min_priority, :max_priority, :max_attempts, :max_run_time, | ||
21 | + :default_priority, :sleep_delay, :logger, :delay_jobs, :queues, | ||
22 | + :read_ahead, :plugins, :destroy_failed_jobs, :exit_on_complete | ||
23 | + | ||
24 | + # Named queue into which jobs are enqueued by default | ||
25 | + cattr_accessor :default_queue_name | ||
26 | + | ||
27 | + cattr_reader :backend | ||
28 | + | ||
29 | + # name_prefix is ignored if name is set directly | ||
30 | + attr_accessor :name_prefix | ||
31 | + | ||
32 | + def self.reset | ||
33 | + self.sleep_delay = DEFAULT_SLEEP_DELAY | ||
34 | + self.max_attempts = DEFAULT_MAX_ATTEMPTS | ||
35 | + self.max_run_time = DEFAULT_MAX_RUN_TIME | ||
36 | + self.default_priority = DEFAULT_DEFAULT_PRIORITY | ||
37 | + self.delay_jobs = DEFAULT_DELAY_JOBS | ||
38 | + self.queues = DEFAULT_QUEUES | ||
39 | + self.read_ahead = DEFAULT_READ_AHEAD | ||
40 | + end | ||
41 | + | ||
42 | + reset | ||
43 | + | ||
44 | + # Add or remove plugins in this list before the worker is instantiated | ||
45 | + self.plugins = [Delayed::Plugins::ClearLocks] | ||
46 | + | ||
14 | # By default failed jobs are destroyed after too many attempts. If you want to keep them around | 47 | # By default failed jobs are destroyed after too many attempts. If you want to keep them around |
15 | # (perhaps to inspect the reason for the failure), set this to false. | 48 | # (perhaps to inspect the reason for the failure), set this to false. |
16 | - cattr_accessor :destroy_failed_jobs | ||
17 | self.destroy_failed_jobs = true | 49 | self.destroy_failed_jobs = true |
18 | - | ||
19 | - self.logger = if defined?(Merb::Logger) | ||
20 | - Merb.logger | 50 | + |
51 | + # By default, Signals INT and TERM set @exit, and the worker exits upon completion of the current job. | ||
52 | + # If you would prefer to raise a SignalException and exit immediately you can use this. | ||
53 | + # Be aware daemons uses TERM to stop and restart | ||
54 | + # false - No exceptions will be raised | ||
55 | + # :term - Will only raise an exception on TERM signals but INT will wait for the current job to finish | ||
56 | + # true - Will raise an exception on TERM and INT | ||
57 | + cattr_accessor :raise_signal_exceptions | ||
58 | + self.raise_signal_exceptions = false | ||
59 | + | ||
60 | + self.logger = if defined?(Rails) | ||
61 | + Rails.logger | ||
21 | elsif defined?(RAILS_DEFAULT_LOGGER) | 62 | elsif defined?(RAILS_DEFAULT_LOGGER) |
22 | RAILS_DEFAULT_LOGGER | 63 | RAILS_DEFAULT_LOGGER |
23 | end | 64 | end |
24 | 65 | ||
25 | - # name_prefix is ignored if name is set directly | ||
26 | - attr_accessor :name_prefix | ||
27 | - | ||
28 | - cattr_reader :backend | ||
29 | - | ||
30 | def self.backend=(backend) | 66 | def self.backend=(backend) |
31 | if backend.is_a? Symbol | 67 | if backend.is_a? Symbol |
68 | + require "delayed/serialization/#{backend}" | ||
32 | require "delayed/backend/#{backend}" | 69 | require "delayed/backend/#{backend}" |
33 | backend = "Delayed::Backend::#{backend.to_s.classify}::Job".constantize | 70 | backend = "Delayed::Backend::#{backend.to_s.classify}::Job".constantize |
34 | end | 71 | end |
35 | @@backend = backend | 72 | @@backend = backend |
36 | silence_warnings { ::Delayed.const_set(:Job, backend) } | 73 | silence_warnings { ::Delayed.const_set(:Job, backend) } |
37 | end | 74 | end |
38 | - | 75 | + |
39 | def self.guess_backend | 76 | def self.guess_backend |
40 | - self.backend ||= if defined?(ActiveRecord) | ||
41 | - :active_record | ||
42 | - elsif defined?(MongoMapper) | ||
43 | - :mongo_mapper | ||
44 | - else | ||
45 | - logger.warn "Could not decide on a backend, defaulting to active_record" | ||
46 | - :active_record | 77 | + warn "[DEPRECATION] guess_backend is deprecated. Please remove it from your code." |
78 | + end | ||
79 | + | ||
80 | + def self.before_fork | ||
81 | + unless @files_to_reopen | ||
82 | + @files_to_reopen = [] | ||
83 | + ObjectSpace.each_object(File) do |file| | ||
84 | + @files_to_reopen << file unless file.closed? | ||
85 | + end | ||
47 | end | 86 | end |
87 | + | ||
88 | + backend.before_fork | ||
89 | + end | ||
90 | + | ||
91 | + def self.after_fork | ||
92 | + # Re-open file handles | ||
93 | + @files_to_reopen.each do |file| | ||
94 | + begin | ||
95 | + file.reopen file.path, "a+" | ||
96 | + file.sync = true | ||
97 | + rescue ::Exception | ||
98 | + end | ||
99 | + end | ||
100 | + | ||
101 | + backend.after_fork | ||
102 | + end | ||
103 | + | ||
104 | + def self.lifecycle | ||
105 | + @lifecycle ||= Delayed::Lifecycle.new | ||
48 | end | 106 | end |
49 | 107 | ||
50 | def initialize(options={}) | 108 | def initialize(options={}) |
51 | - @quiet = options[:quiet] | ||
52 | - self.class.min_priority = options[:min_priority] if options.has_key?(:min_priority) | ||
53 | - self.class.max_priority = options[:max_priority] if options.has_key?(:max_priority) | 109 | + @quiet = options.has_key?(:quiet) ? options[:quiet] : true |
110 | + @failed_reserve_count = 0 | ||
111 | + | ||
112 | + [:min_priority, :max_priority, :sleep_delay, :read_ahead, :queues, :exit_on_complete].each do |option| | ||
113 | + self.class.send("#{option}=", options[option]) if options.has_key?(option) | ||
114 | + end | ||
115 | + | ||
116 | + self.plugins.each { |klass| klass.new } | ||
54 | end | 117 | end |
55 | 118 | ||
56 | # Every worker has a unique name which by default is the pid of the process. There are some | 119 | # Every worker has a unique name which by default is the pid of the process. There are some |
57 | - # advantages to overriding this with something which survives worker retarts: Workers can# | 120 | + # advantages to overriding this with something which survives worker restarts: Workers can |
58 | # safely resume working on tasks which are locked by themselves. The worker will assume that | 121 | # safely resume working on tasks which are locked by themselves. The worker will assume that |
59 | # it crashed before. | 122 | # it crashed before. |
60 | def name | 123 | def name |
@@ -69,35 +132,54 @@ module Delayed | @@ -69,35 +132,54 @@ module Delayed | ||
69 | end | 132 | end |
70 | 133 | ||
71 | def start | 134 | def start |
72 | - say "Starting job worker" | 135 | + trap('TERM') do |
136 | + say 'Exiting...' | ||
137 | + stop | ||
138 | + raise SignalException.new('TERM') if self.class.raise_signal_exceptions | ||
139 | + end | ||
73 | 140 | ||
74 | - trap('TERM') { say 'Exiting...'; $exit = true } | ||
75 | - trap('INT') { say 'Exiting...'; $exit = true } | 141 | + trap('INT') do |
142 | + say 'Exiting...' | ||
143 | + stop | ||
144 | + raise SignalException.new('INT') if self.class.raise_signal_exceptions && self.class.raise_signal_exceptions != :term | ||
145 | + end | ||
76 | 146 | ||
77 | - loop do | ||
78 | - result = nil | 147 | + say "Starting job worker" |
79 | 148 | ||
80 | - realtime = Benchmark.realtime do | ||
81 | - result = work_off | ||
82 | - end | 149 | + self.class.lifecycle.run_callbacks(:execute, self) do |
150 | + loop do | ||
151 | + self.class.lifecycle.run_callbacks(:loop, self) do | ||
152 | + @realtime = Benchmark.realtime do | ||
153 | + @result = work_off | ||
154 | + end | ||
155 | + end | ||
83 | 156 | ||
84 | - count = result.sum | 157 | + count = @result.sum |
85 | 158 | ||
86 | - break if $exit | 159 | + if count.zero? |
160 | + if self.class.exit_on_complete | ||
161 | + say "No more jobs available. Exiting" | ||
162 | + break | ||
163 | + else | ||
164 | + sleep(self.class.sleep_delay) unless stop? | ||
165 | + end | ||
166 | + else | ||
167 | + say "#{count} jobs processed at %.4f j/s, %d failed" % [count / @realtime, @result.last] | ||
168 | + end | ||
87 | 169 | ||
88 | - if count.zero? | ||
89 | - sleep(@@sleep_delay) | ||
90 | - else | ||
91 | - say "#{count} jobs processed at %.4f j/s, %d failed ..." % [count / realtime, result.last] | 170 | + break if stop? |
92 | end | 171 | end |
93 | - | ||
94 | - break if $exit | ||
95 | end | 172 | end |
173 | + end | ||
174 | + | ||
175 | + def stop | ||
176 | + @exit = true | ||
177 | + end | ||
96 | 178 | ||
97 | - ensure | ||
98 | - Delayed::Job.clear_locks!(name) | 179 | + def stop? |
180 | + !!@exit | ||
99 | end | 181 | end |
100 | - | 182 | + |
101 | # Do num jobs and return stats on success/failure. | 183 | # Do num jobs and return stats on success/failure. |
102 | # Exit early if interrupted. | 184 | # Exit early if interrupted. |
103 | def work_off(num = 100) | 185 | def work_off(num = 100) |
@@ -112,80 +194,90 @@ module Delayed | @@ -112,80 +194,90 @@ module Delayed | ||
112 | else | 194 | else |
113 | break # leave if no work could be done | 195 | break # leave if no work could be done |
114 | end | 196 | end |
115 | - break if $exit # leave if we're exiting | 197 | + break if stop? # leave if we're exiting |
116 | end | 198 | end |
117 | 199 | ||
118 | return [success, failure] | 200 | return [success, failure] |
119 | end | 201 | end |
120 | - | 202 | + |
121 | def run(job) | 203 | def run(job) |
204 | + job_say job, 'RUNNING' | ||
122 | runtime = Benchmark.realtime do | 205 | runtime = Benchmark.realtime do |
123 | - Timeout.timeout(self.class.max_run_time.to_i) { job.invoke_job } | 206 | + Timeout.timeout(self.class.max_run_time.to_i, WorkerTimeout) { job.invoke_job } |
124 | job.destroy | 207 | job.destroy |
125 | end | 208 | end |
126 | - say "#{job.name} completed after %.4f" % runtime | 209 | + job_say job, 'COMPLETED after %.4f' % runtime |
127 | return true # did work | 210 | return true # did work |
128 | - rescue Exception => e | ||
129 | - handle_failed_job(job, e) | 211 | + rescue DeserializationError => error |
212 | + job.last_error = "#{error.message}\n#{error.backtrace.join("\n")}" | ||
213 | + failed(job) | ||
214 | + rescue Exception => error | ||
215 | + self.class.lifecycle.run_callbacks(:error, self, job){ handle_failed_job(job, error) } | ||
130 | return false # work failed | 216 | return false # work failed |
131 | end | 217 | end |
132 | - | 218 | + |
133 | # Reschedule the job in the future (when a job fails). | 219 | # Reschedule the job in the future (when a job fails). |
134 | # Uses an exponential scale depending on the number of failed attempts. | 220 | # Uses an exponential scale depending on the number of failed attempts. |
135 | def reschedule(job, time = nil) | 221 | def reschedule(job, time = nil) |
136 | - if (job.attempts += 1) < self.class.max_attempts | ||
137 | - time ||= Job.db_time_now + (job.attempts ** 4) + 5 | 222 | + if (job.attempts += 1) < max_attempts(job) |
223 | + time ||= job.reschedule_at | ||
138 | job.run_at = time | 224 | job.run_at = time |
139 | job.unlock | 225 | job.unlock |
140 | job.save! | 226 | job.save! |
141 | else | 227 | else |
142 | - say "PERMANENTLY removing #{job.name} because of #{job.attempts} consecutive failures.", Logger::INFO | ||
143 | - | ||
144 | - if job.payload_object.respond_to? :on_permanent_failure | ||
145 | - say "Running on_permanent_failure hook" | ||
146 | - failure_method = job.payload_object.method(:on_permanent_failure) | ||
147 | - if failure_method.arity == 1 | ||
148 | - failure_method.call(job) | ||
149 | - else | ||
150 | - failure_method.call | ||
151 | - end | ||
152 | - end | 228 | + job_say job, "REMOVED permanently because of #{job.attempts} consecutive failures", Logger::ERROR |
229 | + failed(job) | ||
230 | + end | ||
231 | + end | ||
153 | 232 | ||
154 | - self.class.destroy_failed_jobs ? job.destroy : job.update_attributes(:failed_at => Delayed::Job.db_time_now) | 233 | + def failed(job) |
234 | + self.class.lifecycle.run_callbacks(:failure, self, job) do | ||
235 | + job.hook(:failure) | ||
236 | + self.class.destroy_failed_jobs ? job.destroy : job.fail! | ||
155 | end | 237 | end |
156 | end | 238 | end |
157 | 239 | ||
158 | - def say(text, level = Logger::INFO) | 240 | + def job_say(job, text, level = DEFAULT_LOG_LEVEL) |
241 | + text = "Job #{job.name} (id=#{job.id}) #{text}" | ||
242 | + say text, level | ||
243 | + end | ||
244 | + | ||
245 | + def say(text, level = DEFAULT_LOG_LEVEL) | ||
159 | text = "[Worker(#{name})] #{text}" | 246 | text = "[Worker(#{name})] #{text}" |
160 | puts text unless @quiet | 247 | puts text unless @quiet |
161 | logger.add level, "#{Time.now.strftime('%FT%T%z')}: #{text}" if logger | 248 | logger.add level, "#{Time.now.strftime('%FT%T%z')}: #{text}" if logger |
162 | end | 249 | end |
163 | 250 | ||
251 | + def max_attempts(job) | ||
252 | + job.max_attempts || self.class.max_attempts | ||
253 | + end | ||
254 | + | ||
164 | protected | 255 | protected |
165 | - | 256 | + |
166 | def handle_failed_job(job, error) | 257 | def handle_failed_job(job, error) |
167 | - job.last_error = error.message + "\n" + error.backtrace.join("\n") | ||
168 | - say "#{job.name} failed with #{error.class.name}: #{error.message} - #{job.attempts} failed attempts", Logger::ERROR | 258 | + job.last_error = "#{error.message}\n#{error.backtrace.join("\n")}" |
259 | + job_say job, "FAILED (#{job.attempts} prior attempts) with #{error.class.name}: #{error.message}", Logger::ERROR | ||
169 | reschedule(job) | 260 | reschedule(job) |
170 | end | 261 | end |
171 | - | 262 | + |
172 | # Run the next job we can get an exclusive lock on. | 263 | # Run the next job we can get an exclusive lock on. |
173 | # If no jobs are left we return nil | 264 | # If no jobs are left we return nil |
174 | def reserve_and_run_one_job | 265 | def reserve_and_run_one_job |
266 | + job = reserve_job | ||
267 | + self.class.lifecycle.run_callbacks(:perform, self, job){ run(job) } if job | ||
268 | + end | ||
175 | 269 | ||
176 | - # We get up to 5 jobs from the db. In case we cannot get exclusive access to a job we try the next. | ||
177 | - # this leads to a more even distribution of jobs across the worker processes | ||
178 | - job = Delayed::Job.find_available(name, 5, self.class.max_run_time).detect do |job| | ||
179 | - if job.lock_exclusively!(self.class.max_run_time, name) | ||
180 | - say "acquired lock on #{job.name}" | ||
181 | - true | ||
182 | - else | ||
183 | - say "failed to acquire exclusive lock for #{job.name}", Logger::WARN | ||
184 | - false | ||
185 | - end | ||
186 | - end | ||
187 | - | ||
188 | - run(job) if job | 270 | + def reserve_job |
271 | + job = Delayed::Job.reserve(self) | ||
272 | + @failed_reserve_count = 0 | ||
273 | + job | ||
274 | + rescue Exception => error | ||
275 | + say "Error while reserving job: #{error}" | ||
276 | + Delayed::Job.recover_from(error) | ||
277 | + @failed_reserve_count += 1 | ||
278 | + raise FatalBackendError if @failed_reserve_count >= 10 | ||
279 | + nil | ||
189 | end | 280 | end |
190 | end | 281 | end |
282 | + | ||
191 | end | 283 | end |
vendor/plugins/delayed_job/lib/delayed/yaml_ext.rb
@@ -2,34 +2,9 @@ | @@ -2,34 +2,9 @@ | ||
2 | # Classes, Modules and Structs | 2 | # Classes, Modules and Structs |
3 | 3 | ||
4 | require 'yaml' | 4 | require 'yaml' |
5 | - | ||
6 | -class Module | ||
7 | - yaml_as "tag:ruby.yaml.org,2002:module" | ||
8 | - | ||
9 | - def self.yaml_new(klass, tag, val) | ||
10 | - val.constantize | ||
11 | - end | ||
12 | - | ||
13 | - def to_yaml( opts = {} ) | ||
14 | - YAML::quick_emit( nil, opts ) { |out| | ||
15 | - out.scalar(taguri, self.name, :plain) | ||
16 | - } | ||
17 | - end | ||
18 | - | ||
19 | - def yaml_tag_read_class(name) | ||
20 | - # Constantize the object so that ActiveSupport can attempt | ||
21 | - # its auto loading magic. Will raise LoadError if not successful. | ||
22 | - name.constantize | ||
23 | - name | ||
24 | - end | ||
25 | - | ||
26 | -end | ||
27 | - | ||
28 | -class Struct | ||
29 | - def self.yaml_tag_read_class(name) | ||
30 | - # Constantize the object so that ActiveSupport can attempt | ||
31 | - # its auto loading magic. Will raise LoadError if not successful. | ||
32 | - name.constantize | ||
33 | - "Struct::#{ name }" | ||
34 | - end | 5 | +if YAML.parser.class.name =~ /syck|yecht/i |
6 | + require File.expand_path('../syck_ext', __FILE__) | ||
7 | + require File.expand_path('../serialization/active_record', __FILE__) | ||
8 | +else | ||
9 | + require File.expand_path('../psych_ext', __FILE__) | ||
35 | end | 10 | end |
vendor/plugins/delayed_job/lib/delayed_job.rb
1 | require 'active_support' | 1 | require 'active_support' |
2 | +require 'delayed/compatibility' | ||
3 | +require 'delayed/exceptions' | ||
4 | +require 'delayed/message_sending' | ||
5 | +require 'delayed/performable_method' | ||
2 | 6 | ||
3 | -require File.dirname(__FILE__) + '/delayed/message_sending' | ||
4 | -require File.dirname(__FILE__) + '/delayed/performable_method' | ||
5 | -require File.dirname(__FILE__) + '/delayed/yaml_ext' | ||
6 | -require File.dirname(__FILE__) + '/delayed/backend/base' | ||
7 | -require File.dirname(__FILE__) + '/delayed/worker' | ||
8 | -require File.dirname(__FILE__) + '/delayed/railtie' if defined?(::Rails::Railtie) | 7 | +if defined?(ActionMailer) |
8 | + require 'action_mailer/version' | ||
9 | + require 'delayed/performable_mailer' | ||
10 | +end | ||
9 | 11 | ||
10 | -Object.send(:include, Delayed::MessageSending) | ||
11 | -Module.send(:include, Delayed::MessageSending::ClassMethods) | 12 | +require 'delayed/yaml_ext' |
13 | +require 'delayed/lifecycle' | ||
14 | +require 'delayed/plugin' | ||
15 | +require 'delayed/plugins/clear_locks' | ||
16 | +require 'delayed/backend/base' | ||
17 | +require 'delayed/worker' | ||
18 | +require 'delayed/deserialization_error' | ||
19 | +require 'delayed/railtie' if defined?(Rails::Railtie) | ||
12 | 20 | ||
13 | -if defined?(Merb::Plugins) | ||
14 | - Merb::Plugins.add_rakefiles File.dirname(__FILE__) / 'delayed' / 'tasks' | ||
15 | -end | 21 | +Object.send(:include, Delayed::MessageSending) |
22 | +Module.send(:include, Delayed::MessageSending::ClassMethods) |
vendor/plugins/delayed_job/lib/generators/delayed_job/delayed_job_generator.rb
1 | require 'rails/generators' | 1 | require 'rails/generators' |
2 | -require 'rails/generators/migration' | 2 | +require 'delayed/compatibility' |
3 | 3 | ||
4 | class DelayedJobGenerator < Rails::Generators::Base | 4 | class DelayedJobGenerator < Rails::Generators::Base |
5 | 5 | ||
6 | - include Rails::Generators::Migration | ||
7 | - | ||
8 | - def self.source_root | ||
9 | - @source_root ||= File.join(File.dirname(__FILE__), 'templates') | ||
10 | - end | 6 | + self.source_paths << File.join(File.dirname(__FILE__), 'templates') |
11 | 7 | ||
12 | - # Implement the required interface for Rails::Generators::Migration. | ||
13 | - # | ||
14 | - def self.next_migration_number(dirname) #:nodoc: | ||
15 | - next_migration_number = current_migration_number(dirname) + 1 | ||
16 | - if ActiveRecord::Base.timestamped_migrations | ||
17 | - [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max | ||
18 | - else | ||
19 | - "%.3d" % next_migration_number | ||
20 | - end | ||
21 | - end | ||
22 | - | ||
23 | - def create_script_file | ||
24 | - template 'script', 'script/delayed_job' | ||
25 | - chmod 'script/delayed_job', 0755 | 8 | + def create_executable_file |
9 | + template "script", "#{Delayed::Compatibility.executable_prefix}/delayed_job" | ||
10 | + chmod "#{Delayed::Compatibility.executable_prefix}/delayed_job", 0755 | ||
26 | end | 11 | end |
27 | - | ||
28 | - def create_migration_file | ||
29 | - if defined?(ActiveRecord) | ||
30 | - migration_template 'migration.rb', 'db/migrate/create_delayed_jobs.rb' | ||
31 | - end | ||
32 | - end | ||
33 | - | ||
34 | -end | ||
35 | \ No newline at end of file | 12 | \ No newline at end of file |
13 | +end |
vendor/plugins/delayed_job/lib/generators/delayed_job/templates/migration.rb
@@ -1,21 +0,0 @@ | @@ -1,21 +0,0 @@ | ||
1 | -class CreateDelayedJobs < ActiveRecord::Migration | ||
2 | - def self.up | ||
3 | - create_table :delayed_jobs, :force => true do |table| | ||
4 | - table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue | ||
5 | - table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually. | ||
6 | - table.text :handler # YAML-encoded string of the object that will do work | ||
7 | - table.text :last_error # reason for last failure (See Note below) | ||
8 | - table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future. | ||
9 | - table.datetime :locked_at # Set when a client is working on this object | ||
10 | - table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead) | ||
11 | - table.string :locked_by # Who is working on this object (if locked) | ||
12 | - table.timestamps | ||
13 | - end | ||
14 | - | ||
15 | - add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority' | ||
16 | - end | ||
17 | - | ||
18 | - def self.down | ||
19 | - drop_table :delayed_jobs | ||
20 | - end | ||
21 | -end | ||
22 | \ No newline at end of file | 0 | \ No newline at end of file |
vendor/plugins/delayed_job/lib/generators/delayed_job/templates/script
0 → 100644
vendor/plugins/delayed_job/rails/init.rb
@@ -1 +0,0 @@ | @@ -1 +0,0 @@ | ||
1 | -require 'delayed_job' |
vendor/plugins/delayed_job/spec/autoloaded/clazz.rb
vendor/plugins/delayed_job/spec/autoloaded/instance_clazz.rb
0 → 100644
vendor/plugins/delayed_job/spec/autoloaded/instance_struct.rb
0 → 100644
vendor/plugins/delayed_job/spec/autoloaded/struct.rb
vendor/plugins/delayed_job/spec/backend/active_record_job_spec.rb
@@ -1,46 +0,0 @@ | @@ -1,46 +0,0 @@ | ||
1 | -require 'spec_helper' | ||
2 | -require 'backend/shared_backend_spec' | ||
3 | -require 'delayed/backend/active_record' | ||
4 | - | ||
5 | -describe Delayed::Backend::ActiveRecord::Job do | ||
6 | - before(:all) do | ||
7 | - @backend = Delayed::Backend::ActiveRecord::Job | ||
8 | - end | ||
9 | - | ||
10 | - before(:each) do | ||
11 | - Delayed::Backend::ActiveRecord::Job.delete_all | ||
12 | - SimpleJob.runs = 0 | ||
13 | - end | ||
14 | - | ||
15 | - after do | ||
16 | - Time.zone = nil | ||
17 | - end | ||
18 | - | ||
19 | - it_should_behave_like 'a backend' | ||
20 | - | ||
21 | - context "db_time_now" do | ||
22 | - it "should return time in current time zone if set" do | ||
23 | - Time.zone = 'Eastern Time (US & Canada)' | ||
24 | - %w(EST EDT).should include(Delayed::Job.db_time_now.zone) | ||
25 | - end | ||
26 | - | ||
27 | - it "should return UTC time if that is the AR default" do | ||
28 | - Time.zone = nil | ||
29 | - ActiveRecord::Base.default_timezone = :utc | ||
30 | - Delayed::Backend::ActiveRecord::Job.db_time_now.zone.should == 'UTC' | ||
31 | - end | ||
32 | - | ||
33 | - it "should return local time if that is the AR default" do | ||
34 | - Time.zone = 'Central Time (US & Canada)' | ||
35 | - ActiveRecord::Base.default_timezone = :local | ||
36 | - %w(CST CDT).should include(Delayed::Backend::ActiveRecord::Job.db_time_now.zone) | ||
37 | - end | ||
38 | - end | ||
39 | - | ||
40 | - describe "after_fork" do | ||
41 | - it "should call reconnect on the connection" do | ||
42 | - ActiveRecord::Base.connection.should_receive(:reconnect!) | ||
43 | - Delayed::Backend::ActiveRecord::Job.after_fork | ||
44 | - end | ||
45 | - end | ||
46 | -end |
vendor/plugins/delayed_job/spec/backend/couch_rest_job_spec.rb
@@ -1,15 +0,0 @@ | @@ -1,15 +0,0 @@ | ||
1 | -require 'spec_helper' | ||
2 | -require 'backend/shared_backend_spec' | ||
3 | -require 'delayed/backend/couch_rest' | ||
4 | - | ||
5 | -describe Delayed::Backend::CouchRest::Job do | ||
6 | - before(:all) do | ||
7 | - @backend = Delayed::Backend::CouchRest::Job | ||
8 | - end | ||
9 | - | ||
10 | - before(:each) do | ||
11 | - @backend.delete_all | ||
12 | - end | ||
13 | - | ||
14 | - it_should_behave_like 'a backend' | ||
15 | -end |
vendor/plugins/delayed_job/spec/backend/data_mapper_job_spec.rb
@@ -1,16 +0,0 @@ | @@ -1,16 +0,0 @@ | ||
1 | -require 'spec_helper' | ||
2 | -require 'backend/shared_backend_spec' | ||
3 | -require 'delayed/backend/data_mapper' | ||
4 | - | ||
5 | -describe Delayed::Backend::DataMapper::Job do | ||
6 | - before(:all) do | ||
7 | - @backend = Delayed::Backend::DataMapper::Job | ||
8 | - end | ||
9 | - | ||
10 | - before(:each) do | ||
11 | - # reset database before each example is run | ||
12 | - DataMapper.auto_migrate! | ||
13 | - end | ||
14 | - | ||
15 | - it_should_behave_like 'a backend' | ||
16 | -end |
vendor/plugins/delayed_job/spec/backend/mongo_mapper_job_spec.rb
@@ -1,94 +0,0 @@ | @@ -1,94 +0,0 @@ | ||
1 | -require 'spec_helper' | ||
2 | -require 'backend/shared_backend_spec' | ||
3 | -require 'delayed/backend/mongo_mapper' | ||
4 | - | ||
5 | -describe Delayed::Backend::MongoMapper::Job do | ||
6 | - before(:all) do | ||
7 | - @backend = Delayed::Backend::MongoMapper::Job | ||
8 | - end | ||
9 | - | ||
10 | - before(:each) do | ||
11 | - MongoMapper.database.collections.each(&:remove) | ||
12 | - end | ||
13 | - | ||
14 | - it_should_behave_like 'a backend' | ||
15 | - | ||
16 | - describe "indexes" do | ||
17 | - it "should have combo index on priority and run_at" do | ||
18 | - @backend.collection.index_information.detect { |index| index[0] == 'priority_1_run_at_1' }.should_not be_nil | ||
19 | - end | ||
20 | - | ||
21 | - it "should have index on locked_by" do | ||
22 | - @backend.collection.index_information.detect { |index| index[0] == 'locked_by_1' }.should_not be_nil | ||
23 | - end | ||
24 | - end | ||
25 | - | ||
26 | - describe "delayed method" do | ||
27 | - class MongoStoryReader | ||
28 | - def read(story) | ||
29 | - "Epilog: #{story.tell}" | ||
30 | - end | ||
31 | - end | ||
32 | - | ||
33 | - class MongoStory | ||
34 | - include ::MongoMapper::Document | ||
35 | - key :text, String | ||
36 | - | ||
37 | - def tell | ||
38 | - text | ||
39 | - end | ||
40 | - end | ||
41 | - | ||
42 | - it "should ignore not found errors because they are permanent" do | ||
43 | - story = MongoStory.create :text => 'Once upon a time...' | ||
44 | - job = story.delay.tell | ||
45 | - story.destroy | ||
46 | - lambda { job.invoke_job }.should_not raise_error | ||
47 | - end | ||
48 | - | ||
49 | - it "should store the object as string" do | ||
50 | - story = MongoStory.create :text => 'Once upon a time...' | ||
51 | - job = story.delay.tell | ||
52 | - | ||
53 | - job.payload_object.class.should == Delayed::PerformableMethod | ||
54 | - job.payload_object.object.should == story | ||
55 | - job.payload_object.method.should == :tell | ||
56 | - job.payload_object.args.should == [] | ||
57 | - job.payload_object.perform.should == 'Once upon a time...' | ||
58 | - end | ||
59 | - | ||
60 | - it "should store arguments as string" do | ||
61 | - story = MongoStory.create :text => 'Once upon a time...' | ||
62 | - job = MongoStoryReader.new.delay.read(story) | ||
63 | - job.payload_object.class.should == Delayed::PerformableMethod | ||
64 | - job.payload_object.method.should == :read | ||
65 | - job.payload_object.args.should == [story] | ||
66 | - job.payload_object.perform.should == 'Epilog: Once upon a time...' | ||
67 | - end | ||
68 | - end | ||
69 | - | ||
70 | - describe "before_fork" do | ||
71 | - after do | ||
72 | - MongoMapper.connection.connect_to_master | ||
73 | - end | ||
74 | - | ||
75 | - it "should disconnect" do | ||
76 | - lambda do | ||
77 | - Delayed::Backend::MongoMapper::Job.before_fork | ||
78 | - end.should change { !!MongoMapper.connection.connected? }.from(true).to(false) | ||
79 | - end | ||
80 | - end | ||
81 | - | ||
82 | - describe "after_fork" do | ||
83 | - before do | ||
84 | - MongoMapper.connection.close | ||
85 | - end | ||
86 | - | ||
87 | - it "should call reconnect" do | ||
88 | - lambda do | ||
89 | - Delayed::Backend::MongoMapper::Job.after_fork | ||
90 | - end.should change { !!MongoMapper.connection.connected? }.from(false).to(true) | ||
91 | - end | ||
92 | - end | ||
93 | - | ||
94 | -end |
vendor/plugins/delayed_job/spec/backend/shared_backend_spec.rb
@@ -1,279 +0,0 @@ | @@ -1,279 +0,0 @@ | ||
1 | -class NamedJob < Struct.new(:perform) | ||
2 | - def display_name | ||
3 | - 'named_job' | ||
4 | - end | ||
5 | -end | ||
6 | - | ||
7 | -shared_examples_for 'a backend' do | ||
8 | - def create_job(opts = {}) | ||
9 | - @backend.create(opts.merge(:payload_object => SimpleJob.new)) | ||
10 | - end | ||
11 | - | ||
12 | - before do | ||
13 | - Delayed::Worker.max_priority = nil | ||
14 | - Delayed::Worker.min_priority = nil | ||
15 | - Delayed::Worker.default_priority = 99 | ||
16 | - SimpleJob.runs = 0 | ||
17 | - end | ||
18 | - | ||
19 | - it "should set run_at automatically if not set" do | ||
20 | - @backend.create(:payload_object => ErrorJob.new ).run_at.should_not be_nil | ||
21 | - end | ||
22 | - | ||
23 | - it "should not set run_at automatically if already set" do | ||
24 | - later = @backend.db_time_now + 5.minutes | ||
25 | - @backend.create(:payload_object => ErrorJob.new, :run_at => later).run_at.should be_close(later, 1) | ||
26 | - end | ||
27 | - | ||
28 | - it "should raise ArgumentError when handler doesn't respond_to :perform" do | ||
29 | - lambda { @backend.enqueue(Object.new) }.should raise_error(ArgumentError) | ||
30 | - end | ||
31 | - | ||
32 | - it "should increase count after enqueuing items" do | ||
33 | - @backend.enqueue SimpleJob.new | ||
34 | - @backend.count.should == 1 | ||
35 | - end | ||
36 | - | ||
37 | - it "should be able to set priority when enqueuing items" do | ||
38 | - @job = @backend.enqueue SimpleJob.new, 5 | ||
39 | - @job.priority.should == 5 | ||
40 | - end | ||
41 | - | ||
42 | - it "should use default priority when it is not set" do | ||
43 | - @job = @backend.enqueue SimpleJob.new | ||
44 | - @job.priority.should == 99 | ||
45 | - end | ||
46 | - | ||
47 | - it "should be able to set run_at when enqueuing items" do | ||
48 | - later = @backend.db_time_now + 5.minutes | ||
49 | - @job = @backend.enqueue SimpleJob.new, 5, later | ||
50 | - @job.run_at.should be_close(later, 1) | ||
51 | - end | ||
52 | - | ||
53 | - it "should work with jobs in modules" do | ||
54 | - M::ModuleJob.runs = 0 | ||
55 | - job = @backend.enqueue M::ModuleJob.new | ||
56 | - lambda { job.invoke_job }.should change { M::ModuleJob.runs }.from(0).to(1) | ||
57 | - end | ||
58 | - | ||
59 | - describe "payload_object" do | ||
60 | - it "should raise a DeserializationError when the job class is totally unknown" do | ||
61 | - job = @backend.new :handler => "--- !ruby/object:JobThatDoesNotExist {}" | ||
62 | - lambda { job.payload_object }.should raise_error(Delayed::Backend::DeserializationError) | ||
63 | - end | ||
64 | - | ||
65 | - it "should raise a DeserializationError when the job struct is totally unknown" do | ||
66 | - job = @backend.new :handler => "--- !ruby/struct:StructThatDoesNotExist {}" | ||
67 | - lambda { job.payload_object }.should raise_error(Delayed::Backend::DeserializationError) | ||
68 | - end | ||
69 | - | ||
70 | - it "should autoload classes that are unknown at runtime" do | ||
71 | - job = @backend.new :handler => "--- !ruby/object:Autoloaded::Clazz {}" | ||
72 | - lambda { job.payload_object }.should_not raise_error(Delayed::Backend::DeserializationError) | ||
73 | - end | ||
74 | - | ||
75 | - it "should autoload structs that are unknown at runtime" do | ||
76 | - job = @backend.new :handler => "--- !ruby/struct:Autoloaded::Struct {}" | ||
77 | - lambda { job.payload_object }.should_not raise_error(Delayed::Backend::DeserializationError) | ||
78 | - end | ||
79 | - end | ||
80 | - | ||
81 | - describe "find_available" do | ||
82 | - it "should not find failed jobs" do | ||
83 | - @job = create_job :attempts => 50, :failed_at => @backend.db_time_now | ||
84 | - @backend.find_available('worker', 5, 1.second).should_not include(@job) | ||
85 | - end | ||
86 | - | ||
87 | - it "should not find jobs scheduled for the future" do | ||
88 | - @job = create_job :run_at => (@backend.db_time_now + 1.minute) | ||
89 | - @backend.find_available('worker', 5, 4.hours).should_not include(@job) | ||
90 | - end | ||
91 | - | ||
92 | - it "should not find jobs locked by another worker" do | ||
93 | - @job = create_job(:locked_by => 'other_worker', :locked_at => @backend.db_time_now - 1.minute) | ||
94 | - @backend.find_available('worker', 5, 4.hours).should_not include(@job) | ||
95 | - end | ||
96 | - | ||
97 | - it "should find open jobs" do | ||
98 | - @job = create_job | ||
99 | - @backend.find_available('worker', 5, 4.hours).should include(@job) | ||
100 | - end | ||
101 | - | ||
102 | - it "should find expired jobs" do | ||
103 | - @job = create_job(:locked_by => 'worker', :locked_at => @backend.db_time_now - 2.minutes) | ||
104 | - @backend.find_available('worker', 5, 1.minute).should include(@job) | ||
105 | - end | ||
106 | - | ||
107 | - it "should find own jobs" do | ||
108 | - @job = create_job(:locked_by => 'worker', :locked_at => (@backend.db_time_now - 1.minutes)) | ||
109 | - @backend.find_available('worker', 5, 4.hours).should include(@job) | ||
110 | - end | ||
111 | - | ||
112 | - it "should find only the right amount of jobs" do | ||
113 | - 10.times { create_job } | ||
114 | - @backend.find_available('worker', 7, 4.hours).should have(7).jobs | ||
115 | - end | ||
116 | - end | ||
117 | - | ||
118 | - context "when another worker is already performing an task, it" do | ||
119 | - | ||
120 | - before :each do | ||
121 | - @job = @backend.create :payload_object => SimpleJob.new, :locked_by => 'worker1', :locked_at => @backend.db_time_now - 5.minutes | ||
122 | - end | ||
123 | - | ||
124 | - it "should not allow a second worker to get exclusive access" do | ||
125 | - @job.lock_exclusively!(4.hours, 'worker2').should == false | ||
126 | - end | ||
127 | - | ||
128 | - it "should allow a second worker to get exclusive access if the timeout has passed" do | ||
129 | - @job.lock_exclusively!(1.minute, 'worker2').should == true | ||
130 | - end | ||
131 | - | ||
132 | - it "should be able to get access to the task if it was started more then max_age ago" do | ||
133 | - @job.locked_at = 5.hours.ago | ||
134 | - @job.save | ||
135 | - | ||
136 | - @job.lock_exclusively! 4.hours, 'worker2' | ||
137 | - @job.reload | ||
138 | - @job.locked_by.should == 'worker2' | ||
139 | - @job.locked_at.should > 1.minute.ago | ||
140 | - end | ||
141 | - | ||
142 | - it "should not be found by another worker" do | ||
143 | - @backend.find_available('worker2', 1, 6.minutes).length.should == 0 | ||
144 | - end | ||
145 | - | ||
146 | - it "should be found by another worker if the time has expired" do | ||
147 | - @backend.find_available('worker2', 1, 4.minutes).length.should == 1 | ||
148 | - end | ||
149 | - | ||
150 | - it "should be able to get exclusive access again when the worker name is the same" do | ||
151 | - @job.lock_exclusively!(5.minutes, 'worker1').should be_true | ||
152 | - @job.lock_exclusively!(5.minutes, 'worker1').should be_true | ||
153 | - @job.lock_exclusively!(5.minutes, 'worker1').should be_true | ||
154 | - end | ||
155 | - end | ||
156 | - | ||
157 | - context "when another worker has worked on a task since the job was found to be available, it" do | ||
158 | - | ||
159 | - before :each do | ||
160 | - @job = @backend.create :payload_object => SimpleJob.new | ||
161 | - @job_copy_for_worker_2 = @backend.find(@job.id) | ||
162 | - end | ||
163 | - | ||
164 | - it "should not allow a second worker to get exclusive access if already successfully processed by worker1" do | ||
165 | - @job.destroy | ||
166 | - @job_copy_for_worker_2.lock_exclusively!(4.hours, 'worker2').should == false | ||
167 | - end | ||
168 | - | ||
169 | - it "should not allow a second worker to get exclusive access if failed to be processed by worker1 and run_at time is now in future (due to backing off behaviour)" do | ||
170 | - @job.update_attributes(:attempts => 1, :run_at => 1.day.from_now) | ||
171 | - @job_copy_for_worker_2.lock_exclusively!(4.hours, 'worker2').should == false | ||
172 | - end | ||
173 | - end | ||
174 | - | ||
175 | - context "#name" do | ||
176 | - it "should be the class name of the job that was enqueued" do | ||
177 | - @backend.create(:payload_object => ErrorJob.new ).name.should == 'ErrorJob' | ||
178 | - end | ||
179 | - | ||
180 | - it "should be the method that will be called if its a performable method object" do | ||
181 | - job = @backend.new(:payload_object => NamedJob.new) | ||
182 | - job.name.should == 'named_job' | ||
183 | - end | ||
184 | - | ||
185 | - it "should be the instance method that will be called if its a performable method object" do | ||
186 | - @job = Story.create(:text => "...").delay.save | ||
187 | - @job.name.should == 'Story#save' | ||
188 | - end | ||
189 | - end | ||
190 | - | ||
191 | - context "worker prioritization" do | ||
192 | - before(:each) do | ||
193 | - Delayed::Worker.max_priority = nil | ||
194 | - Delayed::Worker.min_priority = nil | ||
195 | - end | ||
196 | - | ||
197 | - it "should fetch jobs ordered by priority" do | ||
198 | - 10.times { @backend.enqueue SimpleJob.new, rand(10) } | ||
199 | - jobs = @backend.find_available('worker', 10) | ||
200 | - jobs.size.should == 10 | ||
201 | - jobs.each_cons(2) do |a, b| | ||
202 | - a.priority.should <= b.priority | ||
203 | - end | ||
204 | - end | ||
205 | - | ||
206 | - it "should only find jobs greater than or equal to min priority" do | ||
207 | - min = 5 | ||
208 | - Delayed::Worker.min_priority = min | ||
209 | - 10.times {|i| @backend.enqueue SimpleJob.new, i } | ||
210 | - jobs = @backend.find_available('worker', 10) | ||
211 | - jobs.each {|job| job.priority.should >= min} | ||
212 | - end | ||
213 | - | ||
214 | - it "should only find jobs less than or equal to max priority" do | ||
215 | - max = 5 | ||
216 | - Delayed::Worker.max_priority = max | ||
217 | - 10.times {|i| @backend.enqueue SimpleJob.new, i } | ||
218 | - jobs = @backend.find_available('worker', 10) | ||
219 | - jobs.each {|job| job.priority.should <= max} | ||
220 | - end | ||
221 | - end | ||
222 | - | ||
223 | - context "clear_locks!" do | ||
224 | - before do | ||
225 | - @job = create_job(:locked_by => 'worker', :locked_at => @backend.db_time_now) | ||
226 | - end | ||
227 | - | ||
228 | - it "should clear locks for the given worker" do | ||
229 | - @backend.clear_locks!('worker') | ||
230 | - @backend.find_available('worker2', 5, 1.minute).should include(@job) | ||
231 | - end | ||
232 | - | ||
233 | - it "should not clear locks for other workers" do | ||
234 | - @backend.clear_locks!('worker1') | ||
235 | - @backend.find_available('worker1', 5, 1.minute).should_not include(@job) | ||
236 | - end | ||
237 | - end | ||
238 | - | ||
239 | - context "unlock" do | ||
240 | - before do | ||
241 | - @job = create_job(:locked_by => 'worker', :locked_at => @backend.db_time_now) | ||
242 | - end | ||
243 | - | ||
244 | - it "should clear locks" do | ||
245 | - @job.unlock | ||
246 | - @job.locked_by.should be_nil | ||
247 | - @job.locked_at.should be_nil | ||
248 | - end | ||
249 | - end | ||
250 | - | ||
251 | - context "large handler" do | ||
252 | - before do | ||
253 | - text = "Lorem ipsum dolor sit amet. " * 1000 | ||
254 | - @job = @backend.enqueue Delayed::PerformableMethod.new(text, :length, {}) | ||
255 | - end | ||
256 | - | ||
257 | - it "should have an id" do | ||
258 | - @job.id.should_not be_nil | ||
259 | - end | ||
260 | - end | ||
261 | - | ||
262 | - describe "yaml serialization" do | ||
263 | - it "should reload changed attributes" do | ||
264 | - job = @backend.enqueue SimpleJob.new | ||
265 | - yaml = job.to_yaml | ||
266 | - job.priority = 99 | ||
267 | - job.save | ||
268 | - YAML.load(yaml).priority.should == 99 | ||
269 | - end | ||
270 | - | ||
271 | - it "should ignore destroyed records" do | ||
272 | - job = @backend.enqueue SimpleJob.new | ||
273 | - yaml = job.to_yaml | ||
274 | - job.destroy | ||
275 | - lambda { YAML.load(yaml).should be_nil }.should_not raise_error | ||
276 | - end | ||
277 | - end | ||
278 | - | ||
279 | -end |
@@ -0,0 +1,112 @@ | @@ -0,0 +1,112 @@ | ||
1 | +require 'ostruct' | ||
2 | + | ||
3 | +# An in-memory backend suitable only for testing. Tries to behave as if it were an ORM. | ||
4 | +module Delayed | ||
5 | + module Backend | ||
6 | + module Test | ||
7 | + class Job | ||
8 | + attr_accessor :id | ||
9 | + attr_accessor :priority | ||
10 | + attr_accessor :attempts | ||
11 | + attr_accessor :handler | ||
12 | + attr_accessor :last_error | ||
13 | + attr_accessor :run_at | ||
14 | + attr_accessor :locked_at | ||
15 | + attr_accessor :locked_by | ||
16 | + attr_accessor :failed_at | ||
17 | + attr_accessor :queue | ||
18 | + | ||
19 | + include Delayed::Backend::Base | ||
20 | + | ||
21 | + cattr_accessor :id | ||
22 | + self.id = 0 | ||
23 | + | ||
24 | + def initialize(hash = {}) | ||
25 | + self.attempts = 0 | ||
26 | + self.priority = 0 | ||
27 | + self.id = (self.class.id += 1) | ||
28 | + hash.each{|k,v| send(:"#{k}=", v)} | ||
29 | + end | ||
30 | + | ||
31 | + @jobs = [] | ||
32 | + def self.all | ||
33 | + @jobs | ||
34 | + end | ||
35 | + | ||
36 | + def self.count | ||
37 | + all.size | ||
38 | + end | ||
39 | + | ||
40 | + def self.delete_all | ||
41 | + all.clear | ||
42 | + end | ||
43 | + | ||
44 | + def self.create(attrs = {}) | ||
45 | + new(attrs).tap do |o| | ||
46 | + o.save | ||
47 | + end | ||
48 | + end | ||
49 | + | ||
50 | + def self.create!(*args); create(*args); end | ||
51 | + | ||
52 | + def self.clear_locks!(worker_name) | ||
53 | + all.select{|j| j.locked_by == worker_name}.each {|j| j.locked_by = nil; j.locked_at = nil} | ||
54 | + end | ||
55 | + | ||
56 | + # Find a few candidate jobs to run (in case some immediately get locked by others). | ||
57 | + def self.find_available(worker_name, limit = 5, max_run_time = Worker.max_run_time) | ||
58 | + jobs = all.select do |j| | ||
59 | + j.run_at <= db_time_now && | ||
60 | + (j.locked_at.nil? || j.locked_at < db_time_now - max_run_time || j.locked_by == worker_name) && | ||
61 | + !j.failed? | ||
62 | + end | ||
63 | + | ||
64 | + jobs = jobs.select{|j| Worker.queues.include?(j.queue)} if Worker.queues.any? | ||
65 | + jobs = jobs.select{|j| j.priority >= Worker.min_priority} if Worker.min_priority | ||
66 | + jobs = jobs.select{|j| j.priority <= Worker.max_priority} if Worker.max_priority | ||
67 | + jobs.sort_by{|j| [j.priority, j.run_at]}[0..limit-1] | ||
68 | + end | ||
69 | + | ||
70 | + # Lock this job for this worker. | ||
71 | + # Returns true if we have the lock, false otherwise. | ||
72 | + def lock_exclusively!(max_run_time, worker) | ||
73 | + now = self.class.db_time_now | ||
74 | + if locked_by != worker | ||
75 | + # We don't own this job so we will update the locked_by name and the locked_at | ||
76 | + self.locked_at = now | ||
77 | + self.locked_by = worker | ||
78 | + end | ||
79 | + | ||
80 | + return true | ||
81 | + end | ||
82 | + | ||
83 | + def self.db_time_now | ||
84 | + Time.current | ||
85 | + end | ||
86 | + | ||
87 | + def update_attributes(attrs = {}) | ||
88 | + attrs.each{|k,v| send(:"#{k}=", v)} | ||
89 | + save | ||
90 | + end | ||
91 | + | ||
92 | + def destroy | ||
93 | + self.class.all.delete(self) | ||
94 | + end | ||
95 | + | ||
96 | + def save | ||
97 | + self.run_at ||= Time.current | ||
98 | + | ||
99 | + self.class.all << self unless self.class.all.include?(self) | ||
100 | + true | ||
101 | + end | ||
102 | + | ||
103 | + def save!; save; end | ||
104 | + | ||
105 | + def reload | ||
106 | + reset | ||
107 | + self | ||
108 | + end | ||
109 | + end | ||
110 | + end | ||
111 | + end | ||
112 | +end |
vendor/plugins/delayed_job/spec/delayed/serialization/test.rb
0 → 100644
@@ -0,0 +1,61 @@ | @@ -0,0 +1,61 @@ | ||
1 | +require 'logger' | ||
2 | +require 'rspec' | ||
3 | + | ||
4 | +require 'action_mailer' | ||
5 | +require 'active_support/dependencies' | ||
6 | +require 'active_record' | ||
7 | + | ||
8 | +require 'delayed_job' | ||
9 | +require 'delayed/backend/shared_spec' | ||
10 | + | ||
11 | +require 'simplecov' | ||
12 | +require 'coveralls' | ||
13 | + | ||
14 | +SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ | ||
15 | + SimpleCov::Formatter::HTMLFormatter, | ||
16 | + Coveralls::SimpleCov::Formatter | ||
17 | +] | ||
18 | +SimpleCov.start | ||
19 | + | ||
20 | +Delayed::Worker.logger = Logger.new('/tmp/dj.log') | ||
21 | +ENV['RAILS_ENV'] = 'test' | ||
22 | + | ||
23 | +Delayed::Worker.backend = :test | ||
24 | + | ||
25 | +# Add this directory so the ActiveSupport autoloading works | ||
26 | +ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__) | ||
27 | + | ||
28 | +# Add this to simulate Railtie initializer being executed | ||
29 | +ActionMailer::Base.send(:extend, Delayed::DelayMail) | ||
30 | + | ||
31 | + | ||
32 | +# Used to test interactions between DJ and an ORM | ||
33 | +ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:' | ||
34 | +ActiveRecord::Base.logger = Delayed::Worker.logger | ||
35 | +ActiveRecord::Migration.verbose = false | ||
36 | + | ||
37 | +ActiveRecord::Schema.define do | ||
38 | + create_table :stories, :primary_key => :story_id, :force => true do |table| | ||
39 | + table.string :text | ||
40 | + table.boolean :scoped, :default => true | ||
41 | + end | ||
42 | +end | ||
43 | + | ||
44 | +class Story < ActiveRecord::Base | ||
45 | + self.primary_key = 'story_id' | ||
46 | + def tell; text; end | ||
47 | + def whatever(n, _); tell*n; end | ||
48 | + default_scope { where(:scoped => true) } | ||
49 | + | ||
50 | + handle_asynchronously :whatever | ||
51 | +end | ||
52 | + | ||
53 | +RSpec.configure do |config| | ||
54 | + config.after(:each) do | ||
55 | + Delayed::Worker.reset | ||
56 | + end | ||
57 | + | ||
58 | + config.expect_with :rspec do |c| | ||
59 | + c.syntax = :expect | ||
60 | + end | ||
61 | +end |
@@ -0,0 +1,67 @@ | @@ -0,0 +1,67 @@ | ||
1 | +require 'helper' | ||
2 | + | ||
3 | +describe Delayed::Lifecycle do | ||
4 | + let(:lifecycle) { Delayed::Lifecycle.new } | ||
5 | + let(:callback) { lambda {|*args|} } | ||
6 | + let(:arguments) { [1] } | ||
7 | + let(:behavior) { double(Object, :before! => nil, :after! => nil, :inside! => nil) } | ||
8 | + let(:wrapped_block) { Proc.new { behavior.inside! } } | ||
9 | + | ||
10 | + describe "before callbacks" do | ||
11 | + before(:each) do | ||
12 | + lifecycle.before(:execute, &callback) | ||
13 | + end | ||
14 | + | ||
15 | + it "executes before wrapped block" do | ||
16 | + callback.should_receive(:call).with(*arguments).ordered | ||
17 | + behavior.should_receive(:inside!).ordered | ||
18 | + lifecycle.run_callbacks :execute, *arguments, &wrapped_block | ||
19 | + end | ||
20 | + end | ||
21 | + | ||
22 | + describe "after callbacks" do | ||
23 | + before(:each) do | ||
24 | + lifecycle.after(:execute, &callback) | ||
25 | + end | ||
26 | + | ||
27 | + it "executes after wrapped block" do | ||
28 | + behavior.should_receive(:inside!).ordered | ||
29 | + callback.should_receive(:call).with(*arguments).ordered | ||
30 | + lifecycle.run_callbacks :execute, *arguments, &wrapped_block | ||
31 | + end | ||
32 | + end | ||
33 | + | ||
34 | + describe "around callbacks" do | ||
35 | + before(:each) do | ||
36 | + lifecycle.around(:execute) do |*args, &block| | ||
37 | + behavior.before! | ||
38 | + block.call(*args) | ||
39 | + behavior.after! | ||
40 | + end | ||
41 | + end | ||
42 | + | ||
43 | + it "wraps a block" do | ||
44 | + behavior.should_receive(:before!).ordered | ||
45 | + behavior.should_receive(:inside!).ordered | ||
46 | + behavior.should_receive(:after!).ordered | ||
47 | + lifecycle.run_callbacks :execute, *arguments, &wrapped_block | ||
48 | + end | ||
49 | + | ||
50 | + it "executes multiple callbacks in order" do | ||
51 | + behavior.should_receive(:one).ordered | ||
52 | + behavior.should_receive(:two).ordered | ||
53 | + behavior.should_receive(:three).ordered | ||
54 | + | ||
55 | + lifecycle.around(:execute) { |*args, &block| behavior.one; block.call(*args) } | ||
56 | + lifecycle.around(:execute) { |*args, &block| behavior.two; block.call(*args) } | ||
57 | + lifecycle.around(:execute) { |*args, &block| behavior.three; block.call(*args) } | ||
58 | + | ||
59 | + lifecycle.run_callbacks(:execute, *arguments, &wrapped_block) | ||
60 | + end | ||
61 | + end | ||
62 | + | ||
63 | + it "raises if callback is executed with wrong number of parameters" do | ||
64 | + lifecycle.before(:execute, &callback) | ||
65 | + expect { lifecycle.run_callbacks(:execute, 1,2,3) {} }.to raise_error(ArgumentError, /1 parameter/) | ||
66 | + end | ||
67 | +end |
vendor/plugins/delayed_job/spec/message_sending_spec.rb
1 | -require 'spec_helper' | 1 | +require 'helper' |
2 | 2 | ||
3 | describe Delayed::MessageSending do | 3 | describe Delayed::MessageSending do |
4 | describe "handle_asynchronously" do | 4 | describe "handle_asynchronously" do |
5 | - class Story < ActiveRecord::Base | ||
6 | - def tell!(arg) | ||
7 | - end | 5 | + class Story |
6 | + def tell!(arg);end | ||
8 | handle_asynchronously :tell! | 7 | handle_asynchronously :tell! |
9 | end | 8 | end |
10 | - | ||
11 | - it "should alias original method" do | ||
12 | - Story.new.should respond_to(:tell_without_delay!) | ||
13 | - Story.new.should respond_to(:tell_with_delay!) | 9 | + |
10 | + it "aliases original method" do | ||
11 | + expect(Story.new).to respond_to(:tell_without_delay!) | ||
12 | + expect(Story.new).to respond_to(:tell_with_delay!) | ||
14 | end | 13 | end |
15 | - | ||
16 | - it "should create a PerformableMethod" do | ||
17 | - story = Story.create! | ||
18 | - lambda { | 14 | + |
15 | + it "creates a PerformableMethod" do | ||
16 | + story = Story.create | ||
17 | + expect { | ||
19 | job = story.tell!(1) | 18 | job = story.tell!(1) |
20 | - job.payload_object.class.should == Delayed::PerformableMethod | ||
21 | - job.payload_object.method.should == :tell_without_delay! | ||
22 | - job.payload_object.args.should == [1] | ||
23 | - }.should change { Delayed::Job.count } | 19 | + expect(job.payload_object.class).to eq(Delayed::PerformableMethod) |
20 | + expect(job.payload_object.method_name).to eq(:tell_without_delay!) | ||
21 | + expect(job.payload_object.args).to eq([1]) | ||
22 | + }.to change { Delayed::Job.count } | ||
23 | + end | ||
24 | + | ||
25 | + describe "with options" do | ||
26 | + class Fable | ||
27 | + cattr_accessor :importance | ||
28 | + def tell;end | ||
29 | + handle_asynchronously :tell, :priority => Proc.new { self.importance } | ||
30 | + end | ||
31 | + | ||
32 | + it "sets the priority based on the Fable importance" do | ||
33 | + Fable.importance = 10 | ||
34 | + job = Fable.new.tell | ||
35 | + expect(job.priority).to eq(10) | ||
36 | + | ||
37 | + Fable.importance = 20 | ||
38 | + job = Fable.new.tell | ||
39 | + expect(job.priority).to eq(20) | ||
40 | + end | ||
41 | + | ||
42 | + describe "using a proc with parameters" do | ||
43 | + class Yarn | ||
44 | + attr_accessor :importance | ||
45 | + def spin | ||
46 | + end | ||
47 | + handle_asynchronously :spin, :priority => Proc.new {|y| y.importance } | ||
48 | + end | ||
49 | + | ||
50 | + it "sets the priority based on the Fable importance" do | ||
51 | + job = Yarn.new.tap {|y| y.importance = 10 }.spin | ||
52 | + expect(job.priority).to eq(10) | ||
53 | + | ||
54 | + job = Yarn.new.tap {|y| y.importance = 20 }.spin | ||
55 | + expect(job.priority).to eq(20) | ||
56 | + end | ||
57 | + end | ||
24 | end | 58 | end |
25 | end | 59 | end |
26 | 60 | ||
27 | context "delay" do | 61 | context "delay" do |
28 | - it "should create a new PerformableMethod job" do | ||
29 | - lambda { | 62 | + class FairyTail |
63 | + attr_accessor :happy_ending | ||
64 | + def self.princesses;end | ||
65 | + def tell | ||
66 | + @happy_ending = true | ||
67 | + end | ||
68 | + end | ||
69 | + | ||
70 | + after do | ||
71 | + Delayed::Worker.default_queue_name = nil | ||
72 | + end | ||
73 | + | ||
74 | + it "creates a new PerformableMethod job" do | ||
75 | + expect { | ||
30 | job = "hello".delay.count('l') | 76 | job = "hello".delay.count('l') |
31 | - job.payload_object.class.should == Delayed::PerformableMethod | ||
32 | - job.payload_object.method.should == :count | ||
33 | - job.payload_object.args.should == ['l'] | ||
34 | - }.should change { Delayed::Job.count }.by(1) | 77 | + expect(job.payload_object.class).to eq(Delayed::PerformableMethod) |
78 | + expect(job.payload_object.method_name).to eq(:count) | ||
79 | + expect(job.payload_object.args).to eq(['l']) | ||
80 | + }.to change { Delayed::Job.count }.by(1) | ||
35 | end | 81 | end |
36 | 82 | ||
37 | - it "should set default priority" do | 83 | + it "sets default priority" do |
38 | Delayed::Worker.default_priority = 99 | 84 | Delayed::Worker.default_priority = 99 |
39 | - job = Object.delay.to_s | ||
40 | - job.priority.should == 99 | ||
41 | - Delayed::Worker.default_priority = 0 | 85 | + job = FairyTail.delay.to_s |
86 | + expect(job.priority).to eq(99) | ||
42 | end | 87 | end |
43 | 88 | ||
44 | - it "should set job options" do | 89 | + it "sets default queue name" do |
90 | + Delayed::Worker.default_queue_name = 'abbazabba' | ||
91 | + job = FairyTail.delay.to_s | ||
92 | + expect(job.queue).to eq('abbazabba') | ||
93 | + end | ||
94 | + | ||
95 | + it "sets job options" do | ||
45 | run_at = Time.parse('2010-05-03 12:55 AM') | 96 | run_at = Time.parse('2010-05-03 12:55 AM') |
46 | - job = Object.delay(:priority => 20, :run_at => run_at).to_s | ||
47 | - job.run_at.should == run_at | ||
48 | - job.priority.should == 20 | 97 | + job = FairyTail.delay(:priority => 20, :run_at => run_at).to_s |
98 | + expect(job.run_at).to eq(run_at) | ||
99 | + expect(job.priority).to eq(20) | ||
100 | + end | ||
101 | + | ||
102 | + it "does not delay the job when delay_jobs is false" do | ||
103 | + Delayed::Worker.delay_jobs = false | ||
104 | + fairy_tail = FairyTail.new | ||
105 | + expect { | ||
106 | + expect { | ||
107 | + fairy_tail.delay.tell | ||
108 | + }.to change(fairy_tail, :happy_ending).from(nil).to(true) | ||
109 | + }.not_to change { Delayed::Job.count } | ||
110 | + end | ||
111 | + | ||
112 | + it "does delay the job when delay_jobs is true" do | ||
113 | + Delayed::Worker.delay_jobs = true | ||
114 | + fairy_tail = FairyTail.new | ||
115 | + expect { | ||
116 | + expect { | ||
117 | + fairy_tail.delay.tell | ||
118 | + }.not_to change(fairy_tail, :happy_ending) | ||
119 | + }.to change { Delayed::Job.count }.by(1) | ||
49 | end | 120 | end |
50 | end | 121 | end |
51 | end | 122 | end |
vendor/plugins/delayed_job/spec/performable_mailer_spec.rb
0 → 100644
@@ -0,0 +1,44 @@ | @@ -0,0 +1,44 @@ | ||
1 | +require 'helper' | ||
2 | + | ||
3 | +require 'action_mailer' | ||
4 | +class MyMailer < ActionMailer::Base | ||
5 | + def signup(email) | ||
6 | + mail :to => email, :subject => "Delaying Emails", :from => "delayedjob@example.com",:body => 'Delaying Emails Body' | ||
7 | + end | ||
8 | +end | ||
9 | + | ||
10 | +describe ActionMailer::Base do | ||
11 | + describe "delay" do | ||
12 | + it "enqueues a PerformableEmail job" do | ||
13 | + expect { | ||
14 | + job = MyMailer.delay.signup('john@example.com') | ||
15 | + expect(job.payload_object.class).to eq(Delayed::PerformableMailer) | ||
16 | + expect(job.payload_object.method_name).to eq(:signup) | ||
17 | + expect(job.payload_object.args).to eq(['john@example.com']) | ||
18 | + }.to change { Delayed::Job.count }.by(1) | ||
19 | + end | ||
20 | + end | ||
21 | + | ||
22 | + describe "delay on a mail object" do | ||
23 | + it "raises an exception" do | ||
24 | + expect { | ||
25 | + MyMailer.signup('john@example.com').delay | ||
26 | + }.to raise_error(RuntimeError) | ||
27 | + end | ||
28 | + end | ||
29 | + | ||
30 | + describe Delayed::PerformableMailer do | ||
31 | + describe "perform" do | ||
32 | + it "calls the method and #deliver on the mailer" do | ||
33 | + email = double('email', :deliver => true) | ||
34 | + mailer_class = double('MailerClass', :signup => email) | ||
35 | + mailer = Delayed::PerformableMailer.new(mailer_class, :signup, ['john@example.com']) | ||
36 | + | ||
37 | + mailer_class.should_receive(:signup).with('john@example.com') | ||
38 | + email.should_receive(:deliver) | ||
39 | + mailer.perform | ||
40 | + end | ||
41 | + end | ||
42 | + end | ||
43 | + | ||
44 | +end |
vendor/plugins/delayed_job/spec/performable_method_spec.rb
1 | -require 'spec_helper' | 1 | +require 'helper' |
2 | 2 | ||
3 | describe Delayed::PerformableMethod do | 3 | describe Delayed::PerformableMethod do |
4 | describe "perform" do | 4 | describe "perform" do |
5 | before do | 5 | before do |
6 | @method = Delayed::PerformableMethod.new("foo", :count, ['o']) | 6 | @method = Delayed::PerformableMethod.new("foo", :count, ['o']) |
7 | end | 7 | end |
8 | - | 8 | + |
9 | context "with the persisted record cannot be found" do | 9 | context "with the persisted record cannot be found" do |
10 | before do | 10 | before do |
11 | @method.object = nil | 11 | @method.object = nil |
12 | end | 12 | end |
13 | - | ||
14 | - it "should be a no-op if object is nil" do | ||
15 | - lambda { @method.perform }.should_not raise_error | 13 | + |
14 | + it "does nothing if object is nil" do | ||
15 | + expect{@method.perform}.not_to raise_error | ||
16 | end | 16 | end |
17 | end | 17 | end |
18 | - | ||
19 | - it "should call the method on the object" do | 18 | + |
19 | + it "calls the method on the object" do | ||
20 | @method.object.should_receive(:count).with('o') | 20 | @method.object.should_receive(:count).with('o') |
21 | @method.perform | 21 | @method.perform |
22 | end | 22 | end |
23 | - | ||
24 | - it "should respond to on_permanent_failure when implemented and target object is called via object.delay.do_something" do | ||
25 | - @method = Delayed::PerformableMethod.new(OnPermanentFailureJob.new, :perform, []) | ||
26 | - @method.respond_to?(:on_permanent_failure).should be_true | ||
27 | - @method.object.should_receive(:on_permanent_failure) | ||
28 | - @method.on_permanent_failure | ||
29 | - end | ||
30 | end | 23 | end |
31 | 24 | ||
32 | - it "should raise a NoMethodError if target method doesn't exist" do | ||
33 | - lambda { | 25 | + it "raises a NoMethodError if target method doesn't exist" do |
26 | + expect { | ||
34 | Delayed::PerformableMethod.new(Object, :method_that_does_not_exist, []) | 27 | Delayed::PerformableMethod.new(Object, :method_that_does_not_exist, []) |
35 | - }.should raise_error(NoMethodError) | 28 | + }.to raise_error(NoMethodError) |
36 | end | 29 | end |
37 | - | ||
38 | - it "should not raise NoMethodError if target method is private" do | 30 | + |
31 | + it "does not raise NoMethodError if target method is private" do | ||
39 | clazz = Class.new do | 32 | clazz = Class.new do |
40 | def private_method | 33 | def private_method |
41 | end | 34 | end |
42 | private :private_method | 35 | private :private_method |
43 | end | 36 | end |
44 | - lambda { | 37 | + expect { |
45 | Delayed::PerformableMethod.new(clazz.new, :private_method, []) | 38 | Delayed::PerformableMethod.new(clazz.new, :private_method, []) |
46 | - }.should_not raise_error(NoMethodError) | 39 | + }.not_to raise_error |
40 | + end | ||
41 | + | ||
42 | + describe "hooks" do | ||
43 | + %w(before after success).each do |hook| | ||
44 | + it "delegates #{hook} hook to object" do | ||
45 | + story = Story.create | ||
46 | + job = story.delay.tell | ||
47 | + | ||
48 | + story.should_receive(hook).with(job) | ||
49 | + job.invoke_job | ||
50 | + end | ||
51 | + end | ||
52 | + | ||
53 | + %w(before after success).each do |hook| | ||
54 | + it "delegates #{hook} hook to object" do | ||
55 | + story = Story.create | ||
56 | + job = story.delay.tell | ||
57 | + | ||
58 | + story.should_receive(hook).with(job) | ||
59 | + job.invoke_job | ||
60 | + end | ||
61 | + end | ||
62 | + | ||
63 | + it "delegates enqueue hook to object" do | ||
64 | + story = Story.create | ||
65 | + story.should_receive(:enqueue).with(an_instance_of(Delayed::Job)) | ||
66 | + story.delay.tell | ||
67 | + end | ||
68 | + | ||
69 | + it "delegates error hook to object" do | ||
70 | + story = Story.create | ||
71 | + story.should_receive(:error).with(an_instance_of(Delayed::Job), an_instance_of(RuntimeError)) | ||
72 | + story.should_receive(:tell).and_raise(RuntimeError) | ||
73 | + expect { story.delay.tell.invoke_job }.to raise_error | ||
74 | + end | ||
75 | + | ||
76 | + it "delegates error hook to object when delay_jobs = false" do | ||
77 | + story = Story.create | ||
78 | + story.should_receive(:error).with(an_instance_of(Delayed::Job), an_instance_of(RuntimeError)) | ||
79 | + story.should_receive(:tell).and_raise(RuntimeError) | ||
80 | + expect { story.delay.tell.invoke_job }.to raise_error | ||
81 | + end | ||
82 | + | ||
83 | + it "delegates failure hook to object" do | ||
84 | + method = Delayed::PerformableMethod.new("object", :size, []) | ||
85 | + method.object.should_receive(:failure) | ||
86 | + method.failure | ||
87 | + end | ||
88 | + | ||
89 | + context 'with delay_job == false' do | ||
90 | + before do | ||
91 | + Delayed::Worker.delay_jobs = false | ||
92 | + end | ||
93 | + | ||
94 | + after do | ||
95 | + Delayed::Worker.delay_jobs = true | ||
96 | + end | ||
97 | + | ||
98 | + %w(before after success).each do |hook| | ||
99 | + it "delegates #{hook} hook to object" do | ||
100 | + story = Story.create | ||
101 | + story.should_receive(hook).with(an_instance_of(Delayed::Job)) | ||
102 | + story.delay.tell | ||
103 | + end | ||
104 | + end | ||
105 | + | ||
106 | + %w(before after success).each do |hook| | ||
107 | + it "delegates #{hook} hook to object" do | ||
108 | + story = Story.create | ||
109 | + story.should_receive(hook).with(an_instance_of(Delayed::Job)) | ||
110 | + story.delay.tell | ||
111 | + end | ||
112 | + end | ||
113 | + | ||
114 | + it "delegates error hook to object" do | ||
115 | + story = Story.create | ||
116 | + story.should_receive(:error).with(an_instance_of(Delayed::Job), an_instance_of(RuntimeError)) | ||
117 | + story.should_receive(:tell).and_raise(RuntimeError) | ||
118 | + expect { story.delay.tell }.to raise_error | ||
119 | + end | ||
120 | + | ||
121 | + it "delegates error hook to object when delay_jobs = false" do | ||
122 | + story = Story.create | ||
123 | + story.should_receive(:error).with(an_instance_of(Delayed::Job), an_instance_of(RuntimeError)) | ||
124 | + story.should_receive(:tell).and_raise(RuntimeError) | ||
125 | + expect { story.delay.tell }.to raise_error | ||
126 | + end | ||
127 | + | ||
128 | + it "delegates failure hook to object when delay_jobs = false" do | ||
129 | + Delayed::Worker.delay_jobs = false | ||
130 | + method = Delayed::PerformableMethod.new("object", :size, []) | ||
131 | + method.object.should_receive(:failure) | ||
132 | + method.failure | ||
133 | + end | ||
134 | + end | ||
47 | end | 135 | end |
48 | end | 136 | end |
vendor/plugins/delayed_job/spec/sample_jobs.rb
1 | +class NamedJob < Struct.new(:perform) | ||
2 | + def display_name | ||
3 | + 'named_job' | ||
4 | + end | ||
5 | +end | ||
6 | + | ||
1 | class SimpleJob | 7 | class SimpleJob |
2 | cattr_accessor :runs; self.runs = 0 | 8 | cattr_accessor :runs; self.runs = 0 |
3 | def perform; @@runs += 1; end | 9 | def perform; @@runs += 1; end |
@@ -6,20 +12,64 @@ end | @@ -6,20 +12,64 @@ end | ||
6 | class ErrorJob | 12 | class ErrorJob |
7 | cattr_accessor :runs; self.runs = 0 | 13 | cattr_accessor :runs; self.runs = 0 |
8 | def perform; raise 'did not work'; end | 14 | def perform; raise 'did not work'; end |
9 | -end | 15 | +end |
16 | + | ||
17 | +class CustomRescheduleJob < Struct.new(:offset) | ||
18 | + cattr_accessor :runs; self.runs = 0 | ||
19 | + def perform; raise 'did not work'; end | ||
20 | + def reschedule_at(time, attempts); time + offset; end | ||
21 | +end | ||
10 | 22 | ||
11 | class LongRunningJob | 23 | class LongRunningJob |
12 | def perform; sleep 250; end | 24 | def perform; sleep 250; end |
13 | end | 25 | end |
14 | 26 | ||
15 | class OnPermanentFailureJob < SimpleJob | 27 | class OnPermanentFailureJob < SimpleJob |
16 | - def on_permanent_failure | ||
17 | - end | 28 | + def failure; end |
29 | + def max_attempts; 1; end | ||
18 | end | 30 | end |
19 | 31 | ||
20 | module M | 32 | module M |
21 | class ModuleJob | 33 | class ModuleJob |
22 | cattr_accessor :runs; self.runs = 0 | 34 | cattr_accessor :runs; self.runs = 0 |
23 | - def perform; @@runs += 1; end | 35 | + def perform; @@runs += 1; end |
36 | + end | ||
37 | +end | ||
38 | + | ||
39 | +class CallbackJob | ||
40 | + cattr_accessor :messages | ||
41 | + | ||
42 | + def enqueue(job) | ||
43 | + self.class.messages << 'enqueue' | ||
44 | + end | ||
45 | + | ||
46 | + def before(job) | ||
47 | + self.class.messages << 'before' | ||
48 | + end | ||
49 | + | ||
50 | + def perform | ||
51 | + self.class.messages << 'perform' | ||
52 | + end | ||
53 | + | ||
54 | + def after(job) | ||
55 | + self.class.messages << 'after' | ||
56 | + end | ||
57 | + | ||
58 | + def success(job) | ||
59 | + self.class.messages << 'success' | ||
60 | + end | ||
61 | + | ||
62 | + def error(job, error) | ||
63 | + self.class.messages << "error: #{error.class}" | ||
64 | + end | ||
65 | + | ||
66 | + def failure(job) | ||
67 | + self.class.messages << 'failure' | ||
68 | + end | ||
69 | +end | ||
70 | + | ||
71 | +class EnqueueJobMod < SimpleJob | ||
72 | + def enqueue(job) | ||
73 | + job.run_at = 20.minutes.from_now | ||
24 | end | 74 | end |
25 | end | 75 | end |
vendor/plugins/delayed_job/spec/setup/active_record.rb
@@ -1,33 +0,0 @@ | @@ -1,33 +0,0 @@ | ||
1 | -require 'active_record' | ||
2 | - | ||
3 | -ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:') | ||
4 | -ActiveRecord::Base.logger = Delayed::Worker.logger | ||
5 | -ActiveRecord::Migration.verbose = false | ||
6 | - | ||
7 | -ActiveRecord::Schema.define do | ||
8 | - create_table :delayed_jobs, :force => true do |table| | ||
9 | - table.integer :priority, :default => 0 | ||
10 | - table.integer :attempts, :default => 0 | ||
11 | - table.text :handler | ||
12 | - table.text :last_error | ||
13 | - table.datetime :run_at | ||
14 | - table.datetime :locked_at | ||
15 | - table.datetime :failed_at | ||
16 | - table.string :locked_by | ||
17 | - table.timestamps | ||
18 | - end | ||
19 | - | ||
20 | - add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority' | ||
21 | - | ||
22 | - create_table :stories, :force => true do |table| | ||
23 | - table.string :text | ||
24 | - end | ||
25 | -end | ||
26 | - | ||
27 | -# Purely useful for test cases... | ||
28 | -class Story < ActiveRecord::Base | ||
29 | - def tell; text; end | ||
30 | - def whatever(n, _); tell*n; end | ||
31 | - | ||
32 | - handle_asynchronously :whatever | ||
33 | -end |
vendor/plugins/delayed_job/spec/setup/couch_rest.rb
@@ -1,7 +0,0 @@ | @@ -1,7 +0,0 @@ | ||
1 | -require 'couchrest' | ||
2 | -require 'delayed/backend/couch_rest' | ||
3 | - | ||
4 | -Delayed::Backend::CouchRest::Job.use_database CouchRest::Server.new.database!('delayed_job_spec') | ||
5 | - | ||
6 | -# try to perform a query to check that we can connect | ||
7 | -Delayed::Backend::CouchRest::Job.all | ||
8 | \ No newline at end of file | 0 | \ No newline at end of file |
vendor/plugins/delayed_job/spec/setup/data_mapper.rb
@@ -1,8 +0,0 @@ | @@ -1,8 +0,0 @@ | ||
1 | -require 'dm-core' | ||
2 | -require 'dm-validations' | ||
3 | - | ||
4 | -require 'delayed/backend/data_mapper' | ||
5 | - | ||
6 | -DataMapper.logger = Delayed::Worker.logger | ||
7 | -DataMapper.setup(:default, "sqlite3::memory:") | ||
8 | -DataMapper.auto_migrate! | ||
9 | \ No newline at end of file | 0 | \ No newline at end of file |
vendor/plugins/delayed_job/spec/setup/mongo_mapper.rb
@@ -1,17 +0,0 @@ | @@ -1,17 +0,0 @@ | ||
1 | -require 'mongo_mapper' | ||
2 | - | ||
3 | -MongoMapper.config = { | ||
4 | - RAILS_ENV => {'database' => 'delayed_job'} | ||
5 | -} | ||
6 | -MongoMapper.connect RAILS_ENV | ||
7 | - | ||
8 | -unless defined?(Story) | ||
9 | - class Story | ||
10 | - include ::MongoMapper::Document | ||
11 | - def tell; text; end | ||
12 | - def whatever(n, _); tell*n; end | ||
13 | - def self.count; end | ||
14 | - | ||
15 | - handle_asynchronously :whatever | ||
16 | - end | ||
17 | -end |
vendor/plugins/delayed_job/spec/spec_helper.rb
@@ -1,31 +0,0 @@ | @@ -1,31 +0,0 @@ | ||
1 | -$:.unshift(File.dirname(__FILE__) + '/../lib') | ||
2 | - | ||
3 | -require 'rubygems' | ||
4 | -require 'spec' | ||
5 | -require 'logger' | ||
6 | - | ||
7 | -gem 'activerecord', ENV['RAILS_VERSION'] if ENV['RAILS_VERSION'] | ||
8 | - | ||
9 | -require 'delayed_job' | ||
10 | -require 'sample_jobs' | ||
11 | - | ||
12 | -Delayed::Worker.logger = Logger.new('/tmp/dj.log') | ||
13 | -RAILS_ENV = 'test' | ||
14 | - | ||
15 | -# determine the available backends | ||
16 | -BACKENDS = [] | ||
17 | -Dir.glob("#{File.dirname(__FILE__)}/setup/*.rb") do |backend| | ||
18 | - begin | ||
19 | - backend = File.basename(backend, '.rb') | ||
20 | - require "setup/#{backend}" | ||
21 | - require "backend/#{backend}_job_spec" | ||
22 | - BACKENDS << backend.to_sym | ||
23 | - rescue Exception | ||
24 | - puts "Unable to load #{backend} backend: #{$!}" | ||
25 | - end | ||
26 | -end | ||
27 | - | ||
28 | -Delayed::Worker.backend = BACKENDS.first | ||
29 | - | ||
30 | -# Add this directory so the ActiveSupport autoloading works | ||
31 | -ActiveSupport::Dependencies.load_paths << File.dirname(__FILE__) |
@@ -0,0 +1,13 @@ | @@ -0,0 +1,13 @@ | ||
1 | +require 'helper' | ||
2 | + | ||
3 | +describe Delayed::Backend::Test::Job do | ||
4 | + it_should_behave_like 'a delayed_job backend' | ||
5 | + | ||
6 | + describe "#reload" do | ||
7 | + it "causes the payload object to be reloaded" do | ||
8 | + job = "foo".delay.length | ||
9 | + o = job.payload_object | ||
10 | + expect(o.object_id).not_to eq(job.reload.payload_object.object_id) | ||
11 | + end | ||
12 | + end | ||
13 | +end |
vendor/plugins/delayed_job/spec/worker_spec.rb
1 | -require 'spec_helper' | 1 | +require 'helper' |
2 | 2 | ||
3 | describe Delayed::Worker do | 3 | describe Delayed::Worker do |
4 | - def job_create(opts = {}) | ||
5 | - Delayed::Job.create(opts.merge(:payload_object => SimpleJob.new)) | ||
6 | - end | ||
7 | - | ||
8 | describe "backend=" do | 4 | describe "backend=" do |
9 | before do | 5 | before do |
10 | @clazz = Class.new | 6 | @clazz = Class.new |
11 | Delayed::Worker.backend = @clazz | 7 | Delayed::Worker.backend = @clazz |
12 | end | 8 | end |
13 | 9 | ||
14 | - it "should set the Delayed::Job constant to the backend" do | ||
15 | - Delayed::Job.should == @clazz | 10 | + after do |
11 | + Delayed::Worker.backend = :test | ||
16 | end | 12 | end |
17 | - | ||
18 | - it "should set backend with a symbol" do | ||
19 | - Delayed::Worker.backend = :active_record | ||
20 | - Delayed::Worker.backend.should == Delayed::Backend::ActiveRecord::Job | 13 | + |
14 | + it "sets the Delayed::Job constant to the backend" do | ||
15 | + expect(Delayed::Job).to eq(@clazz) | ||
16 | + end | ||
17 | + | ||
18 | + it "sets backend with a symbol" do | ||
19 | + Delayed::Worker.backend = :test | ||
20 | + expect(Delayed::Worker.backend).to eq(Delayed::Backend::Test::Job) | ||
21 | end | 21 | end |
22 | end | 22 | end |
23 | - | ||
24 | - BACKENDS.each do |backend| | ||
25 | - describe "with the #{backend} backend" do | ||
26 | - before do | ||
27 | - Delayed::Worker.backend = backend | ||
28 | - Delayed::Job.delete_all | ||
29 | 23 | ||
30 | - @worker = Delayed::Worker.new(:max_priority => nil, :min_priority => nil, :quiet => true) | 24 | + describe "job_say" do |
25 | + before do | ||
26 | + @worker = Delayed::Worker.new | ||
27 | + @job = double('job', :id => 123, :name => 'ExampleJob') | ||
28 | + end | ||
31 | 29 | ||
32 | - SimpleJob.runs = 0 | ||
33 | - end | ||
34 | - | ||
35 | - describe "running a job" do | ||
36 | - it "should fail after Worker.max_run_time" do | ||
37 | - begin | ||
38 | - old_max_run_time = Delayed::Worker.max_run_time | ||
39 | - Delayed::Worker.max_run_time = 1.second | ||
40 | - @job = Delayed::Job.create :payload_object => LongRunningJob.new | ||
41 | - @worker.run(@job) | ||
42 | - @job.reload.last_error.should =~ /expired/ | ||
43 | - @job.attempts.should == 1 | ||
44 | - ensure | ||
45 | - Delayed::Worker.max_run_time = old_max_run_time | ||
46 | - end | ||
47 | - end | ||
48 | - end | ||
49 | - | ||
50 | - context "worker prioritization" do | ||
51 | - before(:each) do | ||
52 | - @worker = Delayed::Worker.new(:max_priority => 5, :min_priority => -5, :quiet => true) | ||
53 | - end | 30 | + it "logs with job name and id" do |
31 | + @worker.should_receive(:say). | ||
32 | + with('Job ExampleJob (id=123) message', Delayed::Worker::DEFAULT_LOG_LEVEL) | ||
33 | + @worker.job_say(@job, 'message') | ||
34 | + end | ||
35 | + end | ||
54 | 36 | ||
55 | - it "should only work_off jobs that are >= min_priority" do | ||
56 | - job_create(:priority => -10) | ||
57 | - job_create(:priority => 0) | ||
58 | - @worker.work_off | 37 | + context "worker read-ahead" do |
38 | + before do | ||
39 | + @read_ahead = Delayed::Worker.read_ahead | ||
40 | + end | ||
59 | 41 | ||
60 | - SimpleJob.runs.should == 1 | ||
61 | - end | 42 | + after do |
43 | + Delayed::Worker.read_ahead = @read_ahead | ||
44 | + end | ||
62 | 45 | ||
63 | - it "should only work_off jobs that are <= max_priority" do | ||
64 | - job_create(:priority => 10) | ||
65 | - job_create(:priority => 0) | 46 | + it "reads five jobs" do |
47 | + Delayed::Job.should_receive(:find_available).with(anything, 5, anything).and_return([]) | ||
48 | + Delayed::Job.reserve(Delayed::Worker.new) | ||
49 | + end | ||
66 | 50 | ||
67 | - @worker.work_off | 51 | + it "reads a configurable number of jobs" do |
52 | + Delayed::Worker.read_ahead = 15 | ||
53 | + Delayed::Job.should_receive(:find_available).with(anything, Delayed::Worker.read_ahead, anything).and_return([]) | ||
54 | + Delayed::Job.reserve(Delayed::Worker.new) | ||
55 | + end | ||
56 | + end | ||
68 | 57 | ||
69 | - SimpleJob.runs.should == 1 | ||
70 | - end | ||
71 | - end | 58 | + context "worker exit on complete" do |
59 | + before do | ||
60 | + Delayed::Worker.exit_on_complete = true | ||
61 | + end | ||
72 | 62 | ||
73 | - context "while running with locked and expired jobs" do | ||
74 | - before(:each) do | ||
75 | - @worker.name = 'worker1' | ||
76 | - end | ||
77 | - | ||
78 | - it "should not run jobs locked by another worker" do | ||
79 | - job_create(:locked_by => 'other_worker', :locked_at => (Delayed::Job.db_time_now - 1.minutes)) | ||
80 | - lambda { @worker.work_off }.should_not change { SimpleJob.runs } | ||
81 | - end | ||
82 | - | ||
83 | - it "should run open jobs" do | ||
84 | - job_create | ||
85 | - lambda { @worker.work_off }.should change { SimpleJob.runs }.from(0).to(1) | ||
86 | - end | ||
87 | - | ||
88 | - it "should run expired jobs" do | ||
89 | - expired_time = Delayed::Job.db_time_now - (1.minutes + Delayed::Worker.max_run_time) | ||
90 | - job_create(:locked_by => 'other_worker', :locked_at => expired_time) | ||
91 | - lambda { @worker.work_off }.should change { SimpleJob.runs }.from(0).to(1) | ||
92 | - end | ||
93 | - | ||
94 | - it "should run own jobs" do | ||
95 | - job_create(:locked_by => @worker.name, :locked_at => (Delayed::Job.db_time_now - 1.minutes)) | ||
96 | - lambda { @worker.work_off }.should change { SimpleJob.runs }.from(0).to(1) | ||
97 | - end | ||
98 | - end | ||
99 | - | ||
100 | - describe "failed jobs" do | ||
101 | - before do | ||
102 | - # reset defaults | ||
103 | - Delayed::Worker.destroy_failed_jobs = true | ||
104 | - Delayed::Worker.max_attempts = 25 | ||
105 | - | ||
106 | - @job = Delayed::Job.enqueue ErrorJob.new | ||
107 | - end | ||
108 | - | ||
109 | - it "should record last_error when destroy_failed_jobs = false, max_attempts = 1" do | ||
110 | - Delayed::Worker.destroy_failed_jobs = false | ||
111 | - Delayed::Worker.max_attempts = 1 | ||
112 | - @worker.run(@job) | ||
113 | - @job.reload | ||
114 | - @job.last_error.should =~ /did not work/ | ||
115 | - @job.last_error.should =~ /worker_spec.rb/ | ||
116 | - @job.attempts.should == 1 | ||
117 | - @job.failed_at.should_not be_nil | ||
118 | - end | ||
119 | - | ||
120 | - it "should re-schedule jobs after failing" do | ||
121 | - @worker.run(@job) | ||
122 | - @job.reload | ||
123 | - @job.last_error.should =~ /did not work/ | ||
124 | - @job.last_error.should =~ /sample_jobs.rb:8:in `perform'/ | ||
125 | - @job.attempts.should == 1 | ||
126 | - @job.run_at.should > Delayed::Job.db_time_now - 10.minutes | ||
127 | - @job.run_at.should < Delayed::Job.db_time_now + 10.minutes | ||
128 | - end | ||
129 | - end | ||
130 | - | ||
131 | - context "reschedule" do | ||
132 | - before do | ||
133 | - @job = Delayed::Job.create :payload_object => SimpleJob.new | ||
134 | - end | ||
135 | - | ||
136 | - share_examples_for "any failure more than Worker.max_attempts times" do | ||
137 | - context "when the job's payload has an #on_permanent_failure hook" do | ||
138 | - before do | ||
139 | - @job = Delayed::Job.create :payload_object => OnPermanentFailureJob.new | ||
140 | - @job.payload_object.should respond_to :on_permanent_failure | ||
141 | - end | ||
142 | - | ||
143 | - it "should run that hook" do | ||
144 | - @job.payload_object.should_receive :on_permanent_failure | ||
145 | - Delayed::Worker.max_attempts.times { @worker.reschedule(@job) } | ||
146 | - end | ||
147 | - end | ||
148 | - | ||
149 | - context "when the job's payload has no #on_permanent_failure hook" do | ||
150 | - # It's a little tricky to test this in a straightforward way, | ||
151 | - # because putting a should_not_receive expectation on | ||
152 | - # @job.payload_object.on_permanent_failure makes that object | ||
153 | - # incorrectly return true to | ||
154 | - # payload_object.respond_to? :on_permanent_failure, which is what | ||
155 | - # reschedule uses to decide whether to call on_permanent_failure. | ||
156 | - # So instead, we just make sure that the payload_object as it | ||
157 | - # already stands doesn't respond_to? on_permanent_failure, then | ||
158 | - # shove it through the iterated reschedule loop and make sure we | ||
159 | - # don't get a NoMethodError (caused by calling that nonexistent | ||
160 | - # on_permanent_failure method). | ||
161 | - | ||
162 | - before do | ||
163 | - @job.payload_object.should_not respond_to(:on_permanent_failure) | ||
164 | - end | ||
165 | - | ||
166 | - it "should not try to run that hook" do | ||
167 | - lambda do | ||
168 | - Delayed::Worker.max_attempts.times { @worker.reschedule(@job) } | ||
169 | - end.should_not raise_exception(NoMethodError) | ||
170 | - end | ||
171 | - end | ||
172 | - end | ||
173 | - | ||
174 | - context "and we want to destroy jobs" do | ||
175 | - before do | ||
176 | - Delayed::Worker.destroy_failed_jobs = true | ||
177 | - end | ||
178 | - | ||
179 | - it_should_behave_like "any failure more than Worker.max_attempts times" | ||
180 | - | ||
181 | - it "should be destroyed if it failed more than Worker.max_attempts times" do | ||
182 | - @job.should_receive(:destroy) | ||
183 | - Delayed::Worker.max_attempts.times { @worker.reschedule(@job) } | ||
184 | - end | ||
185 | - | ||
186 | - it "should not be destroyed if failed fewer than Worker.max_attempts times" do | ||
187 | - @job.should_not_receive(:destroy) | ||
188 | - (Delayed::Worker.max_attempts - 1).times { @worker.reschedule(@job) } | ||
189 | - end | ||
190 | - end | ||
191 | - | ||
192 | - context "and we don't want to destroy jobs" do | ||
193 | - before do | ||
194 | - Delayed::Worker.destroy_failed_jobs = false | ||
195 | - end | ||
196 | - | ||
197 | - it_should_behave_like "any failure more than Worker.max_attempts times" | ||
198 | - | ||
199 | - it "should be failed if it failed more than Worker.max_attempts times" do | ||
200 | - @job.reload.failed_at.should == nil | ||
201 | - Delayed::Worker.max_attempts.times { @worker.reschedule(@job) } | ||
202 | - @job.reload.failed_at.should_not == nil | ||
203 | - end | ||
204 | - | ||
205 | - it "should not be failed if it failed fewer than Worker.max_attempts times" do | ||
206 | - (Delayed::Worker.max_attempts - 1).times { @worker.reschedule(@job) } | ||
207 | - @job.reload.failed_at.should == nil | ||
208 | - end | ||
209 | - end | 63 | + after do |
64 | + Delayed::Worker.exit_on_complete = false | ||
65 | + end | ||
66 | + | ||
67 | + it "exits the loop when no jobs are available" do | ||
68 | + worker = Delayed::Worker.new | ||
69 | + Timeout::timeout(2) do | ||
70 | + worker.start | ||
210 | end | 71 | end |
211 | end | 72 | end |
212 | end | 73 | end |
213 | - | 74 | + |
75 | + context "worker job reservation" do | ||
76 | + before do | ||
77 | + Delayed::Worker.exit_on_complete = true | ||
78 | + end | ||
79 | + | ||
80 | + after do | ||
81 | + Delayed::Worker.exit_on_complete = false | ||
82 | + end | ||
83 | + | ||
84 | + it "handles error during job reservation" do | ||
85 | + Delayed::Job.should_receive(:reserve).and_raise(Exception) | ||
86 | + Delayed::Worker.new.work_off | ||
87 | + end | ||
88 | + | ||
89 | + it "gives up after 10 backend failures" do | ||
90 | + Delayed::Job.stub(:reserve).and_raise(Exception) | ||
91 | + worker = Delayed::Worker.new | ||
92 | + 9.times { worker.work_off } | ||
93 | + expect(lambda { worker.work_off }).to raise_exception | ||
94 | + end | ||
95 | + | ||
96 | + it "allows the backend to attempt recovery from reservation errors" do | ||
97 | + Delayed::Job.should_receive(:reserve).and_raise(Exception) | ||
98 | + Delayed::Job.should_receive(:recover_from).with(instance_of(Exception)) | ||
99 | + Delayed::Worker.new.work_off | ||
100 | + end | ||
101 | + end | ||
214 | end | 102 | end |
@@ -0,0 +1,35 @@ | @@ -0,0 +1,35 @@ | ||
1 | +require 'helper' | ||
2 | + | ||
3 | +describe "YAML" do | ||
4 | + it "autoloads classes" do | ||
5 | + expect { | ||
6 | + yaml = "--- !ruby/class Autoloaded::Clazz\n" | ||
7 | + expect(YAML.load(yaml)).to eq(Autoloaded::Clazz) | ||
8 | + }.not_to raise_error | ||
9 | + end | ||
10 | + | ||
11 | + it "autoloads the class of a struct" do | ||
12 | + expect { | ||
13 | + yaml = "--- !ruby/class Autoloaded::Struct\n" | ||
14 | + expect(YAML.load(yaml)).to eq(Autoloaded::Struct) | ||
15 | + }.not_to raise_error | ||
16 | + end | ||
17 | + | ||
18 | + it "autoloads the class for the instance of a struct" do | ||
19 | + expect { | ||
20 | + yaml = "--- !ruby/struct:Autoloaded::InstanceStruct {}" | ||
21 | + expect(YAML.load(yaml).class).to eq(Autoloaded::InstanceStruct) | ||
22 | + }.not_to raise_error | ||
23 | + end | ||
24 | + | ||
25 | + it "autoloads the class for the instance" do | ||
26 | + expect { | ||
27 | + yaml = "--- !ruby/object:Autoloaded::InstanceClazz {}\n" | ||
28 | + expect(YAML.load(yaml).class).to eq(Autoloaded::InstanceClazz) | ||
29 | + }.not_to raise_error | ||
30 | + end | ||
31 | + | ||
32 | + it "does not throw an uninitialized constant Syck::Syck when using YAML.load with poorly formed yaml" do | ||
33 | + expect{ YAML.load(YAML.dump("foo: *bar"))}.not_to raise_error | ||
34 | + end | ||
35 | +end |
vendor/plugins/delayed_job/tasks/jobs.rake
@@ -1 +0,0 @@ | @@ -1 +0,0 @@ | ||
1 | -require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'delayed', 'tasks')) |
@@ -0,0 +1,28 @@ | @@ -0,0 +1,28 @@ | ||
1 | +language: ruby | ||
2 | +before_script: | ||
3 | + - mysql -e 'create database delayed_job_test;' | ||
4 | + - psql -c 'create database delayed_job_test;' -U postgres | ||
5 | +script: bundle exec rspec | ||
6 | +gemfile: | ||
7 | + - gemfiles/mysql/3-0.gemfile | ||
8 | + - gemfiles/mysql/3-1.gemfile | ||
9 | + - gemfiles/mysql/3-2.gemfile | ||
10 | + - gemfiles/mysql/4-0.gemfile | ||
11 | + - gemfiles/postgresql/3-0.gemfile | ||
12 | + - gemfiles/postgresql/3-1.gemfile | ||
13 | + - gemfiles/postgresql/3-2.gemfile | ||
14 | + - gemfiles/postgresql/4-0.gemfile | ||
15 | + - gemfiles/sqlite3/3-0.gemfile | ||
16 | + - gemfiles/sqlite3/3-1.gemfile | ||
17 | + - gemfiles/sqlite3/3-2.gemfile | ||
18 | + - gemfiles/sqlite3/4-0.gemfile | ||
19 | + - gemfiles/sqlite3/4-0-protected_attributes.gemfile | ||
20 | +rvm: | ||
21 | + - rbx-19mode | ||
22 | + - jruby-19mode | ||
23 | + - 1.9.3 | ||
24 | + - 2.0.0 | ||
25 | +matrix: | ||
26 | + allow_failures: | ||
27 | + - rvm: rbx-19mode | ||
28 | + - rvm: jruby-19mode |
vendor/plugins/delayed_job_active_record/CONTRIBUTING.md
0 → 100644
@@ -0,0 +1,14 @@ | @@ -0,0 +1,14 @@ | ||
1 | +## How to contribute | ||
2 | + | ||
3 | +If you find what looks like a bug: | ||
4 | + | ||
5 | +* Search the [mailing list](http://groups.google.com/group/delayed_job) to see if anyone else had the same issue. | ||
6 | +* Check the [GitHub issue tracker](http://github.com/collectiveidea/delayed_job_active_record/issues/) to see if anyone else has reported issue. | ||
7 | +* If you don't see anything, create an issue with information on how to reproduce it. | ||
8 | + | ||
9 | +If you want to contribute an enhancement or a fix: | ||
10 | + | ||
11 | +* Fork the project on github. | ||
12 | +* Make your changes with tests. | ||
13 | +* Commit the changes without making changes to the Rakefile or any other files that aren't related to your enhancement or fix | ||
14 | +* Send a pull request. |
@@ -0,0 +1,28 @@ | @@ -0,0 +1,28 @@ | ||
1 | +source 'https://rubygems.org' | ||
2 | + | ||
3 | +gem 'rake' | ||
4 | + | ||
5 | +group :test do | ||
6 | + platforms :jruby do | ||
7 | + gem 'activerecord-jdbcmysql-adapter' | ||
8 | + gem 'jdbc-mysql' | ||
9 | + | ||
10 | + gem 'activerecord-jdbcpostgresql-adapter' | ||
11 | + gem 'jdbc-postgres' | ||
12 | + | ||
13 | + gem 'activerecord-jdbcsqlite3-adapter' | ||
14 | + gem 'jdbc-sqlite3' | ||
15 | + end | ||
16 | + | ||
17 | + platforms :ruby, :mswin, :mingw do | ||
18 | + gem 'mysql', '~> 2.8.1' | ||
19 | + gem 'pg' | ||
20 | + gem 'sqlite3' | ||
21 | + end | ||
22 | + | ||
23 | + gem 'coveralls', :require => false | ||
24 | + gem 'rspec', '>= 2.11' | ||
25 | + gem 'simplecov', :require => false | ||
26 | +end | ||
27 | + | ||
28 | +gemspec |
@@ -0,0 +1,20 @@ | @@ -0,0 +1,20 @@ | ||
1 | +Copyright (c) 2005 Tobias Lütke | ||
2 | + | ||
3 | +Permission is hereby granted, free of charge, to any person obtaining | ||
4 | +a copy of this software and associated documentation files (the | ||
5 | +"Software"), to deal in the Software without restriction, including | ||
6 | +without limitation the rights to use, copy, modify, merge, publish, | ||
7 | +distribute, sublicense, and/or sell copies of the Software, and to | ||
8 | +permit persons to whom the Software is furnished to do so, subject to | ||
9 | +the following conditions: | ||
10 | + | ||
11 | +The above copyright notice and this permission notice shall be | ||
12 | +included in all copies or substantial portions of the Software. | ||
13 | + | ||
14 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
15 | +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
16 | +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOa AND | ||
17 | +NONINFRINGEMENT. IN NO EVENT SaALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
18 | +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
19 | +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
20 | +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@@ -0,0 +1,31 @@ | @@ -0,0 +1,31 @@ | ||
1 | +# DelayedJob ActiveRecord Backend | ||
2 | + | ||
3 | +[](https://rubygems.org/gems/delayed_job_active_record) | ||
4 | +[](https://travis-ci.org/collectiveidea/delayed_job_active_record) | ||
5 | +[](https://gemnasium.com/collectiveidea/delayed_job_active_record) | ||
6 | +[](https://codeclimate.com/github/collectiveidea/delayed_job_active_record) | ||
7 | +[](https://coveralls.io/r/collectiveidea/delayed_job_active_record) | ||
8 | + | ||
9 | +## Installation | ||
10 | + | ||
11 | +Add the gem to your Gemfile: | ||
12 | + | ||
13 | + gem 'delayed_job_active_record' | ||
14 | + | ||
15 | +Run `bundle install`. | ||
16 | + | ||
17 | +If you're using Rails, run the generator to create the migration for the | ||
18 | +delayed_job table. | ||
19 | + | ||
20 | + rails g delayed_job:active_record | ||
21 | + rake db:migrate | ||
22 | + | ||
23 | +## Upgrading from 2.x to 3.0.0 | ||
24 | + | ||
25 | +If you're upgrading from Delayed Job 2.x, run the upgrade generator to create a | ||
26 | +migration to add a column to your delayed_jobs table. | ||
27 | + | ||
28 | + rails g delayed_job:upgrade | ||
29 | + rake db:migrate | ||
30 | + | ||
31 | +That's it. Use [delayed_job as normal](http://github.com/collectiveidea/delayed_job). |
@@ -0,0 +1,35 @@ | @@ -0,0 +1,35 @@ | ||
1 | +# -*- encoding: utf-8 -*- | ||
2 | +require "bundler/gem_helper" | ||
3 | +Bundler::GemHelper.install_tasks | ||
4 | + | ||
5 | +require "rspec/core/rake_task" | ||
6 | + | ||
7 | +ADAPTERS = %w(mysql postgresql sqlite3) | ||
8 | + | ||
9 | +ADAPTERS.each do |adapter| | ||
10 | + desc "Run RSpec code examples for #{adapter} adapter" | ||
11 | + RSpec::Core::RakeTask.new(adapter => "#{adapter}:adapter") | ||
12 | + | ||
13 | + namespace adapter do | ||
14 | + task :adapter do | ||
15 | + ENV["ADAPTER"] = adapter | ||
16 | + end | ||
17 | + end | ||
18 | +end | ||
19 | + | ||
20 | +task :coverage do | ||
21 | + ENV["COVERAGE"] = "true" | ||
22 | +end | ||
23 | + | ||
24 | +task :adapter do | ||
25 | + ENV["ADAPTER"] = nil | ||
26 | +end | ||
27 | + | ||
28 | +Rake::Task[:spec].enhance do | ||
29 | + require "simplecov" | ||
30 | + require "coveralls" | ||
31 | + | ||
32 | + Coveralls::SimpleCov::Formatter.new.format(SimpleCov.result) | ||
33 | +end | ||
34 | + | ||
35 | +task default: ([:coverage] + ADAPTERS + [:adapter]) |
vendor/plugins/delayed_job_active_record/delayed_job_active_record.gemspec
0 → 100644
@@ -0,0 +1,19 @@ | @@ -0,0 +1,19 @@ | ||
1 | +# coding: utf-8 | ||
2 | + | ||
3 | +Gem::Specification.new do |spec| | ||
4 | + spec.add_dependency 'activerecord', ['>= 3.0', '< 4.1'] | ||
5 | + spec.add_dependency 'delayed_job', ['>= 3.0', '< 4.1'] | ||
6 | + spec.authors = ["Brian Ryckbost", "Matt Griffin", "Erik Michaels-Ober"] | ||
7 | + spec.description = 'ActiveRecord backend for Delayed::Job, originally authored by Tobias Lütke' | ||
8 | + spec.email = ['bryckbost@gmail.com', 'matt@griffinonline.org', 'sferik@gmail.com'] | ||
9 | + spec.files = %w(CONTRIBUTING.md LICENSE.md README.md Rakefile delayed_job_active_record.gemspec) | ||
10 | + spec.files += Dir.glob("lib/**/*.rb") | ||
11 | + spec.files += Dir.glob("spec/**/*") | ||
12 | + spec.homepage = 'http://github.com/collectiveidea/delayed_job_active_record' | ||
13 | + spec.licenses = ['MIT'] | ||
14 | + spec.name = 'delayed_job_active_record' | ||
15 | + spec.require_paths = ['lib'] | ||
16 | + spec.summary = 'ActiveRecord backend for DelayedJob' | ||
17 | + spec.test_files = Dir.glob("spec/**/*") | ||
18 | + spec.version = '4.0.0' | ||
19 | +end |
vendor/plugins/delayed_job_active_record/gemfiles/mysql/3-0.gemfile
0 → 100644
@@ -0,0 +1,23 @@ | @@ -0,0 +1,23 @@ | ||
1 | +source 'https://rubygems.org' | ||
2 | + | ||
3 | +gem 'rake' | ||
4 | + | ||
5 | +group :test do | ||
6 | + platforms :jruby do | ||
7 | + gem 'activerecord-jdbc-adapter' | ||
8 | + gem 'activerecord-jdbcmysql-adapter' | ||
9 | + gem 'jdbc-mysql' | ||
10 | + end | ||
11 | + | ||
12 | + platforms :ruby, :mswin, :mingw do | ||
13 | + gem 'mysql', '~> 2.8.1' | ||
14 | + end | ||
15 | + | ||
16 | + gem 'coveralls', :require => false | ||
17 | + gem 'rspec', '>= 2.11' | ||
18 | + gem 'simplecov', :require => false | ||
19 | + | ||
20 | + gem 'activerecord', "~> 3.0.0" | ||
21 | +end | ||
22 | + | ||
23 | +gemspec :path => "../../" |
vendor/plugins/delayed_job_active_record/gemfiles/mysql/3-1.gemfile
0 → 100644
@@ -0,0 +1,23 @@ | @@ -0,0 +1,23 @@ | ||
1 | +source 'https://rubygems.org' | ||
2 | + | ||
3 | +gem 'rake' | ||
4 | + | ||
5 | +group :test do | ||
6 | + platforms :jruby do | ||
7 | + gem 'activerecord-jdbc-adapter' | ||
8 | + gem 'activerecord-jdbcmysql-adapter' | ||
9 | + gem 'jdbc-mysql' | ||
10 | + end | ||
11 | + | ||
12 | + platforms :ruby, :mswin, :mingw do | ||
13 | + gem 'mysql', '~> 2.8.1' | ||
14 | + end | ||
15 | + | ||
16 | + gem 'coveralls', :require => false | ||
17 | + gem 'rspec', '>= 2.11' | ||
18 | + gem 'simplecov', :require => false | ||
19 | + | ||
20 | + gem 'activerecord', "~> 3.1.0" | ||
21 | +end | ||
22 | + | ||
23 | +gemspec :path => "../../" |
vendor/plugins/delayed_job_active_record/gemfiles/mysql/3-2.gemfile
0 → 100644
@@ -0,0 +1,23 @@ | @@ -0,0 +1,23 @@ | ||
1 | +source 'https://rubygems.org' | ||
2 | + | ||
3 | +gem 'rake' | ||
4 | + | ||
5 | +group :test do | ||
6 | + platforms :jruby do | ||
7 | + gem 'activerecord-jdbc-adapter' | ||
8 | + gem 'activerecord-jdbcmysql-adapter' | ||
9 | + gem 'jdbc-mysql' | ||
10 | + end | ||
11 | + | ||
12 | + platforms :ruby, :mswin, :mingw do | ||
13 | + gem 'mysql', '~> 2.8.1' | ||
14 | + end | ||
15 | + | ||
16 | + gem 'coveralls', :require => false | ||
17 | + gem 'rspec', '>= 2.11' | ||
18 | + gem 'simplecov', :require => false | ||
19 | + | ||
20 | + gem 'activerecord', "~> 3.2.0" | ||
21 | +end | ||
22 | + | ||
23 | +gemspec :path => "../../" |
vendor/plugins/delayed_job_active_record/gemfiles/mysql/4-0.gemfile
0 → 100644
@@ -0,0 +1,25 @@ | @@ -0,0 +1,25 @@ | ||
1 | +source 'https://rubygems.org' | ||
2 | + | ||
3 | +gem 'rake' | ||
4 | + | ||
5 | +group :test do | ||
6 | + platforms :jruby do | ||
7 | + gem 'activerecord-jdbc-adapter' | ||
8 | + gem 'activerecord-jdbcmysql-adapter' | ||
9 | + gem 'jdbc-mysql' | ||
10 | + end | ||
11 | + | ||
12 | + platforms :ruby, :mswin, :mingw do | ||
13 | + gem 'mysql', '~> 2.9' | ||
14 | + end | ||
15 | + | ||
16 | + gem 'coveralls', :require => false | ||
17 | + gem 'rspec', '>= 2.11' | ||
18 | + gem 'simplecov', :require => false | ||
19 | + | ||
20 | + gem 'activerecord', "~> 4.0.0.beta" | ||
21 | + | ||
22 | + gem 'delayed_job', "~> 4.0.0.beta" | ||
23 | +end | ||
24 | + | ||
25 | +gemspec :path => "../../" |
vendor/plugins/delayed_job_active_record/gemfiles/postgresql/3-0.gemfile
0 → 100644
@@ -0,0 +1,23 @@ | @@ -0,0 +1,23 @@ | ||
1 | +source 'https://rubygems.org' | ||
2 | + | ||
3 | +gem 'rake' | ||
4 | + | ||
5 | +group :test do | ||
6 | + platforms :jruby do | ||
7 | + gem 'activerecord-jdbc-adapter' | ||
8 | + gem 'activerecord-jdbcpostgresql-adapter' | ||
9 | + gem 'jdbc-postgres' | ||
10 | + end | ||
11 | + | ||
12 | + platforms :ruby, :mswin, :mingw do | ||
13 | + gem 'pg' | ||
14 | + end | ||
15 | + | ||
16 | + gem 'coveralls', :require => false | ||
17 | + gem 'rspec', '>= 2.11' | ||
18 | + gem 'simplecov', :require => false | ||
19 | + | ||
20 | + gem 'activerecord', "~> 3.0.0" | ||
21 | +end | ||
22 | + | ||
23 | +gemspec :path => "../../" |
vendor/plugins/delayed_job_active_record/gemfiles/postgresql/3-1.gemfile
0 → 100644
@@ -0,0 +1,23 @@ | @@ -0,0 +1,23 @@ | ||
1 | +source 'https://rubygems.org' | ||
2 | + | ||
3 | +gem 'rake' | ||
4 | + | ||
5 | +group :test do | ||
6 | + platforms :jruby do | ||
7 | + gem 'activerecord-jdbc-adapter' | ||
8 | + gem 'activerecord-jdbcpostgresql-adapter' | ||
9 | + gem 'jdbc-postgres' | ||
10 | + end | ||
11 | + | ||
12 | + platforms :ruby, :mswin, :mingw do | ||
13 | + gem 'pg' | ||
14 | + end | ||
15 | + | ||
16 | + gem 'coveralls', :require => false | ||
17 | + gem 'rspec', '>= 2.11' | ||
18 | + gem 'simplecov', :require => false | ||
19 | + | ||
20 | + gem 'activerecord', "~> 3.1.0" | ||
21 | +end | ||
22 | + | ||
23 | +gemspec :path => "../../" |
vendor/plugins/delayed_job_active_record/gemfiles/postgresql/3-2.gemfile
0 → 100644
@@ -0,0 +1,23 @@ | @@ -0,0 +1,23 @@ | ||
1 | +source 'https://rubygems.org' | ||
2 | + | ||
3 | +gem 'rake' | ||
4 | + | ||
5 | +group :test do | ||
6 | + platforms :jruby do | ||
7 | + gem 'activerecord-jdbc-adapter' | ||
8 | + gem 'activerecord-jdbcpostgresql-adapter' | ||
9 | + gem 'jdbc-postgres' | ||
10 | + end | ||
11 | + | ||
12 | + platforms :ruby, :mswin, :mingw do | ||
13 | + gem 'pg' | ||
14 | + end | ||
15 | + | ||
16 | + gem 'coveralls', :require => false | ||
17 | + gem 'rspec', '>= 2.11' | ||
18 | + gem 'simplecov', :require => false | ||
19 | + | ||
20 | + gem 'activerecord', "~> 3.2.0" | ||
21 | +end | ||
22 | + | ||
23 | +gemspec :path => "../../" |
vendor/plugins/delayed_job_active_record/gemfiles/postgresql/4-0.gemfile
0 → 100644
@@ -0,0 +1,25 @@ | @@ -0,0 +1,25 @@ | ||
1 | +source 'https://rubygems.org' | ||
2 | + | ||
3 | +gem 'rake' | ||
4 | + | ||
5 | +group :test do | ||
6 | + platforms :jruby do | ||
7 | + gem 'activerecord-jdbc-adapter' | ||
8 | + gem 'activerecord-jdbcpostgresql-adapter' | ||
9 | + gem 'jdbc-postgres' | ||
10 | + end | ||
11 | + | ||
12 | + platforms :ruby, :mswin, :mingw do | ||
13 | + gem 'pg' | ||
14 | + end | ||
15 | + | ||
16 | + gem 'coveralls', :require => false | ||
17 | + gem 'rspec', '>= 2.11' | ||
18 | + gem 'simplecov', :require => false | ||
19 | + | ||
20 | + gem 'activerecord', "~> 4.0.0.beta" | ||
21 | + | ||
22 | + gem 'delayed_job', "~> 4.0.0.beta" | ||
23 | +end | ||
24 | + | ||
25 | +gemspec :path => "../../" |
vendor/plugins/delayed_job_active_record/gemfiles/sqlite3/3-0.gemfile
0 → 100644
@@ -0,0 +1,23 @@ | @@ -0,0 +1,23 @@ | ||
1 | +source 'https://rubygems.org' | ||
2 | + | ||
3 | +gem 'rake' | ||
4 | + | ||
5 | +group :test do | ||
6 | + platforms :jruby do | ||
7 | + gem 'jruby-openssl' | ||
8 | + gem 'activerecord-jdbc-adapter' | ||
9 | + gem 'activerecord-jdbcsqlite3-adapter' | ||
10 | + end | ||
11 | + | ||
12 | + platforms :ruby, :mswin, :mingw do | ||
13 | + gem 'sqlite3' | ||
14 | + end | ||
15 | + | ||
16 | + gem 'coveralls', :require => false | ||
17 | + gem 'rspec', '>= 2.11' | ||
18 | + gem 'simplecov', :require => false | ||
19 | + | ||
20 | + gem 'activerecord', "~> 3.0.0" | ||
21 | +end | ||
22 | + | ||
23 | +gemspec :path => "../../" |
vendor/plugins/delayed_job_active_record/gemfiles/sqlite3/3-1.gemfile
0 → 100644
@@ -0,0 +1,23 @@ | @@ -0,0 +1,23 @@ | ||
1 | +source 'https://rubygems.org' | ||
2 | + | ||
3 | +gem 'rake' | ||
4 | + | ||
5 | +group :test do | ||
6 | + platforms :jruby do | ||
7 | + gem 'jruby-openssl' | ||
8 | + gem 'activerecord-jdbc-adapter' | ||
9 | + gem 'activerecord-jdbcsqlite3-adapter' | ||
10 | + end | ||
11 | + | ||
12 | + platforms :ruby, :mswin, :mingw do | ||
13 | + gem 'sqlite3' | ||
14 | + end | ||
15 | + | ||
16 | + gem 'coveralls', :require => false | ||
17 | + gem 'rspec', '>= 2.11' | ||
18 | + gem 'simplecov', :require => false | ||
19 | + | ||
20 | + gem 'activerecord', "~> 3.1.0" | ||
21 | +end | ||
22 | + | ||
23 | +gemspec :path => "../../" |
vendor/plugins/delayed_job_active_record/gemfiles/sqlite3/3-2.gemfile
0 → 100644
@@ -0,0 +1,23 @@ | @@ -0,0 +1,23 @@ | ||
1 | +source 'https://rubygems.org' | ||
2 | + | ||
3 | +gem 'rake' | ||
4 | + | ||
5 | +group :test do | ||
6 | + platforms :jruby do | ||
7 | + gem 'jruby-openssl' | ||
8 | + gem 'activerecord-jdbc-adapter' | ||
9 | + gem 'activerecord-jdbcsqlite3-adapter' | ||
10 | + end | ||
11 | + | ||
12 | + platforms :ruby, :mswin, :mingw do | ||
13 | + gem 'sqlite3' | ||
14 | + end | ||
15 | + | ||
16 | + gem 'coveralls', :require => false | ||
17 | + gem 'rspec', '>= 2.11' | ||
18 | + gem 'simplecov', :require => false | ||
19 | + | ||
20 | + gem 'activerecord', "~> 3.2.0" | ||
21 | +end | ||
22 | + | ||
23 | +gemspec :path => "../../" |
vendor/plugins/delayed_job_active_record/gemfiles/sqlite3/4-0-protected_attributes.gemfile
0 → 100644
@@ -0,0 +1,26 @@ | @@ -0,0 +1,26 @@ | ||
1 | +source 'https://rubygems.org' | ||
2 | + | ||
3 | +gem 'rake' | ||
4 | + | ||
5 | +group :test do | ||
6 | + platforms :jruby do | ||
7 | + gem 'jruby-openssl' | ||
8 | + gem 'activerecord-jdbc-adapter' | ||
9 | + gem 'activerecord-jdbcsqlite3-adapter' | ||
10 | + end | ||
11 | + | ||
12 | + platforms :ruby, :mswin, :mingw do | ||
13 | + gem 'sqlite3' | ||
14 | + end | ||
15 | + | ||
16 | + gem 'coveralls', :require => false | ||
17 | + gem 'rspec', '>= 2.11', :require => false | ||
18 | + gem 'simplecov', :require => false | ||
19 | + | ||
20 | + gem 'activerecord', "~> 4.0.0.beta" | ||
21 | + gem 'protected_attributes' | ||
22 | + | ||
23 | + gem 'delayed_job', "~> 4.0.0.beta", :require => false | ||
24 | +end | ||
25 | + | ||
26 | +gemspec :path => "../../" |
vendor/plugins/delayed_job_active_record/gemfiles/sqlite3/4-0.gemfile
0 → 100644
@@ -0,0 +1,25 @@ | @@ -0,0 +1,25 @@ | ||
1 | +source 'https://rubygems.org' | ||
2 | + | ||
3 | +gem 'rake' | ||
4 | + | ||
5 | +group :test do | ||
6 | + platforms :jruby do | ||
7 | + gem 'jruby-openssl' | ||
8 | + gem 'activerecord-jdbc-adapter' | ||
9 | + gem 'activerecord-jdbcsqlite3-adapter' | ||
10 | + end | ||
11 | + | ||
12 | + platforms :ruby, :mswin, :mingw do | ||
13 | + gem 'sqlite3' | ||
14 | + end | ||
15 | + | ||
16 | + gem 'coveralls', :require => false | ||
17 | + gem 'rspec', '>= 2.11' | ||
18 | + gem 'simplecov', :require => false | ||
19 | + | ||
20 | + gem 'activerecord', "~> 4.0.0.beta" | ||
21 | + | ||
22 | + gem 'delayed_job', "~> 4.0.0.beta" | ||
23 | +end | ||
24 | + | ||
25 | +gemspec :path => "../../" |
vendor/plugins/delayed_job_active_record/lib/delayed/backend/active_record.rb
0 → 100644
@@ -0,0 +1,111 @@ | @@ -0,0 +1,111 @@ | ||
1 | +require 'active_record/version' | ||
2 | +module Delayed | ||
3 | + module Backend | ||
4 | + module ActiveRecord | ||
5 | + # A job object that is persisted to the database. | ||
6 | + # Contains the work object as a YAML field. | ||
7 | + class Job < ::ActiveRecord::Base | ||
8 | + include Delayed::Backend::Base | ||
9 | + | ||
10 | + if ::ActiveRecord::VERSION::MAJOR < 4 || defined?(::ActiveRecord::MassAssignmentSecurity) | ||
11 | + attr_accessible :priority, :run_at, :queue, :payload_object, | ||
12 | + :failed_at, :locked_at, :locked_by, :handler | ||
13 | + end | ||
14 | + | ||
15 | + scope :by_priority, lambda { order('priority ASC, run_at ASC') } | ||
16 | + | ||
17 | + before_save :set_default_run_at | ||
18 | + | ||
19 | + def self.set_delayed_job_table_name | ||
20 | + delayed_job_table_name = "#{::ActiveRecord::Base.table_name_prefix}delayed_jobs" | ||
21 | + self.table_name = delayed_job_table_name | ||
22 | + end | ||
23 | + | ||
24 | + self.set_delayed_job_table_name | ||
25 | + | ||
26 | + def self.ready_to_run(worker_name, max_run_time) | ||
27 | + where('(run_at <= ? AND (locked_at IS NULL OR locked_at < ?) OR locked_by = ?) AND failed_at IS NULL', db_time_now, db_time_now - max_run_time, worker_name) | ||
28 | + end | ||
29 | + | ||
30 | + def self.before_fork | ||
31 | + ::ActiveRecord::Base.clear_all_connections! | ||
32 | + end | ||
33 | + | ||
34 | + def self.after_fork | ||
35 | + ::ActiveRecord::Base.establish_connection | ||
36 | + end | ||
37 | + | ||
38 | + # When a worker is exiting, make sure we don't have any locked jobs. | ||
39 | + def self.clear_locks!(worker_name) | ||
40 | + where(:locked_by => worker_name).update_all(:locked_by => nil, :locked_at => nil) | ||
41 | + end | ||
42 | + | ||
43 | + def self.reserve(worker, max_run_time = Worker.max_run_time) | ||
44 | + # scope to filter to records that are "ready to run" | ||
45 | + ready_scope = self.ready_to_run(worker.name, max_run_time) | ||
46 | + | ||
47 | + # scope to filter to the single next eligible job | ||
48 | + ready_scope = ready_scope.where('priority >= ?', Worker.min_priority) if Worker.min_priority | ||
49 | + ready_scope = ready_scope.where('priority <= ?', Worker.max_priority) if Worker.max_priority | ||
50 | + ready_scope = ready_scope.where(:queue => Worker.queues) if Worker.queues.any? | ||
51 | + ready_scope = ready_scope.by_priority | ||
52 | + | ||
53 | + now = self.db_time_now | ||
54 | + | ||
55 | + # Optimizations for faster lookups on some common databases | ||
56 | + case self.connection.adapter_name | ||
57 | + when "PostgreSQL" | ||
58 | + # Custom SQL required for PostgreSQL because postgres does not support UPDATE...LIMIT | ||
59 | + # This locks the single record 'FOR UPDATE' in the subquery (http://www.postgresql.org/docs/9.0/static/sql-select.html#SQL-FOR-UPDATE-SHARE) | ||
60 | + # Note: active_record would attempt to generate UPDATE...LIMIT like sql for postgres if we use a .limit() filter, but it would not use | ||
61 | + # 'FOR UPDATE' and we would have many locking conflicts | ||
62 | + quoted_table_name = self.connection.quote_table_name(self.table_name) | ||
63 | + subquery_sql = ready_scope.limit(1).lock(true).select('id').to_sql | ||
64 | + reserved = self.find_by_sql(["UPDATE #{quoted_table_name} SET locked_at = ?, locked_by = ? WHERE id IN (#{subquery_sql}) RETURNING *", now, worker.name]) | ||
65 | + reserved[0] | ||
66 | + when "MySQL", "Mysql2" | ||
67 | + # This works on MySQL and possibly some other DBs that support UPDATE...LIMIT. It uses separate queries to lock and return the job | ||
68 | + count = ready_scope.limit(1).update_all(:locked_at => now, :locked_by => worker.name) | ||
69 | + return nil if count == 0 | ||
70 | + self.where(:locked_at => now, :locked_by => worker.name, :failed_at => nil).first | ||
71 | + when "MSSQL", "Teradata" | ||
72 | + # The MSSQL driver doesn't generate a limit clause when update_all is called directly | ||
73 | + subsubquery_sql = ready_scope.limit(1).to_sql | ||
74 | + # select("id") doesn't generate a subquery, so force a subquery | ||
75 | + subquery_sql = "SELECT id FROM (#{subsubquery_sql}) AS x" | ||
76 | + quoted_table_name = self.connection.quote_table_name(self.table_name) | ||
77 | + sql = ["UPDATE #{quoted_table_name} SET locked_at = ?, locked_by = ? WHERE id IN (#{subquery_sql})", now, worker.name] | ||
78 | + count = self.connection.execute(sanitize_sql(sql)) | ||
79 | + return nil if count == 0 | ||
80 | + # MSSQL JDBC doesn't support OUTPUT INSERTED.* for returning a result set, so query locked row | ||
81 | + self.where(:locked_at => now, :locked_by => worker.name, :failed_at => nil).first | ||
82 | + else | ||
83 | + # This is our old fashion, tried and true, but slower lookup | ||
84 | + ready_scope.limit(worker.read_ahead).detect do |job| | ||
85 | + count = ready_scope.where(:id => job.id).update_all(:locked_at => now, :locked_by => worker.name) | ||
86 | + count == 1 && job.reload | ||
87 | + end | ||
88 | + end | ||
89 | + end | ||
90 | + | ||
91 | + # Get the current time (GMT or local depending on DB) | ||
92 | + # Note: This does not ping the DB to get the time, so all your clients | ||
93 | + # must have syncronized clocks. | ||
94 | + def self.db_time_now | ||
95 | + if Time.zone | ||
96 | + Time.zone.now | ||
97 | + elsif ::ActiveRecord::Base.default_timezone == :utc | ||
98 | + Time.now.utc | ||
99 | + else | ||
100 | + Time.now | ||
101 | + end | ||
102 | + end | ||
103 | + | ||
104 | + def reload(*args) | ||
105 | + reset | ||
106 | + super | ||
107 | + end | ||
108 | + end | ||
109 | + end | ||
110 | + end | ||
111 | +end |
vendor/plugins/delayed_job_active_record/lib/delayed_job_active_record.rb
0 → 100644
vendor/plugins/delayed_job_active_record/lib/generators/delayed_job/active_record_generator.rb
0 → 100644
@@ -0,0 +1,22 @@ | @@ -0,0 +1,22 @@ | ||
1 | +require 'generators/delayed_job/delayed_job_generator' | ||
2 | +require 'generators/delayed_job/next_migration_version' | ||
3 | +require 'rails/generators/migration' | ||
4 | +require 'rails/generators/active_record' | ||
5 | + | ||
6 | +# Extend the DelayedJobGenerator so that it creates an AR migration | ||
7 | +module DelayedJob | ||
8 | + class ActiveRecordGenerator < ::DelayedJobGenerator | ||
9 | + include Rails::Generators::Migration | ||
10 | + extend NextMigrationVersion | ||
11 | + | ||
12 | + self.source_paths << File.join(File.dirname(__FILE__), 'templates') | ||
13 | + | ||
14 | + def create_migration_file | ||
15 | + migration_template 'migration.rb', 'db/migrate/create_delayed_jobs.rb' | ||
16 | + end | ||
17 | + | ||
18 | + def self.next_migration_number dirname | ||
19 | + ActiveRecord::Generators::Base.next_migration_number dirname | ||
20 | + end | ||
21 | + end | ||
22 | +end |
vendor/plugins/delayed_job_active_record/lib/generators/delayed_job/next_migration_version.rb
0 → 100644
@@ -0,0 +1,14 @@ | @@ -0,0 +1,14 @@ | ||
1 | +module DelayedJob | ||
2 | + module NextMigrationVersion | ||
3 | + # while methods have moved around this has been the implementation | ||
4 | + # since ActiveRecord 3.0 | ||
5 | + def next_migration_number(dirname) | ||
6 | + next_migration_number = current_migration_number(dirname) + 1 | ||
7 | + if ActiveRecord::Base.timestamped_migrations | ||
8 | + [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max | ||
9 | + else | ||
10 | + "%.3d" % next_migration_number | ||
11 | + end | ||
12 | + end | ||
13 | + end | ||
14 | +end |
vendor/plugins/delayed_job_active_record/lib/generators/delayed_job/templates/migration.rb
0 → 100644
@@ -0,0 +1,22 @@ | @@ -0,0 +1,22 @@ | ||
1 | +class CreateDelayedJobs < ActiveRecord::Migration | ||
2 | + def self.up | ||
3 | + create_table :delayed_jobs, :force => true do |table| | ||
4 | + table.integer :priority, :default => 0, :null => false # Allows some jobs to jump to the front of the queue | ||
5 | + table.integer :attempts, :default => 0, :null => false # Provides for retries, but still fail eventually. | ||
6 | + table.text :handler, :null => false # YAML-encoded string of the object that will do work | ||
7 | + table.text :last_error # reason for last failure (See Note below) | ||
8 | + table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future. | ||
9 | + table.datetime :locked_at # Set when a client is working on this object | ||
10 | + table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead) | ||
11 | + table.string :locked_by # Who is working on this object (if locked) | ||
12 | + table.string :queue # The name of the queue this job is in | ||
13 | + table.timestamps | ||
14 | + end | ||
15 | + | ||
16 | + add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority' | ||
17 | + end | ||
18 | + | ||
19 | + def self.down | ||
20 | + drop_table :delayed_jobs | ||
21 | + end | ||
22 | +end |
vendor/plugins/delayed_job_active_record/lib/generators/delayed_job/templates/upgrade_migration.rb
0 → 100644
vendor/plugins/delayed_job_active_record/lib/generators/delayed_job/upgrade_generator.rb
0 → 100644
@@ -0,0 +1,22 @@ | @@ -0,0 +1,22 @@ | ||
1 | +require 'generators/delayed_job/delayed_job_generator' | ||
2 | +require 'generators/delayed_job/next_migration_version' | ||
3 | +require 'rails/generators/migration' | ||
4 | +require 'rails/generators/active_record' | ||
5 | + | ||
6 | +# Extend the DelayedJobGenerator so that it creates an AR migration | ||
7 | +module DelayedJob | ||
8 | + class UpgradeGenerator < ::DelayedJobGenerator | ||
9 | + include Rails::Generators::Migration | ||
10 | + extend NextMigrationVersion | ||
11 | + | ||
12 | + self.source_paths << File.join(File.dirname(__FILE__), 'templates') | ||
13 | + | ||
14 | + def create_migration_file | ||
15 | + migration_template 'upgrade_migration.rb', 'db/migrate/add_queue_to_delayed_jobs.rb' | ||
16 | + end | ||
17 | + | ||
18 | + def self.next_migration_number dirname | ||
19 | + ActiveRecord::Generators::Base.next_migration_number dirname | ||
20 | + end | ||
21 | + end | ||
22 | +end |
vendor/plugins/delayed_job_active_record/spec/database.yml
0 → 100644
vendor/plugins/delayed_job_active_record/spec/delayed/backend/active_record_spec.rb
0 → 100644
@@ -0,0 +1,81 @@ | @@ -0,0 +1,81 @@ | ||
1 | +require 'helper' | ||
2 | +require 'delayed/backend/active_record' | ||
3 | + | ||
4 | +describe Delayed::Backend::ActiveRecord::Job do | ||
5 | + it_behaves_like 'a delayed_job backend' | ||
6 | + | ||
7 | + context "db_time_now" do | ||
8 | + after do | ||
9 | + Time.zone = nil | ||
10 | + ActiveRecord::Base.default_timezone = :local | ||
11 | + end | ||
12 | + | ||
13 | + it "returns time in current time zone if set" do | ||
14 | + Time.zone = 'Eastern Time (US & Canada)' | ||
15 | + expect(%(EST EDT)).to include(Delayed::Job.db_time_now.zone) | ||
16 | + end | ||
17 | + | ||
18 | + it "returns UTC time if that is the AR default" do | ||
19 | + Time.zone = nil | ||
20 | + ActiveRecord::Base.default_timezone = :utc | ||
21 | + expect(Delayed::Backend::ActiveRecord::Job.db_time_now.zone).to eq 'UTC' | ||
22 | + end | ||
23 | + | ||
24 | + it "returns local time if that is the AR default" do | ||
25 | + Time.zone = 'Central Time (US & Canada)' | ||
26 | + ActiveRecord::Base.default_timezone = :local | ||
27 | + expect(%w(CST CDT)).to include(Delayed::Backend::ActiveRecord::Job.db_time_now.zone) | ||
28 | + end | ||
29 | + end | ||
30 | + | ||
31 | + describe "after_fork" do | ||
32 | + it "calls reconnect on the connection" do | ||
33 | + ActiveRecord::Base.should_receive(:establish_connection) | ||
34 | + Delayed::Backend::ActiveRecord::Job.after_fork | ||
35 | + end | ||
36 | + end | ||
37 | + | ||
38 | + describe "enqueue" do | ||
39 | + it "allows enqueue hook to modify job at DB level" do | ||
40 | + later = described_class.db_time_now + 20.minutes | ||
41 | + job = Delayed::Backend::ActiveRecord::Job.enqueue :payload_object => EnqueueJobMod.new | ||
42 | + expect(Delayed::Backend::ActiveRecord::Job.find(job.id).run_at).to be_within(1).of(later) | ||
43 | + end | ||
44 | + end | ||
45 | + | ||
46 | + if ::ActiveRecord::VERSION::MAJOR < 4 || defined?(::ActiveRecord::MassAssignmentSecurity) | ||
47 | + context "ActiveRecord::Base.send(:attr_accessible, nil)" do | ||
48 | + before do | ||
49 | + Delayed::Backend::ActiveRecord::Job.send(:attr_accessible, nil) | ||
50 | + end | ||
51 | + | ||
52 | + after do | ||
53 | + Delayed::Backend::ActiveRecord::Job.send(:attr_accessible, *Delayed::Backend::ActiveRecord::Job.new.attributes.keys) | ||
54 | + end | ||
55 | + | ||
56 | + it "is still accessible" do | ||
57 | + job = Delayed::Backend::ActiveRecord::Job.enqueue :payload_object => EnqueueJobMod.new | ||
58 | + expect(Delayed::Backend::ActiveRecord::Job.find(job.id).handler).to_not be_blank | ||
59 | + end | ||
60 | + end | ||
61 | + end | ||
62 | + | ||
63 | + context "ActiveRecord::Base.table_name_prefix" do | ||
64 | + it "when prefix is not set, use 'delayed_jobs' as table name" do | ||
65 | + ::ActiveRecord::Base.table_name_prefix = nil | ||
66 | + Delayed::Backend::ActiveRecord::Job.set_delayed_job_table_name | ||
67 | + | ||
68 | + expect(Delayed::Backend::ActiveRecord::Job.table_name).to eq 'delayed_jobs' | ||
69 | + end | ||
70 | + | ||
71 | + it "when prefix is set, prepend it before default table name" do | ||
72 | + ::ActiveRecord::Base.table_name_prefix = 'custom_' | ||
73 | + Delayed::Backend::ActiveRecord::Job.set_delayed_job_table_name | ||
74 | + | ||
75 | + expect(Delayed::Backend::ActiveRecord::Job.table_name).to eq 'custom_delayed_jobs' | ||
76 | + | ||
77 | + ::ActiveRecord::Base.table_name_prefix = nil | ||
78 | + Delayed::Backend::ActiveRecord::Job.set_delayed_job_table_name | ||
79 | + end | ||
80 | + end | ||
81 | +end |
vendor/plugins/delayed_job_active_record/spec/delayed/serialization/active_record_spec.rb
0 → 100644
@@ -0,0 +1,15 @@ | @@ -0,0 +1,15 @@ | ||
1 | +require 'helper' | ||
2 | + | ||
3 | +describe ActiveRecord do | ||
4 | + it "loads classes with non-default primary key" do | ||
5 | + expect { | ||
6 | + YAML.load(Story.create.to_yaml) | ||
7 | + }.not_to raise_error | ||
8 | + end | ||
9 | + | ||
10 | + it "loads classes even if not in default scope" do | ||
11 | + expect { | ||
12 | + YAML.load(Story.create(:scoped => false).to_yaml) | ||
13 | + }.not_to raise_error | ||
14 | + end | ||
15 | +end |
@@ -0,0 +1,69 @@ | @@ -0,0 +1,69 @@ | ||
1 | +require 'simplecov' | ||
2 | +require 'coveralls' | ||
3 | + | ||
4 | +SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ | ||
5 | + SimpleCov::Formatter::HTMLFormatter, | ||
6 | + Coveralls::SimpleCov::Formatter | ||
7 | +] | ||
8 | +SimpleCov.start | ||
9 | + | ||
10 | +require 'logger' | ||
11 | +require 'rspec' | ||
12 | + | ||
13 | +begin | ||
14 | + require 'protected_attributes' | ||
15 | +rescue LoadError | ||
16 | +end | ||
17 | +require 'delayed_job_active_record' | ||
18 | +require 'delayed/backend/shared_spec' | ||
19 | + | ||
20 | +Delayed::Worker.logger = Logger.new('/tmp/dj.log') | ||
21 | +ENV['RAILS_ENV'] = 'test' | ||
22 | + | ||
23 | +db_adapter, gemfile = ENV["ADAPTER"], ENV["BUNDLE_GEMFILE"] | ||
24 | +db_adapter ||= gemfile && gemfile[%r(gemfiles/(.*?)/)] && $1 | ||
25 | +db_adapter ||= 'sqlite3' | ||
26 | + | ||
27 | +config = YAML.load(File.read('spec/database.yml')) | ||
28 | +ActiveRecord::Base.establish_connection config[db_adapter] | ||
29 | +ActiveRecord::Base.logger = Delayed::Worker.logger | ||
30 | +ActiveRecord::Migration.verbose = false | ||
31 | + | ||
32 | +ActiveRecord::Schema.define do | ||
33 | + create_table :delayed_jobs, :force => true do |table| | ||
34 | + table.integer :priority, :default => 0 | ||
35 | + table.integer :attempts, :default => 0 | ||
36 | + table.text :handler | ||
37 | + table.text :last_error | ||
38 | + table.datetime :run_at | ||
39 | + table.datetime :locked_at | ||
40 | + table.datetime :failed_at | ||
41 | + table.string :locked_by | ||
42 | + table.string :queue | ||
43 | + table.timestamps | ||
44 | + end | ||
45 | + | ||
46 | + add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority' | ||
47 | + | ||
48 | + create_table :stories, :primary_key => :story_id, :force => true do |table| | ||
49 | + table.string :text | ||
50 | + table.boolean :scoped, :default => true | ||
51 | + end | ||
52 | +end | ||
53 | + | ||
54 | +# Purely useful for test cases... | ||
55 | +class Story < ActiveRecord::Base | ||
56 | + if ::ActiveRecord::VERSION::MAJOR < 4 && ActiveRecord::VERSION::MINOR < 2 | ||
57 | + set_primary_key :story_id | ||
58 | + else | ||
59 | + self.primary_key = :story_id | ||
60 | + end | ||
61 | + def tell; text; end | ||
62 | + def whatever(n, _); tell*n; end | ||
63 | + default_scope { where(:scoped => true) } | ||
64 | + | ||
65 | + handle_asynchronously :whatever | ||
66 | +end | ||
67 | + | ||
68 | +# Add this directory so the ActiveSupport autoloading works | ||
69 | +ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__) |