CODE: Sequoia

Saturday, December 02, 2006

Auto reset event debugging technique

Common error of auto reset event

In Win32 API, you can use an "event object" to synchronize multiple threads. An event has one of two state: signaled or not-signaled. When it's not-signaled, the waiting thread is blocked on WaitForSingleObject call, and it's unblocked when the event is signaled. You can signal a event by SetEvent() API function. Thread synchronization with event objects is known to be error prone. One typical error is "slipped event", or SetEvent() is called on already signaled event and does not have any effect. Let' say, you write a one-length FIFO, where one thread push a value and the other thread pulls it.
// variable to pass value from push thread
// to pull thread.
int plate;

// event is created with auto-reset, initial 
// value is not signaled.
HANDLE hasItem = CreateEvent(NULL, FALSE, FALSE, NULL);

void push(int item){
  plate = item;
  SetEvent(hasItem);
}

int pull(){
  WaitForSingleObject(hasItem, INFINITE);
  return plate;
}
If push() is called twice before pull(), value in plate is overwritten and lost. Slipped event is very common, and hard to avoid. It's also difficult to debug because the effect may be visible long time later.

Slipped event debugging technique

There is a way to make your life easier: Use semaphoe instead of auto-reset event. If you read the MSDN carefully, you may find that auto-reset event is same as semaphoe with maximum count = 1. Therefore, you can safely replace your event with semaphoe. There is a big advantage of semaphoe over event. When you call ReleaseSemaphoe (SetEvent of semaphoe) on a signaled semaphoe (count = 1), it returns FALSE. Therefore, you can detect slipped event by checking the return value of ReleaseSemaphoe.
// variable to pass value from push thread 
// to pull thread.
int plate;

// semaphoe with initial value is 0, maximum
// value is 1. It's equivalent to auto-reset 
// event.
HANDLE hasItem = CreateSemaphoe(NULL, 0, 1, NULL);

void push(int item){
  plate = item;
  BOOL bResult = ReleaseSemaphoe(hasItem, 1, NULL);
  assert(bResult);
}

int pull(){
  WaitForSingleObject(hasItem, INFINITE);
  return plate;
}