Reading Input From a Child Process

Discussion of chess software programming and technical issues.

Moderator: Ras

Richard Allbert
Posts: 794
Joined: Wed Jul 19, 2006 9:58 am

Reading Input From a Child Process

Post by Richard Allbert »

Hi,

I've been making a small program that runs engines through the STS test suite, and produces a log with the score added up for successful moves, including the the 2nd, 3rd, 4th etc best scoring moves.

It works ok, but I'm cheating a bit with the communication program <-> engine.

Code: Select all



void cSTStester::run_engine()
{
    logfile = "log.txt";

    const char *executable = this->EngineName.c_str();

   log.open(logfile.c_str(), ios::trunc);
   log<<"\n\n********** NEW LOG "<<__DATE__<<" "<<__TIME__<<" ***********"<<endl<<endl;

   SECURITY_ATTRIBUTES saAttr;

   printf("\n->Start of parent execution.\n");

   setbuf(stdout, NULL);
   setbuf(stdout, NULL);
   setvbuf(stdin, NULL, _IONBF, 0);
   setvbuf(stdout, NULL, _IONBF, 0);

   saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
   saAttr.bInheritHandle = TRUE;
   saAttr.lpSecurityDescriptor = NULL;

  
   engineoutput.clear();


   if ( ! CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, ULONG_MAX) )
      ErrorExit(TEXT("StdoutRd CreatePipe"));


// Create a pipe for the child process's STDIN.

   if (! CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, ULONG_MAX))
      ErrorExit(TEXT("Stdin CreatePipe"));


// Create the child process.

   readyok = false;

   CreateChildProcess(executable);

   string command;
   cout<<" starting engine... "<<executable<<"\n";
   WriteToPipe("uci\n");
   Sleep(1000);
   WriteToPipe("isready\n");
   Sleep(1000);
   engineoutput.clear();
   run_positions();
   WriteToPipe("quit\n");
   cout<<" stop quit sent.. \n";
   cout<<" closing engine\n";

	if (!CloseHandle(g_hChildStd_OUT_Rd))
      ErrorExit(TEXT("StdOutWr CloseHandle"));
	if (!CloseHandle(g_hChildStd_IN_Rd))
      ErrorExit(TEXT("StdOutWr CloseHandle"));
  
   log.close();

}

void CreateChildProcess(const char *szCmdline)
{
   PROCESS_INFORMATION piProcInfo;
   STARTUPINFO siStartInfo;
   BOOL bSuccess = FALSE;


   ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );

   ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
   siStartInfo.cb = sizeof(STARTUPINFO);
   siStartInfo.hStdError = g_hChildStd_OUT_Wr;
   siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
   siStartInfo.hStdInput = g_hChildStd_IN_Rd;
   siStartInfo.dwFlags |= STARTF_USESTDHANDLES;

   cout<<" creating "<<szCmdline<<endl;

   bSuccess = CreateProcess(NULL,
      (LPSTR) szCmdline,     // command line
      NULL,          // process security attributes
      NULL,          // primary thread security attributes
      TRUE,          // handles are inherited
      0,             // creation flags
      NULL,          // use parent's environment
      NULL,          // use parent's current directory
      &siStartInfo,  // STARTUPINFO pointer
      &piProcInfo);  // receives PROCESS_INFORMATION

   if ( ! bSuccess )
      ErrorExit(TEXT("CreateProcess"));
   else
   {	 
      CloseHandle(piProcInfo.hProcess);
      CloseHandle(piProcInfo.hThread);
   }
}

void WriteToPipe(string input)
{

   log<<"toengine -> "<<input;
   Sleep(50);
   DWORD dwWritten;
   string chBuf = input;
   BOOL bSuccess = FALSE;

      bSuccess = WriteFile(g_hChildStd_IN_Wr, chBuf.c_str(), chBuf.length(), &dwWritten, NULL);

}


void ReadFromPipe(bool start)
{
   DWORD dwRead, dwWritten;
   BOOL bSuccess = FALSE;
   HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
   char *tempstore = NULL;

   trimmed.clear();
   outputlines.clear();
   engineoutput.clear();

   DWORD bytes;
   if(PeekNamedPipe(g_hChildStd_OUT_Rd, NULL, 0, NULL, &bytes, NULL));
    if(bytes-1 >= ULONG_MAX)
    {
        cout<<"\n PIPE BUFFER EXCEEDED ";
        exit(1);
    }
    tempstore = new char[bytes];
    if(bytes)
    {
     bSuccess = ReadFile( g_hChildStd_OUT_Rd, tempstore, bytes, &dwRead, NULL);
    }
    //copy the tempstore to our global output string
    for(uint i = 0; i < bytes; ++i) engineoutput += tempstore[i];

    //split output into lines, and store
    stringsplitter(engineoutput,'\n',outputlines);

    for(uint i = 0; i < outputlines.size(); ++i)
    {
        log<<"fromengine -> "<<outputlines[i];
    }

    log<<"\n bytes was "<<bytes;

    findbest(outputlines,start);

    delete tempstore;
    tempstore = NULL;
}


