# = Synchronous Handler # # === Design: # # A handler can act as a multiplexer to implement multiple services # per server. # # code: tml, drak # # (c) 2004 Navel, all rights reserved. # $Id: handler.rb 71 2004-10-18 10:50:22Z gmosx $ require "timeout" require "n/server" module N; module Sync class HandlerExitException < Exception; end # = Worker # # Override this to create your worker. # class Handler SEPARATOR = "\000" # socket timeout in seconds @@socket_timeout = 30 # handler timeout in seconds @@handler_timeout = 60 * 60 attr :server, :socket attr :thread, :touch_time # status attr_accessor :status STATUS_IDLE = 0 STATUS_RUNNING = 10 STATUS_STOPPED = 20 def initialize(server, socket) @server, @socket = server, socket end def start touch! @thread = Thread.new(&method("run").to_proc) end def stop unless STATUS_STOPPED == @status @status = STATUS_STOPPED $log.debug "Stoping handler" if $DBG begin @socket.close() rescue Exception, StandardError => e # gmosx: this is needed to be FAULT TOLERANT # DRINK IT! $log.error "Cannot close exception when stoping handler." end # gmosx: why not? more FAULT TOLERANT. @server.handlers.delete_if {|h| STATUS_STOPPED == h.status} end end # called by the garbage collector. # def gc! @thread.raise HandlerExitException.new() end def run @status = STATUS_RUNNING while (STATUS_RUNNING == @status) and (not @socket.closed?) begin unless cmd = read() $log.error "Client closed connection" @status = STATUS_IDLE else touch! handle(cmd.chop()) end rescue HandlerExitException => ex @status = STATUS_IDLE $log.debug "Handler exit" if $DBG rescue Exception, StandardError => ex @status = STATUS_IDLE $log.debug ex if $DBG end end begin stop() rescue Exception, StandardError => e # gmosx: this rescue block needed to get debug info! # $log.error pp_exception(e) end end def touch! @touch_time = Time.now() end def live? return Time.now < @touch_time + @@handler_timeout end # Read xml from the socket # def read return unless @status == STATUS_RUNNING cmd = nil # gmosx: no timeout here! cmd = @socket.gets(SEPARATOR) $log.debug "in: #{cmd}" if $DBG return cmd end # Write xml to the socket # def write(cmd) return unless @status == STATUS_RUNNING $log.debug "out: #{cmd}" if $DBG begin timeout(@@socket_timeout) { @socket << "#{cmd}#{SEPARATOR}" } rescue Exception, StandardError => e # gmosx: the socket is invalid close the handler. $log.error pp_exception(e) stop() end end # parse the xml commnad to create a ruby method call. # def parse(cmd) return unless @status == STATUS_RUNNING cmd = REXML::Document.new(cmd).root() method = cmd.name().gsub(/-/, "_") unless cmd.attributes.empty? args = "(#{cmd.attributes.collect { |k, v| ":#{k.gsub(/-/, "_")} => '#{v}'" }.join(", ")})" else args = nil end return "cmd_#{method}#{args}" end # Generic handler. # def handle(cmd) if cmd = parse(cmd) $log.debug "exec: #{cmd}" if $DBG begin eval(cmd) rescue => ex $log.error ex end end end # Send to another handler # def send(cmd, handler) handler.write(cmd) end # Broadcast to all handlers (clients) # def broadcast(cmd) @server.broadcast(cmd) end # Broadcast to all other handlers (clients) # def broadcast_to_others(cmd) for handler in @server.handlers unless self == handler handler.write(cmd) end end end # ------------------------------------------------------------------ def info(txt) write(%||) end def error(txt) write(%||) end # ------------------------------------------------------------------ # Commands def cmd_ping(args) cmd = %{