class Player < BasicPlayer
+ WRITE_THREAD_WATCH_INTERVAL = 20 # sec
def initialize(str, socket, eol=nil)
super()
@socket = socket
@sente = nil
@socket_buffer = []
@main_thread = Thread::current
- @mutex_write_guard = Mutex.new
+ @write_queue = ShogiServer::TimeoutQueue.new(WRITE_THREAD_WATCH_INTERVAL)
+ @player_logger = nil
+ start_write_thread
end
attr_accessor :socket, :status
attr_accessor :main_thread
attr_reader :socket_buffer
+ def setup_logger(dir)
+ log_file = File.join(dir, "%s.log" % [simple_player_id])
+ @player_logger = Logger.new(log_file, 'daily')
+ @player_logger.formatter = ShogiServer::Formatter.new
+ @player_logger.level = $DEBUG ? Logger::DEBUG : Logger::INFO
+ @player_logger.datetime_format = "%Y-%m-%d %H:%M:%S"
+ end
+
+ def log(level, direction, message)
+ return unless @player_logger
+ str = message.chomp
+ case direction
+ when :in
+ str = "IN: %s" % [str]
+ when :out
+ str = "OUT: %s" % [str]
+ else
+ str = "UNKNOWN DIRECTION: %s %s" % [direction, str]
+ end
+ case level
+ when :debug
+ @player_logger.debug(str)
+ when :info
+ @player_logger.info(str)
+ when :warn
+ @player_logger.warn(str)
+ when :error
+ @player_logger.error(str)
+ else
+ @player_logger.debug("UNKNOWN LEVEL: %s %s" % [level, str])
+ end
+ rescue Exception => ex
+ log_error("#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}")
+ end
+
def kill
log_message(sprintf("user %s killed", @name))
if (@game)
@game.kill(self)
end
finish
- Thread::kill(@main_thread) if @main_thread
+ Thread::kill(@main_thread) if @main_thread
+ Thread::kill(@write_thread) if @write_thread
end
def finish
@status = "finished"
log_message(sprintf("user %s finish", @name))
begin
-# @socket.close if (! @socket.closed?)
+ log_debug("Terminating %s's write thread..." % [@name])
+ if @write_thread && @write_thread.alive?
+ write_safe(nil)
+ end
+ @player_logger.close if @player_logger
+ log_debug("done.")
rescue
log_message(sprintf("user %s finish failed", @name))
end
end
end
- def write_safe(str)
- @mutex_write_guard.synchronize do
- begin
- if @socket.closed?
- log_warning("%s's socket has been closed." % [@name])
- return
- end
- if r = select(nil, [@socket], nil, 20)
- r[1].first.write(str)
- else
- log_error("Sending a message to #{@name} timed up.")
+ def start_write_thread
+ @write_thread = Thread.start do
+ Thread.pass
+ while !@socket.closed?
+ begin
+ str = @write_queue.deq
+ if (str == nil)
+ log_debug("%s's write thread terminated" % [@name])
+ break
+ end
+ if (str == :timeout)
+ log_debug("%s's write queue timed out. Try again..." % [@name])
+ next
+ end
+
+ if r = select(nil, [@socket], nil, 20)
+ r[1].first.write(str)
+ log(:info, :out, str)
+ else
+ log_error("Gave a try to send a message to #{@name}, but it timed out.")
+ break
+ end
+ rescue Exception => ex
+ log_error("Failed to send a message to #{@name}. #{ex.class}: #{ex.message}\t#{ex.backtrace[0]}")
+ break
end
- rescue Exception => ex
- log_error("Failed to send a message to #{@name}. #{ex.class}: #{ex.message}\t#{ex.backtrace[0]}")
- end
- end
+ end # while loop
+ log_error("%s's socket closed." % [@name]) if @socket.closed?
+ log_message("At least %d messages are not sent to the client." %
+ [@write_queue.get_messages.size])
+ end # thread
+ end
+
+ #
+ # Note that sending a message is included in the giant lock.
+ #
+ def write_safe(str)
+ @write_queue.enq(str)
end
def to_s
def run(csa_1st_str=nil)
while ( csa_1st_str ||
str = gets_safe(@socket, (@socket_buffer.empty? ? Default_Timeout : 1)) )
+ log(:info, :in, str) if str && str.instance_of?(String)
$mutex.lock
begin
+ if !@write_thread.alive?
+ log_error("%s's write thread is dead. Aborting..." % [@name])
+ return
+ end
if (@game && @game.turn?(self))
@socket_buffer << str
str = @socket_buffer.shift
end
- log_message("%s (%s)" % [str, @socket_buffer.map {|a| String === a ? a.strip : a }.join(",")]) if $DEBUG
+ log_debug("%s (%s)" % [str, @socket_buffer.map {|a| String === a ? a.strip : a }.join(",")])
if (csa_1st_str)
str = csa_1st_str
array_str = str.split(",")
move = array_str.shift
additional = array_str.shift
+ comment = nil
if /^'(.*)/ =~ additional
comment = array_str.unshift("'*#{$1.toeuc}")
end
end
when /^%%SHOW\s+(\S+)/
game_id = $1
- if (LEAGUE.games[game_id])
- write_safe(LEAGUE.games[game_id].show.gsub(/^/, '##[SHOW] '))
+ if ($league.games[game_id])
+ write_safe($league.games[game_id].show.gsub(/^/, '##[SHOW] '))
end
write_safe("##[SHOW] +OK\n")
when /^%%MONITORON\s+(\S+)/
game_id = $1
- if (LEAGUE.games[game_id])
- LEAGUE.games[game_id].monitoron(self)
- write_safe(LEAGUE.games[game_id].show.gsub(/^/, "##[MONITOR][#{game_id}] "))
+ if ($league.games[game_id])
+ $league.games[game_id].monitoron(self)
+ write_safe($league.games[game_id].show.gsub(/^/, "##[MONITOR][#{game_id}] "))
write_safe("##[MONITOR][#{game_id}] +OK\n")
end
when /^%%MONITOROFF\s+(\S+)/
game_id = $1
- if (LEAGUE.games[game_id])
- LEAGUE.games[game_id].monitoroff(self)
+ if ($league.games[game_id])
+ $league.games[game_id].monitoroff(self)
end
when /^%%HELP/
write_safe(
%!##[HELP] available commands "%%WHO", "%%CHAT str", "%%GAME game_name +", "%%GAME game_name -"\n!)
when /^%%RATING/
- players = LEAGUE.rated_players
+ players = $league.rated_players
players.sort {|a,b| b.rate <=> a.rate}.each do |p|
write_safe("##[RATING] %s \t %4d @%s\n" %
[p.simple_player_id, p.rate, p.modified_at.strftime("%Y-%m-%d")])
@sente = nil
else
if (my_sente_str == "*")
- rival = LEAGUE.get_player("game_waiting", game_name, nil, self) # no preference
+ rival = $league.get_player("game_waiting", game_name, nil, self) # no preference
elsif (my_sente_str == "+")
- rival = LEAGUE.get_player("game_waiting", game_name, false, self) # rival must be gote
+ rival = $league.get_player("game_waiting", game_name, false, self) # rival must be gote
elsif (my_sente_str == "-")
- rival = LEAGUE.get_player("game_waiting", game_name, true, self) # rival must be sente
+ rival = $league.get_player("game_waiting", game_name, true, self) # rival must be sente
else
## never reached
write_safe(sprintf("##[ERROR] bad game option\n"))
end
when /^%%CHAT\s+(.+)/
message = $1
- LEAGUE.players.each do |name, player|
+ $league.players.each do |name, player|
if (player.protocol != LoginCSA::PROTOCOL)
player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message))
end
end
when /^%%LIST/
buf = Array::new
- LEAGUE.games.each do |id, game|
+ $league.games.each do |id, game|
buf.push(sprintf("##[LIST] %s\n", id))
end
buf.push("##[LIST] +OK\n")
write_safe(buf.join)
when /^%%WHO/
buf = Array::new
- LEAGUE.players.each do |name, player|
+ $league.players.each do |name, player|
buf.push(sprintf("##[WHO] %s\n", player.to_s))
end
buf.push("##[WHO] +OK\n")