void cSTStester::run_positions()
{
  string line;
  string command;

  for(uint i=0; i < TotalResult.files.size(); ++i)
  {
    this->detailfile<<"\nFile "<<TotalResult.files[i].filename<<endl;
    TotalResult.files[i].positionstested = 0;
    for(uint j = 0; j < TotalResult.files[i].positions.size(); ++j)
    {
      line.clear();
      command.clear();
      line = TotalResult.files[i].positions[j].fen;
      command = "position fen ";
      command += line;
      command +="\n";
      this->detailfile<<"\nposition "<<j+1<<" < "<<line<<" > ";
      this->board.setboard(line);
      //this->board.printboard();
      WriteToPipe(command);
      WriteToPipe("go infinite\n");
      Sleep(this->timelimit);
      WriteToPipe("stop\n");
      Sleep(200);
      ReadFromPipe(false);
      log<<"\nEngine bestmove found "<<trimmed;
      log.flush();
      this->score_position(TotalResult.files[i].positions[j],trimmed);
      TotalResult.files[i].positionstested++;
        Sleep(100);
      if(j>=PositionsToAnalyse) break;
    }
  }

}
What happens above is the start is with run_engine(), which creates the child process (runs the engine to be tested). ReadFromPipe() reads the output from the Child (engine), storing all output into a string, which is then split at each newline and stored in a vector (outputlines). run_positions() sends the fen positions, and reads the output from the engine. Note the frequent use of the Sleep() function to wait until the engine has had a chance to reply to a given command - this is where I think I'm cheating.

If you look in run_positions(), you see that the fen is sent, and then I wait for the specified search time for the position.

After this is up, the pipe from the engine is read, stop is sent and then a 200ms second pause "hopefully" enough time to assure a "bestmove xxxx" has been written by the engine.

When I look at the output from (admittedly, much more proficiently programmed efforts) Winboard, Arena etc, they seem to catch each line from the pipe as it comes.

How do they do this?

I tried a loop reading reading bytes until each newline, but then the full pipe is read, irrespective of a newline or not, so I end up with broken lines.

Sorry if this is not clear.... very much an amateur :) - this program is my effort to start to learn about this part of communication programming.

Richard

PS No doubt Linux has a MUCH easier way...
bob
Posts: 20943
Joined: Mon Feb 27, 2006 7:30 pm
Location: Birmingham, AL

Re: Reading Input From a Child Process

Post by bob »

Richard Allbert wrote:Hi,

I've been making a small program that runs engines through the STS test suite, and produces a log with the score added up for successful moves, including the the 2nd, 3rd, 4th etc best scoring moves.

It works ok, but I'm cheating a bit with the communication program <-> engine.

Code: Select all



void cSTStester::run_engine()
{
    logfile = "log.txt";

    const char *executable = this->EngineName.c_str();

   log.open(logfile.c_str(), ios::trunc);
   log<<"\n\n********** NEW LOG "<<__DATE__<<" "<<__TIME__<<" ***********"<<endl<<endl;

   SECURITY_ATTRIBUTES saAttr;

   printf("\n->Start of parent execution.\n");

   setbuf(stdout, NULL);
   setbuf(stdout, NULL);
   setvbuf(stdin, NULL, _IONBF, 0);
   setvbuf(stdout, NULL, _IONBF, 0);

   saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
   saAttr.bInheritHandle = TRUE;
   saAttr.lpSecurityDescriptor = NULL;

  
   engineoutput.clear();


   if ( ! CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, ULONG_MAX) )
      ErrorExit(TEXT("StdoutRd CreatePipe"));


// Create a pipe for the child process's STDIN.

   if (! CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, ULONG_MAX))
      ErrorExit(TEXT("Stdin CreatePipe"));


