Win32: Fix deadlocks if Sys_Printf() or Sys_Error() was called in thread

If those functions (e.g. called by common->Printf(), common->Error())
weren't called from the mainthread and win_outputEditString was set to 1,
a deadlock could occur.
Specifically, the async thread (handling sound) was calling
common->Warning() -> Sys_Printf() -> Conbuf_AppendText() which called
SendMessageA() which blocks until the main thread handles the message.
The main thread however was in idSampleDecoderLocal::Decode() trying to
enter CRITICAL_SECTION_ONE, which was held by the async thread
(it's used to synchronize sound handling between main and async thread).

So now if Sys_Printf() (or Sys_Error() which should have the same problem)
is not called by the main thread, the text is buffered and
Conbuf_AppendText() is called for the buffered lines in the next frame
in Win_Frame().
This commit is contained in:
Daniel Gibson 2021-06-17 04:11:14 +02:00
parent a523c28c18
commit d5fd0990a1

View file

@ -113,6 +113,9 @@ PWGLSWAPLAYERBUFFERS qwglSwapLayerBuffers;
#endif /* End stuff required for tools */
static bool hadError = false;
static char errorText[4096];
/*
=============
Sys_Error
@ -125,11 +128,28 @@ void Sys_Error( const char *error, ... ) {
char text[4096];
MSG msg;
if ( !Sys_IsMainThread() ) {
// to avoid deadlocks we mustn't call Conbuf_AppendText() etc if not in main thread!
va_start(argptr, error);
vsprintf(errorText, error, argptr);
va_end(argptr);
printf("%s", errorText);
OutputDebugString( errorText );
hadError = true;
return;
}
va_start( argptr, error );
vsprintf( text, error, argptr );
va_end( argptr);
printf("%s", text);
if ( !hadError ) {
// if we had an error in another thread, printf() and OutputDebugString() has already been called for this
printf( "%s", text );
OutputDebugString( text );
}
Conbuf_AppendText( text );
Conbuf_AppendText( "\n" );
@ -183,13 +203,22 @@ void Sys_Quit( void ) {
Sys_Printf
==============
*/
#define MAXPRINTMSG 4096
enum {
MAXPRINTMSG = 4096,
MAXNUMBUFFEREDLINES = 16
};
static char bufferedPrintfLines[MAXNUMBUFFEREDLINES][MAXPRINTMSG];
static int curNumBufferedPrintfLines = 0;
static CRITICAL_SECTION printfCritSect;
void Sys_Printf( const char *fmt, ... ) {
char msg[MAXPRINTMSG];
va_list argptr;
va_start(argptr, fmt);
idStr::vsnPrintf( msg, MAXPRINTMSG-1, fmt, argptr );
int len = idStr::vsnPrintf( msg, MAXPRINTMSG-1, fmt, argptr );
va_end(argptr);
msg[sizeof(msg)-1] = '\0';
@ -199,7 +228,18 @@ void Sys_Printf( const char *fmt, ... ) {
OutputDebugString( msg );
}
if ( win32.win_outputEditString.GetBool() ) {
Conbuf_AppendText( msg );
if ( Sys_IsMainThread() ) {
Conbuf_AppendText( msg );
} else {
EnterCriticalSection( &printfCritSect );
int idx = curNumBufferedPrintfLines++;
if ( idx < MAXNUMBUFFEREDLINES ) {
if ( len >= MAXPRINTMSG )
len = MAXPRINTMSG - 1;
memcpy( bufferedPrintfLines[idx], msg, len + 1 );
}
LeaveCriticalSection( &printfCritSect );
}
}
}
@ -747,6 +787,22 @@ void Win_Frame( void ) {
}
win32.win_viewlog.ClearModified();
}
if ( curNumBufferedPrintfLines > 0 ) {
// if Sys_Printf() had been called in another thread, add those lines to the windows console now
EnterCriticalSection( &printfCritSect );
int n = Min( curNumBufferedPrintfLines, (int)MAXNUMBUFFEREDLINES );
for ( int i = 0; i < n; ++i ) {
Conbuf_AppendText( bufferedPrintfLines[i] );
}
curNumBufferedPrintfLines = 0;
LeaveCriticalSection( &printfCritSect );
}
if ( hadError ) {
// if Sys_Error() had been called in another thread, handle it now
Sys_Error( "%s", errorText );
}
}
// the MFC tools use Win_GetWindowScalingFactor() for High-DPI support
@ -921,6 +977,8 @@ WinMain
int main(int argc, char *argv[]) {
const HCURSOR hcurSave = ::SetCursor( LoadCursor( 0, IDC_WAIT ) );
InitializeCriticalSection( &printfCritSect );
#ifdef ID_DEDICATED
MSG msg;
#else