From: beatles Date: Wed, 1 Oct 2008 15:03:50 +0000 (+0000) Subject: Under debugging X-Git-Tag: 20170902~239 X-Git-Url: http://git.sourceforge.jp/view?a=commitdiff_plain;h=57e00845d62e520576f71c7ed968af274cc5da50;p=shogi-server%2Fshogi-server.git Under debugging --- diff --git a/shogi-server b/shogi-server index ebca314..2d90ff8 100755 --- a/shogi-server +++ b/shogi-server @@ -20,6 +20,9 @@ $:.unshift File.dirname(__FILE__) require 'shogi_server' +require 'shogi_server/board' # not autoloaded +require 'shogi_server/player' +require 'shogi_server/game' ################################################# # MAIN @@ -32,7 +35,7 @@ def gets_safe(socket, timeout=nil) return :timeout end rescue Exception => ex - log_error("#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}") + log_error("gets_safe: #{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}") return :exception end @@ -113,20 +116,21 @@ def write_pid_file(file) end def mutex_watchdog(mutex, sec) + sec = 1 if sec < 1 + queue = [] while true - begin - timeout(sec) do - begin - mutex.lock - ensure - mutex.unlock - end + if mutex.try_lock + queue.clear + mutex.unlock + else + queue.push(Object.new) + if queue.size > sec + # timeout + log_error("mutex watchdog timeout: %d sec" % [sec]) + queue.clear end - sleep(sec) - rescue TimeoutError - log_error("mutex watchdog timeout") - exit(1) end + sleep(1) end end @@ -182,7 +186,40 @@ def setup_watchdog_for_giant_lock end end +def setup_floodgate + Thread.start do + Thread.pass + floodgate = ShogiServer::League::Floodgate.new(LEAGUE) + log_message("Flooddgate reloaded. The next match will start at %s." % + [floodgate.next_time]) + + while (true) + begin + sleep(10) + next if Time.now < floodgate.next_time + LEAGUE.reload + floodgate.match_game + floodgate.charge + next_time = floodgate.next_time + $mutex.synchronize do + Dependencies.clear + end + floodgate = ShogiServer::League::Floodgate.new(LEAGUE, next_time) + log_message("Flooddgate reloaded. The next match will start at %s." % + [floodgate.next_time]) + rescue Exception => ex + # ignore errors + log_error("[in Floodgate's thread] #{ex} #{ex.backtrace}") + end + end + end +end + def main + + [ShogiServer::League::Floodgate, ShogiServer::Pairing].each do |klass| + Dependencies.unloadable klass + end setup_watchdog_for_giant_lock @@ -219,12 +256,16 @@ def main config[:Port] = port config[:ServerType] = WEBrick::Daemon if $options["daemon"] config[:Logger] = $logger - if $options["pid-file"] - pid_file = File.expand_path($options["pid-file"]) - config[:StartCallback] = Proc.new do - write_pid_file(pid_file) + + config[:StartCallback] = Proc.new do + if $options["pid-file"] + write_pid_file($options["pid-file"]) end - config[:StopCallback] = Proc.new do + setup_floodgate + end + + config[:StopCallback] = Proc.new do + if $options["pid-file"] FileUtils.rm(pid_file, :force => true) end end @@ -234,12 +275,13 @@ def main trap(signal) do LEAGUE.shutdown server.shutdown + # TODO Shutdown Floodgate's thread end end trap("HUP") do LEAGUE.shutdown Dependencies.clear - LEAGUE.restart + # TODO Restart Floodgate's thread end $stderr.puts("server started as a deamon [Revision: #{ShogiServer::Revision}]") if $options["daemon"] log_message("server started [Revision: #{ShogiServer::Revision}]") @@ -276,6 +318,10 @@ if ($0 == __FILE__) TCPSocket.do_not_reverse_lookup = true Thread.abort_on_exception = $DEBUG ? true : false + begin LEAGUE = ShogiServer::League::new - main + main + rescue Exception => ex + log_error("main: #{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}") + end end diff --git a/shogi_server/board.rb b/shogi_server/board.rb index 613ac79..efc1e36 100644 --- a/shogi_server/board.rb +++ b/shogi_server/board.rb @@ -20,19 +20,36 @@ module ShogiServer # for a namespace class Board - def initialize + def initialize(move_count=0) @sente_hands = Array::new @gote_hands = Array::new @history = Hash::new(0) @sente_history = Hash::new(0) @gote_history = Hash::new(0) @array = [[], [], [], [], [], [], [], [], [], []] - @move_count = 0 + @move_count = move_count @teban = nil # black => true, white => false end attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history, :teban attr_reader :move_count + def deep_copy + # return Marshal.load(Marshal.dump(self)) + board = Board.new(self.move_count) + board.sente_hands = self.sente_hands.clone + board.gote_hands = self.gote_hands.clone + board.history = self.history.clone + board.history.default = 0 + board.sente_history = self.sente_history.clone + board.sente_history.default = 0 + board.gote_history = self.gote_history.clone + board.gote_history.default = 0 + board.array = [] + self.array.each {|a| board.array.push(a.clone)} + board.teban = self.teban + return board + end + def initial PieceKY::new(self, 1, 1, false) PieceKE::new(self, 2, 1, false) @@ -82,10 +99,12 @@ class Board if ((x0 == 0) || (y0 == 0)) piece = have_piece?(hands, name) - return :illegal if (! piece.move_to?(x1, y1, name)) # TODO null check for the piece? + return :illegal if (piece == nil || ! piece.move_to?(x1, y1, name)) piece.move_to(x1, y1) else - return :illegal if (! @array[x0][y0].move_to?(x1, y1, name)) # TODO null check? + if (@array[x0][y0] == nil || !@array[x0][y0].move_to?(x1, y1, name)) + return :illegal + end if (@array[x0][y0].name != name) # promoted ? @array[x0][y0].promoted = true end @@ -169,7 +188,7 @@ class Board ## case: rival_ou is moving rival_ou.movable_grids.each do |(cand_x, cand_y)| - tmp_board = Marshal.load(Marshal.dump(self)) + tmp_board = deep_copy s = tmp_board.move_to(rival_ou.x, rival_ou.y, cand_x, cand_y, "OU", ! sente) raise "internal error" if (s != true) if (! tmp_board.checkmated?(! sente)) # good move @@ -197,7 +216,7 @@ class Board end end names.map! do |name| - tmp_board = Marshal.load(Marshal.dump(self)) + tmp_board = deep_copy s = tmp_board.move_to(x, y, fu_x, fu_y, name, ! sente) if s == :illegal s # result @@ -380,7 +399,7 @@ class Board return :illegal # can't put on existing piece end - tmp_board = Marshal.load(Marshal.dump(self)) + tmp_board = deep_copy return :illegal if (tmp_board.move_to(x0, y0, x1, y1, name, sente) == :illegal) return :oute_kaihimore if (tmp_board.checkmated?(sente)) tmp_board.update_sennichite(sente) diff --git a/shogi_server/game.rb b/shogi_server/game.rb index 154db72..cde9142 100644 --- a/shogi_server/game.rb +++ b/shogi_server/game.rb @@ -364,6 +364,7 @@ class Game end move_status = @board.handle_one_move(str, @sente == @current_player) + # log_debug("move_status: %s for %s's %s" % [move_status, @sente == @current_player ? "BLACK" : "WHITE", str]) if [:illegal, :uchifuzume, :oute_kaihimore].include?(move_status) @fh.printf("'ILLEGAL_MOVE(%s)\n", str) diff --git a/shogi_server/league.rb b/shogi_server/league.rb index 19f7db2..0eb37ff 100644 --- a/shogi_server/league.rb +++ b/shogi_server/league.rb @@ -17,9 +17,6 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -#require 'shogi_server/league/floodgate' -#require 'shogi_server/league/persistent' - module ShogiServer # for a namespace ###################################################### @@ -33,8 +30,6 @@ class League @players = Hash::new @event = nil @dir = File.dirname(__FILE__) - @floodgate = Floodgate.new(self) - #@floodgate.run end attr_accessor :players, :games, :event, :dir @@ -44,12 +39,6 @@ class League @persistent.save(player) end end - @floodgate.shutdown - end - - def restart - @floodgate = Floodgate.new(self) - #@floodgate.run end # this should be called just after instanciating a League object. diff --git a/shogi_server/league/floodgate.rb b/shogi_server/league/floodgate.rb index 5f91057..3e0433e 100644 --- a/shogi_server/league/floodgate.rb +++ b/shogi_server/league/floodgate.rb @@ -8,48 +8,30 @@ class League end end - def initialize(league) + attr_reader :next_time, :league + + def initialize(league, next_time=nil) @league = league - @next_time = nil + @next_time = next_time charge end - def run - @thread = Thread.new do - Thread.pass - while (true) - begin - sleep(10) - next if Time.now < @next_time - @league.reload - match_game - charge - rescue Exception => ex - # ignore errors - log_error("[in Floodgate's thread] #{ex} #{ex.backtrace}") - end - end - end - end - - def shutdown - @thread.kill if @thread - end - - # private - def charge now = Time.now - # if now.min < 30 - # @next_time = Time.mktime(now.year, now.month, now.day, now.hour, 30) - # else - # @next_time = Time.mktime(now.year, now.month, now.day, now.hour) + 3600 - # end - # for test - if now.sec < 30 - @next_time = Time.mktime(now.year, now.month, now.day, now.hour, now.min, 30) + unless $DEBUG + # each 30 minutes + if now.min < 30 + @next_time = Time.mktime(now.year, now.month, now.day, now.hour, 30) + else + @next_time = Time.mktime(now.year, now.month, now.day, now.hour) + 3600 + end else - @next_time = Time.mktime(now.year, now.month, now.day, now.hour, now.min) + 60 + # for test, each 30 seconds + if now.sec < 30 + @next_time = Time.mktime(now.year, now.month, now.day, now.hour, now.min, 30) + else + @next_time = Time.mktime(now.year, now.month, now.day, now.hour, now.min) + 60 + end end end diff --git a/shogi_server/player.rb b/shogi_server/player.rb index 5acb702..07303cb 100644 --- a/shogi_server/player.rb +++ b/shogi_server/player.rb @@ -17,6 +17,8 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +require 'monitor' + module ShogiServer # for a namespace class BasicPlayer @@ -110,7 +112,9 @@ class Player < BasicPlayer @sente = nil @socket_buffer = [] @main_thread = Thread::current - @write_queue = Queue.new + @write_queue = [] + @wq_mon = Monitor.new + @wq_cond = @wq_mon.new_cond @player_logger = nil start_write_thread end @@ -186,16 +190,23 @@ class Player < BasicPlayer while !@socket.closed? str = "" begin - begin - timeout(5) do - str = @write_queue.deq + timeout_flg = false + @wq_mon.synchronize do + if @write_queue.empty? + if @wq_cond.wait(15) + #timeout + timeout_flg = true + # log_debug("write_queue health check timeout") + end end - if str == nil - break + if !timeout_flg && !@write_queue.empty? + str = @write_queue.shift + # log_debug("Dequeued %s from %s's write_queue." % [str, @name]) end - rescue TimeoutError - next - end + end # synchronize + next if timeout_flg + break if str == nil # exit + if r = select(nil, [@socket], nil, 20) r[1].first.write(str) log(:info, :out, str) @@ -216,7 +227,12 @@ class Player < BasicPlayer # Note that sending a message is included in the giant lock. # def write_safe(str) - @write_queue.enq str + @wq_mon.synchronize do + # log_debug("Enqueuing %s..." % [str]) + @write_queue.push(str) + # log_debug("Enqueued %s into %s's write_queue." % [str, @name]) + @wq_cond.broadcast + end end def to_s diff --git a/shogi_server/timeout_queue.rb b/shogi_server/timeout_queue.rb new file mode 100644 index 0000000..8b367b4 --- /dev/null +++ b/shogi_server/timeout_queue.rb @@ -0,0 +1,84 @@ +# queue = Queue.new +# timeout(5) do +# queue.deq +# end +# +# is not good since not all of stdlib is safe with respect to +# asynchronous exceptions. +# This class is a safe implementation. +# See: http://www.ruby-forum.com/topic/107864 +# + +require 'thread' +require 'monitor' + +module ShogiServer + +class TimeoutQueue + def initialize + @lock = Mutex.new + @messages = [] + @readers = [] + end + + def enq(msg) + @lock.synchronize do + unless @readers.empty? + @readers.pop << msg + else + @messages.push msg + end + end + end + + # + # @param timeout + # @return nil if timeout + # + def deq(timeout=5) + timeout_thread = nil + mon = nil + empty_cond = nil + + begin + reader = nil + @lock.synchronize do + unless @messages.empty? + # fast path + return @messages.shift + else + reader = Queue.new + @readers.push reader + if timeout + mon = Monitor.new + empty_cond = mon.new_cond + + timeout_thread = Thread.new do + mon.synchronize do + if empty_cond.wait(timeout) + # timeout + @lock.synchronize do + @readers.delete reader + reader << nil + end + else + # timeout_thread was waked up before timeout + end + end + end # thread + end + end + end + # either timeout or writer will send to us + return reader.shift + ensure + # (try to) clean up timeout thread + if timeout_thread + mon.synchronize { empty_cond.signal } + Thread.pass + end + end + end +end + +end