activejob-retry 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -3
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +116 -126
- data/README.md +24 -22
- data/Rakefile +10 -9
- data/activejob-retry.gemspec +1 -1
- data/lib/active_job/retry.rb +75 -65
- data/lib/active_job/retry/constant_backoff_strategy.rb +1 -1
- data/lib/active_job/retry/constant_options_validator.rb +1 -1
- data/lib/active_job/retry/errors.rb +1 -1
- data/lib/active_job/retry/exponential_backoff_strategy.rb +1 -1
- data/lib/active_job/retry/exponential_options_validator.rb +1 -1
- data/lib/active_job/retry/variable_backoff_strategy.rb +1 -1
- data/lib/active_job/retry/variable_options_validator.rb +1 -1
- data/lib/active_job/retry/version.rb +2 -2
- data/spec/retry_spec.rb +41 -31
- data/spec/spec_helper.rb +1 -1
- data/test/jobs/callback_job.rb +1 -1
- data/test/jobs/gid_job.rb +1 -1
- data/test/jobs/hello_job.rb +1 -1
- data/test/jobs/logging_job.rb +1 -1
- data/test/jobs/nested_job.rb +1 -1
- data/test/jobs/rescue_job.rb +1 -2
- data/test/support/integration/dummy_app_template.rb +1 -2
- metadata +4 -5
- data/test/cases/argument_serialization_test.rb +0 -84
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d4ed8260c84b5c95a405c8cadb2a2b0e93ba5070
|
4
|
+
data.tar.gz: 1b7d2e89e1843ee73c6461374397a5e0450637c5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c03c64268ed4f9a40a0d35b178e96550d24ad6b03b7cc65a1e43440845d6ff39f78fda571e5e06dcc6b83b6a736d97c9449768abca698d2bfd20830cd03d99a
|
7
|
+
data.tar.gz: 64233f0b5a15269e040f5f9b306ab58cb27f437dd93fe610dc68f880b28f1758e878674aaa547479b9faac12c74820bec73714891afd743f5690f1c6db78ce5f
|
data/.travis.yml
CHANGED
@@ -9,10 +9,9 @@ matrix:
|
|
9
9
|
- rvm: rbx-2
|
10
10
|
|
11
11
|
rvm:
|
12
|
-
- 1.9.3
|
13
|
-
- 2.0.0
|
14
12
|
- 2.1
|
15
13
|
- 2.2
|
14
|
+
- 2.3.1
|
16
15
|
- jruby
|
17
16
|
- rbx-2
|
18
17
|
|
@@ -20,7 +19,6 @@ script:
|
|
20
19
|
- bundle exec rubocop
|
21
20
|
- bundle exec rspec spec
|
22
21
|
- bundle exec rake test
|
23
|
-
- bundle exec rake test:integration
|
24
22
|
|
25
23
|
services:
|
26
24
|
- redis
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
## 0.6.0 - May 18, 2016
|
2
|
+
|
3
|
+
- Change API usage to make improper use harder (by [@isaacseymour](https://github.com/isaacseymour))
|
4
|
+
|
1
5
|
## 0.5.1 - October 28, 2015
|
2
6
|
|
3
7
|
- Stop warning about QueueClassic - it supports delayed execution from 3.1+ (by [@senny](https://github.com/senny))
|
data/Gemfile.lock
CHANGED
@@ -1,189 +1,179 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
activejob-retry (0.
|
4
|
+
activejob-retry (0.6.0)
|
5
5
|
activejob (>= 4.2)
|
6
6
|
activesupport (>= 4.2)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
actionmailer (4.2.
|
12
|
-
actionpack (= 4.2.
|
13
|
-
actionview (= 4.2.
|
14
|
-
activejob (= 4.2.
|
11
|
+
actionmailer (4.2.6)
|
12
|
+
actionpack (= 4.2.6)
|
13
|
+
actionview (= 4.2.6)
|
14
|
+
activejob (= 4.2.6)
|
15
15
|
mail (~> 2.5, >= 2.5.4)
|
16
16
|
rails-dom-testing (~> 1.0, >= 1.0.5)
|
17
|
-
actionpack (4.2.
|
18
|
-
actionview (= 4.2.
|
19
|
-
activesupport (= 4.2.
|
20
|
-
rack (~> 1.6
|
17
|
+
actionpack (4.2.6)
|
18
|
+
actionview (= 4.2.6)
|
19
|
+
activesupport (= 4.2.6)
|
20
|
+
rack (~> 1.6)
|
21
21
|
rack-test (~> 0.6.2)
|
22
22
|
rails-dom-testing (~> 1.0, >= 1.0.5)
|
23
|
-
rails-html-sanitizer (~> 1.0, >= 1.0.
|
24
|
-
actionview (4.2.
|
25
|
-
activesupport (= 4.2.
|
23
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
24
|
+
actionview (4.2.6)
|
25
|
+
activesupport (= 4.2.6)
|
26
26
|
builder (~> 3.1)
|
27
27
|
erubis (~> 2.7.0)
|
28
28
|
rails-dom-testing (~> 1.0, >= 1.0.5)
|
29
|
-
rails-html-sanitizer (~> 1.0, >= 1.0.
|
30
|
-
activejob (4.2.
|
31
|
-
activesupport (= 4.2.
|
29
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
30
|
+
activejob (4.2.6)
|
31
|
+
activesupport (= 4.2.6)
|
32
32
|
globalid (>= 0.3.0)
|
33
|
-
activemodel (4.2.
|
34
|
-
activesupport (= 4.2.
|
33
|
+
activemodel (4.2.6)
|
34
|
+
activesupport (= 4.2.6)
|
35
35
|
builder (~> 3.1)
|
36
|
-
activerecord (4.2.
|
37
|
-
activemodel (= 4.2.
|
38
|
-
activesupport (= 4.2.
|
36
|
+
activerecord (4.2.6)
|
37
|
+
activemodel (= 4.2.6)
|
38
|
+
activesupport (= 4.2.6)
|
39
39
|
arel (~> 6.0)
|
40
|
-
activesupport (4.2.
|
40
|
+
activesupport (4.2.6)
|
41
41
|
i18n (~> 0.7)
|
42
42
|
json (~> 1.7, >= 1.7.7)
|
43
43
|
minitest (~> 5.1)
|
44
44
|
thread_safe (~> 0.3, >= 0.3.4)
|
45
45
|
tzinfo (~> 1.1)
|
46
|
-
arel (6.0.
|
47
|
-
ast (2.
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
dante (~> 0.1.5)
|
53
|
-
beaneater (0.3.3)
|
46
|
+
arel (6.0.3)
|
47
|
+
ast (2.2.0)
|
48
|
+
backburner (1.3.0)
|
49
|
+
beaneater (~> 1.0)
|
50
|
+
dante (> 0.1.5)
|
51
|
+
beaneater (1.0.0)
|
54
52
|
builder (3.2.2)
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
delayed_job (>= 3.0, < 4.1)
|
53
|
+
concurrent-ruby (1.0.2)
|
54
|
+
connection_pool (2.2.0)
|
55
|
+
dante (0.2.0)
|
56
|
+
delayed_job (4.1.2)
|
57
|
+
activesupport (>= 3.0, < 5.1)
|
58
|
+
delayed_job_active_record (4.1.1)
|
59
|
+
activerecord (>= 3.0, < 5.1)
|
60
|
+
delayed_job (>= 3.0, < 5)
|
64
61
|
diff-lcs (1.2.5)
|
65
62
|
erubis (2.7.0)
|
66
|
-
globalid (0.3.
|
63
|
+
globalid (0.3.6)
|
67
64
|
activesupport (>= 4.1.0)
|
68
|
-
hike (1.2.3)
|
69
|
-
hitimes (1.2.2)
|
70
65
|
i18n (0.7.0)
|
71
|
-
json (1.8.
|
72
|
-
loofah (2.0.
|
66
|
+
json (1.8.3)
|
67
|
+
loofah (2.0.3)
|
73
68
|
nokogiri (>= 1.5.9)
|
74
|
-
mail (2.6.
|
75
|
-
mime-types (>= 1.16, <
|
76
|
-
mime-types (
|
77
|
-
|
78
|
-
|
69
|
+
mail (2.6.4)
|
70
|
+
mime-types (>= 1.16, < 4)
|
71
|
+
mime-types (3.0)
|
72
|
+
mime-types-data (~> 3.2015)
|
73
|
+
mime-types-data (3.2016.0221)
|
74
|
+
mini_portile2 (2.0.0)
|
75
|
+
minitest (5.9.0)
|
79
76
|
mono_logger (1.1.0)
|
80
|
-
multi_json (1.
|
81
|
-
nokogiri (1.6.
|
82
|
-
|
83
|
-
parser (2.
|
84
|
-
ast (
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
rack (1.6.0)
|
77
|
+
multi_json (1.12.0)
|
78
|
+
nokogiri (1.6.7.2)
|
79
|
+
mini_portile2 (~> 2.0.0.rc2)
|
80
|
+
parser (2.3.1.0)
|
81
|
+
ast (~> 2.2)
|
82
|
+
pg (0.18.4)
|
83
|
+
powerpack (0.1.1)
|
84
|
+
que (0.11.5)
|
85
|
+
rack (1.6.4)
|
90
86
|
rack-protection (1.5.3)
|
91
87
|
rack
|
92
|
-
rack-test (0.6.
|
88
|
+
rack-test (0.6.3)
|
93
89
|
rack (>= 1.0)
|
94
|
-
rails (4.2.
|
95
|
-
actionmailer (= 4.2.
|
96
|
-
actionpack (= 4.2.
|
97
|
-
actionview (= 4.2.
|
98
|
-
activejob (= 4.2.
|
99
|
-
activemodel (= 4.2.
|
100
|
-
activerecord (= 4.2.
|
101
|
-
activesupport (= 4.2.
|
90
|
+
rails (4.2.6)
|
91
|
+
actionmailer (= 4.2.6)
|
92
|
+
actionpack (= 4.2.6)
|
93
|
+
actionview (= 4.2.6)
|
94
|
+
activejob (= 4.2.6)
|
95
|
+
activemodel (= 4.2.6)
|
96
|
+
activerecord (= 4.2.6)
|
97
|
+
activesupport (= 4.2.6)
|
102
98
|
bundler (>= 1.3.0, < 2.0)
|
103
|
-
railties (= 4.2.
|
99
|
+
railties (= 4.2.6)
|
104
100
|
sprockets-rails
|
105
101
|
rails-deprecated_sanitizer (1.0.3)
|
106
102
|
activesupport (>= 4.2.0.alpha)
|
107
|
-
rails-dom-testing (1.0.
|
103
|
+
rails-dom-testing (1.0.7)
|
108
104
|
activesupport (>= 4.2.0.beta, < 5.0)
|
109
105
|
nokogiri (~> 1.6.0)
|
110
106
|
rails-deprecated_sanitizer (>= 1.0.1)
|
111
|
-
rails-html-sanitizer (1.0.
|
107
|
+
rails-html-sanitizer (1.0.3)
|
112
108
|
loofah (~> 2.0)
|
113
|
-
railties (4.2.
|
114
|
-
actionpack (= 4.2.
|
115
|
-
activesupport (= 4.2.
|
109
|
+
railties (4.2.6)
|
110
|
+
actionpack (= 4.2.6)
|
111
|
+
activesupport (= 4.2.6)
|
116
112
|
rake (>= 0.8.7)
|
117
113
|
thor (>= 0.18.1, < 2.0)
|
118
|
-
rainbow (2.
|
119
|
-
rake (
|
120
|
-
redis (3.
|
121
|
-
redis-namespace (1.5.
|
114
|
+
rainbow (2.1.0)
|
115
|
+
rake (11.1.2)
|
116
|
+
redis (3.3.0)
|
117
|
+
redis-namespace (1.5.2)
|
122
118
|
redis (~> 3.0, >= 3.0.4)
|
123
|
-
resque (1.
|
119
|
+
resque (1.26.0)
|
124
120
|
mono_logger (~> 1.0)
|
125
121
|
multi_json (~> 1.0)
|
126
122
|
redis-namespace (~> 1.3)
|
127
123
|
sinatra (>= 0.9.2)
|
128
124
|
vegas (~> 0.1.2)
|
129
|
-
resque-scheduler (4.
|
125
|
+
resque-scheduler (4.2.0)
|
130
126
|
mono_logger (~> 1.0)
|
131
127
|
redis (~> 3.0)
|
132
128
|
resque (~> 1.25)
|
133
|
-
rufus-scheduler (~> 3.
|
134
|
-
rspec (3.
|
135
|
-
rspec-core (~> 3.
|
136
|
-
rspec-expectations (~> 3.
|
137
|
-
rspec-mocks (~> 3.
|
138
|
-
rspec-core (3.
|
139
|
-
rspec-support (~> 3.
|
140
|
-
rspec-expectations (3.
|
129
|
+
rufus-scheduler (~> 3.2)
|
130
|
+
rspec (3.4.0)
|
131
|
+
rspec-core (~> 3.4.0)
|
132
|
+
rspec-expectations (~> 3.4.0)
|
133
|
+
rspec-mocks (~> 3.4.0)
|
134
|
+
rspec-core (3.4.4)
|
135
|
+
rspec-support (~> 3.4.0)
|
136
|
+
rspec-expectations (3.4.0)
|
141
137
|
diff-lcs (>= 1.2.0, < 2.0)
|
142
|
-
rspec-support (~> 3.
|
143
|
-
rspec-its (1.
|
138
|
+
rspec-support (~> 3.4.0)
|
139
|
+
rspec-its (1.2.0)
|
144
140
|
rspec-core (>= 3.0.0)
|
145
141
|
rspec-expectations (>= 3.0.0)
|
146
|
-
rspec-mocks (3.1
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
parser (>= 2.
|
152
|
-
powerpack (~> 0.
|
142
|
+
rspec-mocks (3.4.1)
|
143
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
144
|
+
rspec-support (~> 3.4.0)
|
145
|
+
rspec-support (3.4.1)
|
146
|
+
rubocop (0.40.0)
|
147
|
+
parser (>= 2.3.1.0, < 3.0)
|
148
|
+
powerpack (~> 0.1)
|
153
149
|
rainbow (>= 1.99.1, < 3.0)
|
154
|
-
ruby-progressbar (~> 1.
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
sequel (4.
|
159
|
-
sidekiq (
|
160
|
-
|
161
|
-
connection_pool (>= 2.
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
sinatra (1.4.5)
|
166
|
-
rack (~> 1.4)
|
150
|
+
ruby-progressbar (~> 1.7)
|
151
|
+
unicode-display_width (~> 1.0, >= 1.0.1)
|
152
|
+
ruby-progressbar (1.8.1)
|
153
|
+
rufus-scheduler (3.2.1)
|
154
|
+
sequel (4.34.0)
|
155
|
+
sidekiq (4.1.2)
|
156
|
+
concurrent-ruby (~> 1.0)
|
157
|
+
connection_pool (~> 2.2, >= 2.2.0)
|
158
|
+
redis (~> 3.2, >= 3.2.1)
|
159
|
+
sinatra (1.4.7)
|
160
|
+
rack (~> 1.5)
|
167
161
|
rack-protection (~> 1.4)
|
168
|
-
tilt (
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
activesupport (>= 3.0)
|
178
|
-
sprockets (>= 2.8, < 4.0)
|
179
|
-
sqlite3 (1.3.10)
|
162
|
+
tilt (>= 1.3, < 3)
|
163
|
+
sprockets (3.6.0)
|
164
|
+
concurrent-ruby (~> 1.0)
|
165
|
+
rack (> 1, < 3)
|
166
|
+
sprockets-rails (3.0.4)
|
167
|
+
actionpack (>= 4.0)
|
168
|
+
activesupport (>= 4.0)
|
169
|
+
sprockets (>= 3.0.0)
|
170
|
+
sqlite3 (1.3.11)
|
180
171
|
thor (0.19.1)
|
181
|
-
thread_safe (0.3.
|
182
|
-
tilt (
|
183
|
-
timers (4.0.1)
|
184
|
-
hitimes
|
172
|
+
thread_safe (0.3.5)
|
173
|
+
tilt (2.0.4)
|
185
174
|
tzinfo (1.2.2)
|
186
175
|
thread_safe (~> 0.1)
|
176
|
+
unicode-display_width (1.0.5)
|
187
177
|
vegas (0.1.11)
|
188
178
|
rack (>= 1.0.0)
|
189
179
|
|
@@ -209,4 +199,4 @@ DEPENDENCIES
|
|
209
199
|
sqlite3
|
210
200
|
|
211
201
|
BUNDLED WITH
|
212
|
-
1.
|
202
|
+
1.12.3
|
data/README.md
CHANGED
@@ -1,26 +1,27 @@
|
|
1
|
-
ActiveJob::Retry [![Build Status](https://travis-ci.org/
|
1
|
+
ActiveJob::Retry [![Build Status](https://travis-ci.org/isaacseymour/activejob-retry.svg?branch=master)](https://travis-ci.org/isaacseymour/activejob-retry)
|
2
2
|
================
|
3
3
|
|
4
4
|
**This is an alpha library** in active development, so the API may change.
|
5
5
|
|
6
|
-
Automatic retry functionality for ActiveJob. Just `include ActiveJob::Retry
|
7
|
-
|
8
|
-
retry strategy:
|
6
|
+
Automatic retry functionality for ActiveJob. Just `include ActiveJob::Retry.new(strategy:
|
7
|
+
:something, **options)` in your job class:
|
9
8
|
|
10
9
|
```ruby
|
11
10
|
class ProcessWebhook < ActiveJob::Base
|
12
|
-
include ActiveJob::Retry
|
13
|
-
|
14
11
|
queue_as :webhooks
|
15
12
|
|
16
13
|
# Constant delay between attempts:
|
17
|
-
|
14
|
+
include ActiveJob::Retry.new(strategy: :constant,
|
15
|
+
limit: 3,
|
16
|
+
delay: 5.minutes,
|
17
|
+
retryable_exceptions: [TimeoutError, NetworkError])
|
18
18
|
|
19
19
|
# Or, variable delay between attempts:
|
20
|
-
|
20
|
+
include ActiveJob::Retry.new(strategy: :variable,
|
21
|
+
delays: [1.minute, 5.minutes, 10.minutes, 30.minutes])
|
21
22
|
|
22
23
|
# Or, exponential delay between attempts:
|
23
|
-
|
24
|
+
include ActiveJob::Retry.new(strategy: :exponential, limit: 25)
|
24
25
|
|
25
26
|
# You can also use a custom backoff strategy by passing an object which responds to
|
26
27
|
# `should_retry?(attempt, exception)`, and `retry_delay(attempt, exception)`
|
@@ -35,7 +36,7 @@ class ProcessWebhook < ActiveJob::Base
|
|
35
36
|
end
|
36
37
|
end
|
37
38
|
|
38
|
-
|
39
|
+
include ActiveJob::Retry.new(strategy: ChaoticBackoffStrategy)
|
39
40
|
|
40
41
|
def perform(webhook)
|
41
42
|
webhook.process!
|
@@ -43,10 +44,10 @@ class ProcessWebhook < ActiveJob::Base
|
|
43
44
|
end
|
44
45
|
```
|
45
46
|
|
46
|
-
The retry will get executed before any `rescue_from` blocks, which will only get executed
|
47
|
+
The retry will get executed **before** any `rescue_from` blocks, which will only get executed
|
47
48
|
if the exception is not going to be retried, or has failed the final retry.
|
48
49
|
|
49
|
-
####
|
50
|
+
#### constant options
|
50
51
|
| Option | Default | Description |
|
51
52
|
|:---------------------- |:------- |:-------------- |
|
52
53
|
| `limit` | `1` | Maximum number of times to attempt the job (default: 1).
|
@@ -55,40 +56,41 @@ if the exception is not going to be retried, or has failed the final retry.
|
|
55
56
|
| `retryable_exceptions` | `nil` | A whitelist of exceptions to retry. When `nil`, all exceptions will result in a retry.
|
56
57
|
| `fatal_exceptions` | `[]` | A blacklist of exceptions to not retry (default: []).
|
57
58
|
|
58
|
-
####
|
59
|
+
#### exponential options
|
59
60
|
| Option | Default | Description |
|
60
61
|
|:---------------------- |:------- |:-------------- |
|
61
62
|
| `limit` | `1` | Maximum number of times to attempt the job (default: 1).
|
62
63
|
| `unlimited_retries` | `false` | If set to `true`, this job will be repeated indefinitely until in succeeds. Use with extreme caution.
|
63
|
-
| `retryable_exceptions` | `nil` | Same as for [
|
64
|
-
| `fatal_exceptions` | `[]` | Same as for [
|
64
|
+
| `retryable_exceptions` | `nil` | Same as for [constant](#constant-options).
|
65
|
+
| `fatal_exceptions` | `[]` | Same as for [constant](#constant-options).
|
65
66
|
|
66
|
-
####
|
67
|
+
#### variable options
|
67
68
|
|
68
69
|
| Option | Default | Description |
|
69
70
|
|:---------------------- |:------- |:------------- |
|
70
71
|
| `delays` | | __required__ An array of delays between attempts in seconds. The first attempt will occur whenever you originally enqueued the job to happen.
|
71
72
|
| `min_delay_multiplier` | | If supplied, each delay will be multiplied by a random number between this and `max_delay_multiplier`.
|
72
73
|
| `max_delay_multiplier` | | The other end of the range for `min_delay_multiplier`. If one is supplied, both must be.
|
73
|
-
| `retryable_exceptions` | `nil` | Same as for [
|
74
|
-
| `fatal_exceptions` | `[]` | Same as for [
|
74
|
+
| `retryable_exceptions` | `nil` | Same as for [constant](#constant-options).
|
75
|
+
| `fatal_exceptions` | `[]` | Same as for [constant](#constant-options).
|
75
76
|
|
76
77
|
## Supported backends
|
77
78
|
|
78
79
|
Any queue adapter which supports delayed enqueuing (i.e. the `enqueue_at`
|
79
|
-
method) will work with ActiveJob::Retry
|
80
|
+
method) will work with `ActiveJob::Retry`, however some queue backends have
|
80
81
|
automatic retry logic, which should be disabled. The cleanest way to do this is
|
81
82
|
to use a `rescue_from` in the jobs for which you're using ActiveJob::Retry, so
|
82
83
|
the queue backend never perceives the job as having failed. E.g.:
|
83
84
|
|
84
85
|
```ruby
|
85
86
|
class MyJob < ActiveJob::Base
|
86
|
-
include ActiveJob::Retry
|
87
|
+
include ActiveJob::Retry.new(strategy: :constant,
|
88
|
+
limit: 3,
|
89
|
+
delay: 5,
|
90
|
+
retryable_exceptions: [TimeoutError, NetworkError])
|
87
91
|
|
88
92
|
queue_as :some_job
|
89
93
|
|
90
|
-
constant_retry limit: 3, delay: 5, retryable_exceptions: [TimeoutError, NetworkError]
|
91
|
-
|
92
94
|
rescue_from(StandardError) { |error| MyErrorService.record(error) }
|
93
95
|
|
94
96
|
def perform
|
data/Rakefile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'rake/testtask'
|
2
2
|
require 'rubygems/package_task'
|
3
3
|
|
4
|
-
ACTIVEJOB_ADAPTERS = %w(backburner delayed_job que resque sidekiq)
|
4
|
+
ACTIVEJOB_ADAPTERS = %w(backburner delayed_job que resque sidekiq).freeze
|
5
5
|
|
6
6
|
task default: :test
|
7
7
|
task test: 'test:default'
|
@@ -49,14 +49,15 @@ namespace :test do
|
|
49
49
|
|
50
50
|
namespace :integration do
|
51
51
|
Rake::TestTask.new(
|
52
|
-
adapter => ["test:env:#{adapter}", 'test:env:integration']
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
52
|
+
adapter => ["test:env:#{adapter}", 'test:env:integration']
|
53
|
+
) do |t|
|
54
|
+
t.description = "Run integration tests for #{adapter}"
|
55
|
+
t.libs << 'test'
|
56
|
+
t.test_files = FileList['test/integration/**/*_test.rb']
|
57
|
+
t.verbose = true
|
58
|
+
t.warning = true
|
59
|
+
t.ruby_opts = ['--dev'] if defined?(JRUBY_VERSION)
|
60
|
+
end
|
60
61
|
end
|
61
62
|
end
|
62
63
|
end
|
data/activejob-retry.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
|
|
18
18
|
* Exponential backoff (varying the delay between retries).
|
19
19
|
* Light and easy to override retry logic.
|
20
20
|
EOL
|
21
|
-
s.homepage = '
|
21
|
+
s.homepage = 'https://github.com/isaacseymour/activejob-retry'
|
22
22
|
s.license = 'MIT'
|
23
23
|
|
24
24
|
s.has_rdoc = false
|
data/lib/active_job/retry.rb
CHANGED
@@ -11,8 +11,17 @@ unless ActiveJob::Base.method_defined?(:deserialize)
|
|
11
11
|
require 'active_job/retry/deserialize_monkey_patch'
|
12
12
|
end
|
13
13
|
|
14
|
+
def choose_strategy(strategy, options)
|
15
|
+
case strategy
|
16
|
+
when :constant then ActiveJob::Retry::ConstantBackoffStrategy.new(options)
|
17
|
+
when :variable then ActiveJob::Retry::VariableBackoffStrategy.new(options)
|
18
|
+
when :exponential then ActiveJob::Retry::ExponentialBackoffStrategy.new(options)
|
19
|
+
else strategy
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
14
23
|
module ActiveJob
|
15
|
-
|
24
|
+
class Retry < Module
|
16
25
|
PROBLEMATIC_ADAPTERS = [
|
17
26
|
'ActiveJob::QueueAdapters::InlineAdapter',
|
18
27
|
'ActiveJob::QueueAdapters::QuAdapter',
|
@@ -20,88 +29,89 @@ module ActiveJob
|
|
20
29
|
'ActiveJob::QueueAdapters::SuckerPunchAdapter'
|
21
30
|
].freeze
|
22
31
|
|
23
|
-
def self.included(base)
|
24
|
-
if PROBLEMATIC_ADAPTERS.include?(ActiveJob::Base.queue_adapter.name)
|
25
|
-
warn("#{ActiveJob::Base.queue_adapter.name} does not support delayed retries, " \
|
26
|
-
'so does not work with ActiveJob::Retry. You may experience strange ' \
|
27
|
-
'behaviour.')
|
28
|
-
end
|
29
|
-
|
30
|
-
base.extend(ClassMethods)
|
31
|
-
end
|
32
|
-
|
33
32
|
#################
|
34
33
|
# Configuration #
|
35
34
|
#################
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
def variable_retry(options)
|
45
|
-
retry_with(VariableBackoffStrategy.new(options))
|
35
|
+
def initialize(strategy: nil, **options)
|
36
|
+
check_adapter!
|
37
|
+
@backoff_strategy = choose_strategy(strategy, options)
|
38
|
+
|
39
|
+
unless backoff_strategy_valid?
|
40
|
+
raise InvalidConfigurationError,
|
41
|
+
'Backoff strategies must define `should_retry?(attempt, exception)`, ' \
|
42
|
+
'and `retry_delay(attempt, exception)`.'
|
46
43
|
end
|
44
|
+
end
|
47
45
|
|
48
|
-
|
49
|
-
|
50
|
-
|
46
|
+
def included(base)
|
47
|
+
define_backoff_strategy(base)
|
48
|
+
define_retry_attempt_tracking(base)
|
49
|
+
define_retry_method(base)
|
50
|
+
define_retry_logic(base)
|
51
|
+
end
|
51
52
|
|
52
|
-
|
53
|
-
unless backoff_strategy_valid?(backoff_strategy)
|
54
|
-
raise InvalidConfigurationError,
|
55
|
-
'Backoff strategies must define `should_retry?(attempt, exception)`, ' \
|
56
|
-
'and `retry_delay(attempt, exception)`.'
|
57
|
-
end
|
53
|
+
private
|
58
54
|
|
59
|
-
|
60
|
-
end
|
55
|
+
attr_reader :backoff_strategy
|
61
56
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
backoff_strategy.method(:should_retry?).arity == 2 &&
|
66
|
-
backoff_strategy.method(:retry_delay).arity == 2
|
67
|
-
end
|
57
|
+
def define_backoff_strategy(klass)
|
58
|
+
klass.instance_variable_set(:@backoff_strategy, @backoff_strategy)
|
59
|
+
klass.define_singleton_method(:backoff_strategy) { @backoff_strategy }
|
68
60
|
end
|
69
61
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
62
|
+
def define_retry_attempt_tracking(klass)
|
63
|
+
klass.instance_eval do
|
64
|
+
define_method(:serialize) do |*args|
|
65
|
+
super(*args).merge('retry_attempt' => retry_attempt)
|
66
|
+
end
|
67
|
+
define_method :deserialize do |job_data|
|
68
|
+
super(job_data)
|
69
|
+
@retry_attempt = job_data['retry_attempt']
|
70
|
+
end
|
71
|
+
define_method(:retry_attempt) { @retry_attempt ||= 1 }
|
72
|
+
end
|
76
73
|
end
|
77
74
|
|
78
|
-
def
|
79
|
-
|
80
|
-
|
75
|
+
def define_retry_method(klass)
|
76
|
+
klass.instance_eval do
|
77
|
+
define_method :internal_retry do |exception|
|
78
|
+
this_delay = self.class.backoff_strategy.retry_delay(retry_attempt, exception)
|
79
|
+
# TODO: This breaks DelayedJob and Resque for some weird ActiveSupport reason.
|
80
|
+
# logger.info("Retrying (attempt #{retry_attempt + 1}, waiting #{this_delay}s)")
|
81
|
+
@retry_attempt += 1
|
82
|
+
retry_job(wait: this_delay)
|
83
|
+
end
|
84
|
+
end
|
81
85
|
end
|
82
86
|
|
83
|
-
def
|
84
|
-
|
87
|
+
def define_retry_logic(klass)
|
88
|
+
klass.instance_eval do
|
89
|
+
# Override `rescue_with_handler` to make sure our catch is before callbacks,
|
90
|
+
# so `rescue_from`s will only be run after any retry attempts have been exhausted.
|
91
|
+
define_method :rescue_with_handler do |exception|
|
92
|
+
if self.class.backoff_strategy.should_retry?(retry_attempt, exception)
|
93
|
+
internal_retry(exception)
|
94
|
+
return true # Exception has been handled
|
95
|
+
else
|
96
|
+
return super(exception)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
85
100
|
end
|
86
101
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
# so `rescue_from`s will only be run after any retry attempts have been exhausted.
|
93
|
-
def rescue_with_handler(exception)
|
94
|
-
unless self.class.backoff_strategy.should_retry?(retry_attempt, exception)
|
95
|
-
return super
|
102
|
+
def check_adapter!
|
103
|
+
if PROBLEMATIC_ADAPTERS.include?(ActiveJob::Base.queue_adapter.name)
|
104
|
+
warn("#{ActiveJob::Base.queue_adapter.name} does not support delayed retries, " \
|
105
|
+
'so does not work with ActiveJob::Retry. You may experience strange ' \
|
106
|
+
'behaviour.')
|
96
107
|
end
|
108
|
+
end
|
97
109
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
true # Exception has been handled
|
110
|
+
def backoff_strategy_valid?
|
111
|
+
backoff_strategy.respond_to?(:should_retry?) &&
|
112
|
+
backoff_strategy.respond_to?(:retry_delay) &&
|
113
|
+
backoff_strategy.method(:should_retry?).arity == 2 &&
|
114
|
+
backoff_strategy.method(:retry_delay).arity == 2
|
105
115
|
end
|
106
116
|
end
|
107
117
|
end
|
@@ -2,7 +2,7 @@ require 'active_job/retry/constant_backoff_strategy'
|
|
2
2
|
require 'active_job/retry/variable_options_validator'
|
3
3
|
|
4
4
|
module ActiveJob
|
5
|
-
|
5
|
+
class Retry < Module
|
6
6
|
class VariableBackoffStrategy < ConstantBackoffStrategy
|
7
7
|
def initialize(options)
|
8
8
|
super(options)
|
data/spec/retry_spec.rb
CHANGED
@@ -1,19 +1,22 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe ActiveJob::Retry do
|
4
|
-
|
4
|
+
let(:strategy) { :constant }
|
5
|
+
let(:options) { {} }
|
6
|
+
let(:retry_instance) { described_class.new(strategy: strategy, **options) }
|
7
|
+
let(:job) do
|
5
8
|
Class.new(ActiveJob::Base) do
|
6
|
-
include ActiveJob::Retry
|
7
|
-
|
8
9
|
def perform(*_args)
|
9
10
|
raise RuntimeError
|
10
11
|
end
|
11
|
-
end
|
12
|
+
end.include(retry_instance)
|
12
13
|
end
|
13
14
|
|
14
15
|
describe '.constant_retry' do
|
16
|
+
let(:strategy) { :constant }
|
17
|
+
let(:options) { { limit: 10, delay: 5 } }
|
18
|
+
|
15
19
|
it 'sets a ConstantBackoffStrategy' do
|
16
|
-
job.constant_retry(limit: 10, delay: 5)
|
17
20
|
expect(job.backoff_strategy).to be_a(ActiveJob::Retry::ConstantBackoffStrategy)
|
18
21
|
end
|
19
22
|
|
@@ -21,15 +24,17 @@ RSpec.describe ActiveJob::Retry do
|
|
21
24
|
let(:options) { { limit: -2 } }
|
22
25
|
|
23
26
|
specify do
|
24
|
-
expect {
|
27
|
+
expect { retry_instance }.
|
25
28
|
to raise_error(ActiveJob::Retry::InvalidConfigurationError)
|
26
29
|
end
|
27
30
|
end
|
28
31
|
end
|
29
32
|
|
30
33
|
describe '.variable_retry' do
|
34
|
+
let(:strategy) { :variable }
|
35
|
+
let(:options) { { delays: [0, 5, 10, 60, 200] } }
|
36
|
+
|
31
37
|
it 'sets a VariableBackoffStrategy' do
|
32
|
-
job.variable_retry(delays: [0, 5, 10, 60, 200])
|
33
38
|
expect(job.backoff_strategy).to be_a(ActiveJob::Retry::VariableBackoffStrategy)
|
34
39
|
end
|
35
40
|
|
@@ -37,52 +42,58 @@ RSpec.describe ActiveJob::Retry do
|
|
37
42
|
let(:options) { {} }
|
38
43
|
|
39
44
|
specify do
|
40
|
-
expect {
|
45
|
+
expect { retry_instance }.
|
41
46
|
to raise_error(ActiveJob::Retry::InvalidConfigurationError)
|
42
47
|
end
|
43
48
|
end
|
44
49
|
end
|
45
50
|
|
46
51
|
describe '.exponential_retry' do
|
52
|
+
let(:strategy) { :exponential }
|
53
|
+
let(:options) { { limit: 10 } }
|
54
|
+
|
47
55
|
it 'sets an ExponentialBackoffStrategy' do
|
48
|
-
job.exponential_retry(limit: 10)
|
49
56
|
expect(job.backoff_strategy).to be_a(ActiveJob::Retry::ExponentialBackoffStrategy)
|
50
57
|
end
|
51
58
|
|
52
|
-
context 'invalid
|
59
|
+
context 'invalid limit' do
|
53
60
|
let(:options) { { limit: -2 } }
|
54
|
-
let(:options_with_delay) { { limit: 2, delay: 3 } }
|
55
61
|
|
56
62
|
specify do
|
57
|
-
expect {
|
63
|
+
expect { retry_instance }.
|
58
64
|
to raise_error(ActiveJob::Retry::InvalidConfigurationError)
|
59
65
|
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'invalid option included' do
|
69
|
+
let(:options) { { limit: 2, delay: 3 } }
|
60
70
|
|
61
71
|
specify do
|
62
|
-
expect {
|
72
|
+
expect { retry_instance }.
|
63
73
|
to raise_error(ActiveJob::Retry::InvalidConfigurationError)
|
64
74
|
end
|
65
75
|
end
|
66
76
|
end
|
67
77
|
|
68
|
-
describe '
|
78
|
+
describe 'custom strategy' do
|
79
|
+
module CustomBackoffStrategy
|
80
|
+
def self.should_retry?(_attempt, _exception)
|
81
|
+
true
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.retry_delay(_attempt, _exception)
|
85
|
+
5
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
69
89
|
it 'rejects invalid backoff strategies' do
|
70
|
-
expect {
|
90
|
+
expect { described_class.new(strategy: Object.new) }.
|
71
91
|
to raise_error(ActiveJob::Retry::InvalidConfigurationError)
|
72
92
|
end
|
73
93
|
|
74
|
-
|
75
|
-
module CustomBackoffStrategy
|
76
|
-
def self.should_retry?(_attempt, _exception)
|
77
|
-
true
|
78
|
-
end
|
79
|
-
|
80
|
-
def self.retry_delay(_attempt, _exception)
|
81
|
-
5
|
82
|
-
end
|
83
|
-
end
|
94
|
+
let(:strategy) { CustomBackoffStrategy }
|
84
95
|
|
85
|
-
|
96
|
+
it 'sets the backoff_strategy when it is valid' do
|
86
97
|
expect(job.backoff_strategy).to eq(CustomBackoffStrategy)
|
87
98
|
end
|
88
99
|
end
|
@@ -143,17 +154,16 @@ RSpec.describe ActiveJob::Retry do
|
|
143
154
|
end
|
144
155
|
|
145
156
|
describe '#rescue_with_handler' do
|
146
|
-
let(:
|
157
|
+
let(:mod) { described_class.new(strategy: :constant, limit: 100) }
|
147
158
|
let(:instance) { job.new }
|
148
|
-
before { job.retry_with(backoff_strategy) }
|
149
159
|
subject(:perform) { instance.perform_now }
|
150
160
|
|
151
161
|
context 'when the job should be retried' do
|
152
162
|
before do
|
153
|
-
expect(backoff_strategy).to receive(:should_retry?).
|
163
|
+
expect(job.backoff_strategy).to receive(:should_retry?).
|
154
164
|
with(1, instance_of(RuntimeError)).
|
155
165
|
and_return(true)
|
156
|
-
expect(backoff_strategy).to receive(:retry_delay).
|
166
|
+
expect(job.backoff_strategy).to receive(:retry_delay).
|
157
167
|
with(1, instance_of(RuntimeError)).
|
158
168
|
and_return(5)
|
159
169
|
end
|
@@ -179,7 +189,7 @@ RSpec.describe ActiveJob::Retry do
|
|
179
189
|
|
180
190
|
context 'when the job should not be retried' do
|
181
191
|
before do
|
182
|
-
expect(backoff_strategy).to receive(:should_retry?).
|
192
|
+
expect(job.backoff_strategy).to receive(:should_retry?).
|
183
193
|
with(1, instance_of(RuntimeError)).
|
184
194
|
and_return(false)
|
185
195
|
end
|
data/spec/spec_helper.rb
CHANGED
data/test/jobs/callback_job.rb
CHANGED
data/test/jobs/gid_job.rb
CHANGED
data/test/jobs/hello_job.rb
CHANGED
data/test/jobs/logging_job.rb
CHANGED
data/test/jobs/nested_job.rb
CHANGED
data/test/jobs/rescue_job.rb
CHANGED
@@ -12,10 +12,9 @@ CODE
|
|
12
12
|
|
13
13
|
file 'app/jobs/test_job.rb', <<-CODE
|
14
14
|
class TestJob < ActiveJob::Base
|
15
|
-
include ActiveJob::Retry
|
15
|
+
include ActiveJob::Retry.new(strategy: :constant, limit: 2, delay: 3)
|
16
16
|
|
17
17
|
queue_as :integration_tests
|
18
|
-
constant_retry limit: 2, delay: 3
|
19
18
|
|
20
19
|
rescue_from(RuntimeError) do |e|
|
21
20
|
if arguments[3]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activejob-retry
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Isaac Seymour
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-05-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -151,7 +151,6 @@ files:
|
|
151
151
|
- test/adapters/sneakers.rb
|
152
152
|
- test/adapters/sucker_punch.rb
|
153
153
|
- test/cases/adapter_test.rb
|
154
|
-
- test/cases/argument_serialization_test.rb
|
155
154
|
- test/cases/callbacks_test.rb
|
156
155
|
- test/cases/job_serialization_test.rb
|
157
156
|
- test/cases/logging_test.rb
|
@@ -183,7 +182,7 @@ files:
|
|
183
182
|
- test/support/integration/test_case_helpers.rb
|
184
183
|
- test/support/job_buffer.rb
|
185
184
|
- test/support/que/inline.rb
|
186
|
-
homepage:
|
185
|
+
homepage: https://github.com/isaacseymour/activejob-retry
|
187
186
|
licenses:
|
188
187
|
- MIT
|
189
188
|
metadata: {}
|
@@ -203,7 +202,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
203
202
|
version: '0'
|
204
203
|
requirements: []
|
205
204
|
rubyforge_project:
|
206
|
-
rubygems_version: 2.
|
205
|
+
rubygems_version: 2.5.1
|
207
206
|
signing_key:
|
208
207
|
specification_version: 4
|
209
208
|
summary: Automatic retry functionality for ActiveJob.
|
@@ -1,84 +0,0 @@
|
|
1
|
-
require 'helper'
|
2
|
-
require 'active_job/arguments'
|
3
|
-
require 'models/person'
|
4
|
-
require 'active_support/core_ext/hash/indifferent_access'
|
5
|
-
|
6
|
-
class ArgumentSerializationTest < ActiveSupport::TestCase
|
7
|
-
setup do
|
8
|
-
@person = Person.find('5')
|
9
|
-
end
|
10
|
-
|
11
|
-
[ nil, 1, 1.0, 1_000_000_000_000_000_000_000,
|
12
|
-
'a', true, false,
|
13
|
-
[ 1, 'a' ],
|
14
|
-
{ 'a' => 1 }
|
15
|
-
].each do |arg|
|
16
|
-
test "serializes #{arg.class} verbatim" do
|
17
|
-
assert_arguments_unchanged arg
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
[ :a, Object.new, self, Person.find('5').to_gid ].each do |arg|
|
22
|
-
test "does not serialize #{arg.class}" do
|
23
|
-
assert_raises ActiveJob::SerializationError do
|
24
|
-
ActiveJob::Arguments.serialize [ arg ]
|
25
|
-
end
|
26
|
-
|
27
|
-
assert_raises ActiveJob::DeserializationError do
|
28
|
-
ActiveJob::Arguments.deserialize [ arg ]
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
test 'should convert records to Global IDs' do
|
34
|
-
assert_arguments_roundtrip [@person], ['_aj_globalid' => @person.to_gid.to_s]
|
35
|
-
end
|
36
|
-
|
37
|
-
test 'should dive deep into arrays and hashes' do
|
38
|
-
assert_arguments_roundtrip [3, [@person]], [3, ['_aj_globalid' => @person.to_gid.to_s]]
|
39
|
-
assert_arguments_roundtrip [{ 'a' => @person }], [{ 'a' => { '_aj_globalid' => @person.to_gid.to_s }}.with_indifferent_access]
|
40
|
-
end
|
41
|
-
|
42
|
-
test 'should stringify symbol hash keys' do
|
43
|
-
assert_equal [ 'a' => 1 ], ActiveJob::Arguments.serialize([ a: 1 ])
|
44
|
-
end
|
45
|
-
|
46
|
-
test 'should disallow non-string/symbol hash keys' do
|
47
|
-
assert_raises ActiveJob::SerializationError do
|
48
|
-
ActiveJob::Arguments.serialize [ { 1 => 2 } ]
|
49
|
-
end
|
50
|
-
|
51
|
-
assert_raises ActiveJob::SerializationError do
|
52
|
-
ActiveJob::Arguments.serialize [ { :a => [{ 2 => 3 }] } ]
|
53
|
-
end
|
54
|
-
|
55
|
-
assert_raises ActiveJob::SerializationError do
|
56
|
-
ActiveJob::Arguments.serialize [ '_aj_globalid' => 1 ]
|
57
|
-
end
|
58
|
-
|
59
|
-
assert_raises ActiveJob::SerializationError do
|
60
|
-
ActiveJob::Arguments.serialize [ :_aj_globalid => 1 ]
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
test 'should not allow non-primitive objects' do
|
65
|
-
assert_raises ActiveJob::SerializationError do
|
66
|
-
ActiveJob::Arguments.serialize [Object.new]
|
67
|
-
end
|
68
|
-
|
69
|
-
assert_raises ActiveJob::SerializationError do
|
70
|
-
ActiveJob::Arguments.serialize [1, [Object.new]]
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
private
|
75
|
-
def assert_arguments_unchanged(*args)
|
76
|
-
assert_arguments_roundtrip args, args
|
77
|
-
end
|
78
|
-
|
79
|
-
def assert_arguments_roundtrip(args, expected_serialized_args)
|
80
|
-
serialized = ActiveJob::Arguments.serialize(args)
|
81
|
-
assert_equal expected_serialized_args, serialized
|
82
|
-
assert_equal args, ActiveJob::Arguments.deserialize(serialized)
|
83
|
-
end
|
84
|
-
end
|