require 'thread' require 'singleton' Thread.abort_on_exception = true module Mauve # # This is a class to wrap our threads that have processing loops. # class MauveThread # # Set the thread up # def initialize @thread = nil @last_polled_at = nil end # @return [Log4r::Logger] def logger @logger ||= Log4r::Logger.new(self.class.to_s) end # This determines if a thread should stop # # @return [Boolean] def should_stop? self.state == :stopping end # This is the current state of the thread. It can be one of # [:stopped, :starting, :started, :stopping, :killing, :killed] # # If the thread is not alive it will be +:stopped+. # # @return [Symbol] One of [:stopped, :starting, :started, :stopping, :killing, :killed] def state if self.alive? @thread.key?(:state) ? @thread[:state] : :unknown else :stopped end end # This sets the state of a thread. It also records the last time the # thread changed status. # # @param [Symbol] s One of [:stopped, :starting, :started, :stopping, :killing, :killed] # @raise [ArgumentError] if +s+ is not a valid symbol or the thread is not # ready # @return [Symbol] the current thread state. # def state=(s) raise ArgumentError, "Bad state for mauve_thread #{s.inspect}" unless [:stopped, :starting, :started, :stopping, :killing, :killed].include?(s) raise ArgumentError, "Thread not ready yet." unless @thread.is_a?(Thread) unless @thread[:state] == s @thread[:state] = s @thread[:last_state_change] = Time.now logger.debug(s.to_s.capitalize) end @thread[:state] end # This returns the time of the last state change, or nil if the thread is dead. # # @return [Time or Nilclass] def last_state_change if self.alive? and @thread.key?(:last_state_change) return @thread[:last_state_change] else return nil end end # This starts the thread. It wakes it up if it is alive, or starts it from # fresh if it is dead. # def run if self.alive? # Wake up if we're stopped. if self.stop? @thread.wakeup end else @logger = nil Thread.new do run_thread { main_loop } end end end alias start run alias thaw run # This checks to see if the thread is alive # # @return [Boolean] def alive? @thread.is_a?(Thread) and @thread.alive? end # This checks to see if the thread is stopped # # @return [Boolean] def stop? self.alive? and @thread.stop? end # This joins the thread # def join @thread.join if @thread.is_a?(Thread) end # def raise(ex) # @thread.raise(ex) # end # This returns the thread's backtrace # # @return [Array or Nilclass] def backtrace @thread.backtrace if @thread.is_a?(Thread) end # This restarts the thread # # def restart self.stop self.start end # This stops the thread # # def stop self.state = :stopping @thread.wakeup 10.times do break unless self.alive? Kernel.sleep 1 if self.alive? end # # OK I've had enough now. # self.kill if self.alive? self.join end alias exit stop # This kills the thread -- faster than #stop # def kill self.state = :killing @thread.kill self.state = :killed end # This returns the thread itself. # # @return [Thread] def thread @thread end # # Returns the time the thread loop was last run, or nil if it has never run. # def last_polled_at @last_polled_at end private # This is the main run loop for the thread. # # This thread will run untill the thread state is changed to :stopping. # def run_thread # # Good to go. # @thread = Thread.current self.state = :starting while self.state != :stopping do self.state = :started @last_polled_at = Time.now yield # # This is a little sleep to stop cpu hogging. # Kernel.sleep 0.001 end self.state = :stopped end end end