module Stud

A class for holding a secret. The main goal is to prevent the common mistake of accidentally logging or printing passwords or other secrets.

See <github.com/jordansissel/software-patterns/blob/master/dont-log-secrets/ruby/> for a discussion of why this implementation is useful.

Constants

STUD_STOP_REQUESTED
TRY

Public Class Methods

interrupted?(target = Thread.current)

also support Stud.interrupted? for backward compatibility.

Alias for: stop?
interval(time, opts = {}, &block) click to toggle source

This implementation tries to keep clock more accurately. Prior implementations still permitted skew, where as this one will attempt to correct for skew.

The execution patterns of this method should be that the start time of ‘block.call’ should always be at time T*interval

# File lib/stud/interval.rb, line 12
def self.interval(time, opts = {}, &block)
  start = Time.now
  while true
    if opts[:sleep_then_run]
      start = sleep_for_interval(time, start)
      break if stop?
      block.call
    else
      block.call
      start = sleep_for_interval(time, start)
      break if stop?
    end
  end # loop forever
end
simulate_signal(signal) click to toggle source

Simulate a signal. This lets you force an interrupt without sending a signal to yourself.

# File lib/stud/trap.rb, line 44
def self.simulate_signal(signal)
  #puts "Simulate: #{signal} w/ #{@traps[signal].count} callbacks"
  @traps[signal].each(&:call)
end
stop!(target = Thread.current) click to toggle source

stop! instructs interval to stop and exit its execution loop before going to sleep between block executions. NOW the tricky part is: this is typically an operation that will be called from another thread than the thread running the interval loop in which case the target parameter must be set to the Thread object which is running the interval loop. Note that the stop logic is compatible with Stud::Task so if interval is run inside a Stud::Task, calling Stud::Task#stop! will stop the interval the same way as calling stop! on the interval itself. @param target [Thread] the target thread to stop, defaut to Thread.current

# File lib/stud/interval.rb, line 41
def self.stop!(target = Thread.current)
  # setting/getting threalocal var is thread safe in JRuby
  target[STUD_STOP_REQUESTED] = true
  begin
    target.wakeup
  rescue ThreadError => e
    # The thread is dead, so there's nothing to do.
    # There's no race-free way to detect this sadly
  end
  nil
end
stop?(target = Thread.current) click to toggle source

stop? returns true if stop! has been called @param target [Thread] the target thread to check for stop, defaut to Thread.current @return [Boolean] true if the stop! has been called

# File lib/stud/interval.rb, line 56
def self.stop?(target = Thread.current)
  # setting/getting threalocal var is thread safe in JRuby
  target[STUD_STOP_REQUESTED]
end
Also aliased as: interrupted?
stoppable_sleep(duration, stop_condition_interval = 1.0, &stop_condition_block) click to toggle source

stoppable_sleep will try to sleep for the given duration seconds (which may be any number, including a Float with fractional seconds). an optional stop_condition_block can be supplied to verify for sleep interruption if the block returns a truthy value. if not block is supplied it will check for the Stud.stop? condition. this check will be performed at 1s interval by default or you can supply a different stop_condition_interval.

note that to achieve this, stoppable_sleep will actually perform a series of incremental sleeps but will try accurately spend the requested duration period in the overall stoppable_sleep method call. in other words this means that the duration supplied will be accurate for the time spent in the stoppable_sleep method not the actual total time spent in the underlying multiple sleep calls.

@param duration [Numeric] sleep time in (fractional) seconds @param stop_condition_interval [Numeric] optional interval in (fractional) seconds to perform the sleep interruption verification, default is 1s @param stop_condition_block [Proc] optional sleep interruption code block that must evaluate to a truthy value, default is to use Stud.stop? @return [Numeric] the actual duration in (fractional) seconds spent in stoppable_sleep

# File lib/stud/interval.rb, line 81
def self.stoppable_sleep(duration, stop_condition_interval = 1.0, &stop_condition_block)
  sleep_start = Time.now

  # default to using Stud.stop? as the condition block
  stop_condition_block ||= lambda { stop? }

  while (remaining_duration = (duration - (Time.now - sleep_start))) >= stop_condition_interval
    # sleep X if there is more than X remaining to sleep in relation to the loop start time
    sleep(stop_condition_interval)

    return(Time.now - sleep_start) if stop_condition_block.call
  end

  # here we know we have less than 1s reminding to sleep,
  sleep(remaining_duration) if remaining_duration > 0.0

  Time.now - sleep_start
end
trap(signal, &block) click to toggle source

Bind a block to be called when a certain signal is received.

Same arguments to Signal::trap.

The behavior of this method is different than Signal::trap because multiple handlers can request notification for the same signal.

For example, this is valid:

Stud.trap("INT") { puts "Hello" }
Stud.trap("INT") { puts "World" }

When SIGINT is received, both callbacks will be invoked, in order.

This helps avoid the situation where a library traps a signal outside of your control.

If something has already used Signal::trap, that callback will be saved and scheduled the same way as any other Stud::trap.

# File lib/stud/trap.rb, line 21
def self.trap(signal, &block)
  @traps ||= Hash.new { |h,k| h[k] = [] }

  if !@traps.include?(signal)
    # First trap call for this signal, tell ruby to invoke us.
    previous_trap = Signal::trap(signal) { simulate_signal(signal) }
    # If there was a previous trap (via Kernel#trap) set, make sure we remember it.
    if previous_trap.is_a?(Proc)
      # MRI's default traps are "DEFAULT" string
      # JRuby's default traps are Procs with a source_location of "(internal")
      if RUBY_ENGINE != "jruby" || previous_trap.source_location.first != "(internal)"
        @traps[signal] << previous_trap
      end
    end
  end

  @traps[signal] << block

  return block.object_id
end
untrap(signal, id) click to toggle source

Remove a previously set signal trap.

‘signal’ is the name of the signal (“INT”, etc) ‘id’ is the value returned by a previous Stud.trap() call

# File lib/stud/trap.rb, line 53
def self.untrap(signal, id)
  @traps[signal].delete_if { |block| block.object_id == id }

  # Restore the default handler if there are no custom traps anymore.
  if @traps[signal].empty?
    @traps.delete(signal)
    Signal::trap(signal, "DEFAULT")
  end
end

Private Class Methods

sleep_for_interval(time, start) click to toggle source
# File lib/stud/interval.rb, line 102
def self.sleep_for_interval(time, start)
  duration = Time.now - start

  # sleep only if the duration was less than the time interval
  if duration < time
    stoppable_sleep(time - duration)
    start += time
  else
    # duration exceeded interval time, reset the clock and do not sleep.
    start = Time.now
  end
end

Public Instance Methods

interval(time, opts = {}, &block) click to toggle source
# File lib/stud/interval.rb, line 27
def interval(time, opts = {}, &block)
  Stud.interval(time, opts, &block)
end
try(enumerable=Stud::Try::FOREVER, exceptions=Try::DEFAULT_CATCHABLE_EXCEPTIONS, &block) click to toggle source

A simple try method for the common case.

# File lib/stud/try.rb, line 122
def try(enumerable=Stud::Try::FOREVER, exceptions=Try::DEFAULT_CATCHABLE_EXCEPTIONS, &block)
  return TRY.try(enumerable, exceptions, &block)
end