/* fmutex.c (emx+gcc) -- Copyright (c) 1996 by Eberhard Mattes */

#define INCL_DOSSEMAPHORES
#define INCL_DOSPROCESS
#define INCL_DOSERRORS
#include <os2.h>
#include <stdlib.h>
#include <sys/builtin.h>
#include <sys/fmutex.h>
#include <sys/smutex.h>

/* These functions are available even in single-thread libraries. */


unsigned _fmutex_create (_fmutex *sem, unsigned flags)
{
  sem->fs = _FMS_AVAILABLE;
  if (_osmode == DOS_MODE)
    return 0;
  return DosCreateEventSem (NULL, (PHEV)&sem->hev,
                            (flags & _FMC_SHARED) ? DC_SEM_SHARED : 0,
                            FALSE);
}


unsigned _fmutex_open (_fmutex *sem)
{
  if (_osmode == DOS_MODE)
    return 0;
  return DosOpenEventSem (NULL, (PHEV)&sem->hev);
}


unsigned _fmutex_close (_fmutex *sem)
{
  if (_osmode == DOS_MODE)
    return 0;
  return DosCloseEventSem (sem->hev);
}


unsigned __fmutex_request_internal (_fmutex *sem, unsigned flags,
                                    signed char fs)
{
  ULONG rc, count;

  if (fs == _FMS_UNINIT)
    return ERROR_INVALID_HANDLE;

  if (flags & _FMR_NOWAIT)
    {
      if (fs == _FMS_OWNED_HARD)
        {
          if (__cxchg (&sem->fs, _FMS_OWNED_HARD) == _FMS_AVAILABLE)
            return 0;
        }
      return ERROR_MUTEX_OWNED;
    }

  for (;;)
    {
      rc = DosResetEventSem (sem->hev, &count);
      if (rc != 0 && rc != ERROR_ALREADY_RESET)
        return rc;
      if (__cxchg (&sem->fs, _FMS_OWNED_HARD) == _FMS_AVAILABLE)
        return 0;
      do
        {
          rc = DosWaitEventSem (sem->hev, SEM_INDEFINITE_WAIT);
        } while (rc == ERROR_INTERRUPT && (flags & _FMR_IGNINT));
      if (rc != 0)
        return rc;
    }
}


unsigned __fmutex_release_internal (_fmutex *sem)
{
  ULONG rc;

  rc = DosPostEventSem (sem->hev);
  if (rc != 0 && rc != ERROR_ALREADY_POSTED)
    return rc;

  /* Give up our time slice to give other threads a chance.  Without
     doing so, a thread which continuously requests and releases a
     _fmutex semaphore may prevent all other threads requesting the
     _fmutex semaphore from running, forever.

     Why?  Without DosSleep, the thread will keep the CPU for the rest
     of the time slice.  It may request (and get) the semaphore again
     in the same time slice, perhaps multiple times.  Assume that the
     semaphore is still owned at the end of the time slice.  In one of
     the next time slices, all the other threads blocking on the
     semaphore will wake up for a short while only to see that the
     semaphore is owned; they will all go sleeping again.  The thread
     which owns the semaphore will get the CPU again.  And may happen
     to own the semaphore again at the end of the time slice.  And so
     on.  DosSleep reduces that problem a bit.

     Even with DosSleep(0), assignment of time slices to threads
     requesting a _fmutex semaphore can be quite unbalanced: The more
     frequently a thread requests a _fmutex semaphore, the more CPU
     time it will get.  In consequence, a thread which only rarely
     needs the resource protected by the _fmutex semaphore will get
     much less CPU time than threads competing heavily for the
     resource.

     DosSleep(1) would make _fmutex semaphores behave a bit better,
     but would also give up way too much CPU time (in the average,
     half a time slice (or one and a half?) per call of this
     function).

     DosReleaseMutexSem does a better job; it gives the time slice to
     the thread which has waited longest on the semaphore (if all the
     threads have the same priority).

     Unfortunately, we cannot use an HMTX instead of an HEV because
     only the owner (the thread which got assigned ownership by
     DosRequestMutexSem) can release the HMTX with DosReleaseMutexSem.
     If we could, that would take advantage of the FIFO of blocked
     threads maintained by the kernel for each HMTX.

     Using an HMTX would require the first thread requesting an owned
     _fmutex semaphore to request the HMTX on behalf of the thread
     owning the _fmutex semaphore. */

  DosSleep (0);
  return 0;
}


void _fmutex_dummy (_fmutex *sem)
{
  sem->fs = _FMS_AVAILABLE;
  sem->hev = 0;
}
