That surely localizes the trouble, but I still cannot say I understand it. ReserveGame(), called from GameEnd() ends as follows:
Code: Select all
if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
q[nextGame] = '*';
}
fseek(tf, -(strlen(p)+4), SEEK_END);
c = fgetc(tf);
if(c != '"') // depending on DOS or Unix line endings we can be one off
fseek(tf, -(strlen(p)+2), SEEK_END);
else fseek(tf, -(strlen(p)+3), SEEK_END);
fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
DisplayMessage(buf, "");
free(p); appData.results = q;
if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
(gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
int round = appData.defaultMatchGames * appData.tourneyType;
if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
UnloadEngine(&first); // next game belongs to other pairing;
UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
}
if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
We see the printf at the start of that section, with correct values (it changed the result of game 12 from * to +, and picked game 16 to do next, which is just beyond the end of the -result string. Then it does some seeking and writing on the tourneyfile 'tf' (which I know to be succesfully opened), restores the message field (DisplayMessage) with the message it contained previously (which was saved in 'buf'), overwriting the "pick new game" which it displayed earlier (and indeed is not in the message field, which still contains Thinking Output). So it seems up to that point everything worked.
The final printf, "Reserved ..." never appears, though. (Or the DisplayMessage("pick next game") was even never executed; this is always tricky, because much stuff happens asynchronously, just putting a draw or expose event in the event queue, and then only executing that after the current code (still handling the consequences of the engine move) has returned. So what is in the display is not as reliable an indicator as what is in the debug file.) So it must somehow have gone astray in the "if(nextGame..." clause. Which I suppose should indeed be executed, as game 16 and game 12 will not have the same engines in a multi-gauntlet with 2 games per pairing.
So UnloadEngine should have been called. And this also contains a debug print (which never appeared!):
Code: Select all
void
UnloadEngine (ChessProgramState *cps)
{
/* Kill off first chess program */
if (cps->isr != NULL)
RemoveInputSource(cps->isr);
cps->isr = NULL;
if (cps->pr != NoProc) {
ExitAnalyzeMode();
DoSleep( appData.delayBeforeQuit );
SendToProgram("quit\n", cps);
DestroyChildProcess(cps->pr, 4*!cps->isUCI + cps->useSigterm);
}
cps->pr = NoProc;
if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
}
Now this code can potentially stall at the DoSleep (if -delayBeforeQuit has a strange value, but it should be configured to 0, in which case it is a no-op), which is just a platform-independent wrapper for the Win API Sleep() call. The DestroyChildProcess() should just close the pipes to the engine here (as these are UCI engines, and WB will not try to kill the adapter after some timeout). In fact it should do nothing here, as UCI2WB specifies feature reuse=0, which would already have caused GameEnd() to terminate the engines (and set their cps->pr field to NoProc and cps->isr to NULL) before it called ReserveGame(). So this routine should be a complete no-op, except for the debug printing, which does not appear!
The point is that even when it would stall in this code, or in its caller ReserveGame(), I don't see how it could ever remain responsive and handle the ExitEvent() later. WinBoard is not really a multi-threaded program; there is just one thread monitoring the event queue (blocking while that is empty), and executing the various event handlers (which might seem indpendent processes) one after the other. So if one event handler gets stuck, it will never return to monitoring the event queue for handling later events. So it is like it mysteriously managed to skip several printfs, and return to the caller of GameEnd() without clearing endingGame. This is of course in theory possible through corruption of the stack, which stores return addresses of the function, and if a number of those addresses gets popped of the stack, or an earlier return address copied to the top, a return from a function could skip several levels of callers, and return to the main program directly. This is at best extremely unlikely, and I just don't see how that could happen here at all.