]> git.sesse.net Git - stockfish/commitdiff
Rewrite how commands from GUI are read
authorMarco Costalba <mcostalba@gmail.com>
Sat, 5 Nov 2011 06:53:19 +0000 (07:53 +0100)
committerMarco Costalba <mcostalba@gmail.com>
Sat, 5 Nov 2011 07:35:17 +0000 (08:35 +0100)
Instead of polling for input use a dedicated listener
thread to read commands from the GUI independently
from other threads.

To do this properly we have to delegate to the listener
all the reading from the GUI: while searching but also
while waiting for a command, like in std::getline().

So we have two possible behaviours: in-sync mode, in which
the thread mimics std::getline() and the caller blocks until
something is read from GUI, and async mode where the listener
continuously reads and processes GUI commands while other
threads are searching.

No functional change.

Signed-off-by: Marco Costalba <mcostalba@gmail.com>
src/search.cpp
src/thread.cpp
src/thread.h
src/uci.cpp

index 851797dd22d49abf8e62d414f35289fcaf8f86fa..320f480c2df854ac575ab4f4776f120d5c933a84 100644 (file)
@@ -439,6 +439,10 @@ bool think(Position& pos, const SearchLimits& limits, Move searchMoves[]) {
           << endl;
   }
 
+  // Start async mode to catch UCI commands sent to us while searching,
+  // like "quit", "stop", etc.
+  Threads.start_listener();
+
   // We're ready to start thinking. Call the iterative deepening loop function
   Move ponderMove = MOVE_NONE;
   Move bestMove = id_loop(pos, searchMoves, &ponderMove);
@@ -462,6 +466,9 @@ bool think(Position& pos, const SearchLimits& limits, Move searchMoves[]) {
   // This makes all the threads to go to sleep
   Threads.set_size(1);
 
+  // From now on any UCI command will be read in-sync with Threads.getline()
+  Threads.stop_listener();
+
   // If we are pondering or in infinite search, we shouldn't print the
   // best move before we are told to do so.
   if (!StopRequest && (Limits.ponder || Limits.infinite))
@@ -1912,38 +1919,6 @@ split_point_start: // At split points actual search starts from here
     static int lastInfoTime;
     int t = current_search_time();
 
-    //  Poll for input
-    if (input_available())
-    {
-        // We are line oriented, don't read single chars
-        string command;
-
-        if (!std::getline(std::cin, command) || command == "quit")
-        {
-            // Quit the program as soon as possible
-            Limits.ponder = false;
-            QuitRequest = StopRequest = true;
-            return;
-        }
-        else if (command == "stop")
-        {
-            // Stop calculating as soon as possible, but still send the "bestmove"
-            // and possibly the "ponder" token when finishing the search.
-            Limits.ponder = false;
-            StopRequest = true;
-        }
-        else if (command == "ponderhit")
-        {
-            // The opponent has played the expected move. GUI sends "ponderhit" if
-            // we were told to ponder on the same move the opponent has played. We
-            // should continue searching but switching from pondering to normal search.
-            Limits.ponder = false;
-
-            if (StopOnPonderhit)
-                StopRequest = true;
-        }
-    }
-
     // Print search information
     if (t < 1000)
         lastInfoTime = 0;
@@ -1988,14 +1963,14 @@ split_point_start: // At split points actual search starts from here
 
   void wait_for_stop_or_ponderhit() {
 
-    string command;
+    string cmd;
 
     // Wait for a command from stdin
-    while (   std::getline(std::cin, command)
-           && command != "ponderhit" && command != "stop" && command != "quit") {};
+    while (cmd != "ponderhit" && cmd != "stop" && cmd != "quit")
+        Threads.getline(cmd);
 
-    if (command != "ponderhit" && command != "stop")
-        QuitRequest = true; // Must be "quit" or getline() returned false
+    if (cmd == "quit")
+        QuitRequest = true;
   }
 
 
@@ -2248,3 +2223,34 @@ void Thread::idle_loop(SplitPoint* sp) {
       }
   }
 }
