Event objects in POSIX

by Lars (dot) Melander (at) gmail (dot) com

Contrary to popular belief, event objects – a construct particular to MS Windows and not found in *NIX OS:es – are useful in many instances where exact thread execution control is needed. For instance, you may want to have a thread pausing itself while starting another one.

However, if you post a question on a *NIX forum about how to implement event objects or something similar, you will likely get an answer along the lines of “just use mutexes”. Unfortunately, there is no “just” about it. Implementing event objects is a bit tricky, as there are several instances where race conditions or deadlocks can occur.

There are a few web sites dedicated to the topic, but they are either extremely brief or overly complicated.

If you need to use event objects, chances are that you already have a win32 application that you want to port to a POSIX system. Porting should not require any extensive rewriting efforts. All you should need are a few lines like:

#ifdef UNIX
#include "event_objects_posix.c"
#endif

Details

There are a couple of constants missing that need to be defined:

#define DWORD            unsigned int
#define BOOL             int

#define INFINITE         -1     // usually set to -1
#define WAIT_OBJECT_0    0      // usually set to 0
#define WAIT_TIMEOUT     64     // arbitrary number
#define WAIT_FAILED      96     //       -"-
#define WAIT_ABANDONED_0 128    //       -"-

#define MIL 1000000L            // one million
#define BIL 1000000000L         // one billion
#ifndef ETIMEDOUT
#define ETIMEDOUT 110
#endif

ETIMEDOUT is a constant returned by some POSIX functions. It was however not defined on my system.

Every time a thread starts waiting for an event object, a list_element object is added to the event object:

typedef struct t_list_element
{
  pthread_mutex_t mutex;  // mutex for the conditional wait
  pthread_cond_t cond;
  struct t_list_element *prev, *next;
} *list_element;

If more than one thread is waiting for the same event object, the elements will be added in a linked list.

This is the event object:

typedef struct t_HANDLE
{
  list_element start, end;
  pthread_mutex_t mutex;
  BOOL flag;
} *HANDLE;

@

void AddElement(HANDLE event_object, list_element le)
{
  if (event_object->start == NULL)
    event_object->start = event_object->end = le;
  else
    {
      event_object->end->next = le;
      le->prev = event_object->end;
      event_object->end = le;
    }
}

void RemoveElement(HANDLE event_object, list_element le)
{
  list_element ptr;

  if (event_object->start == event_object->end)
    event_object->start = event_object->end = NULL;
  else if (le == event_object->start)
    event_object->start = event_object->start->next;
  else if (le == event_object->end)
    event_object->end = event_object->end->prev;
  else
    for (ptr = event_object->start->next; ; ptr = ptr->next)
      if (ptr == le)
	{
	  ptr->prev->next = ptr->next;
	  ptr->next->prev = ptr->prev;
	  break;
	}
}

// attribute not used, name not used
// manual_reset is ignored, always treated as true
HANDLE CreateEvent(void *attribute, BOOL manual_reset, BOOL initial_state, void *name)
{
  HANDLE event_object = malloc(sizeof (struct t_HANDLE));
  event_object->start = event_object->end = NULL;
  pthread_mutex_init(&event_object->mutex, NULL);
  event_object->flag = initial_state;
  return event_object;
}

BOOL SetEvent(HANDLE event_object)
{
  list_element ptr;

  pthread_mutex_lock(&event_object->mutex);
  event_object->flag = TRUE;
  for (ptr = event_object->start; ptr != NULL; ptr = ptr->next)
    {
      pthread_mutex_lock(&ptr->mutex);
      pthread_cond_signal(&ptr->cond);
      pthread_mutex_unlock(&ptr->mutex);
      if (ptr == event_object->end)
	break;
    }
  pthread_mutex_unlock(&event_object->mutex);
  return TRUE;
}

BOOL ResetEvent(HANDLE event_object)
{
  pthread_mutex_lock(&event_object->mutex);
  event_object->flag = FALSE;
  pthread_mutex_unlock(&event_object->mutex);
  return TRUE;
}

BOOL CloseHandle(HANDLE event_object)
{
  if (event_object->start != NULL)
    return FALSE;
  pthread_mutex_destroy(&event_object->mutex);
  free(event_object);
  return TRUE;
}

DWORD WaitForMultipleObjects(DWORD, HANDLE *, BOOL, int);
DWORD WaitForSingleObject(HANDLE event_object, int timeout_ms)
{
  return WaitForMultipleObjects(1, &event_object, FALSE, timeout_ms);
}

DWORD WaitForMultipleObjects(DWORD count, HANDLE *event_object, BOOL wait_all, int timeout_ms)
{
  struct t_list_element le;
  DWORD i, return_value;
  int check_value = -1;
  struct timespec t;
  struct timeval tv;

  if (count == 0)
    return WAIT_FAILED;

  for (i = 0; i < count; ++i)
    pthread_mutex_lock(&event_object[i]->mutex);

  pthread_mutex_init(&le.mutex, NULL);
  pthread_cond_init(&le.cond, NULL);

  for (i = 0; i < count; ++i)
    {
      AddElement(event_object[i], &le);
      if (event_object[i]->flag)
        check_value = 1;
    }

  if (check_value == -1)
    {
      pthread_mutex_lock(&le.mutex);

      if (timeout_ms != INFINITE)
        {
          gettimeofday(&tv);
          t.tv_nsec = tv.tv_usec * 1000  + ((long) timeout_ms) * MIL;
          t.tv_sec = tv.tv_sec;
          if (t.tv_nsec >= BIL)
            {
              t.tv_sec += t.tv_nsec / BIL;
              t.tv_nsec %= BIL;
            }
        }

      for (i = 0; i < count; ++i)
        pthread_mutex_unlock(&event_object[i]->mutex);

      if (timeout_ms == INFINITE)
        check_value = pthread_cond_wait(&le.cond, &le.mutex);
      else
        check_value = pthread_cond_timedwait(&le.cond, &le.mutex, &t);
      if (check_value == ETIMEDOUT)
        return_value = WAIT_TIMEOUT;
      else if (check_value != 0)
        return_value = WAIT_FAILED;

      pthread_mutex_unlock(&le.mutex);

      for (i = 0; i < count; ++i)
        pthread_mutex_lock(&event_object[i]->mutex);
    }

  for (i = 0; i < count; ++i)
    {
      RemoveElement(event_object[i], &le);
      if (event_object[i]->flag)
        return_value = i;
    }

  for (i = 0; i < count; ++i)
    pthread_mutex_unlock(&event_object[i]->mutex);

  pthread_mutex_destroy(&le.mutex);
  pthread_cond_destroy(&le.cond);

  return return_value;
}