Module: Stud

Extended by:
Stud
Included in:
Stud
Defined in:
lib/stud/secret.rb,
lib/stud/try.rb,
lib/stud/pool.rb,
lib/stud/task.rb,
lib/stud/trap.rb,
lib/stud/with.rb,
lib/stud/buffer.rb,
lib/stud/interval.rb,
lib/stud/temporary.rb

Overview

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.

Defined Under Namespace

Modules: Buffer, Temporary, With Classes: Pool, Secret, Task, Try

Constant Summary collapse

TRY =

class Stud::Try

Try.new
STUD_STOP_REQUESTED =
:stud_stop_requested

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.interval(time, opts = {}, &block) ⇒ Object

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



12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 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) ⇒ Object

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



44
45
46
47
# 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) ⇒ Object

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.

Parameters:

  • target (Thread) (defaults to: Thread.current)

    the target thread to stop, defaut to Thread.current



41
42
43
44
45
46
# 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
  target.wakeup
  nil
end

.stop?(target = Thread.current) ⇒ Boolean Also known as: interrupted?

stop? returns true if stop! has been called

Parameters:

  • target (Thread) (defaults to: Thread.current)

    the target thread to check for stop, defaut to Thread.current

Returns:

  • (Boolean)

    true if the stop! has been called



51
52
53
54
# File 'lib/stud/interval.rb', line 51

def self.stop?(target = Thread.current)
  # setting/getting threalocal var is thread safe in JRuby
  target[STUD_STOP_REQUESTED]
end

.stoppable_sleep(duration, stop_condition_interval = 1.0, &stop_condition_block) ⇒ Numeric

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.

Parameters:

  • duration (Numeric)

    sleep time in (fractional) seconds

  • stop_condition_interval (Numeric) (defaults to: 1.0)

    optional interval in (fractional) seconds to perform the sleep interruption verification, default is 1s

  • stop_condition_block (Proc)

    optional sleep interruption code block that must evaluate to a truthy value, default is to use Stud.stop?

Returns:

  • (Numeric)

    the actual duration in (fractional) seconds spent in stoppable_sleep



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/stud/interval.rb', line 76

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) ⇒ Object

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.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 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) ⇒ Object

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



53
54
55
56
57
58
59
60
61
# 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

Instance Method Details

#interval(time, opts = {}, &block) ⇒ Object

def interval



27
28
29
# 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) ⇒ Object

A simple try method for the common case.



122
123
124
# 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