Limits::ponder was used as a signal between uci and search threads,
but is not an atomic variable, leading to the following race as
flagged by a sanitized binary.
Expect input:
```
spawn ./stockfish
send "uci\n"
expect "uciok"
send "setoption name Ponder value true\n"
send "go wtime 4000 btime 4000\n"
expect "bestmove"
send "position startpos e2e4 d7d5\n"
send "go wtime 4000 btime 4000 ponder\n"
sleep 0.01
send "ponderhit\n"
expect "bestmove"
send "quit\n"
expect eof
```
Race:
```
WARNING: ThreadSanitizer: data race (pid=7191)
Read of size 4 at 0x0000005c2260 by thread T1:
Previous write of size 4 at 0x0000005c2260 by main thread:
Location is global 'Search::Limits' of size 88 at 0x0000005c2220 (stockfish+0x0000005c2260)
```
The reason of teh race is that ponder is not just set in UCI go()
assignment but also is signaled by an async ponderhit in uci.cpp:
else if (token == "ponderhit")
Search::Limits.ponder = 0; // Switch to normal search
The fix is to add an atomic bool to the threads structure to
signal the ponder status, letting Search::Limits to reflect just
what was passed to 'go'.
No functional change.
// the UCI protocol states that we shouldn't print the best move before the
// GUI sends a "stop" or "ponderhit" command. We therefore simply wait here
// until the GUI sends one of those commands (which also raises Threads.stop).
// the UCI protocol states that we shouldn't print the best move before the
// GUI sends a "stop" or "ponderhit" command. We therefore simply wait here
// until the GUI sends one of those commands (which also raises Threads.stop).
- if (!Threads.stop && (Limits.ponder || Limits.infinite))
+ if (!Threads.stop && (Threads.ponder || Limits.infinite))
{
Threads.stopOnPonderhit = true;
wait(Threads.stop);
{
Threads.stopOnPonderhit = true;
wait(Threads.stop);
{
// If we are allowed to ponder do not stop the search now but
// keep pondering until the GUI sends "ponderhit" or "stop".
{
// If we are allowed to ponder do not stop the search now but
// keep pondering until the GUI sends "ponderhit" or "stop".
Threads.stopOnPonderhit = true;
else
Threads.stop = true;
Threads.stopOnPonderhit = true;
else
Threads.stop = true;
}
// An engine may not stop pondering until told so by the GUI
}
// An engine may not stop pondering until told so by the GUI
return;
if ( (Limits.use_time_management() && elapsed > Time.maximum() - 10)
return;
if ( (Limits.use_time_management() && elapsed > Time.maximum() - 10)
/// LimitsType struct stores information sent by GUI about available time to
/// LimitsType struct stores information sent by GUI about available time to
-/// search the current move, maximum depth/time, if we are in analysis mode or
-/// if we have to ponder while it's our opponent's turn to move.
+/// search the current move, maximum depth/time, or if we are in analysis mode.
struct LimitsType {
LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC
nodes = time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] =
struct LimitsType {
LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC
nodes = time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] =
- npmsec = movestogo = depth = movetime = mate = infinite = ponder = 0;
+ npmsec = movestogo = depth = movetime = mate = infinite = 0;
}
bool use_time_management() const {
}
bool use_time_management() const {
}
std::vector<Move> searchmoves;
}
std::vector<Move> searchmoves;
- int time[COLOR_NB], inc[COLOR_NB], npmsec, movestogo, depth, movetime, mate, infinite, ponder;
+ int time[COLOR_NB], inc[COLOR_NB], npmsec, movestogo, depth, movetime, mate, infinite;
int64_t nodes;
TimePoint startTime;
};
int64_t nodes;
TimePoint startTime;
};
main()->wait_for_search_finished();
main()->wait_for_search_finished();
- stopOnPonderhit = stop = false;
+ ponder = stopOnPonderhit = stop = false;
Search::Limits = limits;
Search::RootMoves rootMoves;
Search::Limits = limits;
Search::RootMoves rootMoves;
uint64_t nodes_searched() const;
uint64_t tb_hits() const;
uint64_t nodes_searched() const;
uint64_t tb_hits() const;
- std::atomic_bool stop, stopOnPonderhit;
+ std::atomic_bool stop, ponder, stopOnPonderhit;
private:
StateListPtr setupStates;
private:
StateListPtr setupStates;
else if (token == "movetime") is >> limits.movetime;
else if (token == "mate") is >> limits.mate;
else if (token == "infinite") limits.infinite = 1;
else if (token == "movetime") is >> limits.movetime;
else if (token == "mate") is >> limits.mate;
else if (token == "infinite") limits.infinite = 1;
- else if (token == "ponder") limits.ponder = 1;
+ else if (token == "ponder") Threads.ponder = true;
Threads.start_thinking(pos, States, limits);
}
Threads.start_thinking(pos, States, limits);
}
Threads.main()->start_searching(true); // Could be sleeping
}
else if (token == "ponderhit")
Threads.main()->start_searching(true); // Could be sleeping
}
else if (token == "ponderhit")
- Search::Limits.ponder = 0; // Switch to normal search
+ Threads.ponder = false; // Switch to normal search
else if (token == "uci")
sync_cout << "id name " << engine_info(true)
else if (token == "uci")
sync_cout << "id name " << engine_info(true)