cutoff 0.4.1 → 0.4.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ac27ce016591276e55afa82f64c08de370a969cb6552cc0d5ae69093d3a9e0c
4
- data.tar.gz: 31e989979ae04de967a22e8d2457d6da654862603f380dd7521e415c9220148b
3
+ metadata.gz: d00ff1f831beec4fed582b5faa9ed42c350c13b32217bdcab0b850445ba9c358
4
+ data.tar.gz: 77d466db54a822240d2d27cfdb120f98aa7f7435ec62ae15191292702580de79
5
5
  SHA512:
6
- metadata.gz: 4241c63fc4c647c44860aa0781e2966a5eadc1d7edea259851018308d1877d664c46a8d90bd5553f725d2e1fa5f767995b0f27327482bf2ce5128701908aa684
7
- data.tar.gz: 64d599559055f6d8b3c384b88b5268b02317784bd44fd6be2384fd3c56b1f9da5a9cfb435c4af05c083ba20af6d8865f0c73a8fc46afff261ad70d43fc9c35e4
6
+ metadata.gz: 9821d7010a4663ce25497a129c5a04166b5f701bd30d1d7a20f183cd274444605f3ac14099857c22117d45ac980056f4ccbaf09e8b65403e83a93abc923ced90
7
+ data.tar.gz: ec1f98a8885f8938ba421d87cb82f7f16a79161a778d2e4c230204f2243009b001e4c5de8c95ca1153746a8eebb90f969ca5efe80390cd08f901ee943a06581f
data/CHANGELOG.md CHANGED
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.2] - 2021-10-14
11
+
12
+ - Add sidekiq middleware
13
+ - Select checkpoints to enable or disable
14
+
10
15
  ## [0.4.1] - 2021-10-02
11
16
 
12
17
  - Fix Net::HTTP patch to override timeouts given to start
@@ -34,7 +39,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
34
39
  - Mysql2 patch
35
40
 
36
41
  [Unreleased]: https://github.com/justinhoward/cutoff/compare/v0.4.1...HEAD
37
- [0.4.0]: https://github.com/justinhoward/cutoff/compare/v0.4.0...v0.4.1
42
+ [0.4.2]: https://github.com/justinhoward/cutoff/compare/v0.4.1...v0.4.2
43
+ [0.4.1]: https://github.com/justinhoward/cutoff/compare/v0.4.0...v0.4.1
38
44
  [0.4.0]: https://github.com/justinhoward/cutoff/compare/v0.3.0...v0.4.0
39
45
  [0.3.0]: https://github.com/justinhoward/cutoff/compare/v0.2.0...v0.3.0
40
46
  [0.2.0]: https://github.com/justinhoward/cutoff/compare/v0.1.0...v0.2.0
data/README.md CHANGED
@@ -217,6 +217,29 @@ Cutoff.wrap(3) do
217
217
  end
218
218
  ```
219
219
 
220
+ Selecting Checkpoints
221
+ ---------------------------
222
+
223
+ In some cases, you may want to select some checkpoints to use, but not others.
224
+ For example, you may want to run some code that contains MySQL queries, but not
225
+ use the mysql2 patch. The `exclude` and `only` options support this.
226
+
227
+ ```ruby
228
+ Cutoff.wrap(10, exclude: :mysql2) do
229
+ # The mysql2 patch won't be used here
230
+ end
231
+
232
+ Cutoff.wrap(10, only: %i[foo bar]) do
233
+ # These checkpoints will be used
234
+ Cutoff.checkpoint!(:foo)
235
+ Cutoff.checkpoint!(:bar)
236
+
237
+ # These checkpoints will be skipped
238
+ Cutoff.checkpoint!(:asdf)
239
+ Cutoff.checkpoint!
240
+ end
241
+ ```
242
+
220
243
  Timing a Rails Controller
221
244
  ---------------------------
222
245
 
@@ -277,6 +300,26 @@ class ApplicationController < ActionController::Base
277
300
  end
278
301
  ```
279
302
 
303
+ Timing Sidekiq Workers
304
+ ------------
305
+
306
+ If Sidekiq is loaded, Cutoff includes middleware to support a `:cutoff` option.
307
+
308
+ ```ruby
309
+ class MyWorker
310
+ include Sidekiq::Worker
311
+
312
+ sidekiq_options cutoff: 6.0
313
+
314
+ def perform
315
+ # ...
316
+ Cutoff.checkpoint!
317
+ # ...
318
+ end
319
+ end
320
+ ```
321
+
322
+
280
323
  Disabling Cutoff for Testing and Development
281
324
  ------------
282
325
 
@@ -6,7 +6,8 @@ require 'mysql2'
6
6
  class Cutoff
7
7
  module Patch
8
8
  # Sets the max execution time for SELECT queries if there is an active
9
- # cutoff and it has time remaining
9
+ # cutoff and it has time remaining. You can select this patch with
10
+ # `exclude` or `only` using the checkpoint name `:mysql2`.
10
11
  module Mysql2
