sidekiq-max-jobs 0.0.5 → 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 +4 -4
- data/.rubocop_todo.yml +10 -5
- data/README.md +19 -13
- data/VERSION +1 -1
- data/lib/sidekiq/middleware/server/max_jobs.rb +135 -83
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e52017db21742cf50ed432d2773add32e29a8119341a1d9dc757b9817a3aee9
|
4
|
+
data.tar.gz: 3c5609487bb19a7de4c24cbf389e5faf6a54819a61c7fef368562c74bbc22f02
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78b270607d7c393cd62fb73e111332197177310856b43a7ec4496ae69beb08ead4edd8ccdf15ecbb99fa8fe3892bb2a150aac315ee657cbe9070b29058a3ba2c
|
7
|
+
data.tar.gz: fb7cb69631840c6082d91333a6b387e1abd2a1173ab71948908b11e0b4a064b7c8a4915bdd2893495581b52b89d351b82ab2146b665e4f21ac5db38aa04dc132
|
data/.rubocop_todo.yml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config`
|
3
|
-
# on 2020-06-
|
3
|
+
# on 2020-06-21 09:12:56 -0500 using RuboCop version 0.85.1.
|
4
4
|
# The point is for the user to remove these configuration records
|
5
5
|
# one by one as the offenses are removed from the code base.
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
@@ -14,7 +14,7 @@ Lint/RescueException:
|
|
14
14
|
# Offense count: 1
|
15
15
|
# Configuration parameters: IgnoredMethods.
|
16
16
|
Metrics/AbcSize:
|
17
|
-
Max:
|
17
|
+
Max: 31
|
18
18
|
|
19
19
|
# Offense count: 1
|
20
20
|
# Configuration parameters: CountComments, ExcludedMethods.
|
@@ -25,17 +25,22 @@ Metrics/BlockLength:
|
|
25
25
|
# Offense count: 1
|
26
26
|
# Configuration parameters: CountComments.
|
27
27
|
Metrics/ClassLength:
|
28
|
-
Max:
|
28
|
+
Max: 193
|
29
29
|
|
30
30
|
# Offense count: 1
|
31
31
|
# Configuration parameters: IgnoredMethods.
|
32
32
|
Metrics/CyclomaticComplexity:
|
33
|
-
Max:
|
33
|
+
Max: 10
|
34
34
|
|
35
35
|
# Offense count: 1
|
36
36
|
# Configuration parameters: CountComments, ExcludedMethods.
|
37
37
|
Metrics/MethodLength:
|
38
|
-
Max:
|
38
|
+
Max: 28
|
39
|
+
|
40
|
+
# Offense count: 1
|
41
|
+
# Configuration parameters: IgnoredMethods.
|
42
|
+
Metrics/PerceivedComplexity:
|
43
|
+
Max: 10
|
39
44
|
|
40
45
|
# Offense count: 1
|
41
46
|
# Cop supports --auto-correct.
|
data/README.md
CHANGED
@@ -52,7 +52,7 @@ end
|
|
52
52
|
If everything above is successful the next time you start your worker you will
|
53
53
|
see a message like the following:
|
54
54
|
```bash
|
55
|
-
2020-06-10T00:23:31.789Z pid=73703 tid=oxifk6l13 INFO: Max-Jobs middleware enabled, shutting down pid: 73703 when
|
55
|
+
2020-06-10T00:23:31.789Z pid=73703 tid=oxifk6l13 INFO: Max-Jobs middleware enabled, shutting down pid: 73703 when quota is reached
|
56
56
|
```
|
57
57
|
|
58
58
|
Configuration Options
|
@@ -62,30 +62,36 @@ Above we covered how to get started, but that's only the beginning. There are a
|
|
62
62
|
few configuration options available to you to customize the middleware's
|
63
63
|
behavior (currently only configurable via the environment):
|
64
64
|
|
65
|
-
* `MAX_JOBS`: The number of jobs to process before terminating (default: `
|
65
|
+
* `MAX_JOBS`: The number of jobs to process before terminating (default: `500`)
|
66
66
|
* `MAX_JOBS_JITTER`: Used as the upper-bound for calculating a random number
|
67
67
|
between 1 and the value specified. This value is added to the `MAX_JOBS` value,
|
68
68
|
mentioned above, to decrease the likelihood that all of your `Worker(s)`
|
69
|
-
restart at / around the same time (default: `rand(1)`)
|
69
|
+
restart at / around the same time (default: `rand(-1)`)
|
70
70
|
* `MAX_JOBS_<QUEUE>`: The number of jobs to process for a specific queue before
|
71
|
-
terminating (default: `
|
71
|
+
terminating (default: `-1`)
|
72
72
|
* `MAX_JOBS_JITTER_<QUEUE>`: Used as the upper-bound for calculating a random
|
73
73
|
number between 1 and the value specified. This value is added to the
|
74
74
|
`MAX_JOBS_<QUEUE>` value, mentioned above, to decreased the likelihood that all
|
75
75
|
of your `Worker(s)` restart at / around the same time (default:
|
76
|
-
`rand(
|
76
|
+
`rand(-1)`)
|
77
|
+
* `MAX_JOBS_RUNTIME`: The total time in seconds to run before terminating
|
78
|
+
(default: `-1`)
|
79
|
+
* `MAX_JOBS_RUNTIME_JITTER`: Used as the upper-bound for calculating a random
|
80
|
+
number between 1 and the value specified. This value is added to the
|
81
|
+
`MAX_JOBS_RUNTIME` value, mentioned above, to decrease the likelihood that all
|
82
|
+
of your `Worker(s)` restart at / around the same time (default: `rand(-1)`)
|
77
83
|
|
78
84
|
Important Note
|
79
85
|
--------------
|
80
86
|
|
81
|
-
When determining if the max-job quota has been reached the
|
82
|
-
|
83
|
-
`Worker(s)` are handling multiple queues it is
|
84
|
-
set the total value to the same value as your highest queue
|
85
|
-
had `MAX_JOBS_FOO=100` and `MAX_JOBS_BAR=200` it probably
|
86
|
-
`MAX_JOBS=200`, if not a little bit lower). Setting the right
|
87
|
-
depends on the intensity / resource needs of the work being
|
88
|
-
rule of thumb applies to `MAX_JOBS_JITTER` as well.
|
87
|
+
When determining if the max-job quota has been reached the runtime is checked
|
88
|
+
first, followed by the total jobs processed, followed by the jobs processed for
|
89
|
+
the current queue. If your `Worker(s)` are handling multiple queues it is
|
90
|
+
recommended that you set the total value to the same value as your highest queue
|
91
|
+
value (e.g. if you had `MAX_JOBS_FOO=100` and `MAX_JOBS_BAR=200` it probably
|
92
|
+
makes sense to set `MAX_JOBS=200`, if not a little bit lower). Setting the right
|
93
|
+
limits ultimately depends on the intensity / resource needs of the work being
|
94
|
+
performed. The same rule of thumb applies to `MAX_JOBS_JITTER` as well.
|
89
95
|
|
90
96
|
Contributing
|
91
97
|
------------
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0
|
1
|
+
0.1.0
|
@@ -4,6 +4,8 @@ module Sidekiq
|
|
4
4
|
module Middleware
|
5
5
|
module Server
|
6
6
|
class MaxJobs
|
7
|
+
# Version
|
8
|
+
|
7
9
|
VERSION = File.read(
|
8
10
|
File.join(
|
9
11
|
File.dirname(__FILE__),
|
@@ -16,143 +18,185 @@ module Sidekiq
|
|
16
18
|
).strip
|
17
19
|
|
18
20
|
class << self
|
21
|
+
# Constant(s)
|
22
|
+
|
23
|
+
COUNTER_FOR_QUEUE_KEY_TEMPLATE = 'COUNTER_%s'
|
24
|
+
COUNTER_KEY = 'COUNTER'
|
25
|
+
LOG_INITIALIZATION_TEMPLATE = \
|
26
|
+
'Max-Jobs middleware enabled, shutting down pid: %d when quota is reached'
|
27
|
+
LOG_MAX_JOBS_QUOTA_MET_FOR_QUEUE_TEMPLATE = \
|
28
|
+
'Max-Jobs queue quota met for: "%s", shutting down pid: %d'
|
29
|
+
LOG_MAX_JOBS_QUOTA_MET_TEMPLATE = \
|
30
|
+
'Max-Jobs total quota met, shutting down pid: %d'
|
31
|
+
LOG_MAX_JOBS_RUNTIME_QUOTA_MET_TEMPLATE = \
|
32
|
+
'Max-Jobs runtime quota met, shutting down pid: %d'
|
33
|
+
MAX_JOBS_FOR_QUEUE_KEY_TEMPLATE = 'MAX_JOBS_%s'
|
34
|
+
MAX_JOBS_JITTER_FOR_QUEUE_KEY_TEMPLATE = 'MAX_JOBS_JITTER_%s'
|
35
|
+
MAX_JOBS_JITTER_KEY = 'MAX_JOBS_JITTER'
|
36
|
+
MAX_JOBS_KEY = 'MAX_JOBS'
|
37
|
+
MAX_JOBS_RUNTIME_JITTER_KEY = 'MAX_JOBS_RUNTIME_JITTER'
|
38
|
+
MAX_JOBS_RUNTIME_WITH_JITTER_KEY = 'MAX_JOBS_RUNTIME_WITH_JITTER'
|
39
|
+
MAX_JOBS_RUNTIME_KEY = 'MAX_JOBS_RUNTIME'
|
40
|
+
MAX_JOBS_WITH_JITTER_FOR_QUEUE_KEY_TEMPLATE = \
|
41
|
+
'MAX_JOBS_WITH_JITTER_%s'
|
42
|
+
MAX_JOBS_WITH_JITTER_KEY = 'MAX_JOBS_WITH_JITTER'
|
43
|
+
MUTEX_KEY = 'MUTEX'
|
44
|
+
PID_KEY = 'PID'
|
45
|
+
START_TIME_KEY = 'START_TIME'
|
46
|
+
TERM = 'TERM'
|
47
|
+
TERMINATING_KEY = 'TERMINATING'
|
48
|
+
|
49
|
+
# Default(s)
|
50
|
+
|
51
|
+
DEFAULT_MAX_JOBS = 500
|
52
|
+
DEFAULT_MAX_JOBS_FOR_QUEUE = -1
|
53
|
+
DEFAULT_MAX_JOBS_JITTER = -1
|
54
|
+
DEFAULT_MAX_JOBS_JITTER_FOR_QUEUE = -1
|
55
|
+
DEFAULT_MAX_JOBS_RUNTIME = -1
|
56
|
+
DEFAULT_MAX_JOBS_RUNTIME_JITTER = -1
|
57
|
+
|
58
|
+
# Helper Method(s)
|
59
|
+
|
19
60
|
def cache
|
20
61
|
@cache ||= {}
|
21
62
|
end
|
22
63
|
|
23
64
|
def counter
|
24
|
-
key =
|
65
|
+
key = COUNTER_KEY
|
25
66
|
cache[key] ||= 0
|
26
67
|
end
|
27
68
|
|
28
69
|
def counter_for_queue(queue)
|
29
|
-
key =
|
70
|
+
key = format(COUNTER_FOR_QUEUE_KEY_TEMPLATE, queue.upcase)
|
30
71
|
cache[key] ||= 0
|
31
72
|
end
|
32
73
|
|
33
|
-
def counter_for_queue_key(queue)
|
34
|
-
"COUNTER_#{queue.upcase}"
|
35
|
-
end
|
36
|
-
|
37
|
-
def counter_key
|
38
|
-
'COUNTER'
|
39
|
-
end
|
40
|
-
|
41
|
-
def default_max_jobs
|
42
|
-
100
|
43
|
-
end
|
44
|
-
|
45
|
-
def default_max_jobs_jitter
|
46
|
-
1
|
47
|
-
end
|
48
|
-
|
49
74
|
def increment_counter!
|
50
|
-
key =
|
75
|
+
key = COUNTER_KEY
|
51
76
|
cache[key] = (cache[key] || 0).next
|
52
77
|
end
|
53
78
|
|
54
79
|
def increment_counter_for_queue!(queue)
|
55
|
-
key =
|
80
|
+
key = format(COUNTER_FOR_QUEUE_KEY_TEMPLATE, queue.upcase)
|
56
81
|
cache[key] = (cache[key] || 0).next
|
57
82
|
end
|
58
83
|
|
59
84
|
def log_info(message)
|
60
|
-
|
85
|
+
logger_defined = defined?(::Sidekiq.logger)
|
86
|
+
logger_defined ? ::Sidekiq.logger.info(message) : puts(message)
|
61
87
|
end
|
62
88
|
|
63
89
|
def log_initialization!
|
64
|
-
|
90
|
+
message = format(LOG_INITIALIZATION_TEMPLATE, pid)
|
91
|
+
log_info(message)
|
65
92
|
end
|
66
93
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
94
|
+
def log_max_jobs_quota_met!
|
95
|
+
message = format(LOG_MAX_JOBS_QUOTA_MET_TEMPLATE, pid)
|
96
|
+
log_info(message)
|
70
97
|
end
|
71
98
|
|
72
|
-
def
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
)
|
99
|
+
def log_max_jobs_quota_met_for_queue!(queue)
|
100
|
+
message = format(
|
101
|
+
LOG_MAX_JOBS_QUOTA_MET_FOR_QUEUE_TEMPLATE,
|
102
|
+
queue,
|
103
|
+
pid
|
104
|
+
)
|
105
|
+
log_info(message)
|
106
|
+
end
|
107
|
+
|
108
|
+
def log_max_jobs_runtime_quota_met!
|
109
|
+
message = format(LOG_MAX_JOBS_RUNTIME_QUOTA_MET_TEMPLATE, pid)
|
110
|
+
log_info(message)
|
79
111
|
end
|
80
112
|
|
81
|
-
def
|
82
|
-
|
113
|
+
def max_jobs
|
114
|
+
key = MAX_JOBS_KEY
|
115
|
+
cache[key] ||= (ENV[key] || DEFAULT_MAX_JOBS).to_i
|
116
|
+
end
|
117
|
+
|
118
|
+
def max_jobs_for_queue(queue)
|
119
|
+
key = format(MAX_JOBS_FOR_QUEUE_KEY_TEMPLATE, queue.upcase)
|
120
|
+
cache[key] ||= (ENV[key] || DEFAULT_MAX_JOBS_FOR_QUEUE).to_i
|
83
121
|
end
|
84
122
|
|
85
123
|
def max_jobs_jitter
|
86
|
-
key =
|
87
|
-
cache[key] ||= rand((ENV[key] ||
|
124
|
+
key = MAX_JOBS_JITTER_KEY
|
125
|
+
cache[key] ||= rand((ENV[key] || DEFAULT_MAX_JOBS_JITTER).to_i)
|
88
126
|
end
|
89
127
|
|
90
128
|
def max_jobs_jitter_for_queue(queue)
|
91
|
-
key =
|
92
|
-
cache[key] ||=
|
93
|
-
(
|
94
|
-
ENV[key] ||
|
95
|
-
ENV[max_jobs_jitter_key] ||
|
96
|
-
default_max_jobs_jitter
|
97
|
-
).to_i
|
98
|
-
)
|
129
|
+
key = format(MAX_JOBS_JITTER_FOR_QUEUE_KEY_TEMPLATE, queue.upcase)
|
130
|
+
cache[key] ||= \
|
131
|
+
rand((ENV[key] || DEFAULT_MAX_JOBS_JITTER_FOR_QUEUE).to_i)
|
99
132
|
end
|
100
133
|
|
101
|
-
def
|
102
|
-
|
134
|
+
def max_jobs_quota_met?
|
135
|
+
quota = max_jobs_with_jitter
|
136
|
+
quota.positive? ? counter == quota : false
|
137
|
+
end
|
138
|
+
|
139
|
+
def max_jobs_quota_met_for_queue?(queue)
|
140
|
+
quota = max_jobs_with_jitter_for_queue(queue)
|
141
|
+
quota.positive? ? counter_for_queue(queue) == quota : false
|
142
|
+
end
|
143
|
+
|
144
|
+
def max_jobs_runtime
|
145
|
+
key = MAX_JOBS_RUNTIME_KEY
|
146
|
+
cache[key] ||= (ENV[key] || DEFAULT_MAX_JOBS_RUNTIME).to_i
|
147
|
+
end
|
148
|
+
|
149
|
+
def max_jobs_runtime_jitter
|
150
|
+
key = MAX_JOBS_RUNTIME_JITTER_KEY
|
151
|
+
cache[key] ||= \
|
152
|
+
rand((ENV[key] || DEFAULT_MAX_JOBS_RUNTIME_JITTER).to_i)
|
103
153
|
end
|
104
154
|
|
105
|
-
def
|
106
|
-
|
155
|
+
def max_jobs_runtime_quota_met?
|
156
|
+
quota = max_jobs_runtime_with_jitter
|
157
|
+
quota.positive? ? (::Time.now.to_i - start_time) >= quota : false
|
107
158
|
end
|
108
159
|
|
109
|
-
def
|
110
|
-
|
160
|
+
def max_jobs_runtime_with_jitter
|
161
|
+
key = MAX_JOBS_RUNTIME_WITH_JITTER_KEY
|
162
|
+
cache[key] ||= (max_jobs_runtime + max_jobs_runtime_jitter)
|
111
163
|
end
|
112
164
|
|
113
165
|
def max_jobs_with_jitter
|
114
|
-
key =
|
166
|
+
key = MAX_JOBS_WITH_JITTER_KEY
|
115
167
|
cache[key] ||= (max_jobs + max_jobs_jitter)
|
116
168
|
end
|
117
169
|
|
118
170
|
def max_jobs_with_jitter_for_queue(queue)
|
119
|
-
key =
|
171
|
+
key = \
|
172
|
+
format(MAX_JOBS_WITH_JITTER_FOR_QUEUE_KEY_TEMPLATE, queue.upcase)
|
120
173
|
cache[key] ||= \
|
121
174
|
(max_jobs_for_queue(queue) + max_jobs_jitter_for_queue(queue))
|
122
175
|
end
|
123
176
|
|
124
|
-
def max_jobs_with_jitter_for_queue_key(queue)
|
125
|
-
"MAX_JOBS_WITH_JITTER_#{queue.upcase}"
|
126
|
-
end
|
127
|
-
|
128
|
-
def max_jobs_with_jitter_key
|
129
|
-
'MAX_JOBS_WITH_JITTER'
|
130
|
-
end
|
131
|
-
|
132
177
|
def mutex
|
133
|
-
key =
|
178
|
+
key = MUTEX_KEY
|
134
179
|
cache[key] ||= ::Mutex.new
|
135
180
|
end
|
136
181
|
|
137
|
-
def mutex_key
|
138
|
-
'MUTEX'
|
139
|
-
end
|
140
|
-
|
141
182
|
def pid
|
142
|
-
key =
|
183
|
+
key = PID_KEY
|
143
184
|
cache[key] ||= ::Process.pid
|
144
185
|
end
|
145
186
|
|
146
|
-
def
|
147
|
-
|
187
|
+
def start_time
|
188
|
+
key = START_TIME_KEY
|
189
|
+
cache[key] ||= ::Time.now.to_i
|
148
190
|
end
|
149
191
|
|
150
|
-
def
|
151
|
-
|
192
|
+
def terminate!
|
193
|
+
key = TERMINATING_KEY
|
194
|
+
cache[key] = true && ::Process.kill(TERM, pid)
|
152
195
|
end
|
153
196
|
|
154
|
-
def
|
155
|
-
|
197
|
+
def terminating?
|
198
|
+
key = TERMINATING_KEY
|
199
|
+
cache[key] == true
|
156
200
|
end
|
157
201
|
end
|
158
202
|
|
@@ -165,36 +209,44 @@ module Sidekiq
|
|
165
209
|
begin
|
166
210
|
yield
|
167
211
|
rescue Exception
|
168
|
-
# Set the `exception_raised` boolean to `true` so that the
|
169
|
-
#
|
212
|
+
# Set the `exception_raised` boolean to `true` so that the counter
|
213
|
+
# *is not* incremented in the `ensure` block
|
170
214
|
exception_raised = true
|
171
215
|
# Re-raise the `Exception` so that _Sidekiq_ can deal w/ it
|
172
216
|
raise
|
173
217
|
ensure
|
174
|
-
if !exception_raised
|
218
|
+
if !exception_raised && !self.class.terminating?
|
175
219
|
self.class.mutex.synchronize do
|
220
|
+
# Controls whether or not the process will be TERMinated at the
|
221
|
+
# end of the block
|
176
222
|
terminate = false
|
177
223
|
|
224
|
+
# First check if the runtime quota has been met
|
225
|
+
if self.class.max_jobs_runtime_quota_met?
|
226
|
+
self.class.log_max_jobs_runtime_quota_met!
|
227
|
+
terminate = true
|
228
|
+
end
|
229
|
+
|
178
230
|
# Increment the total counter
|
179
231
|
self.class.increment_counter!
|
180
232
|
|
181
|
-
#
|
182
|
-
if self.class.
|
183
|
-
self.class.
|
233
|
+
# Next, check if the total quota has been met
|
234
|
+
if !terminate && self.class.max_jobs_quota_met?
|
235
|
+
self.class.log_max_jobs_quota_met!
|
184
236
|
terminate = true
|
185
237
|
end
|
186
238
|
|
187
239
|
# Increment the queue specific counter
|
188
240
|
self.class.increment_counter_for_queue!(queue)
|
189
241
|
|
190
|
-
#
|
191
|
-
if !terminate && self.class.
|
192
|
-
self.class.
|
242
|
+
# Last[ly], check if the queue quota has been met
|
243
|
+
if !terminate && self.class.max_jobs_quota_met_for_queue?(queue)
|
244
|
+
self.class.log_max_jobs_quota_met_for_queue!(queue)
|
193
245
|
terminate = true
|
194
246
|
end
|
195
247
|
|
196
|
-
# If applicable,
|
197
|
-
|
248
|
+
# If applicable, terminate
|
249
|
+
self.class.terminate! if terminate
|
198
250
|
end
|
199
251
|
end
|
200
252
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidekiq-max-jobs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan W. Zaleski
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-06-
|
11
|
+
date: 2020-06-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sidekiq
|