stud-0.0.22/0000755000004100000410000000000012601567271012611 5ustar www-datawww-datastud-0.0.22/lib/0000755000004100000410000000000012601567271013357 5ustar www-datawww-datastud-0.0.22/lib/stud/0000755000004100000410000000000012601567271014336 5ustar www-datawww-datastud-0.0.22/lib/stud/buffer.rb0000644000004100000410000002227012601567271016137 0ustar www-datawww-datamodule Stud # @author {Alex Dean}[http://github.com/alexdean] # # Implements a generic framework for accepting events which are later flushed # in batches. Flushing occurs whenever +:max_items+ or +:max_interval+ (seconds) # has been reached. # # Including class must implement +flush+, which will be called with all # accumulated items either when the output buffer fills (+:max_items+) or # when a fixed amount of time (+:max_interval+) passes. # # == batch_receive and flush # General receive/flush can be implemented in one of two ways. # # === batch_receive(event) / flush(events) # +flush+ will receive an array of events which were passed to +buffer_receive+. # # batch_receive('one') # batch_receive('two') # # will cause a flush invocation like # # flush(['one', 'two']) # # === batch_receive(event, group) / flush(events, group) # flush() will receive an array of events, plus a grouping key. # # batch_receive('one', :server => 'a') # batch_receive('two', :server => 'b') # batch_receive('three', :server => 'a') # batch_receive('four', :server => 'b') # # will result in the following flush calls # # flush(['one', 'three'], {:server => 'a'}) # flush(['two', 'four'], {:server => 'b'}) # # Grouping keys can be anything which are valid Hash keys. (They don't have to # be hashes themselves.) Strings or Fixnums work fine. Use anything which you'd # like to receive in your +flush+ method to help enable different handling for # various groups of events. # # == on_flush_error # Including class may implement +on_flush_error+, which will be called with an # Exception instance whenever buffer_flush encounters an error. # # * +buffer_flush+ will automatically re-try failed flushes, so +on_flush_error+ # should not try to implement retry behavior. # * Exceptions occurring within +on_flush_error+ are not handled by # +buffer_flush+. # # == on_full_buffer_receive # Including class may implement +on_full_buffer_receive+, which will be called # whenever +buffer_receive+ is called while the buffer is full. # # +on_full_buffer_receive+ will receive a Hash like {:pending => 30, # :outgoing => 20} which describes the internal state of the module at # the moment. # # == final flush # Including class should call buffer_flush(:final => true) # during a teardown/shutdown routine (after the last call to buffer_receive) # to ensure that all accumulated messages are flushed. module Buffer public # Initialize the buffer. # # Call directly from your constructor if you wish to set some non-default # options. Otherwise buffer_initialize will be called automatically during the # first buffer_receive call. # # Options: # * :max_items, Max number of items to buffer before flushing. Default 50. # * :max_interval, Max number of seconds to wait between flushes. Default 5. # * :logger, A logger to write log messages to. No default. Optional. # # @param [Hash] options def buffer_initialize(options={}) if ! self.class.method_defined?(:flush) raise ArgumentError, "Any class including Stud::Buffer must define a flush() method." end @buffer_config = { :max_items => options[:max_items] || 50, :max_interval => options[:max_interval] || 5, :logger => options[:logger] || nil, :has_on_flush_error => self.class.method_defined?(:on_flush_error), :has_on_full_buffer_receive => self.class.method_defined?(:on_full_buffer_receive) } @buffer_state = { # items accepted from including class :pending_items => {}, :pending_count => 0, # guard access to pending_items & pending_count :pending_mutex => Mutex.new, # items which are currently being flushed :outgoing_items => {}, :outgoing_count => 0, # ensure only 1 flush is operating at once :flush_mutex => Mutex.new, # data for timed flushes :last_flush => Time.now, :timer => Thread.new do loop do sleep(@buffer_config[:max_interval]) buffer_flush(:force => true) end end } # events we've accumulated buffer_clear_pending end # Determine if +:max_items+ has been reached. # # buffer_receive calls will block while buffer_full? == true. # # @return [bool] Is the buffer full? def buffer_full? @buffer_state[:pending_count] + @buffer_state[:outgoing_count] >= @buffer_config[:max_items] end # Save an event for later delivery # # Events are grouped by the (optional) group parameter you provide. # Groups of events, plus the group name, are later passed to +flush+. # # This call will block if +:max_items+ has been reached. # # @see Stud::Buffer The overview has more information on grouping and flushing. # # @param event An item to buffer for flushing later. # @param group Optional grouping key. All events with the same key will be # passed to +flush+ together, along with the grouping key itself. def buffer_receive(event, group=nil) buffer_initialize if ! @buffer_state # block if we've accumulated too many events while buffer_full? do on_full_buffer_receive( :pending => @buffer_state[:pending_count], :outgoing => @buffer_state[:outgoing_count] ) if @buffer_config[:has_on_full_buffer_receive] sleep 0.1 end @buffer_state[:pending_mutex].synchronize do @buffer_state[:pending_items][group] << event @buffer_state[:pending_count] += 1 end buffer_flush end # Try to flush events. # # Returns immediately if flushing is not necessary/possible at the moment: # * :max_items have not been accumulated # * :max_interval seconds have not elapased since the last flush # * another flush is in progress # # buffer_flush(:force => true) will cause a flush to occur even # if +:max_items+ or +:max_interval+ have not been reached. A forced flush # will still return immediately (without flushing) if another flush is # currently in progress. # # buffer_flush(:final => true) is identical to buffer_flush(:force => true), # except that if another flush is already in progress, buffer_flush(:final => true) # will block/wait for the other flush to finish before proceeding. # # @param [Hash] options Optional. May be {:force => true} or {:final => true}. # @return [Fixnum] The number of items successfully passed to +flush+. def buffer_flush(options={}) force = options[:force] || options[:final] final = options[:final] # final flush will wait for lock, so we are sure to flush out all buffered events if options[:final] @buffer_state[:flush_mutex].lock elsif ! @buffer_state[:flush_mutex].try_lock # failed to get lock, another flush already in progress return 0 end items_flushed = 0 begin time_since_last_flush = (Time.now - @buffer_state[:last_flush]) return 0 if @buffer_state[:pending_count] == 0 return 0 if (!force) && (@buffer_state[:pending_count] < @buffer_config[:max_items]) && (time_since_last_flush < @buffer_config[:max_interval]) @buffer_state[:pending_mutex].synchronize do @buffer_state[:outgoing_items] = @buffer_state[:pending_items] @buffer_state[:outgoing_count] = @buffer_state[:pending_count] buffer_clear_pending end @buffer_config[:logger].debug("Flushing output", :outgoing_count => @buffer_state[:outgoing_count], :time_since_last_flush => time_since_last_flush, :outgoing_events => @buffer_state[:outgoing_items], :batch_timeout => @buffer_config[:max_interval], :force => force, :final => final ) if @buffer_config[:logger] @buffer_state[:outgoing_items].each do |group, events| begin if group.nil? flush(events,final) else flush(events, group, final) end @buffer_state[:outgoing_items].delete(group) events_size = events.size @buffer_state[:outgoing_count] -= events_size items_flushed += events_size rescue => e @buffer_config[:logger].warn("Failed to flush outgoing items", :outgoing_count => @buffer_state[:outgoing_count], :exception => e.class.name, :backtrace => e.backtrace ) if @buffer_config[:logger] if @buffer_config[:has_on_flush_error] on_flush_error e end sleep 1 retry end @buffer_state[:last_flush] = Time.now end ensure @buffer_state[:flush_mutex].unlock end return items_flushed end private def buffer_clear_pending @buffer_state[:pending_items] = Hash.new { |h, k| h[k] = [] } @buffer_state[:pending_count] = 0 end end end stud-0.0.22/lib/stud/task.rb0000644000004100000410000000412612601567271015630 0ustar www-datawww-datarequire "thread" require "stud/interval" module Stud # A Task spawns a thread to execute the given block. execution completion and result retrieval is # done using the Task#wait method. A Task is run once and the thread exists upon block completion. # A task and its underlying thread are not reusable. # # Task does not provide a mean to force-interrupt a running task, it only provides the #stop! # method to signal the task for a stop request. The task or code block can use the #stop? method # to check for a stop request. Note that the #stop! and #stop? methods are thread safe. class Task # provide access to the underlying thread if ever needed. attr_reader :thread def initialize(*args, &block) # A queue to receive the result of the block # TODO(sissel): Don't use a queue, just store it in an instance variable. @queue = Queue.new @thread = Thread.new(@queue, *args) do |queue, *args| begin result = block.call(*args) queue << [:return, result] rescue => e queue << [:exception, e] end end # thread end # def initialize # wait waits for the task thread to complete and return the block return value # if the block raises an exception, this exception is propagated in this # wait method. # @return [Object, Exception] block return value def wait @thread.join reason, result = @queue.pop if reason == :exception #raise StandardError.new(result) raise result else return result end end # def wait # stop! requests the task to stop. the Thread#wakeup method is also # called so that a sleeping task is waked up and has a chance to verify # the stop request using the #stop? method. also see Stud.stop! def stop! Stud.stop!(@thread) end # stop? returns true if this task stop! has been called # See Stud.stop? # @return [Boolean] true if the stop! has been called def stop? Stud.stop?(@thread) end alias_method :interrupted?, :stop? end # class Task end # module Stud stud-0.0.22/lib/stud/trap.rb0000644000004100000410000000471612601567271015641 0ustar www-datawww-datamodule Stud # 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. 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 # def self.trap # Simulate a signal. This lets you force an interrupt without # sending a signal to yourself. def self.simulate_signal(signal) #puts "Simulate: #{signal} w/ #{@traps[signal].count} callbacks" @traps[signal].each(&:call) end # def self.simulate_signal # 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 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 # def self.untrap end # module Stud # Monkey-patch the main 'trap' stuff? This could be useful. #module Signal #def trap(signal, value=nil, &block) #if value.nil? #Stud.trap(signal, &block) #else ## do nothing? #end #end # def trap #end # #module Kernel #def trap(signal, value=nil, &block) #Signal.trap(signal, value, &block) #end #end stud-0.0.22/lib/stud/secret.rb0000644000004100000410000000226312601567271016153 0ustar www-datawww-data # 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 # # for a discussion of why this implementation is useful. module Stud class Secret # Initialize a new secret with a given value. # # value - anything you want to keep secret from loggers, etc. def initialize(secret_value) # Redefine the 'value' method on this instance. This exposes no instance # variables to be accidentally leaked by things like awesome_print, etc. # This makes any #value call return the secret value. (class << self; self; end).class_eval do define_method(:value) { secret_value } end end # def initialize # Emit simply "" when printed or logged. def to_s return "" end # def to_s alias_method :inspect, :to_s # Get the secret value. def value # Nothing, this will be filled in by Secret.new # But we'll still document this so rdoc/yard know the method exists. end # def value end # class Secret end # class Stud stud-0.0.22/lib/stud/with.rb0000644000004100000410000000105512601567271015637 0ustar www-datawww-datamodule Stud module With # Run a block with arguments. This is sometimes useful in lieu of # explicitly assigning variables. # # I find mainly that using 'with' is a clue that I can factor out # a given segment of code into a method or function. # # Example usage: # # with(TCPSocket.new("google.com", 80)) do |s| # s.write("GET / HTTP/1.0\r\nHost: google.com\r\n\r\n") # puts s.read # s.close # end def with(*args, &block) block.call(*args) end extend self end end stud-0.0.22/lib/stud/try.rb0000644000004100000410000001064312601567271015505 0ustar www-datawww-datamodule Stud # A class implementing 'retry-on-failure' # # Example: # # Try.new.try(5.times) { your_code } # # A failure is indicated by any exception being raised. # On success, the return value of the block is the return value of the try # call. # # On final failure (ran out of things to try), the last exception is raised. class Try # An infinite enumerator class Forever include Enumerable def each(&block) a = 0 yield a += 1 while true end end # class Forever FOREVER = Forever.new BACKOFF_SCHEDULE = [0.01, 0.02, 0.04, 0.08, 0.16, 0.32, 0.64, 1.28, 2.0] DEFAULT_CATCHABLE_EXCEPTIONS = [StandardError] # Log a failure. # # You should override this method if you want a better logger. def log_failure(exception, fail_count, message) puts "Failed (#{exception}). #{message}" end # def log_failure # This method is called when a try attempt fails. # # The default implementation will sleep with exponential backoff up to a # maximum of 2 seconds (see BACKOFF_SCHEDULE) # # exception - the exception causing the failure # fail_count - how many times we have failed. def failure(exception, fail_count) backoff = BACKOFF_SCHEDULE[fail_count] || BACKOFF_SCHEDULE.last log_failure(exception, fail_count, "Sleeping for #{backoff}") sleep(backoff) end # def failure # Public: try a block of code until either it succeeds or we give up. # # enumerable - an Enumerable or omitted/nil, #each is invoked and is tried # that number of times. If this value is omitted or nil, we will try until # success with no limit on the number of tries. # # exceptions - the type of exceptions to retry, we use `StandardError` by default. # # Returns the return value of the block once the block succeeds. # Raises the last seen exception if we run out of tries. # # Examples # # # Try 10 times to fetch http://google.com/ # response = try(10.times) { Net::HTTP.get_response("google.com", "/") } # # # Try many times, yielding the value of the enumeration to the block. # # This allows you to try different inputs. # response = try([0, 2, 4, 6]) { |val| 50 / val } # # Output: # Failed (divided by 0). Retrying in 0.01 seconds... # => 25 # # # Try forever # return_value = try { ... } def try(enumerable=FOREVER, exceptions=DEFAULT_CATCHABLE_EXCEPTIONS, &block) if block.arity == 0 # If the block takes no arguments, give none procedure = lambda { |val| return block.call } else # Otherwise, pass the current 'enumerable' value to the block. procedure = lambda { |val| return block.call(val) } end # Track the last exception so we can reraise it on failure. last_exception = nil # When 'enumerable' runs out of things, if we still haven't succeeded, # we'll reraise fail_count = 0 enumerable.each do |val| begin # If the 'procedure' (the block, really) succeeds, we'll break # and return the return value of the block. Win! return procedure.call(val) rescue NoMethodError, NameError # Abort immediately on exceptions that are unlikely to recover. raise rescue *exceptions => exception last_exception = exception fail_count += 1 # Note: Since we can't reliably detect the 'end' of an enumeration, we # will call 'failure' for the final iteration (if it failed) and sleep # even though there's no strong reason to backoff on the last error. failure(exception, fail_count) end end # enumerable.each # generally make the exception appear from the 'try' method itself, not from # any deeply nested enumeration/begin/etc # It is my hope that this makes the backtraces easier to read, not more # difficult. If you find this is not the case, please please please let me # know. last_exception.set_backtrace(StandardError.new.backtrace) raise last_exception end # def try end # class Stud::Try TRY = Try.new # A simple try method for the common case. def try(enumerable=Stud::Try::FOREVER, exceptions=Try::DEFAULT_CATCHABLE_EXCEPTIONS, &block) return TRY.try(enumerable, exceptions, &block) end # def try extend self end # module Stud stud-0.0.22/lib/stud/temporary.rb0000644000004100000410000000315112601567271016705 0ustar www-datawww-datarequire "securerandom" # for uuid generation require "fileutils" module Stud module Temporary DEFAULT_PREFIX = "studtmp" # Returns a string for a randomly-generated temporary path. # # This does not create any files. def pathname(prefix=DEFAULT_PREFIX) root = ENV["TMP"] || ENV["TMPDIR"] || ENV["TEMP"] || "/tmp" return File.join(root, "#{prefix}-#{SecureRandom.hex(30)}") end # Return a File handle to a randomly-generated path. # # Any arguments beyond the first (prefix) argument will be # given to File.new. # # If no file args are given, the default file mode is "w+" def file(prefix=DEFAULT_PREFIX, *args, &block) args << "w+" if args.empty? file = File.new(pathname(prefix), *args) if block_given? begin block.call(file) ensure file.close unless file.closed? File.unlink(file.path) end else return file end end # Make a temporary directory. # # If given a block, the directory path is given to the block. WHen the # block finishes, the directory and all its contents will be deleted. # # If no block given, it will return the path to a newly created directory. # You are responsible for then cleaning up. def directory(prefix=DEFAULT_PREFIX, &block) path = pathname(prefix) Dir.mkdir(path) if block_given? begin block.call(path) ensure FileUtils.rm_r(path) end else return path end end extend self end # module Temporary end # module Stud stud-0.0.22/lib/stud/interval.rb0000644000004100000410000001063212601567271016511 0ustar www-datawww-datarequire "thread" module Stud STUD_STOP_REQUESTED = :stud_stop_requested # 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 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 # def interval def interval(time, opts = {}, &block) Stud.interval(time, opts, &block) end # def interval # 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 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? 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 def self.stop?(target = Thread.current) # setting/getting threalocal var is thread safe in JRuby target[STUD_STOP_REQUESTED] end class << Stud # also support Stud.interrupted? for backward compatibility. alias_method :interrupted?, :stop? 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 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 private 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 end # module Stud stud-0.0.22/lib/stud/pool.rb0000644000004100000410000001524212601567271015640 0ustar www-datawww-datarequire "thread" module Stud # Public: A thread-safe, generic resource pool. # # This class is agnostic as to the resources in the pool. You can put # database connections, sockets, threads, etc. It's up to you! # # Examples: # # pool = Pool.new # pool.add(Sequel.connect("postgres://pg-readonly-1/prod")) # pool.add(Sequel.connect("postgres://pg-readonly-2/prod")) # pool.add(Sequel.connect("postgres://pg-readonly-3/prod")) # # pool.fetch # => Returns one of the Sequel::Database values from the pool class Pool class Error < StandardError; end # An error indicating a given resource is busy. class ResourceBusy < Error; end # An error indicating a given resource is not found. class NotFound < Error; end # You performed an invalid action. class InvalidAction < Error; end # Default all methods to private. See the bottom of the class definition # for public method declarations. private # Public: initialize a new pool. # # max_size - if specified, limits the number of resources allowed in the pool. def initialize(max_size=nil) # Available resources @available = Hash.new # Busy resources @busy = Hash.new # The pool lock @lock = Mutex.new # Locks for blocking {#fetch} calls if the pool is full. @full_lock = Mutex.new @full_cv = ConditionVariable.new # Maximum size of this pool. @max_size = max_size end # def initialize # Private: Is this pool size-limited? # # Returns true if this pool was created with a max_size. False, otherwise. def sized? return !@max_size.nil? end # def sized? # Private: Is this pool full? # # Returns true if the pool is sized and the count of resources is at maximum. def full? return sized? && (count == @max_size) end # def full? # Public: the count of resources in the pool # # Returns the count of resources in the pool. def count return (@busy.size + @available.size) end # def count # Public: Add a new resource to this pool. # # The resource, once added, is assumed to be available for use. # That means once you add it, you must not use it unless you receive it from # {Pool#fetch} # # resource - the object resource to add to the pool. # # Returns nothing def add(resource) @lock.synchronize do @available[resource.object_id] = resource end return nil end # def add # Public: Fetch an available resource. # # If no resource is available, and the pool is not full, the # default_value_block will be called and the return value of it used as the # resource. # # If no resource is availabe, and the pool is full, this call will block # until a resource is available. # # Returns a resource ready to be used. def fetch(&default_value_block) @lock.synchronize do object_id, resource = @available.shift if !resource.nil? @busy[resource.object_id] = resource return resource end end @full_lock.synchronize do if full? # This should really use a logger. puts "=> Pool is full and nothing available. Waiting for a release..." @full_cv.wait(@full_lock) return fetch(&default_value_block) end end # TODO(sissel): If no block is given, we should block until a resource is # available. # If we get here, no resource is available and the pool is not full. resource = default_value_block.call # Only add the resource if the default_value_block returned one. if !resource.nil? add(resource) return fetch end end # def fetch # Public: Remove a resource from the pool. # # This is useful if the resource is no longer useful. For example, if it is # a database connection and that connection has failed. # # This resource *MUST* be available and not busy. # # Raises Pool::NotFound if no such resource is found. # Raises Pool::ResourceBusy if the resource is found but in use. def remove(resource) # Find the object by object_id #p [:internal, :busy => @busy, :available => @available] @lock.synchronize do if available?(resource) raise InvalidAction, "This resource must be busy for you to remove " \ "it (ie; it must be fetched from the pool)" end @busy.delete(resource.object_id) end end # def remove # Private: Verify this resource is in the pool. # # You *MUST* call this method only when you are holding @lock. # # Returns :available if it is available, :busy if busy, false if not in the pool. def include?(resource) if @available.include?(resource.object_id) return :available elsif @busy.include?(resource.object_id) return :busy else return false end end # def include? # Private: Is this resource available? # You *MUST* call this method only when you are holding @lock. # # Returns true if this resource is available in the pool. # Raises NotFound if the resource given is not in the pool at all. def available?(resource) case include?(resource) when :available; return true when :busy; return false else; raise NotFound, "No resource, #{resource.inspect}, found in pool" end end # def avilable? # Private: Is this resource busy? # # You *MUST* call this method only when you are holding @lock. # # Returns true if this resource is busy. # Raises NotFound if the resource given is not in the pool at all. def busy?(resource) return !available?(resource) end # def busy? # Public: Release this resource back to the pool. # # After you finish using a resource you received with {#fetch}, you must # release it back to the pool using this method. # # Alternately, you can {#remove} it if you want to remove it from the pool # instead of releasing it. def release(resource) @lock.synchronize do if !include?(resource) raise NotFound, "No resource, #{resource.inspect}, found in pool" end # Release is a no-op if this resource is already available. #return if available?(resource) @busy.delete(resource.object_id) @available[resource.object_id] = resource # Notify any threads waiting on a resource from the pool. @full_lock.synchronize { @full_cv.signal } end end # def release public(:add, :remove, :fetch, :release, :sized?, :count, :initialize) end # class Pool end # module Stud stud-0.0.22/metadata.yml0000644000004100000410000000365012601567271015120 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: stud version: !ruby/object:Gem::Version version: 0.0.22 platform: ruby authors: - Jordan Sissel autorequire: bindir: bin cert_chain: [] date: 2015-09-17 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: insist requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' description: small reusable bits of code I'm tired of writing over and over. A library form of my software-patterns github repo. email: jls@semicomplete.com executables: [] extensions: [] extra_rdoc_files: [] files: - CHANGELIST - LICENSE - README.md - lib/stud/buffer.rb - lib/stud/interval.rb - lib/stud/pool.rb - lib/stud/secret.rb - lib/stud/task.rb - lib/stud/temporary.rb - lib/stud/trap.rb - lib/stud/try.rb - lib/stud/with.rb homepage: https://github.com/jordansissel/ruby-stud licenses: [] metadata: {} post_install_message: rdoc_options: [] require_paths: - lib - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.4.6 signing_key: specification_version: 4 summary: stud - common code techniques test_files: [] has_rdoc: stud-0.0.22/LICENSE0000644000004100000410000000107612601567271013622 0ustar www-datawww-dataCopyright 2012-2013 Jordan Sissel and contributors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. stud-0.0.22/CHANGELIST0000644000004100000410000000000012601567271014143 0ustar www-datawww-datastud-0.0.22/README.md0000644000004100000410000000207612601567271014075 0ustar www-datawww-data# Stud. Ruby's stdlib is missing many things I use to solve most of my software problems. Things like like retrying on a failure, supervising workers, resource pools, etc. In general, I started exploring solutions to these things in code over in my [software-patterns](https://github.com/jordansissel/software-patterns) repo. This library (stud) aims to be a well-tested, production-quality implementation of the patterns described in that repo. For now, these all exist in a single repo because, so far, implementations of each 'pattern' are quite small by code size. ## Features * {Stud::Try} (and {Stud.try}) - retry on failure, with back-off, where failure is any exception. * {Stud::Pool} - generic resource pools * {Stud::Task} - tasks (threads that can return values, exceptions, etc) * {Stud.interval} - interval execution (do X every N seconds) * {Stud::Buffer} - batch & flush behavior. ## TODO: * Make sure all things are documented. rubydoc.info should be able to clearly show folks how to use features of this library. * Add tests to cover all supported features.