11
12
  # Overrides `Mysql2::Client#query` to insert a MAX_EXECUTION_TIME query
12
13
  # hint with the remaining cutoff time
@@ -19,9 +20,9 @@ class Cutoff
19
20
  # be executed in this case.
20
21
  def query(sql, options = {})
21
22
  cutoff = Cutoff.current
22
- return super unless cutoff
23
+ return super unless cutoff&.selected?(:mysql2)
23
24
 
24
- cutoff.checkpoint!
25
+ cutoff.checkpoint!(:mysql2)
25
26
  sql = QueryWithMaxTime.new(sql, cutoff.ms_remaining.ceil).to_s
26
27
  super
27
28
  end
@@ -4,6 +4,9 @@ require 'net/http'
4
4
 
5
5
  class Cutoff
6
6
  module Patch
7
+ # Set checkpoints for Ruby HTTP requests. Also sets the Net::HTTP timeouts
8
+ # to the remaining cutoff time. You can select this patch with
9
+ # `exclude` or `only` using the checkpoint name `:net_http`.
7
10
  module NetHttp
8
11
  def self.gen_timeout_method(name)
9
12
  <<~RUBY
@@ -23,13 +26,13 @@ class Cutoff
23
26
  # @see Net::HTTP#start
24
27
  module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
25
28
  def start
26
- if (cutoff = Cutoff.current)
29
+ if (cutoff = Cutoff.current) && cutoff.selected?(:net_http)
27
30
  remaining = cutoff.seconds_remaining
28
31
  #{gen_timeout_method('open_timeout')}
29
32
  #{gen_timeout_method('read_timeout')}
30
33
  #{gen_timeout_method('write_timeout') if use_write_timeout?}
31
34
  #{gen_timeout_method('continue_timeout')}
32
- Cutoff.checkpoint!
35
+ Cutoff.checkpoint!(:net_http)
33
36
  end
34
37
  super
35
38
  end
data/lib/cutoff/rails.rb CHANGED
@@ -1,7 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Namespace for Rails integration
4
- module Rails
5
- end
6
-
7
3
  require 'cutoff/rails/controller' if Gem.loaded_specs['actionpack']
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sidekiq'
4
+
5
+ class Cutoff
6
+ module Sidekiq
7
+ # Add an option `cutoff` for sidekiq workers
8
+ #
9
+ # @example
10
+ # class MyWorker
11
+ # include Sidekiq::Worker
12
+ #
13
+ # sidekiq_options cutoff: 6.0
14
+ #
15
+ # def perform
16
+ # # ...
17
+ # end
18
+ # end
19
+ class ServerMiddleware
20
+ # @param worker [Object] the worker instance
21
+ # @param _job [Hash] the full job payload
22
+ # @param _queue [String] queue the name of the queue the job was pulled
23
+ # from
24
+ # @yield the next middleware in the chain or worker `perform` method
25
+ # @return [void]
26
+ def call(worker, _job, _queue)
27
+ allowed_seconds = worker.class.sidekiq_options['cutoff']
28
+ return yield if allowed_seconds.nil?
29
+
30
+ Cutoff.wrap(allowed_seconds) { yield }
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ ::Sidekiq.configure_server do |config|
37
+ config.server_middleware do |chain|
38
+ chain.add(Cutoff::Sidekiq::ServerMiddleware)
39
+ end
40
+ end
@@ -3,6 +3,6 @@
3
3
  class Cutoff
4
4
  # @return [Gem::Version] The current version of the cutoff gem
5
5
  def self.version
6
- Gem::Version.new('0.4.1')
6
+ Gem::Version.new('0.4.2')
7
7
  end
8
8
  end
data/lib/cutoff.rb CHANGED
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal:true
2
2
 
3
+ require 'set'
4
+
3
5
  require 'cutoff/version'
4
6
  require 'cutoff/error'
5
7
  require 'cutoff/patch'
6
8
  require 'cutoff/timer'
9
+
7
10
  require 'cutoff/rails'
11
+ require 'cutoff/sidekiq' if Gem.loaded_specs['sidekiq']
8
12
 
9
13
  class Cutoff
10
14
  CURRENT_STACK_KEY = 'cutoff_deadline_stack'
@@ -25,13 +29,14 @@ class Cutoff
25
29
  # If a cutoff is already started for this thread, then `start` uses the
26
30
  # minimum of the current remaining time and the given time
27
31
  #
28
- # @param seconds [Float, Integer] The number of seconds for the cutoff. May
29
- # be overridden if there is an active cutoff and it has less remaining
30
- # time.
32
+ # @param (see #initialize)
31
33
  # @return [Cutoff] The {Cutoff} instance
32
- def start(seconds)
33
- seconds = [seconds, current.seconds_remaining].min if current
34
- cutoff = Cutoff.new(seconds)
34
+ def start(allowed_seconds, **options)
35
+ if current
36
+ allowed_seconds = [allowed_seconds, current.seconds_remaining].min
37
+ end
38
+
39
+ cutoff = new(allowed_seconds, **options)
35
40
  Thread.current[CURRENT_STACK_KEY] ||= []
