Currently, some, but not all, PortAudio Host APIs will use a finite timeout when joining the audio streaming thread in Pa_StopStream(). Examples include:
MME:
|
/* Calculate timeOut longer than longest time it could take to return all buffers. */ |
|
timeoutMs = (DWORD)(stream->allBuffersDurationMs * 1.5); |
|
if( timeoutMs < PA_MME_MIN_TIMEOUT_MSEC_ ) |
|
timeoutMs = PA_MME_MIN_TIMEOUT_MSEC_; |
|
|
|
PA_DEBUG(("WinMME StopStream: waiting for background thread.\n")); |
|
|
|
waitResult = WaitForSingleObject( stream->processingThread, timeoutMs ); |
DirectSound:
|
if( WaitForSingleObject( stream->processingThreadCompleted, 30*100 ) == WAIT_TIMEOUT ) |
In contrast, WASAPI uses an unbounded wait:
|
// Issue command to thread to stop processing and wait for thread exit |
|
if (!stream->isBlocking) |
|
{ |
|
SignalObjectAndWait(stream->hCloseRequest, stream->hThreadExit, INFINITE, FALSE); |
And so does WDM-KS:
|
if (GetExitCodeThread(stream->streamThread, &dwExitCode) && dwExitCode == STILL_ACTIVE) |
|
{ |
|
if (WaitForSingleObject(stream->streamThread, INFINITE) != WAIT_OBJECT_0) |
(I have not looked into non-Windows host APIs.)
I believe it is incorrect to use a finite timeout, and this can lead to race conditions and undefined behavior. MME and DirectSound should be changed to wait forever.
The reason why I'm advocating this is because there is no telling how long the streaming thread may be blocked for. If the streaming thread suddenly resumes execution after Pa_StopStream() gave up, then the resources that the streaming thread is using may have been freed already - in fact, the entire PortAudio DLL may have been freed, as everything has been cleaned up as far as the caller code is concerned. This leads to hilarity in the form of undefined behavior.
Now, is it likely that the streaming thread will be blocked for that long? No, but "unlikely" is not the same as "impossible", even in the absence of any deadlock/bugs. A non-real-time OS is allowed to suspend the execution of a thread indefinitely. For example, this could happen if the machine is overloaded, if the relevant memory pages have been swapped out and are taking some time to swap back in, if the process is suspended, or even if the computer is going to standby/sleep. In all these cases, the bounded timeout runs the risk of triggering undefined behavior. In my opinion this is not reasonable.
I presume that the reason why this timeout was introduced is to protect against the case where the streaming thread may be deadlocked. I don't think this makes sense because this is not something that is supposed to occur in a valid program. If the streaming thread deadlocks, then things are already going off the rails and there is no point in trying to "cover up" the problem - in fact, this is doing the developer a disservice because it only serves to hide bugs. This is precisely what happened in dechamps/FlexASIO#235, where PortAudio DirectSound ended up "hiding" a deadlock issue in user code, but PortAudio WASAPI made it obvious by deadlocking the entire app (as it should!). The behavior of waiting with a timeout and returning an error would be reasonable if the failure mode was recoverable, but strictly speaking it isn't, because (1) the code cannot know if the streaming thread is truly deadlocked and (2) even if it is, the streaming thread will linger around forever and leak various resources anyway.
For these reasons, I believe the correct behavior is to wait forever.
Currently, some, but not all, PortAudio Host APIs will use a finite timeout when joining the audio streaming thread in
Pa_StopStream(). Examples include:MME:
portaudio/src/hostapi/wmme/pa_win_wmme.c
Lines 3453 to 3460 in 18a606e
DirectSound:
portaudio/src/hostapi/dsound/pa_win_ds.c
Line 3092 in 18a606e
In contrast, WASAPI uses an unbounded wait:
portaudio/src/hostapi/wasapi/pa_win_wasapi.c
Lines 4517 to 4520 in 18a606e
And so does WDM-KS:
portaudio/src/hostapi/wdmks/pa_win_wdmks.c
Lines 6289 to 6291 in 18a606e
(I have not looked into non-Windows host APIs.)
I believe it is incorrect to use a finite timeout, and this can lead to race conditions and undefined behavior. MME and DirectSound should be changed to wait forever.
The reason why I'm advocating this is because there is no telling how long the streaming thread may be blocked for. If the streaming thread suddenly resumes execution after
Pa_StopStream()gave up, then the resources that the streaming thread is using may have been freed already - in fact, the entire PortAudio DLL may have been freed, as everything has been cleaned up as far as the caller code is concerned. This leads to hilarity in the form of undefined behavior.Now, is it likely that the streaming thread will be blocked for that long? No, but "unlikely" is not the same as "impossible", even in the absence of any deadlock/bugs. A non-real-time OS is allowed to suspend the execution of a thread indefinitely. For example, this could happen if the machine is overloaded, if the relevant memory pages have been swapped out and are taking some time to swap back in, if the process is suspended, or even if the computer is going to standby/sleep. In all these cases, the bounded timeout runs the risk of triggering undefined behavior. In my opinion this is not reasonable.
I presume that the reason why this timeout was introduced is to protect against the case where the streaming thread may be deadlocked. I don't think this makes sense because this is not something that is supposed to occur in a valid program. If the streaming thread deadlocks, then things are already going off the rails and there is no point in trying to "cover up" the problem - in fact, this is doing the developer a disservice because it only serves to hide bugs. This is precisely what happened in dechamps/FlexASIO#235, where PortAudio DirectSound ended up "hiding" a deadlock issue in user code, but PortAudio WASAPI made it obvious by deadlocking the entire app (as it should!). The behavior of waiting with a timeout and returning an error would be reasonable if the failure mode was recoverable, but strictly speaking it isn't, because (1) the code cannot know if the streaming thread is truly deadlocked and (2) even if it is, the streaming thread will linger around forever and leak various resources anyway.
For these reasons, I believe the correct behavior is to wait forever.