lib/zeusd/daemon.rb in zeusd-0.0.1 vs lib/zeusd/daemon.rb in zeusd-0.1.0

- old
+ new

@@ -1,102 +1,130 @@ require 'thread' require 'childprocess' require 'pathname' +require 'hooks' module Zeusd class DaemonException < StandardError; end class Daemon - attr_reader :cwd, :verbose - attr_reader :queue - attr_reader :state - attr_reader :child_process, :reader, :writer + include Hooks + define_hooks :after_output, :after_start!, :after_stop!, :after_start_process! - def initialize(options = {}) - @cwd = Pathname.new(options.fetch(:cwd, Dir.pwd)).realpath - @verbose = options.fetch(:verbose, false) - @queue = Queue.new - @state = StateInterpreter.new - on_update(&method(:puts)) if verbose - end + after_start! { log(:start) } + after_stop! { log(:stop) } + after_output {|x| log(x, :zeus) } - def stop! - processes = process ? Array([process.descendants, process]).flatten : [] - if processes.any? - Zeusd::Process.kill!(processes.map(&:pid)) - end + after_start_process! :ensure_log_worker + + after_stop! do (socket_file.delete rescue nil) if socket_file.exist? - if (alive_processes = processes).all?(&:alive?) - raise DaemonException, "Unable to KILL processes: " + alive_processes.join(', ') - else - @process = nil - true - end + @process = nil end + after_output do |output| + interpreter.translate(output) + puts(output) if verbose? + end + + attr_reader :cwd, :verbose, :log_file, :log_queue, :interpreter, :child_process + + def initialize(options = {}) + @cwd = Pathname.new(options.fetch(:cwd, Dir.pwd)).realpath + @verbose = options.fetch(:verbose, false) + @interpreter = Interpreter.new + end + def start!(options = {}) - @process = Zeusd::Process.find(start_child_process!.pid) + @process = Zeusd::Process.find(start_process!.pid) if options.fetch(:block, false) - loop do - if loaded? - puts state.last_status - break - end - sleep(0.1) - end + sleep(0.1) until loaded? end - process + self + ensure + run_hook :after_start! end - def process - @process ||= Process.all.find do |p| - !!p.command[/zeus.*start$/] && p.cwd == cwd + def restart!(options = {}) + stop!.start!(options) + end + + def stop! + return self unless process + + # Kill Pids and Wait + process.kill!(:recursive => true, :wait => true) + + # Check for remaining processes + if[process, process.descendants].flatten.select(&:alive?).any? + raise DaemonException, "Unable to KILL processes: " + alive_processes.join(', ') end + + self + ensure + run_hook :after_stop! end + def process + @process ||= Process.all.find {|p| !!p.command[/zeus.*start$/] && p.cwd == cwd } + end + def loaded? - process.descendants.all?(&:asleep?) + interpreter.complete? end - def on_update(&block) - @on_update = block if block_given? - @on_update + + def log_file + cwd.join('log/zeusd.log') end def socket_file cwd.join('.zeus.sock') end + def verbose? + !!verbose + end + protected - def start_child_process! + def log(entry, type = :zeusd) + log_queue << "<#{type.to_s} utc='#{Time.now.utc}'>#{entry}</#{type.to_s}>\n" + end + + def log_queue + @log_queue ||= Queue.new + end + + def ensure_log_worker + @log_worker ||= Thread.new do + while value = log_queue.shift + log_file.open("a+") {|f| f.write(value) } + end + end + end + + def start_process! @reader, @writer = IO.pipe - @child_process = ChildProcess.build("zeus", "start") + @child_process = ChildProcess.build("zeus", "start") @child_process.environment["BUNDLE_GEMFILE"] = cwd.join("Gemfile").to_path @child_process.io.stdout = @child_process.io.stderr = @writer @child_process.cwd = cwd.to_path @child_process.detach = true @child_process.start @writer.close + run_hook :after_start_process! + Thread.new do - while (buffer = (reader.readpartial(10000) rescue nil)) do - state << buffer - queue << buffer + while (buffer = (@reader.readpartial(10000) rescue nil)) do + run_hook :after_output, buffer end end - if on_update.is_a?(Proc) - Thread.new do - while output = queue.pop - on_update.call(output) - end - end - end - - sleep(0.1) until state.commands.any? + sleep 0.1 until interpreter.commands.any? @child_process end end \ No newline at end of file