setEvent is called without ResetEvent

2.8k views Asked by At

what happens if a manual-reset event is set using setEvent but not reset using ResetEvent; and that event is triggered multiple times.i.e. while the event is getting processed, again the event is set.

following is the sample task:

void foo()
{
...
   SetEvent(hEvent1);
...
}
void foo1()
{
...
SetEvent(hEvent2);
...
}
int MainHandler()
{
...
 dwEvent = WaitForMultipleObjects(2,
 ghEvents,     // array of objects
 FALSE,       // wait for any object
 5000); 
switch(dwEvent)
{
case hEvent1:
//do something
break;
case hEvent2:
//do something
break;
}
}

Now, suppose while hEvent1's case is executing(i.e. it is still set), somehow again hEvent1 is triggered. I have deliberately not put ResetEvent(hEvent1) even though it is a manual-reset events. So, do we have a race condition?

2

There are 2 answers

5
Roger Rowland On BEST ANSWER

In your example using WaitForMultipleObjects, you may have a potential problem if the events you are waiting on are not listed in increasing order of frequency in the array of event handles. Also note my comment that your code above assumes that WaitForMultipleObjects returns an event handle. It doesn't.

WaitForMultipleObjects will stop waiting when the first signaled event is seen, looking at the array from index zero upwards.

So if you have an event that gets set (or does not get reset) as the first entry in the array, then the other events will get starved (i.e. will never be seen).

So, in your example, as long as hEvent1 is still signaled, hEvent2 will not be seen.

As an example of a common pattern, suppose we have some worker thread, whose thread function has been routed back to some owning class that contains the event objects and mutexes or whatever. The worker thread responds to just two events - a request to close tidily and a request to do some work. The code might look something like this:

UINT CMyClass::ThreadFunc()
{
    // make an array of some interesting event handles
    HANDLE hEvents[2] = { m_hKillEvent, m_hWorkEvent };

    // main loop - do work until killed
    while (true)
    {
        // wait for either event
        DWORD dwRet = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE);

        // see why we got signalled
        if (dwRet == WAIT_OBJECT_0)
        {
            // kill requested - exit loop
            break;
        }
        else if (dwRet == WAIT_OBJECT_0 + 1)
        {
            // work requested - do some work here
        }
        else
        {
            // error handling - bad handles?
        }
    }

    // normal exit
    return 0;
}

As coded, this will work fine - the main thread calls SetEvent(m_hWorkEvent) to trigger the background thread to do some work and it calls SetEvent(m_hKillEvent) to make the worker thread close. Closing may be protected with some timeout in case the thread is mid-work, so something like:

// close worker thread
SetEvent(m_hKillEvent);

// wait for thread to die peacefully
DWORD dwRet = WaitForSingleObject(m_hWorkerThread, 5000);
if (dwRet == WAIT_TIMEOUT)
{
    // worker failed to respond - error handling here
}

Now, this closedown process will work fine, even if m_hWorkEvent is being signalled very frequently - for example, by the time do some work here has finished, the event has again been signalled. This is because WaitForMultipleObjects will always check the kill event first because it is the first one in the array.

However, if the array had been defined like this:

    // make an array of some interesting event handles
    HANDLE hEvents[2] = { m_hWorkEvent, m_hKillEvent };

And if m_hWorkEvent is continually signalled (e.g. it gets set again during a long-running do some work here, or it is a manual reset event and you never reset it), then the thread will never exit cleanly because it will never see the kill signal. It will always try to do some work first.

This is what I mean about ordering the events in the array in increasing order of frequency. The kill event has the lowest frequency (it is signaled only once), so it goes first. If you have three or more events for different work requests, you need to maintain the same ordering or some events will get starved.

Whatever you decide to do, it's also worth noting that even if WaitForMultipleObjects got release on the "wrong" event, you can still check if a particular event is signaled by waiting with a zero timeout:

if (WaitForSingleObject(hSomeEvent, 0) == WAIT_OBJECT_0)
{
    // ... hSomeEvent was signaled
}

This can allow you to put intermediate checks for a kill event in suitable parts of a long-running background work procedure.

0
Igor Tandetnik On

Events are like boolean flags - it's OK to assign true to it twice. No one can possibly be waiting on an event that's currently signaled, so nothing happens when you set it to signaled again.

I'm not sure what you mean by "event is getting processed". It seems you use the word "event" twice with two different meanings - a kernel object represented by a HANDLE, and "something that my program has to do".