Use a semaphore to signal the event handler

Semaphores allow for semi-persistent signals, compared to a condition variable
which requires a mutex for proper detection. A semaphore can be 'post'ed after
writing some data on one thread, and another thread will be able to recognize
it quickly even if the post occured in between checking for data and waiting.

This more correctly fixes a race condition with events since the mixer
shouldn't be using mutexes, and arbitrary wake-ups just to make sure an event
wasn't missed was quite inefficient.
This commit is contained in:
Chris Robinson
2018-02-01 18:09:16 -08:00
parent 4ec31291c0
commit e240351d81
4 changed files with 9 additions and 19 deletions
+3 -2
View File
@@ -2613,7 +2613,7 @@ static ALvoid InitContext(ALCcontext *Context)
ATOMIC_FLAG_TEST_AND_SET(&Context->PropsClean, almemory_order_relaxed);
ATOMIC_INIT(&Context->DeferUpdates, AL_FALSE);
almtx_init(&Context->EventThrdLock, almtx_plain);
alcnd_init(&Context->EventCnd);
alsem_init(&Context->EventSem, 0);
Context->AsyncEvents = NULL;
ATOMIC_INIT(&Context->EnabledEvts, 0);
almtx_init(&Context->EventCbLock, almtx_plain);
@@ -2753,12 +2753,13 @@ static void FreeContext(ALCcontext *context)
while(ll_ringbuffer_write_space(context->AsyncEvents) == 0)
althrd_yield();
ll_ringbuffer_write(context->AsyncEvents, (const char*)&kill_evt, 1);
alsem_post(&context->EventSem);
althrd_join(context->EventThread, NULL);
}
almtx_destroy(&context->EventCbLock);
almtx_destroy(&context->EventThrdLock);
alcnd_destroy(&context->EventCnd);
alsem_destroy(&context->EventSem);
ll_ringbuffer_free(context->AsyncEvents);
context->AsyncEvents = NULL;
+1 -1
View File
@@ -754,7 +754,7 @@ ALboolean MixSource(ALvoice *voice, ALuint SourceID, ALCcontext *Context, ALsize
AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, SourceID, 0, "Buffer completed"
);
} while(--buffers_done > 0);
alcnd_signal(&Context->EventCnd);
alsem_post(&Context->EventSem);
}
return isplaying;
+1 -1
View File
@@ -675,7 +675,7 @@ struct ALCcontext_struct {
almtx_t EventThrdLock;
althrd_t EventThread;
alcnd_t EventCnd;
alsem_t EventSem;
struct ll_ringbuffer *AsyncEvents;
ATOMIC(ALbitfieldSOFT) EnabledEvts;
almtx_t EventCbLock;
+4 -15
View File
@@ -13,7 +13,6 @@ static int EventThread(void *arg)
{
ALCcontext *context = arg;
almtx_lock(&context->EventCbLock);
while(1)
{
AsyncEvent evt;
@@ -21,30 +20,19 @@ static int EventThread(void *arg)
if(ll_ringbuffer_read_space(context->AsyncEvents) == 0)
{
/* Wait 50ms before checking again. Because events are delivered
* asynchronously by the mixer, it's possible for one to be written
* in between checking for a readable element and sleeping. So to
* ensure events don't get left to go stale in the ringbuffer, we
* need to keep checking regardless of being signaled.
*/
struct timespec ts;
altimespec_get(&ts, AL_TIME_UTC);
ts.tv_nsec += 50000000;
ts.tv_sec += ts.tv_nsec/1000000000;
ts.tv_nsec %= 1000000000;
alcnd_timedwait(&context->EventCnd, &context->EventCbLock, &ts);
alsem_wait(&context->EventSem);
continue;
}
ll_ringbuffer_read(context->AsyncEvents, (char*)&evt, 1);
if(!evt.EnumType) break;
/* Should check the actual type is enabled here too. */
almtx_lock(&context->EventCbLock);
enabledevts = ATOMIC_LOAD(&context->EnabledEvts, almemory_order_acquire);
if(context->EventCb && (enabledevts&evt.EnumType) == evt.EnumType)
context->EventCb(evt.Type, evt.ObjectId, evt.Param, (ALsizei)strlen(evt.Message),
evt.Message, context->EventParam);
almtx_unlock(&context->EventCbLock);
}
almtx_unlock(&context->EventCbLock);
return 0;
}
@@ -114,6 +102,7 @@ AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, A
while(ll_ringbuffer_write_space(context->AsyncEvents) == 0)
althrd_yield();
ll_ringbuffer_write(context->AsyncEvents, (const char*)&kill_evt, 1);
alsem_post(&context->EventSem);
althrd_join(context->EventThread, NULL);
}
else