// Create the child process.

   readyok = false;

   CreateChildProcess(executable);

   string command;
   cout<<" starting engine... "<<executable<<"\n";
   WriteToPipe("uci\n");
   Sleep(1000);
   WriteToPipe("isready\n");
   Sleep(1000);
   engineoutput.clear();
   run_positions();
   WriteToPipe("quit\n");
   cout<<" stop quit sent.. \n";
   cout<<" closing engine\n";

	if (!CloseHandle(g_hChildStd_OUT_Rd))
      ErrorExit(TEXT("StdOutWr CloseHandle"));
	if (!CloseHandle(g_hChildStd_IN_Rd))
      ErrorExit(TEXT("StdOutWr CloseHandle"));
  
   log.close();

}

void CreateChildProcess(const char *szCmdline)
{
   PROCESS_INFORMATION piProcInfo;
   STARTUPINFO siStartInfo;
   BOOL bSuccess = FALSE;


   ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );

   ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
   siStartInfo.cb = sizeof(STARTUPINFO);
   siStartInfo.hStdError = g_hChildStd_OUT_Wr;
   siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
   siStartInfo.hStdInput = g_hChildStd_IN_Rd;
   siStartInfo.dwFlags |= STARTF_USESTDHANDLES;

   cout<<" creating "<<szCmdline<<endl;

   bSuccess = CreateProcess(NULL,
      (LPSTR) szCmdline,     // command line
      NULL,          // process security attributes
      NULL,          // primary thread security attributes
      TRUE,          // handles are inherited
      0,             // creation flags
      NULL,          // use parent's environment
      NULL,          // use parent's current directory
      &siStartInfo,  // STARTUPINFO pointer
      &piProcInfo);  // receives PROCESS_INFORMATION

   if ( ! bSuccess )
      ErrorExit(TEXT("CreateProcess"));
   else
   {	 
      CloseHandle(piProcInfo.hProcess);
      CloseHandle(piProcInfo.hThread);
   }
}

void WriteToPipe(string input)
{

   log<<"toengine -> "<<input;
   Sleep(50);
   DWORD dwWritten;
   string chBuf = input;
   BOOL bSuccess = FALSE;

      bSuccess = WriteFile(g_hChildStd_IN_Wr, chBuf.c_str(), chBuf.length(), &dwWritten, NULL);

}


void ReadFromPipe(bool start)
{
   DWORD dwRead, dwWritten;
   BOOL bSuccess = FALSE;
   HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
   char *tempstore = NULL;

   trimmed.clear();
   outputlines.clear();
   engineoutput.clear();

   DWORD bytes;
   if(PeekNamedPipe(g_hChildStd_OUT_Rd, NULL, 0, NULL, &bytes, NULL));
    if(bytes-1 >= ULONG_MAX)
    {
        cout<<"\n PIPE BUFFER EXCEEDED ";
        exit(1);
    }
    tempstore = new char[bytes];
    if(bytes)
    {
     bSuccess = ReadFile( g_hChildStd_OUT_Rd, tempstore, bytes, &dwRead, NULL);
    }
    //copy the tempstore to our global output string
    for(uint i = 0; i < bytes; ++i) engineoutput += tempstore[i];

    //split output into lines, and store
    stringsplitter(engineoutput,'\n',outputlines);

    for(uint i = 0; i < outputlines.size(); ++i)
    {
        log<<"fromengine -> "<<outputlines[i];
    }

    log<<"\n bytes was "<<bytes;

    findbest(outputlines,start);

    delete tempstore;
    tempstore = NULL;
}


void cSTStester::run_positions()
{
  string line;
  string command;

  for(uint i=0; i < TotalResult.files.size(); ++i)
  {
    this->detailfile<<"\nFile "<<TotalResult.files[i].filename<<endl;
    TotalResult.files[i].positionstested = 0;
    for(uint j = 0; j < TotalResult.files[i].positions.size(); ++j)
    {
      line.clear();
      command.clear();
      line = TotalResult.files[i].positions[j].fen;
      command = "position fen ";
      command += line;
      command +="\n";
      this->detailfile<<"\nposition "<<j+1<<" < "<<line<<" > ";
      this->board.setboard(line);
      //this->board.printboard();
      WriteToPipe(command);
      WriteToPipe("go infinite\n");
      Sleep(this->timelimit);
      WriteToPipe("stop\n");
      Sleep(200);
      ReadFromPipe(false);
      log<<"\nEngine bestmove found "<<trimmed;
      log.flush();
      this->score_position(TotalResult.files[i].positions[j],trimmed);
      TotalResult.files[i].positionstested++;
        Sleep(100);
      if(j>=PositionsToAnalyse) break;
    }
  }

}
What happens above is the start is with run_engine(), which creates the child process (runs the engine to be tested). ReadFromPipe() reads the output from the Child (engine), storing all output into a string, which is then split at each newline and stored in a vector (outputlines). run_positions() sends the fen positions, and reads the output from the engine. Note the frequent use of the Sleep() function to wait until the engine has had a chance to reply to a given command - this is where I think I'm cheating.

