Class: Cutoff

Inherits:
Object
  • Object
show all
Extended by:
Timer
Defined in:
lib/cutoff/error.rb,
lib/cutoff.rb,
lib/cutoff/patch.rb,
lib/cutoff/timer.rb,
lib/cutoff/sidekiq.rb,
lib/cutoff/version.rb,
lib/cutoff/patch/mysql2.rb,
lib/cutoff/patch/net_http.rb,
lib/cutoff/rails/controller.rb

Overview

frozen_string_literal:true

Defined Under Namespace

Modules: CutoffError, Patch, Rails, Sidekiq, Timer Classes: CutoffExceededError

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Timer

now

Constructor Details

#initialize(allowed_seconds, exclude: nil, only: nil) ⇒ Cutoff

Create a new cutoff

The timer starts immediately upon creation

Parameters:

  • allowed_seconds (Float, Integer)

    The total number of seconds to allow

  • exclude (Enumberable<Symbol>, Symbol, nil) (defaults to: nil)

    If given a name or list of checkpoint names to skip

  • only (Enumberable<Symbol>, Symbol, nil) (defaults to: nil)

    If given a name or list of checkpoint names to allow



137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/cutoff.rb', line 137

def initialize(allowed_seconds, exclude: nil, only: nil)
  @allowed_seconds = allowed_seconds.to_f
  @start_time = Cutoff.now

  if exclude
    @exclude = Set.new(exclude.is_a?(Enumerable) ? exclude : [exclude])
  end

  if only
    @only = Set.new(only.is_a?(Enumerable) ? only : [only])
  end
end

Instance Attribute Details

#allowed_secondsFloat (readonly)

Returns The total number of seconds for this cutoff.

Returns:

  • (Float)

    The total number of seconds for this cutoff



126
127
128
# File 'lib/cutoff.rb', line 126

def allowed_seconds
  @allowed_seconds
end

Class Method Details

.checkpoint!(name = nil) ⇒ void

This method returns an undefined value.

Raise an exception if there is an active expired cutoff

Does nothing if no active cutoff is set

Raises:

  • CutoffExceededError If there is an active expired cutoff



92
93
94
95
96
97
# File 'lib/cutoff.rb', line 92

def checkpoint!(name = nil)
  cutoff = current
  return unless cutoff

  cutoff.checkpoint!(name)
end

.clear_allvoid

This method returns an undefined value.

Clear the entire stack for this thread



65
66
67
# File 'lib/cutoff.rb', line 65

def clear_all
  Thread.current[CURRENT_STACK_KEY] = nil
end

.currentObject

Get the current Cutoff if one is set



22
23
24
# File 'lib/cutoff.rb', line 22

def current
  Thread.current[CURRENT_STACK_KEY]&.last
end

.disable!void

This method returns an undefined value.

Disable Cutoff globally. Useful for testing and debugging

Should not be used in production



104
105
106
# File 'lib/cutoff.rb', line 104

def disable!
  @disabled = true
end

.disabled?Boolean

True if cutoff was disabled with disable!

Returns:

  • (Boolean)

    True if disabled



120
121
122
# File 'lib/cutoff.rb', line 120

def disabled?
  @disabled == true
end

.enable!void

This method returns an undefined value.

Enable Cutoff globally if it has been disabled

Should not be used in production



113
114
115
# File 'lib/cutoff.rb', line 113

def enable!
  @disabled = false
end

.start(allowed_seconds, **options) ⇒ Cutoff

Add a new Cutoff to the stack

This Cutoff will be specific to this thread

If a cutoff is already started for this thread, then start uses the minimum of the current remaining time and the given time

Parameters:

  • allowed_seconds (Float, Integer)

    The total number of seconds to allow

  • exclude (Enumberable<Symbol>, Symbol, nil)

    If given a name or list of checkpoint names to skip

  • only (Enumberable<Symbol>, Symbol, nil)

    If given a name or list of checkpoint names to allow

Returns:



35
36
37
38
39
40
41
42
43
44
# File 'lib/cutoff.rb', line 35

