sidekiq-max-jobs 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9bf624b8b84bbfd78039b22826e440c7218c9850207a55a1a24bfc046a0812a4
4
- data.tar.gz: 364ca8c57ac52151636f7c9a0feb7ffe18d758fd95772fb692b0802fb634bef6
3
+ metadata.gz: 9e52017db21742cf50ed432d2773add32e29a8119341a1d9dc757b9817a3aee9
4
+ data.tar.gz: 3c5609487bb19a7de4c24cbf389e5faf6a54819a61c7fef368562c74bbc22f02
5
5
  SHA512:
6
- metadata.gz: 12548204b50a4f9d3dfec6470d3488b03fc672c7c41c12e6554ed350004ce63b42d322043c21dfc023fbd6fc819a724c5a167d62494a0615be0c8d4e71bfda19
7
- data.tar.gz: 7e704fde12c9f64b200ac001f6824821ec6e0698235d4493e01a30ff49f27e3742373ae38f0ef91c013b5ee08986bf619cb6c2922eff44c79b3898444b4c3984
6
+ metadata.gz: 78b270607d7c393cd62fb73e111332197177310856b43a7ec4496ae69beb08ead4edd8ccdf15ecbb99fa8fe3892bb2a150aac315ee657cbe9070b29058a3ba2c
7
+ data.tar.gz: fb7cb69631840c6082d91333a6b387e1abd2a1173ab71948908b11e0b4a064b7c8a4915bdd2893495581b52b89d351b82ab2146b665e4f21ac5db38aa04dc132
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2020-06-17 22:45:10 -0500 using RuboCop version 0.85.1.
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: 27
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: 152
28
+ Max: 193
29
29
 
30
30
  # Offense count: 1
31
31
  # Configuration parameters: IgnoredMethods.
32
32
  Metrics/CyclomaticComplexity:
33
- Max: 7
33
+ Max: 10
34
34
 
35
35
  # Offense count: 1
36
36
  # Configuration parameters: CountComments, ExcludedMethods.
37
37
  Metrics/MethodLength:
38
- Max: 24
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 max-jobs quota is reached
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: `100`)
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: `(ENV['MAX_JOBS'] || 100).to_i`)
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((ENV['MAX_JOBS_JITTER'] || 1).to_i)`)
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 total jobs processed
82
- is checked first, followed by the jobs processed for the current queue. If your
83
- `Worker(s)` are handling multiple queues it is generally recommended that you
84
- set the total value to the same value as your highest queue value (e.g. if you
85
- had `MAX_JOBS_FOO=100` and `MAX_JOBS_BAR=200` it probably makes sense to set
86
- `MAX_JOBS=200`, if not a little bit lower). Setting the right limits ultimately
87
- depends on the intensity / resource needs of the work being performed. The same
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.5
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 = counter_key
65
+ key = COUNTER_KEY
25
66
  cache[key] ||= 0
26
67
  end
27
68
 
28
69
  def counter_for_queue(queue)
29
- key = counter_for_queue_key(queue)
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 = counter_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 = counter_for_queue_key(queue)
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
- ::Sidekiq.logger.info(message) if defined?(::Sidekiq.logger)
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
- log_info("Max-Jobs middleware enabled, shutting down pid: #{pid} when max-jobs quota is reached")
90
+ message = format(LOG_INITIALIZATION_TEMPLATE, pid)
91
+ log_info(message)
65
92
  end
66
93
 
67
- def max_jobs
68
- key = max_jobs_key
69
- cache[key] ||= (ENV[key] || default_max_jobs).to_i
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 max_jobs_for_queue(queue)
73
- key = max_jobs_for_queue_key(queue)
74
- cache[key] ||= (
75
- ENV[key] ||
76
- ENV[max_jobs_key] ||
77
- default_max_jobs
78
- ).to_i
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 max_jobs_for_queue_key(queue)
82
- "MAX_JOBS_#{queue.upcase}"
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 = max_jobs_jitter_key
87
- cache[key] ||= rand((ENV[key] || default_max_jobs_jitter).to_i)
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 = max_jobs_jitter_for_queue_key(queue)
92
- cache[key] ||= rand(
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 max_jobs_jitter_for_queue_key(queue)
102
- "MAX_JOBS_JITTER_#{queue.upcase}"
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 max_jobs_jitter_key
106
- 'MAX_JOBS_JITTER'
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 max_jobs_key
110
- 'MAX_JOBS'
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 = max_jobs_with_jitter_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 = max_jobs_with_jitter_for_queue_key(queue)
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 = mutex_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 = pid_key
183
+ key = PID_KEY
143
184
  cache[key] ||= ::Process.pid
144
185
  end
145
186
 
146
- def pid_key
147
- 'PID'
187
+ def start_time
188
+ key = START_TIME_KEY
189
+ cache[key] ||= ::Time.now.to_i
148
190
  end
149
191
 
150
- def quota_met?
151
- counter == max_jobs_with_jitter
192
+ def terminate!
193
+ key = TERMINATING_KEY
194
+ cache[key] = true && ::Process.kill(TERM, pid)
152
195
  end
153
196
 
154
- def quota_met_for_queue?(queue)
155
- counter_for_queue(queue) == max_jobs_with_jitter_for_queue(queue)
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
- # job-counter *is not* incremented in the `ensure` block
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
- # First check if the total quota has been met
182
- if self.class.quota_met?
183
- self.class.log_info("Max-Jobs quota met, shutting down pid: #{self.class.pid}")
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
- # Now check if the queue specific quota has been met
191
- if !terminate && self.class.quota_met_for_queue?(queue)
192
- self.class.log_info(%(Max-Jobs quota met for queue: "#{queue}", shutting down pid: #{self.class.pid}))
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, TERMinate the `Process`
197
- ::Process.kill('TERM', self.class.pid) if terminate
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.5
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-18 00:00:00.000000000 Z
11
+ date: 2020-06-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sidekiq