If you look in run_positions(), you see that the fen is sent, and then I wait for the specified search time for the position.

After this is up, the pipe from the engine is read, stop is sent and then a 200ms second pause "hopefully" enough time to assure a "bestmove xxxx" has been written by the engine.

When I look at the output from (admittedly, much more proficiently programmed efforts) Winboard, Arena etc, they seem to catch each line from the pipe as it comes.

How do they do this?

I tried a loop reading reading bytes until each newline, but then the full pipe is read, irrespective of a newline or not, so I end up with broken lines.

Sorry if this is not clear.... very much an amateur :) - this program is my effort to start to learn about this part of communication programming.

Richard

PS No doubt Linux has a MUCH easier way...
The classic Unix solution is to do a read() which will completely empty the pipe's buffer on your end. Then find the first \n character and use the input up to that point. Then find the next \n character and repeat. If you end up with left-over text with no terminating \n character, just do another read and append that new input to what you have, and start the process over...
Richard Allbert
Posts: 794
Joined: Wed Jul 19, 2006 9:58 am

Re: Reading Input From a Child Process

Post by Richard Allbert »

bob wrote:
just do another read and append that new input to what you have, and start the process over...
:D

Thanks for the reply.... that part at the end was for some reason oblivious to me.

Richard
User avatar
Don
Posts: 5106
Joined: Tue Apr 29, 2008 4:27 pm

Re: Reading Input From a Child Process

Post by Don »

I have a graphical tcl/tk utility which runs this test and scores it. It is free for anyone who asks.

Don

Richard Allbert wrote:Hi,

I've been making a small program that runs engines through the STS test suite, and produces a log with the score added up for successful moves, including the the 2nd, 3rd, 4th etc best scoring moves.

It works ok, but I'm cheating a bit with the communication program <-> engine.

Code: Select all



void cSTStester::run_engine()
{
    logfile = "log.txt";

    const char *executable = this->EngineName.c_str();

   log.open(logfile.c_str(), ios::trunc);
   log<<"\n\n********** NEW LOG "<<__DATE__<<" "<<__TIME__<<" ***********"<<endl<<endl;

   SECURITY_ATTRIBUTES saAttr;

   printf("\n->Start of parent execution.\n");

   setbuf(stdout, NULL);
   setbuf(stdout, NULL);
   setvbuf(stdin, NULL, _IONBF, 0);
   setvbuf(stdout, NULL, _IONBF, 0);

   saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
   saAttr.bInheritHandle = TRUE;
   saAttr.lpSecurityDescriptor = NULL;

  
   engineoutput.clear();


   if ( ! CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, ULONG_MAX) )
      ErrorExit(TEXT("StdoutRd CreatePipe"));


// Create a pipe for the child process's STDIN.

   if (! CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, ULONG_MAX))
      ErrorExit(TEXT("Stdin CreatePipe"));


// Create the child process.

   readyok = false;

   CreateChildProcess(executable);

   string command;
   cout<<" starting engine... "<<executable<<"\n";
   WriteToPipe("uci\n");
   Sleep(1000);
   WriteToPipe("isready\n");
   Sleep(1000);
   engineoutput.clear();
   run_positions();
   WriteToPipe("quit\n");
   cout<<" stop quit sent.. \n";
   cout<<" closing engine\n";

	if (!CloseHandle(g_hChildStd_OUT_Rd))
      ErrorExit(TEXT("StdOutWr CloseHandle"));
	if (!CloseHandle(g_hChildStd_IN_Rd))
      ErrorExit(TEXT("StdOutWr CloseHandle"));
  
   log.close();

}

void CreateChildProcess(const char *szCmdline)
{
   PROCESS_INFORMATION piProcInfo;
   STARTUPINFO siStartInfo;
   BOOL bSuccess = FALSE;


   ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );

   ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
   siStartInfo.cb = sizeof(STARTUPINFO);
   siStartInfo.hStdError = g_hChildStd_OUT_Wr;
   siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
   siStartInfo.hStdInput = g_hChildStd_IN_Rd;
   siStartInfo.dwFlags |= STARTF_USESTDHANDLES;

   cout<<" creating "<<szCmdline<<endl;

   bSuccess = CreateProcess(NULL,
      (LPSTR) szCmdline,     // command line
      NULL,          // process security attributes
      NULL,          // primary thread security attributes
      TRUE,          // handles are inherited
      0,             // creation flags
      NULL,          // use parent's environment
      NULL,          // use parent's current directory
      &siStartInfo,  // STARTUPINFO pointer
      &piProcInfo);  // receives PROCESS_INFORMATION

   if ( ! bSuccess )
      ErrorExit(TEXT("CreateProcess"));
   else
   {	 
      CloseHandle(piProcInfo.hProcess);
      CloseHandle(piProcInfo.hThread);
   }
}

