std::stringstream arbeitet unzuverlässig
-
Hallo,
ich habe folgenden Unit-Test-Code mit dem ich Befehle an meine Schach-Engine schicke:
TEST(UCICommunicationTest, ManyMoves) { std::stringstream inputStream; std::stringstream outputStream; std::stringstream errorStream; UCICommunication uciCom(inputStream, outputStream, errorStream); std::thread communicationThread([&uciCom]{ uciCom.startCommunication(); }); constexpr auto fenString = "position startpos moves a2a4 "; inputStream << fenString << "\n"; inputStream << "go depth 6\n" << std::flush; std::this_thread::sleep_for(1s); inputStream << "quit\n" << std::flush; communicationThread.join(); // ... }
Die Kommunikations-Schnittstelle nimmt die abstrakten Datentypen
std::istream
undstd::ostream
entgegen, sodass ich bei Unit-Tests mitstd::stringstream
arbeiten und testen kann und beim Produktiv-Code mitstd::cout
undstd::cin
:UCICommunication::UCICommunication(std::istream &inputStream, std::ostream &outputStream, std::ostream &errorStream) : m_inputStream(inputStream), m_outputStream(outputStream), m_errorStream(errorStream), m_timeToSearch(std::numeric_limits<int64_t>::max()), m_searchThread(&UCICommunication::searchBestMove, this) // Für diese Methode nutze ich einen Thread! {}
Das hier ist die
startCommunication()
Methode, die sowohl vom Test- als auch Produktiv-Code aufgerufen wird:void UCICommunication::startCommunication() { registerToUI(); std::string uiCommand; for (getInput(uiCommand); ;getInput(uiCommand)) { // make sure uiCommand is available UCIParser parser(uiCommand); if (parser.uiQuitGame()) { std::cout << "Game quit!!!!" << std::endl; quitGame(); break; } // ... else if (parser.uiHasSentGoCommand()) { executeGoCommand(parser); } } } void UCICommunication::getInput(std::string &uiCommand) { std::getline(m_inputStream, uiCommand); }
Das Problem ist nun, sobald ich Threads in der
UCICommunication
-Klasse verwende, kommen die Kommandos nicht mehr an, obwohl ich den Thread nicht beim Parsen, sondern nur bei der Suche verwende. Lasse ich den Thread weg, funktioniert alles zuverlässig.
Was auch zuverlässig funktioniert ist, wenn ich mit einer GUI überstd::cout
undstd::cin
kommuniziere. Nur meine Tests hängen beimjoin()
descommunicationThread
(siehe letzte Zeile beim Test) fest.Hat jemand eine Idee, weshalb die Befehle verschluckt werden? Muss ich noch irgendeinen Buffer leeren, oder so?
Falls jemand den Code kompilieren und ausführen möchte: Hier ist der Test bzw. das Repository.
Die einzige Abhängigkeit die das Projekt hat, ist Google Test.Danke im Voraus für Vorschläge!
Steffo
-
Hast du denn mal bei dem Testprojekt mit dem Debugger geschaut, wo die Schleife in der
startCommunication()
hängt?
So wie ich den Code verstehe, wird die Schleife (und damit die Funktion) doch nur bei einem "quit" verlassen. Du solltest evtl. noch Fehlerflags des Streams abfragen.Und was passiert, wenn du erst den
inputStream
befüllst und dann dencommunicationThread
startest?
-
Hi @Steffo , ich würde dir empfehlen, den Stockfish port for Java zu verwenden ( http://www.rahular.com/stockfish-port-for-java/ ), und ganz auf das C++ Geraffel zu verzichten, denn damit wirst du wahrscheinlich nicht glücklich...
Bilderkennung, etc. mache ich auch alles mit Java, und das funktioniert gut!
-
@Fragender sagte in std::stringstream arbeitet unzuverlässig:
Hi @Steffo , ich würde dir empfehlen, den Stockfish port for Java zu verwenden ( http://www.rahular.com/stockfish-port-for-java/ ), und ganz auf das C++ Geraffel zu verzichten, denn damit wirst du wahrscheinlich nicht glücklich...
Bilderkennung, etc. mache ich auch alles mit Java, und das funktioniert gut!
Woher nimmst du die Info, dass Steffo Stockfish verwendet bzw. verwenden will?
Wenn man eine eigene Chess Engine baut, dann ergibt es nicht so viel Sinn, Stockfish zu nutzen
-
@Th69 sagte in std::stringstream arbeitet unzuverlässig:
Hast du denn mal bei dem Testprojekt mit dem Debugger geschaut, wo die Schleife in der
startCommunication()
hängt?
So wie ich den Code verstehe, wird die Schleife (und damit die Funktion) doch nur bei einem "quit" verlassen. Du solltest evtl. noch Fehlerflags des Streams abfragen.Und was passiert, wenn du erst den
inputStream
befüllst und dann dencommunicationThread
startest?Wenn ich den Debugger einfach starte und ein paar Sekunden abwarte und dann den Prozess pausiere, dann liest
inputStream
nur noch leere Zeichen ein.
Bin ich aber mit dem Debugger von Anfang an dabei und parse die einzelnen Inputs, dann verhält sich alles ganz normal. - Hat etwas von einem Heisen-Bug: Schaue ich zu, verhält sich alles normal. Lasse ich alles laufen, hängt sich das Programm auf, da das "quit" Kommando nicht verarbeitet wurde.
Das Bad-Bit wird nicht gesetzt, allerdings das Fail-Bit, was wohl daran liegt, dass irgendwann kein weiterer Input kommt.@Fragender Du bist echt eine lustige Nase! Ich schreibe meine eigene Schach-Engine für den Lern-Effekt und dieser "Stockfish-Port" ist keiner, sondern er kommuniziert einfach nur über wenige Zeilen Java-Code mit Stockfish, der übrigens in C++ geschrieben ist!
-
Vielleicht ist das Problem ist, dass
std::stringstream
nicht thread-safe ist undstd::cout
undstd::cin
schon und das deshalb mit Produktiv-Code funktioniert.
Im Test schreibe ich vom Main-Thread in dencommunicationThread
. Dieser Thread sieht nicht unbedingt die neuesten Updates.
Dass das alles funktioniert, wenn ich denm_searchThread
nicht verwende, kann reiner Zufall sein bzw. damit zu tun haben, dass ich densleep
nicht drin habe und das irgendwie auf den Datensichtbarkeit auswirkt.
Die Frage ist nun: Wie kann man das nun mit Unit-Tests testen?
-
@Leon0402 Hast recht. Ich hatte nicht alles gelesen, aber für mich sah es so aus, als wollte er einen FEN-String an eine Schach-Engine weitergeben und da hatte ich an Stockfish bzw. UCI gedacht.
-
@Steffo sagte in std::stringstream arbeitet unzuverlässig:
Vielleicht ist das Problem ist, dass
std::stringstream
nicht thread-safe ist undstd::cout
undstd::cin
schon und das deshalb mit Produktiv-Code funktioniert.
Im Test schreibe ich vom Main-Thread in dencommunicationThread
. Dieser Thread sieht nicht unbedingt die neuesten Updates.
Dass das alles funktioniert, wenn ich denm_searchThread
nicht verwende, kann reiner Zufall sein bzw. damit zu tun haben, dass ich densleep
nicht drin habe und das irgendwie auf den Datensichtbarkeit auswirkt.
Die Frage ist nun: Wie kann man das nun mit Unit-Tests testen?jaein std::cout/std::cin sind thread safe bezogen auf "data race" aber nicht bezogen auf "race condition".
Wobei das nur explizit für std::cin/cout/clog gilt. Für restliche streams nicht!z.b. in diesem abschnitt in deinem code
inputStream << fenString << "\n";
Wenn inputstream "std::cout" wäre, dann ist nur garantiert, dass der "fenstring" part atomar ausgegeben wird, aber bevor der "\n" part ausgegeben wird, kann ein anderer thread was ausgeben.
Das andere ist, dass im falle von std::cin der aufruf des >> operators blockiert, wenn keine Daten im stream sind. Beim std::stringstream muss das aber nicht der fall sein (konnte jetzt keine Information dazu finden ob das stimmt oder nicht)
Aber generell sollte man den zugriff auf eine shared ressource, wie in deinem beispiel der stringstream synchronisieren.
Und zusätzlich solltest du prüfen ob der stream nicht in einen Fehlerzustand gegangen ist.
Weil dann, wenn ich mich recht entsinne, keinerlei ein/ausgaben mehr erfolgen können.Nach meinem Verständnis sieht es so aus, als ob dein code implizit erwartet, dass der inputstream blockiert, wenn keine Daten vorhanden sind (wie das bei std::cin der fall ist)
Daher funktioniert der code auch nur mit einem inputstream (std::istream), welcher so ein verhalten aufweist.Das gleiche Problem hast du in deinem Unit test code wo du auf das Ergebnis der engine warten möchtest, welche via outputStream/errorStream ausgegeben wird.
Daher solltest du überlegen ob du die eingabe von befehlen komplett von der Verarbeitung dieser trennst.
Das gleiche gilt natürlich auch für die Verwendung der anderen streams.Eine Randbemerkung. In deiner UCICommunication::startCommunication() Methode hast du ein std::cout drinn, wodurch du diese ausgabe der engine auch nicht testen kannst.
-
@firefly: Ja, das sehe ich genauso.
Daher habe ich ja auchUnd was passiert, wenn du erst den inputStream befüllst und dann den communicationThread startest?
geschrieben.
Ich sehe beim Unittest aber auch keinen Sinn dadrin, überhaupt noch einen Thread zu benutzen, sondern einfach direkt hintereinander:inputStream << fenString << "\n"; // ... inputStream << "quit\n" << std::flush; UCICommunication uciCom(inputStream, outputStream, errorStream); uciCom.startCommunication();
Du willst ja die Logik testen und nicht das Terminalverhalten.
-
Wenn ich folgende bei Änderungen hinzufüge, funktioniert es, aber keine diese Änderungen funktioniert alleine, sondern man braucht beide!
void UCICommunication::startCommunication() { registerToUI(); std::string uiCommand; for (getInput(uiCommand); ;getInput(uiCommand)) { if (uiCommand.empty()) // Neu hinzu { continue; } // make sure uiCommand is available UCIParser parser(uiCommand); // ... } } void UCICommunication::getInput(std::string &uiCommand) { m_inputStream.clear(); // Clear errors: Neu hinzu std::getline(m_inputStream, uiCommand); }
@Th69 Deinen Vorschlag kann ich so nicht umsetzen, da der Thread sofort beendet wird, bevor überhaupt angefangen wird zu rechnen.
-
Ok, wie sich herausstellt, funktioniert der Ansatz in vielen Fällen, aber nicht zu 100 %.