Hello
I have been experiencing a crash for a while. I tried to find the source of the problem, but no luck so far.
Here is what I am doing:
- Create an FInteractiveProcess
- Start reading and writing through pipes with the process,
- After done create another process and do the whole stuff again.
Everything works very fine for a while, but at some point seemingly random crashes happen.
Note: I will provide more info if needed.
Some SS for crash informations(I added some from Qt Creator hoping to get more info):
Here is how I use and communicate with that FInteractiveProcess. Please don’t care about the naming of my classes. Also chess engines are some sort of console programs that communicates through pipes. Their entire purpose is to play chess games.
class CHESSGAME_API ChessEngineHolder
{
...
...
private:
// Holds pointer to interactive process
TSharedPtr<FInteractiveProcess> CEProcess;
...
}
~~~~~~~ChessEngineHolder.cpp file~~~~~~~
bool ChessEngineHolder::Launch(UChessEngineCommunicator *ChessEngineCommunicator,
const FString &Path,
const FString &FileName,
bool isTestLaunch)
{
// If it is running already
if (CEProcess.IsValid()) {
return false;
}
CECommunicator = ChessEngineCommunicator;
ChessEngineInfo.EngineFileName = FileName;
CEProcess = TSharedPtr<FInteractiveProcess>(new FInteractiveProcess(Path, "", true, true));
if (!CEProcess.IsValid() || !CEProcess->Launch()) {
return false;
}
if (isTestLaunch) {
CEProcess->OnOutput().BindRaw(this, &ChessEngineHolder::ProcessTest);
}
else {
CEProcess->OnOutput().BindRaw(this, &ChessEngineHolder::OnProcessOutput);
CEProcess->OnCompleted().BindRaw(this, &ChessEngineHolder::OnProcessCompleted);
}
// This needs to be sent like this and SendCommandToCE should not be used. Because
// SendCommandToCE checks if bIsReady is true and not send the command
// if it is not. So sending uci command by SendCommandToCE would be suicide!
CEProcess->SendWhenReady("uci");
return true;
}
void ChessEngineHolder::SendCommandToCE(const FString &Command, bool isTestRun)
{
if (!isTestRun) {
if (CEProcess.IsValid() && CEProcess->IsRunning()) {
if (bIsReady) {
UE_LOG(LogChessEngineHolder, Log, TEXT("Parent: %s"), *Command);
CEProcess->SendWhenReady(Command);
}
else {
YetNotSentCommands.Add(Command);
}
}
}
else {
if (CEProcess.IsValid() && CEProcess->IsRunning()) {
UE_LOG(LogChessEngineHolder, Log, TEXT("Parent: %s"), *Command);
CEProcess->SendWhenReady(Command);
}
}
}
void ChessEngineHolder::OnProcessOutput(FString Message)
{
/*
FFunctionGraphTask::CreateAndDispatchWhenReady([&, Message]() {
// code here runs on game thread
UE_LOG(LogChessEngineHolder, Log, TEXT("ChessEngine: %s"), *Message);
}, TStatId(), NULL, ENamedThreads::GameThread);
*/
if (CEProcess.IsValid() && CEProcess->IsRunning()) {
int32 From = Message.Find("bestmove ");
if (From >= 0) {
if (CECommunicator && CECommunicator->IsValidLowLevelFast())
CECommunicator->BestMoveReceived(this, Message);
return;
}
From = Message.Find("currmove");
if (From >= 0) {
if (CECommunicator && CECommunicator->IsValidLowLevelFast())
CECommunicator->ChessEngineIsCalculating(this, Message);
return;
}
From = Message.Find("uciok");
if (From >= 0) {
// This needs to be sent like this and SendCommandToCE should not be used. Because
// SendCommandToCE checks if bIsReady is true and not send the command
// if it is not. So sending isready command by SendCommandToCE would be suicide!
CEProcess->SendWhenReady("isready");
return;
}
From = Message.Find("readyok");
if (From >= 0) {
// Be sure to make bIsReady true before calling SendCommandToCE("ucinewgame")
bIsReady = true;
// Send yet not sent commands to chess engine and empty the array
for (const FString &command : YetNotSentCommands) {
SendCommandToCE(command);
}
YetNotSentCommands.Empty();
return;
}
}
}
~~~~~~~CECommunication.h file~~~~~~~
UCLASS(BlueprintType, Blueprintable)
class CHESSGAME_API UChessEngineCommunicator : public UObject
{
GENERATED_BODY()
public:
UChessEngineCommunicator();
...
private:
TSharedPtr<ChessEngineHolder> ChessEngine0;
TSharedPtr<ChessEngineHolder> ChessEngine1;
...
};
~~~~~~~CECommunication.cpp file~~~~~~~
void UChessEngineCommunicator::LaunchChessEngine(const FString &EngineFileName)
{
if (this->IsValidLowLevelFast() == false)
return;
int32 which = -1;
for (int32 i = 0; i < ChessEngineExecutables.Num(); i++) {
if (ChessEngineExecutables[i] == EngineFileName) {
which = i;
break;
}
}
if (which < 0) {
UE_LOG(LogTemp, Warning, TEXT("Invalid CE File Name: %s"), *EngineFileName);
return;
}
FString tempPathToCE = PathToCEFolder;
tempPathToCE.Append(ChessEngineExecutables[which]);
if (ChessEngine0.IsValid() == false) {
ChessEngine0 = TSharedPtr<ChessEngineHolder>(new ChessEngineHolder());
if (ChessEngine0.IsValid()) {
ChessEngine0->Launch(this, tempPathToCE, ChessEngineExecutables[which], false);
}
else {
UE_LOG(LogTemp, Warning, TEXT("CE can't be initialized: %s"), *EngineFileName);
}
}
else if (ChessEngine1.IsValid() == false) {
ChessEngine1 = TSharedPtr<ChessEngineHolder>(new ChessEngineHolder());
if (ChessEngine1.IsValid()) {
ChessEngine1->Launch(this, tempPathToCE, ChessEngineExecutables[which], false);
}
else {
UE_LOG(LogTemp, Warning, TEXT("CE can't be initialized: %s"), *EngineFileName);
}
}
else {
UE_LOG(LogTemp, Warning, TEXT("Both CE slots are occupied, CE can't be initialized: %s"), *EngineFileName);
}
}
void UChessEngineCommunicator::StopChessEngines()
{
if (ChessEngine0.IsValid()) {
ChessEngine0->SendCommandToCE("quit");
ChessEngine0.Reset();
}
if (ChessEngine1.IsValid()) {
ChessEngine1->SendCommandToCE("quit");
ChessEngine1.Reset();
}
}
void UChessEngineCommunicator::BestMoveReceived(ChessEngineHolder * ChessEngine, const FString &Message)
{
FChessMove2 BestMoveReceivedMove = GetBestMove(Message);
if (ChessEngine && ChessEngine == ChessEngine0.Get()) {
FFunctionGraphTask::CreateAndDispatchWhenReady([&, BestMoveReceivedMove]() {
// code here runs on game thread
OnBestMoveReceived.Broadcast(0, BestMoveReceivedMove);
}, TStatId(), NULL, ENamedThreads::GameThread);
}
else if (ChessEngine && ChessEngine == ChessEngine1.Get()) {
FFunctionGraphTask::CreateAndDispatchWhenReady([&, BestMoveReceivedMove]() {
// code here runs on game thread
OnBestMoveReceived.Broadcast(1, BestMoveReceivedMove);
}, TStatId(), NULL, ENamedThreads::GameThread);
}
}