activejob-traffic_control 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.codeclimate.yml +16 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +219 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/README.md +67 -0
- data/Rakefile +10 -0
- data/activejob-traffic_control.gemspec +30 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/active_job/traffic_control/base.rb +57 -0
- data/lib/active_job/traffic_control/concurrency.rb +53 -0
- data/lib/active_job/traffic_control/disable.rb +51 -0
- data/lib/active_job/traffic_control/throttle.rb +50 -0
- data/lib/active_job/traffic_control/version.rb +5 -0
- data/lib/active_job/traffic_control.rb +68 -0
- metadata +172 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3a536acd2cfc994594b0ae3c7e463b4aac834ba5
|
4
|
+
data.tar.gz: 87ab13103b57fe115c1b287c18714662892a42e9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fa226f177e0f5cb5408222de47f3652ccf88ee591719a39521db19fbc68ff21fca906e6a8c06e2eba7615f5be7b3ec04561121a539198d5e75a297d8cbd151da
|
7
|
+
data.tar.gz: fe358db32250fbb19b129d7061d2abd3cd5129e86a149a16e9b6c234d05cca72a400d06692d691426bb3927b5384cfd9544db39d570fcf97a1727e22fe08410e
|
data/.codeclimate.yml
ADDED
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
AllCops:
|
2
|
+
Exclude:
|
3
|
+
- .git/**/*
|
4
|
+
- tmp/**/*
|
5
|
+
- zhong.gemspec
|
6
|
+
|
7
|
+
Lint/DuplicateMethods:
|
8
|
+
Enabled: true
|
9
|
+
|
10
|
+
Lint/DeprecatedClassMethods:
|
11
|
+
Enabled: true
|
12
|
+
|
13
|
+
Style/TrailingWhitespace:
|
14
|
+
Enabled: true
|
15
|
+
|
16
|
+
Style/Tab:
|
17
|
+
Enabled: true
|
18
|
+
|
19
|
+
Style/TrailingBlankLines:
|
20
|
+
Enabled: true
|
21
|
+
|
22
|
+
Style/NilComparison:
|
23
|
+
Enabled: true
|
24
|
+
|
25
|
+
Style/NonNilCheck:
|
26
|
+
Enabled: true
|
27
|
+
|
28
|
+
Style/Not:
|
29
|
+
Enabled: true
|
30
|
+
|
31
|
+
Style/RedundantReturn:
|
32
|
+
Enabled: true
|
33
|
+
|
34
|
+
Style/ClassCheck:
|
35
|
+
Enabled: true
|
36
|
+
|
37
|
+
Style/EmptyLines:
|
38
|
+
Enabled: true
|
39
|
+
|
40
|
+
Style/EmptyLiteral:
|
41
|
+
Enabled: true
|
42
|
+
|
43
|
+
Style/Alias:
|
44
|
+
Enabled: true
|
45
|
+
|
46
|
+
Style/MethodCallParentheses:
|
47
|
+
Enabled: true
|
48
|
+
|
49
|
+
Style/MethodDefParentheses:
|
50
|
+
Enabled: true
|
51
|
+
|
52
|
+
Style/SpaceBeforeBlockBraces:
|
53
|
+
Enabled: true
|
54
|
+
|
55
|
+
Style/SpaceInsideBlockBraces:
|
56
|
+
Enabled: true
|
57
|
+
|
58
|
+
Style/SpaceInsideParens:
|
59
|
+
Enabled: true
|
60
|
+
|
61
|
+
Style/DeprecatedHashMethods:
|
62
|
+
Enabled: true
|
63
|
+
|
64
|
+
Style/HashSyntax:
|
65
|
+
Enabled: true
|
66
|
+
|
67
|
+
Style/SpaceInsideHashLiteralBraces:
|
68
|
+
Enabled: true
|
69
|
+
EnforcedStyle: no_space
|
70
|
+
|
71
|
+
Style/SpaceInsideBrackets:
|
72
|
+
Enabled: true
|
73
|
+
|
74
|
+
Style/AndOr:
|
75
|
+
Enabled: false
|
76
|
+
|
77
|
+
Style/TrailingCommaInLiteral:
|
78
|
+
Enabled: true
|
79
|
+
|
80
|
+
Style/SpaceBeforeComma:
|
81
|
+
Enabled: true
|
82
|
+
|
83
|
+
Style/SpaceBeforeComment:
|
84
|
+
Enabled: true
|
85
|
+
|
86
|
+
Style/SpaceBeforeSemicolon:
|
87
|
+
Enabled: true
|
88
|
+
|
89
|
+
Style/SpaceAroundBlockParameters:
|
90
|
+
Enabled: true
|
91
|
+
|
92
|
+
Style/SpaceAroundOperators:
|
93
|
+
Enabled: true
|
94
|
+
|
95
|
+
Style/SpaceAfterColon:
|
96
|
+
Enabled: true
|
97
|
+
|
98
|
+
Style/SpaceAfterComma:
|
99
|
+
Enabled: true
|
100
|
+
|
101
|
+
Style/SpaceAroundKeyword:
|
102
|
+
Enabled: true
|
103
|
+
|
104
|
+
Style/SpaceAfterNot:
|
105
|
+
Enabled: true
|
106
|
+
|
107
|
+
Style/SpaceAfterSemicolon:
|
108
|
+
Enabled: true
|
109
|
+
|
110
|
+
Lint/UselessComparison:
|
111
|
+
Enabled: true
|
112
|
+
|
113
|
+
Lint/InvalidCharacterLiteral:
|
114
|
+
Enabled: true
|
115
|
+
|
116
|
+
Lint/LiteralInInterpolation:
|
117
|
+
Enabled: true
|
118
|
+
|
119
|
+
Lint/LiteralInCondition:
|
120
|
+
Enabled: true
|
121
|
+
|
122
|
+
Lint/UnusedBlockArgument:
|
123
|
+
Enabled: true
|
124
|
+
|
125
|
+
Style/VariableInterpolation:
|
126
|
+
Enabled: true
|
127
|
+
|
128
|
+
Style/RedundantSelf:
|
129
|
+
Enabled: true
|
130
|
+
|
131
|
+
Style/ParenthesesAroundCondition:
|
132
|
+
Enabled: true
|
133
|
+
|
134
|
+
Style/WhileUntilDo:
|
135
|
+
Enabled: true
|
136
|
+
|
137
|
+
Style/EmptyLineBetweenDefs:
|
138
|
+
Enabled: true
|
139
|
+
|
140
|
+
Style/EmptyLinesAroundAccessModifier:
|
141
|
+
Enabled: true
|
142
|
+
|
143
|
+
Style/EmptyLinesAroundMethodBody:
|
144
|
+
Enabled: true
|
145
|
+
|
146
|
+
Style/ColonMethodCall:
|
147
|
+
Enabled: true
|
148
|
+
|
149
|
+
Lint/SpaceBeforeFirstArg:
|
150
|
+
Enabled: true
|
151
|
+
|
152
|
+
Lint/UnreachableCode:
|
153
|
+
Enabled: true
|
154
|
+
|
155
|
+
Style/UnlessElse:
|
156
|
+
Enabled: true
|
157
|
+
|
158
|
+
Style/ClassVars:
|
159
|
+
Enabled: true
|
160
|
+
|
161
|
+
Style/StringLiterals:
|
162
|
+
Enabled: true
|
163
|
+
EnforcedStyle: double_quotes
|
164
|
+
|
165
|
+
Metrics/CyclomaticComplexity:
|
166
|
+
Max: 8
|
167
|
+
|
168
|
+
Metrics/LineLength:
|
169
|
+
Max: 128
|
170
|
+
|
171
|
+
Metrics/MethodLength:
|
172
|
+
Max: 32
|
173
|
+
|
174
|
+
Metrics/PerceivedComplexity:
|
175
|
+
Max: 8
|
176
|
+
|
177
|
+
# Disabled
|
178
|
+
|
179
|
+
Style/EvenOdd:
|
180
|
+
Enabled: false
|
181
|
+
|
182
|
+
Style/AsciiComments:
|
183
|
+
Enabled: false
|
184
|
+
|
185
|
+
Style/NumericLiterals:
|
186
|
+
Enabled: false
|
187
|
+
|
188
|
+
Style/UnneededPercentQ:
|
189
|
+
Enabled: false
|
190
|
+
|
191
|
+
Style/SpecialGlobalVars:
|
192
|
+
Enabled: false
|
193
|
+
|
194
|
+
Style/TrivialAccessors:
|
195
|
+
Enabled: false
|
196
|
+
|
197
|
+
Style/PerlBackrefs:
|
198
|
+
Enabled: false
|
199
|
+
|
200
|
+
Metrics/AbcSize:
|
201
|
+
Enabled: false
|
202
|
+
|
203
|
+
Metrics/BlockNesting:
|
204
|
+
Enabled: false
|
205
|
+
|
206
|
+
Metrics/ClassLength:
|
207
|
+
Enabled: false
|
208
|
+
|
209
|
+
Metrics/MethodLength:
|
210
|
+
Enabled: false
|
211
|
+
|
212
|
+
Metrics/ParameterLists:
|
213
|
+
Enabled: false
|
214
|
+
|
215
|
+
Metrics/PerceivedComplexity:
|
216
|
+
Enabled: false
|
217
|
+
|
218
|
+
Style/Documentation:
|
219
|
+
Enabled: false
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# ActiveJob::TrafficControl [](https://travis-ci.org/nickelser/activejob-traffic_control) [](https://codeclimate.com/github/nickelser/activejob-traffic_control) [](https://codeclimate.com/github/nickelser/activejob-traffic_control) [](http://badge.fury.io/rb/activejob-traffic_control)
|
2
|
+
|
3
|
+
Rate controls for your `ActiveJob`s, powered by [Suo](https://github.com/nickelser/suo), a distributed semaphore library backed by Redis or Memcached.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'activejob-traffic_control'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install activejob-traffic_control
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
`ActiveJob::TrafficControl` adds three modules you can mixin to your job classes as needed, or to `ApplicationJob` if you are using ActiveJob 5+ (or you have created a base job class yourself).
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
ActiveJob::TrafficControl.client = ConnectionPool.new(size: 5, timeout: 5) { Redis.new } # set thresholds as needed
|
27
|
+
|
28
|
+
class CanDisableJob < ActiveJob::Base
|
29
|
+
include ActiveJob::TrafficControl::Disable
|
30
|
+
|
31
|
+
def perform
|
32
|
+
# you can pause this job from running by executing `CanDisableJob.disable!` (which will cause the job to be re-enqueued),
|
33
|
+
# or have it be dropped entirely via `CanDisableJob.disable!(drop: true)`
|
34
|
+
# enable it again via `CanDisableJob.enable!`
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class CanThrottleJob < ActiveJob::Base
|
39
|
+
include ActiveJob::TrafficControl::Throttle
|
40
|
+
|
41
|
+
throttle threshold: 2, period: 1.second, drop: true
|
42
|
+
|
43
|
+
def perform
|
44
|
+
# no more than two of `CanThrottleJob` will run every second
|
45
|
+
# if more than that attempt to run, they will be dropped (you can set `drop: false` to have the re-enqueued instead)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class ConcurrencyTestJob < ActiveJob::Base
|
50
|
+
include ActiveJob::TrafficControl::Concurrency
|
51
|
+
|
52
|
+
concurrency 5, drop: false
|
53
|
+
|
54
|
+
def perform
|
55
|
+
# only five `ConcurrencyTestJob` will ever run simultaneously
|
56
|
+
end
|
57
|
+
|
58
|
+
## Development
|
59
|
+
|
60
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
61
|
+
|
62
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
63
|
+
|
64
|
+
## Contributing
|
65
|
+
|
66
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/nickelser/activejob-traffic_control. Please look at the `.rubocop.yml` for the style guide.
|
67
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "active_job/traffic_control/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "activejob-traffic_control"
|
8
|
+
spec.version = ActiveJob::TrafficControl::VERSION
|
9
|
+
spec.authors = ["Nick Elser"]
|
10
|
+
spec.email = ["nick.elser@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Traffic control for ActiveJob}
|
13
|
+
spec.description = %q{Traffic control for ActiveJob: Concurrency/enabling/throttling}
|
14
|
+
spec.homepage = "https://www.activejobtrafficcontrol.com"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "activejob", ">= 4.2"
|
22
|
+
spec.add_dependency "activesupport", ">= 4.2"
|
23
|
+
spec.add_dependency "suo"
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.12"
|
26
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
27
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
28
|
+
spec.add_development_dependency "codeclimate-test-reporter", "~> 0.4.7"
|
29
|
+
spec.add_development_dependency "connection_pool"
|
30
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "active_job/traffic_control"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
module TrafficControl
|
5
|
+
module Base
|
6
|
+
extend ::ActiveSupport::Concern
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators ActiveJob::TrafficControl, :cache_client, :client, :client_klass
|
10
|
+
|
11
|
+
class_methods do
|
12
|
+
def cleaned_name
|
13
|
+
name.to_s.gsub(/\W/, "_")
|
14
|
+
end
|
15
|
+
|
16
|
+
def cache_client
|
17
|
+
ActiveJob::TrafficControl.cache_client
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# convenience methods
|
22
|
+
def cleaned_name
|
23
|
+
self.class.cleaned_name
|
24
|
+
end
|
25
|
+
|
26
|
+
def reenqueue(range, reason)
|
27
|
+
later_delay = rand(range).seconds
|
28
|
+
retry_job(wait: later_delay)
|
29
|
+
logger.error "Re-enqueing #{self.class.name} to run in #{later_delay}s due to #{reason}"
|
30
|
+
ActiveSupport::Notifications.instrument "re_enqueue.active_job", job: self, reason: reason
|
31
|
+
end
|
32
|
+
|
33
|
+
def drop(reason)
|
34
|
+
logger.error "Dropping #{self.class.name} due to #{reason}"
|
35
|
+
ActiveSupport::Notifications.instrument "dropped.active_job", job: self, reason: reason
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def with_raw_client
|
41
|
+
if client.respond_to?(:with)
|
42
|
+
client.with do |pooled_client|
|
43
|
+
yield pooled_client
|
44
|
+
end
|
45
|
+
else
|
46
|
+
yield client
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def with_lock_client(key, options)
|
51
|
+
with_raw_client do |cli|
|
52
|
+
yield client_klass.new(key, options.merge(client: cli))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module ActiveJob
|
2
|
+
module TrafficControl
|
3
|
+
module Concurrency
|
4
|
+
extend ::ActiveSupport::Concern
|
5
|
+
|
6
|
+
class_methods do
|
7
|
+
attr_accessor :job_concurrency
|
8
|
+
|
9
|
+
def concurrency(threshold, drop: true, key: nil, wait_timeout: 0.1, stale_timeout: 60 * 10)
|
10
|
+
raise ArgumentError, "Concurrent jobs needs to be an integer > 0" if threshold.to_i < 1
|
11
|
+
@job_concurrency = {threshold: threshold.to_i, drop: drop, wait_timeout: wait_timeout.to_f, stale_timeout: stale_timeout.to_f, key: key}
|
12
|
+
end
|
13
|
+
|
14
|
+
def concurrency_key
|
15
|
+
if job_concurrency
|
16
|
+
@concurrency_key ||= job_concurrency[:key].present? ? job_concurrency[:key] : "traffic_control:concurrency:#{cleaned_name}".freeze
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
included do
|
22
|
+
include ActiveJob::TrafficControl::Base
|
23
|
+
|
24
|
+
around_perform do |_, block|
|
25
|
+
if self.class.job_concurrency.present?
|
26
|
+
lock_options = {
|
27
|
+
resources: self.class.job_concurrency[:threshold],
|
28
|
+
acquisition_lock: self.class.job_concurrency[:wait_timeout],
|
29
|
+
stale_lock_expiration: self.class.job_concurrency[:stale_timeout]
|
30
|
+
}
|
31
|
+
|
32
|
+
with_lock_client(self.class.concurrency_key, lock_options) do |client|
|
33
|
+
locked = client.lock do
|
34
|
+
block.call
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
unless locked
|
39
|
+
if self.class.job_concurrency[:drop]
|
40
|
+
drop("concurrency".freeze)
|
41
|
+
else
|
42
|
+
reenqueue(CONCURRENCY_REENQUEUE_DELAY, "concurrency".freeze)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
else
|
47
|
+
block.call
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module ActiveJob
|
2
|
+
module TrafficControl
|
3
|
+
module Disable
|
4
|
+
extend ::ActiveSupport::Concern
|
5
|
+
|
6
|
+
DISABLED_REENQUEUE_DELAY = 60...60 * 10
|
7
|
+
SHOULD_DROP = "drop".freeze
|
8
|
+
SHOULD_DISABLE = "true".freeze
|
9
|
+
|
10
|
+
private_constant :SHOULD_DROP, :SHOULD_DISABLE, :DISABLED_REENQUEUE_DELAY
|
11
|
+
|
12
|
+
class_methods do
|
13
|
+
def disable!(drop: false)
|
14
|
+
cache_client.write(disable_key, drop ? SHOULD_DROP : SHOULD_DISABLE)
|
15
|
+
end
|
16
|
+
|
17
|
+
def enable!
|
18
|
+
cache_client.delete(disable_key)
|
19
|
+
end
|
20
|
+
|
21
|
+
def disabled?
|
22
|
+
cache_client && !cache_client.read(disable_key).nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def disable_key
|
26
|
+
@disable_key ||= "traffic_control:disable:#{cleaned_name}".freeze
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
included do
|
31
|
+
include ActiveJob::TrafficControl::Base
|
32
|
+
|
33
|
+
around_perform do |_, block|
|
34
|
+
if cache_client
|
35
|
+
disabled = cache_client.read(self.class.disable_key)
|
36
|
+
|
37
|
+
if disabled == SHOULD_DROP
|
38
|
+
drop("disabled".freeze)
|
39
|
+
elsif disabled == SHOULD_DISABLE
|
40
|
+
reenqueue(DISABLED_REENQUEUE_DELAY, "disabled".freeze)
|
41
|
+
else
|
42
|
+
block.call
|
43
|
+
end
|
44
|
+
else
|
45
|
+
block.call
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module ActiveJob
|
2
|
+
module TrafficControl
|
3
|
+
module Throttle
|
4
|
+
extend ::ActiveSupport::Concern
|
5
|
+
|
6
|
+
class_methods do
|
7
|
+
attr_accessor :job_throttling
|
8
|
+
|
9
|
+
def throttle(threshold:, period:, drop: false, key: nil)
|
10
|
+
raise ArgumentError, "Threshold needs to be an integer > 0" if threshold.to_i < 1
|
11
|
+
@job_throttling = {threshold: threshold, period: period, drop: drop, key: key}
|
12
|
+
end
|
13
|
+
|
14
|
+
def throttling_key
|
15
|
+
if job_throttling
|
16
|
+
@throttling_key ||= job_throttling[:key].present? ? job_throttling[:key] : "traffic_control:throttling:#{cleaned_name}".freeze
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
included do
|
22
|
+
include ActiveJob::TrafficControl::Base
|
23
|
+
|
24
|
+
around_perform do |_, block|
|
25
|
+
if self.class.job_throttling.present?
|
26
|
+
lock_options = {
|
27
|
+
resources: self.class.job_throttling[:threshold],
|
28
|
+
stale_lock_expiration: self.class.job_throttling[:period]
|
29
|
+
}
|
30
|
+
|
31
|
+
with_lock_client(self.class.throttling_key, lock_options) do |client|
|
32
|
+
token = client.lock
|
33
|
+
|
34
|
+
if token
|
35
|
+
block.call
|
36
|
+
elsif self.class.job_throttling[:drop]
|
37
|
+
drop("throttling".freeze)
|
38
|
+
else
|
39
|
+
period = self.class.job_throttling[:period]
|
40
|
+
reenqueue(period...period*5, "throttling".freeze)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
else
|
44
|
+
block.call
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require "active_job"
|
2
|
+
require "active_support/all"
|
3
|
+
require "suo"
|
4
|
+
|
5
|
+
require "active_job/traffic_control/version"
|
6
|
+
|
7
|
+
require "active_job/traffic_control/base"
|
8
|
+
require "active_job/traffic_control/concurrency"
|
9
|
+
require "active_job/traffic_control/disable"
|
10
|
+
require "active_job/traffic_control/throttle"
|
11
|
+
|
12
|
+
module ActiveJob
|
13
|
+
module TrafficControl
|
14
|
+
class << self
|
15
|
+
attr_writer :cache_client
|
16
|
+
attr_accessor :client_klass
|
17
|
+
|
18
|
+
def cache_client
|
19
|
+
@cache_client ||= begin
|
20
|
+
if defined?(Rails.cache)
|
21
|
+
Rails.cache
|
22
|
+
else
|
23
|
+
logger.error "defaulting to `ActiveSupport::Cache::MemoryStore`; please set"\
|
24
|
+
" `ActiveJob::TrafficControl.cache_client` to a `ActiveSupport::Cache` compatible class."
|
25
|
+
ActiveSupport::Cache::MemoryStore.new
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def logger
|
31
|
+
ActiveJob::Base.logger
|
32
|
+
end
|
33
|
+
|
34
|
+
def client
|
35
|
+
@client ||= begin
|
36
|
+
logger.error "defaulting to Redis as the lock client; please set "\
|
37
|
+
" `ActiveJob::TrafficControl.client` to a Redis or Memcached client,"
|
38
|
+
@client_klass = Suo::Client::Redis
|
39
|
+
Redis.new(url: ENV["REDIS_URL"])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def client=(cli)
|
44
|
+
@client = cli
|
45
|
+
|
46
|
+
if client.respond_to?(:checkout) # handle ConnectionPools
|
47
|
+
unwrapped_client = client.checkout
|
48
|
+
@client_klass = client_class_type(unwrapped_client)
|
49
|
+
client.checkin
|
50
|
+
else
|
51
|
+
@client_klass = client_class_type(client)
|
52
|
+
end
|
53
|
+
|
54
|
+
client
|
55
|
+
end
|
56
|
+
|
57
|
+
def client_class_type(client)
|
58
|
+
if client.instance_of?(Dalli::Client)
|
59
|
+
Suo::Client::Memcached
|
60
|
+
elsif client.instance_of?(::Redis)
|
61
|
+
Suo::Client::Redis
|
62
|
+
else
|
63
|
+
raise ArgumentError, "Unsupported client type: #{client}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
metadata
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activejob-traffic_control
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nick Elser
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-06-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activejob
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: suo
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.12'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.12'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: minitest
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '5.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '5.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: codeclimate-test-reporter
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.4.7
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.4.7
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: connection_pool
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: 'Traffic control for ActiveJob: Concurrency/enabling/throttling'
|
126
|
+
email:
|
127
|
+
- nick.elser@gmail.com
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- ".codeclimate.yml"
|
133
|
+
- ".gitignore"
|
134
|
+
- ".rubocop.yml"
|
135
|
+
- ".travis.yml"
|
136
|
+
- Gemfile
|
137
|
+
- README.md
|
138
|
+
- Rakefile
|
139
|
+
- activejob-traffic_control.gemspec
|
140
|
+
- bin/console
|
141
|
+
- bin/setup
|
142
|
+
- lib/active_job/traffic_control.rb
|
143
|
+
- lib/active_job/traffic_control/base.rb
|
144
|
+
- lib/active_job/traffic_control/concurrency.rb
|
145
|
+
- lib/active_job/traffic_control/disable.rb
|
146
|
+
- lib/active_job/traffic_control/throttle.rb
|
147
|
+
- lib/active_job/traffic_control/version.rb
|
148
|
+
homepage: https://www.activejobtrafficcontrol.com
|
149
|
+
licenses: []
|
150
|
+
metadata: {}
|
151
|
+
post_install_message:
|
152
|
+
rdoc_options: []
|
153
|
+
require_paths:
|
154
|
+
- lib
|
155
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
161
|
+
requirements:
|
162
|
+
- - ">="
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
version: '0'
|
165
|
+
requirements: []
|
166
|
+
rubyforge_project:
|
167
|
+
rubygems_version: 2.5.1
|
168
|
+
signing_key:
|
169
|
+
specification_version: 4
|
170
|
+
summary: Traffic control for ActiveJob
|
171
|
+
test_files: []
|
172
|
+
has_rdoc:
|