36
41
  Thread.current[CURRENT_STACK_KEY] << cutoff
37
42
  cutoff
@@ -68,9 +73,10 @@ class Cutoff
68
73
  #
69
74
  # @see .start
70
75
  # @see .stop
76
+ # @param (see #initialize)
71
77
  # @return The value that returned from the block
72
- def wrap(seconds)
73
- cutoff = start(seconds)
78
+ def wrap(allowed_seconds, **options)
79
+ cutoff = start(allowed_seconds, **options)
74
80
  yield cutoff
75
81
  ensure
76
82
  stop(cutoff)
@@ -82,11 +88,11 @@ class Cutoff
82
88
  #
83
89
  # @raise CutoffExceededError If there is an active expired cutoff
84
90
  # @return [void]
85
- def checkpoint!
91
+ def checkpoint!(name = nil)
86
92
  cutoff = current
87
93
  return unless cutoff
88
94
 
89
- cutoff.checkpoint!
95
+ cutoff.checkpoint!(name)
90
96
  end
91
97
 
92
98
  # Disable Cutoff globally. Useful for testing and debugging
@@ -107,7 +113,7 @@ class Cutoff
107
113
  @disabled = false
108
114
  end
109
115
 
110
- # True if cutoff was disabled with {#disable!}
116
+ # True if cutoff was disabled with {.disable!}
111
117
  #
112
118
  # @return [Boolean] True if disabled
113
119
  def disabled?
@@ -122,10 +128,22 @@ class Cutoff
122
128
  #
123
129
  # The timer starts immediately upon creation
124
130
  #
125
- # @param allowed_seconds [Integer, Float] The total number of seconds to allow
126
- def initialize(allowed_seconds)
131
+ # @param allowed_seconds [Float, Integer] The total number of seconds to allow
132
+ # @param exclude [Enumberable<Symbol>, Symbol, nil] If given a name or
133
+ # list of checkpoint names to skip
134
+ # @param only [Enumberable<Symbol>, Symbol, nil] If given a name or
135
+ # list of checkpoint names to allow
136
+ def initialize(allowed_seconds, exclude: nil, only: nil)
127
137
  @allowed_seconds = allowed_seconds.to_f
128
138
  @start_time = Cutoff.now
139
+
140
+ if exclude
141
+ @exclude = Set.new(exclude.is_a?(Enumerable) ? exclude : [exclude])
142
+ end
143
+
144
+ if only
145
+ @only = Set.new(only.is_a?(Enumerable) ? only : [only])
146
+ end
129
147
  end
130
148
 
131
149
  # The number of seconds left on the clock
@@ -162,9 +180,29 @@ class Cutoff
162
180
  #
163
181
  # @raise CutoffExceededError If there is an active expired cutoff
164
182
  # @return [void]
165
- def checkpoint!
183
+ def checkpoint!(name = nil)
184
+ unless name.nil? || name.is_a?(Symbol)
185
+ raise ArgumentError, 'name must be a symbol'
186
+ end
187
+
188
+ return unless selected?(name)
166
189
  raise CutoffExceededError, self if exceeded?
167
190
 
168
191
  nil
169
192
  end
193
+
194
+ # True if the named checkpoint is selected by the `exclude` and `only`
195
+ # options. If these options are not given, a checkpoint is considered to be
196
+ # selected. If the checkpoint is not named, it is also considered to be
197
+ # selected.
198
+ #
199
+ # @param name [Symbol, nil] The name of the checkpoint
200
+ # @return [Boolean] True if the checkpoint is selected
201
+ def selected?(name)
202
+ return true if name.nil? && @exclude
203
+ return false if @exclude&.include?(name)
204
+ return false if @only && !@only.include?(name)
205
+
206
+ true
207
+ end
170
208
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cutoff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Howard
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-02 00:00:00.000000000 Z
11
+ date: 2021-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -104,6 +104,7 @@ files:
104
104
  - lib/cutoff/patch/net_http.rb
105
105
  - lib/cutoff/rails.rb
106
106
  - lib/cutoff/rails/controller.rb
107
+ - lib/cutoff/sidekiq.rb
107
108
  - lib/cutoff/timer.rb
108
109
  - lib/cutoff/version.rb
109
110
  homepage: https://github.com/justinhoward/cutoff
@@ -111,7 +112,7 @@ licenses:
111
112
  - MIT
112
113
  metadata:
113
114
  changelog_uri: https://github.com/justinhoward/cutoff/blob/master/CHANGELOG.md
114
- documentation_uri: https://www.rubydoc.info/gems/cutoff/0.4.1
115
+ documentation_uri: https://www.rubydoc.info/gems/cutoff/0.4.2
115
116
  post_install_message:
116
117
  rdoc_options: []
117
118
  require_paths: