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
also support Stud.interrupted?
for backward compatibility.
Source
# 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
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
Source
# 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
Simulate a signal. This lets you force an interrupt without sending a signal to yourself.
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
Source
# 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! 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
Source
# 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
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
Source
# 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
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
Source
# 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
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
.
Source
# 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
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
Public Instance Methods
Source
# File lib/stud/interval.rb, line 27 def interval(time, opts = {}, &block) Stud.interval(time, opts, &block) end
Source
# 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
A simple try method for the common case.