void WriteToPipe(string input)
{

   log<<"toengine -> "<<input;
   Sleep(50);
   DWORD dwWritten;
   string chBuf = input;
   BOOL bSuccess = FALSE;

      bSuccess = WriteFile(g_hChildStd_IN_Wr, chBuf.c_str(), chBuf.length(), &dwWritten, NULL);

}


void ReadFromPipe(bool start)
{
   DWORD dwRead, dwWritten;
   BOOL bSuccess = FALSE;
   HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
   char *tempstore = NULL;

   trimmed.clear();
   outputlines.clear();
   engineoutput.clear();

   DWORD bytes;
   if(PeekNamedPipe(g_hChildStd_OUT_Rd, NULL, 0, NULL, &bytes, NULL));
    if(bytes-1 >= ULONG_MAX)
    {
        cout<<"\n PIPE BUFFER EXCEEDED ";
        exit(1);
    }
    tempstore = new char[bytes];
    if(bytes)
    {
     bSuccess = ReadFile( g_hChildStd_OUT_Rd, tempstore, bytes, &dwRead, NULL);
    }
    //copy the tempstore to our global output string
    for(uint i = 0; i < bytes; ++i) engineoutput += tempstore[i];

    //split output into lines, and store
    stringsplitter(engineoutput,'\n',outputlines);

    for(uint i = 0; i < outputlines.size(); ++i)
    {
        log<<"fromengine -> "<<outputlines[i];
    }

    log<<"\n bytes was "<<bytes;

    findbest(outputlines,start);

    delete tempstore;
    tempstore = NULL;
}


void cSTStester::run_positions()
{
  string line;
  string command;

  for(uint i=0; i < TotalResult.files.size(); ++i)
  {
    this->detailfile<<"\nFile "<<TotalResult.files[i].filename<<endl;
    TotalResult.files[i].positionstested = 0;
    for(uint j = 0; j < TotalResult.files[i].positions.size(); ++j)
    {
      line.clear();
      command.clear();
      line = TotalResult.files[i].positions[j].fen;
      command = "position fen ";
      command += line;
      command +="\n";
      this->detailfile<<"\nposition "<<j+1<<" < "<<line<<" > ";
      this->board.setboard(line);
      //this->board.printboard();
      WriteToPipe(command);
      WriteToPipe("go infinite\n");
      Sleep(this->timelimit);
      WriteToPipe("stop\n");
      Sleep(200);
      ReadFromPipe(false);
      log<<"\nEngine bestmove found "<<trimmed;
      log.flush();
      this->score_position(TotalResult.files[i].positions[j],trimmed);
      TotalResult.files[i].positionstested++;
        Sleep(100);
      if(j>=PositionsToAnalyse) break;
    }
  }

}
What happens above is the start is with run_engine(), which creates the child process (runs the engine to be tested). ReadFromPipe() reads the output from the Child (engine), storing all output into a string, which is then split at each newline and stored in a vector (outputlines). run_positions() sends the fen positions, and reads the output from the engine. Note the frequent use of the Sleep() function to wait until the engine has had a chance to reply to a given command - this is where I think I'm cheating.

If you look in run_positions(), you see that the fen is sent, and then I wait for the specified search time for the position.

After this is up, the pipe from the engine is read, stop is sent and then a 200ms second pause "hopefully" enough time to assure a "bestmove xxxx" has been written by the engine.

When I look at the output from (admittedly, much more proficiently programmed efforts) Winboard, Arena etc, they seem to catch each line from the pipe as it comes.

How do they do this?

I tried a loop reading reading bytes until each newline, but then the full pipe is read, irrespective of a newline or not, so I end up with broken lines.

Sorry if this is not clear.... very much an amateur :) - this program is my effort to start to learn about this part of communication programming.

Richard

PS No doubt Linux has a MUCH easier way...
Richard Allbert
Posts: 794
Joined: Wed Jul 19, 2006 9:58 am

Re: Reading Input From a Child Process

Post by Richard Allbert »

:D

Well, I'll keep going - It's a nice little project to understand process communication.