/* sync.cc: Synchronization functions for cygwin.

   This file implements the methods for controlling the "muto" class
   which is intended to operate similarly to a mutex but attempts to
   avoid making expensive calls to the kernel.

This file is part of Cygwin.

This software is a copyrighted work licensed under the terms of the
Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
details. */

#include "winsup.h"
#include "miscfuncs.h"
#include "sync.h"
#include "thread.h"
#include "cygtls.h"

#undef WaitForSingleObject

muto NO_COPY lock_process::locker;

void
muto::grab ()
{
  tls = &_my_tls;
}

/* Constructor */
muto *
muto::init (const char *s)
{
  char *already_exists = (char *) InterlockedExchangePointer ((PVOID *) &name,
							      (PVOID) s);
  if (already_exists)
    while (!bruteforce)
      yield ();
  else
    {
      waiters = -1;
      /* Create event which is used in the fallback case when blocking is necessary */
      bruteforce = CreateEvent (&sec_none_nih, FALSE, FALSE, NULL);
      if (!bruteforce)
	  api_fatal ("couldn't allocate muto '%s', %E", s);
    }

  return this;
}

#if 0 /* FIXME: Do we need this? mutos aren't destroyed until process exit */
/* Destructor (racy?) */
muto::~muto ()
{
  while (visits)
    release ();

  HANDLE h = bruteforce;
  bruteforce = NULL;
  /* Just need to close the event handle */
  if (h)
    CloseHandle (h);
}
#endif

/* Acquire the lock.  Argument is the number of milliseconds to wait for
   the lock.  Multiple visits from the same thread are allowed and should
   be handled correctly.

   Note: The goal here is to minimize, as much as possible, calls to the
   OS.  Hence the use of InterlockedIncrement, etc., rather than (much) more
   expensive OS mutexes.  */
int
muto::acquire (DWORD ms)
{
  void *this_tls = &_my_tls;

  if (tls != this_tls)
    {
      /* Increment the waiters part of the class.  Need to do this first to
	 avoid potential races. */
      LONG was_waiting = ms ? InterlockedIncrement (&waiters) : 0;

      while (was_waiting || InterlockedExchange (&sync, 1) != 0)
	switch (WaitForSingleObject (bruteforce, ms))
	    {
	    case WAIT_OBJECT_0:
	      was_waiting = 0;
	      break;
	    default:
	      return 0;	/* failed. */
	    }

      /* Have to do it this way to avoid a race */
      if (!ms)
	InterlockedIncrement (&waiters);

      tls = this_tls;	/* register this thread. */
    }

  return ++visits;	/* Increment visit count. */
}

bool
muto::acquired ()
{
  return tls == &_my_tls;
}

/* Return the muto lock.  Needs to be called once per every acquire. */
int
muto::release (_cygtls *this_tls)
{
  if (tls != this_tls || !visits)
    {
      SetLastError (ERROR_NOT_OWNER);	/* Didn't have the lock. */
      return 0;	/* failed. */
    }

  /* FIXME: Need to check that other thread has not exited, too. */
  if (!--visits)
    {
      tls = 0;		/* We were the last unlocker. */
      InterlockedExchange (&sync, 0); /* Reset trigger. */
      /* This thread had incremented waiters but had never decremented it.
	 Decrement it now.  If it is >= 0 then there are possibly other
	 threads waiting for the lock, so trigger bruteforce.  */
      if (InterlockedDecrement (&waiters) >= 0)
	SetEvent (bruteforce); /* Wake up one of the waiting threads */
      else if (*name == '!')
	{
	  CloseHandle (bruteforce);	/* If *name == '!' and there are no
					   other waiters, then this is the
					   last time this muto will ever be
					   used, so close the handle. */
#ifdef DEBUGGING
	  bruteforce = NULL;
#endif
	}
    }

  return 1;	/* success. */
}
