# = 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 = %{"
write(cmd)
end
end
end; end #modules