module Lipsiadmin
  module Loops
    class Worker#:nodoc:
      attr_reader :logger
      attr_reader :name
      attr_reader :pid

      def initialize(name, logger, engine, &blk)
        raise "Need a worker block!" unless block_given?

        @name = name
        @logger = logger
        @engine = engine
        @worker_block = blk

        @shutdown = false
      end

      def shutdown?
        @shutdown
      end

      def run
        return if shutdown?
        if @engine == 'fork'
          @pid = Kernel.fork do
            $0 = "loop worker: #{@name}\0"
            @pid = Process.pid
            @worker_block.call
            exit(0)
          end
        elsif @engine == 'thread'
          @thread = Thread.start do
            @worker_block.call
          end
        else
          raise "Invalid engine name: #{@engine}"
        end
      rescue Exception => e
        logger.error("Exception from worker: #{e} at #{e.backtrace.first}")
      end

      def running?(verbose = false)
        return false if shutdown?
        if @engine == 'fork'
          return false unless @pid
          begin
            Process.waitpid(@pid, Process::WNOHANG)
            res = Process.kill(0, @pid)
            logger.debug("KILL(#{@pid}) = #{res}") if verbose
            return true
          rescue Errno::ESRCH, Errno::ECHILD, Errno::EPERM => e
            logger.error("Exception from kill: #{e} at #{e.backtrace.first}") if verbose
            return false
          end
        elsif @engine == 'thread'
          @thread && @thread.alive?
        else
          raise "Invalid engine name: #{@engine}"
        end
      end

      def stop(force = false)
        @shutdown = true
        if @engine == 'fork'
          begin
            sig = force ? 'SIGKILL' : 'SIGTERM'
            logger.debug("Sending #{sig} to ##{@pid}")
            Process.kill(sig, @pid)
          rescue Errno::ESRCH, Errno::ECHILD, Errno::EPERM=> e
            logger.error("Exception from kill: #{e} at #{e.backtrace.first}")
          end
        elsif @engine == 'thread'
          force && !defined?(::JRuby) ? @thread.kill! : @thread.kill
        else
          raise "Invalid engine name: #{@engine}"
        end
      end
    end
  end
end
