OSDN Git Service

Under debugging
authorbeatles <beatles@b8c68f68-1e22-0410-b08e-880e1f8202b4>
Wed, 1 Oct 2008 15:03:50 +0000 (15:03 +0000)
committerbeatles <beatles@b8c68f68-1e22-0410-b08e-880e1f8202b4>
Wed, 1 Oct 2008 15:03:50 +0000 (15:03 +0000)
shogi-server
shogi_server/board.rb
shogi_server/game.rb
shogi_server/league.rb
shogi_server/league/floodgate.rb
shogi_server/player.rb
shogi_server/timeout_queue.rb [new file with mode: 0644]

index ebca314..2d90ff8 100755 (executable)
@@ -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
index 613ac79..efc1e36 100644 (file)
 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)
index 154db72..cde9142 100644 (file)
@@ -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)
index 19f7db2..0eb37ff 100644 (file)
@@ -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.
index 5f91057..3e0433e 100644 (file)
@@ -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
 
index 5acb702..07303cb 100644 (file)
@@ -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 (file)
index 0000000..8b367b4
--- /dev/null
@@ -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