+
+
+// ThreadsManager::do_uci_async_cmd() processes the commands from GUI received
+// by listener thread while the other threads are searching.
+
+void ThreadsManager::do_uci_async_cmd(const std::string& cmd) {
+
+  if (cmd == "quit")
+  {
+      // Quit the program as soon as possible
+      Limits.ponder = false;
+      QuitRequest = StopRequest = true;
+  }
+  else if (cmd == "stop")
+  {
+      // Stop calculating as soon as possible, but still send the "bestmove"
+      // and possibly the "ponder" token when finishing the search.
+      Limits.ponder = false;
+      StopRequest = true;
+  }
+  else if (cmd == "ponderhit")
+  {
+      // The opponent has played the expected move. GUI sends "ponderhit" if
+      // we were told to ponder on the same move the opponent has played. We
+      // should continue searching but switching from pondering to normal search.
+      Limits.ponder = false;
+
+      if (StopOnPonderhit)
+          StopRequest = true;
+  }
+}
index 16bda2809147f9d299d9b425ae8d00d9997ff071..49a598f1d41da631a8416794686af24ff8f77079 100644 (file)
@@ -27,27 +27,22 @@ ThreadsManager Threads; // Global object definition
 namespace { extern "C" {
 
  // start_routine() is the C function which is called when a new thread
- // is launched. It simply calls idle_loop() of the supplied thread.
- // There are two versions of this function; one for POSIX threads and
- // one for Windows threads.
+ // is launched. It simply calls idle_loop() of the supplied thread. The
+ // last thread is dedicated to I/O and so runs in listener_loop().
 
 #if defined(_MSC_VER)
-
   DWORD WINAPI start_routine(LPVOID thread) {
-
-    ((Thread*)thread)->idle_loop(NULL);
-    return 0;
-  }
-
 #else
-
   void* start_routine(void* thread) {
+#endif
 
-    ((Thread*)thread)->idle_loop(NULL);
-    return NULL;
-  }
+    if (((Thread*)thread)->threadID == MAX_THREADS)
+        ((Thread*)thread)->listener_loop();
+    else
+        ((Thread*)thread)->idle_loop(NULL);
 
-#endif
+    return 0;
+  }
 
 } }
 