def start(allowed_seconds, **options)
  if current
    allowed_seconds = [allowed_seconds, current.seconds_remaining].min
  end

  cutoff = new(allowed_seconds, **options)
  Thread.current[CURRENT_STACK_KEY] ||= []
  Thread.current[CURRENT_STACK_KEY] << cutoff
  cutoff
end

.stop(cutoff = nil) ⇒ Cutoff?

Remove the top Cutoff from the stack

Parameters:

  • cutoff (Cutoff) (defaults to: nil)

    If given, the top instance will only be removed if it matches the given cutoff instance

Returns:

  • (Cutoff, nil)

    If a cutoff was removed it is returned



51
52
53
54
55
56
57
58
59
60
# File 'lib/cutoff.rb', line 51

def stop(cutoff = nil)
  stack = Thread.current[CURRENT_STACK_KEY]
  return unless stack

  top = stack.last
  stack.pop if cutoff.nil? || top == cutoff
  clear_all if stack.empty?

  cutoff
end

.versionGem::Version

Returns The current version of the cutoff gem.

Returns:

  • (Gem::Version)

    The current version of the cutoff gem



5
6
7
# File 'lib/cutoff/version.rb', line 5

def self.version
  Gem::Version.new('0.5.2')
end

.wrap(allowed_seconds, **options) ⇒ Object

Wrap a block in a cutoff

Same as calling start and stop manually, but safer since you can't forget to stop a cutoff and it handles exceptions raised inside the block

Parameters:

  • allowed_seconds (Float, Integer)

    The total number of seconds to allow

  • exclude (Enumberable<Symbol>, Symbol, nil)

    If given a name or list of checkpoint names to skip

  • only (Enumberable<Symbol>, Symbol, nil)

    If given a name or list of checkpoint names to allow

Returns:

  • The value that returned from the block

See Also:



79
80
81
82
83
84
# File 'lib/cutoff.rb', line 79

def wrap(allowed_seconds, **options)
  cutoff = start(allowed_seconds, **options)
  yield cutoff
ensure
  stop(cutoff)
end

Instance Method Details

#checkpoint!(name = nil) ⇒ void

This method returns an undefined value.

Raises an error if this Cutoff has been exceeded

Raises:

  • CutoffExceededError If there is an active expired cutoff



184
185
186
187
188
189
190
191
192
193
# File 'lib/cutoff.rb', line 184

def checkpoint!(name = nil)
  unless name.nil? || name.is_a?(Symbol)
    raise ArgumentError, 'name must be a symbol'
  end

  return unless selected?(name)
  raise CutoffExceededError, self if exceeded?

  nil
end

#elapsed_secondsFloat

The number of seconds elapsed since this Cutoff was created

Returns:

  • (Float)

    The number of seconds



167
168
169
170
171
# File 'lib/cutoff.rb', line 167

def elapsed_seconds
  return 0 if Cutoff.disabled?

  Cutoff.now - @start_time
end

#exceeded?Boolean

Has the Cutoff been exceeded?

Returns:

  • (Boolean)

    True if the timer expired



176
177
178
# File 'lib/cutoff.rb', line 176

def exceeded?
  seconds_remaining.negative?
end

#ms_remainingFloat

The number of milliseconds left on the clock

Returns:

  • (Float)

    The number of milliseconds



160
161
162
# File 'lib/cutoff.rb', line 160

def ms_remaining
  seconds_remaining * 1000
end

#seconds_remainingFloat

The number of seconds left on the clock

Returns:

  • (Float)

    The number of seconds



153
154
155
# File 'lib/cutoff.rb', line 153

def seconds_remaining
  @allowed_seconds - elapsed_seconds
end

#selected?(name) ⇒ Boolean

True if the named checkpoint is selected by the exclude and only options. If these options are not given, a checkpoint is considered to be selected. If the checkpoint is not named, it is also considered to be selected.

Parameters:

  • name (Symbol, nil)

    The name of the checkpoint

Returns:

  • (Boolean)

    True if the checkpoint is selected



202
203
204
205
206
207
208
# File 'lib/cutoff.rb', line 202

def selected?(name)
  return true if name.nil? && @exclude
  return false if @exclude&.include?(name)
  return false if @only && !@only.include?(name)

  true
end