@@ -147,11 +142,14 @@ void ThreadsManager::set_size(int cnt) {
 
 void ThreadsManager::init() {
 
+  // Initialize sleep condition used to block waiting for GUI input
+  cond_init(&sleepCond);
+
   // Initialize threads lock, used when allocating slaves during splitting
   lock_init(&threadsLock);
 
   // Initialize sleep and split point locks
-  for (int i = 0; i < MAX_THREADS; i++)
+  for (int i = 0; i <= MAX_THREADS; i++)
   {
       lock_init(&threads[i].sleepLock);
       cond_init(&threads[i].sleepCond);
@@ -167,7 +165,7 @@ void ThreadsManager::init() {
 
   // Create and launch all the threads but the main that is already running,
   // threads will go immediately to sleep.
-  for (int i = 1; i < MAX_THREADS; i++)
+  for (int i = 1; i <= MAX_THREADS; i++)
   {
       threads[i].is_searching = false;
       threads[i].threadID = i;
@@ -192,18 +190,13 @@ void ThreadsManager::init() {
 
 void ThreadsManager::exit() {
 
-  // Wake up all the slave threads at once. This is faster than "wake and wait"
-  // for each thread and avoids a rare crash once every 10K games under Linux.
-  for (int i = 1; i < MAX_THREADS; i++)
-  {
-      threads[i].do_terminate = true;
-      threads[i].wake_up();
-  }
-
-  for (int i = 0; i < MAX_THREADS; i++)
+  for (int i = 0; i <= MAX_THREADS; i++)
   {
       if (i != 0)
       {
+          threads[i].do_terminate = true;
+          threads[i].wake_up();
+
           // Wait for slave termination
 #if defined(_MSC_VER)
           WaitForSingleObject(threads[i].handle, 0);
@@ -222,6 +215,7 @@ void ThreadsManager::exit() {
   }
 
   lock_destroy(&threadsLock);
+  cond_destroy(&sleepCond);
 }
 
 
@@ -353,3 +347,113 @@ Value ThreadsManager::split(Position& pos, SearchStack* ss, Value alpha, Value b
 // Explicit template instantiations
 template Value ThreadsManager::split<false>(Position&, SearchStack*, Value, Value, Value, Depth, Move, int, MovePicker*, int);
 template Value ThreadsManager::split<true>(Position&, SearchStack*, Value, Value, Value, Depth, Move, int, MovePicker*, int);
+
+
+// Thread::listner_loop() is where the last thread, used for IO, waits for input.
+// Input is read in sync with main thread (that blocks) when is_searching is set
+// to false, otherwise IO thread reads any input asynchronously and processes
+// the input line calling do_uci_async_cmd().
+
+void Thread::listener_loop() {
+
+  std::string cmd;
+
+  while (true)
+  {
+      lock_grab(&sleepLock);
+
+      Threads.inputLine = cmd;
+      do_sleep = !is_searching;
+
+      // Here the thread is parked in sync mode after a line has been read
+      while (do_sleep && !do_terminate) // Catches spurious wake ups
+      {
+          cond_signal(&Threads.sleepCond);   // Wake up main thread
+          cond_wait(&sleepCond, &sleepLock); // Sleep here
+      }
+
+      lock_release(&sleepLock);
+
+      if (do_terminate)
+          return;
+
+      if (!std::getline(std::cin, cmd)) // Block waiting for input
+          cmd = "quit";
+
+      lock_grab(&sleepLock);
+
+      // If we are in async mode then process the command now
+      if (is_searching)
+      {
+          // Command "quit" is the last one received by the GUI, so park the
+          // thread waiting for exiting.
+          if (cmd == "quit")
+              is_searching = false;
+
+          Threads.do_uci_async_cmd(cmd);
+          cmd = ""; // Input has been consumed
+      }
+
+      lock_release(&sleepLock);
+  }
+}
+
+
+// ThreadsManager::getline() is used by main thread to block and wait for input,
+// the behaviour mimics std::getline().
+
+void ThreadsManager::getline(std::string& cmd) {
+
+  Thread& listener = threads[MAX_THREADS];
+
+  lock_grab(&listener.sleepLock);
+
+  listener.is_searching = false; // Set sync mode
+
+  // If there is already some input to grab then skip without to wake up the
+  // listener. This can happen if after we send the "bestmove", the GUI sends
+  // a command that the listener buffers in inputLine before going to sleep.
+  if (inputLine.empty())
+  {
+      listener.do_sleep = false;
+      cond_signal(&listener.sleepCond); // Wake up listener thread
+
+      while (!listener.do_sleep)
+          cond_wait(&sleepCond, &listener.sleepLock); // Wait for input
+  }
+
+  cmd = inputLine;
+  inputLine = ""; // Input has been consumed
+
+  lock_release(&listener.sleepLock);
+}
+
+
+// ThreadsManager::start_listener() is called at the beginning of the search to
+// swith from sync behaviour (default) to async and so be able to read from UCI
+// while other threads are searching. This avoids main thread polling for input.
+
+void ThreadsManager::start_listener() {
+
+  Thread& listener = threads[MAX_THREADS];
+
+  lock_grab(&listener.sleepLock);
+  listener.is_searching = true;
+  listener.do_sleep = false;
+  cond_signal(&listener.sleepCond); // Wake up listener thread
+  lock_release(&listener.sleepLock);
+}
+
+
+// ThreadsManager::stop_listener() is called before to send "bestmove" to GUI to
+// return to in-sync behaviour. This is needed because while in async mode any
+// command is discarded without being processed (except for a very few ones).
+
+void ThreadsManager::stop_listener() {
+
+  Thread& listener = threads[MAX_THREADS];
+
+  lock_grab(&listener.sleepLock);
+  listener.is_searching = false;
+  lock_release(&listener.sleepLock);
+}
index b7d426e25e8a14b05e30ed107ad73bf5829e6403..124815b4215d368125dbc38a2d4a50cf2d4a3fc8 100644 (file)
@@ -69,6 +69,7 @@ struct Thread {
   bool cutoff_occurred() const;
   bool is_available_to(int master) const;
   void idle_loop(SplitPoint* sp);
+  void listener_loop();
 
   SplitPoint splitPoints[MAX_ACTIVE_SPLIT_POINTS];
   MaterialInfoTable materialTable;
@@ -113,16 +114,25 @@ public:
   void read_uci_options();
   bool available_slave_exists(int master) const;
 
+  void getline(std::string& cmd);
+  void do_uci_async_cmd(const std::string& cmd);
+  void start_listener();
+  void stop_listener();
+
   template <bool Fake>
   Value split(Position& pos, SearchStack* ss, Value alpha, Value beta, Value bestValue,
               Depth depth, Move threatMove, int moveCount, MovePicker* mp, int nodeType);
 private:
-  Thread threads[MAX_THREADS];
+  friend struct Thread;
+
+  Thread threads[MAX_THREADS + 1];
   Lock threadsLock;
   Depth minimumSplitDepth;
   int maxThreadsPerSplitPoint;
   int activeThreads;
   bool useSleepingThreads;
+  WaitCondition sleepCond;
+  std::string inputLine;
 };
 
 extern ThreadsManager Threads;
index 86fe629a84f69708fd6057c9897790fbdc816063..b7f72d9d4c8cb655df23ef9822f40724502e9d7b 100644 (file)
@@ -28,6 +28,7 @@
 #include "move.h"
 #include "position.h"
 #include "search.h"
+#include "thread.h"
 #include "ucioption.h"
 
 using namespace std;
@@ -60,8 +61,10 @@ void uci_loop() {
   string cmd, token;
   bool quit = false;
 
-  while (!quit && getline(cin, cmd))
+  while (!quit)
   {
+      Threads.getline(cmd);
+
       istringstream is(cmd);
 
       is >> skipws >> token;