Blame gl/tests/windows-rwlock.c

Packit Service 991b93
/* Read-write locks (native Windows implementation).
Packit Service 991b93
   Copyright (C) 2005-2020 Free Software Foundation, Inc.
Packit Service 991b93
Packit Service 991b93
   This program is free software; you can redistribute it and/or modify
Packit Service 991b93
   it under the terms of the GNU General Public License as published by
Packit Service 991b93
   the Free Software Foundation; either version 3, or (at your option)
Packit Service 991b93
   any later version.
Packit Service 991b93
Packit Service 991b93
   This program is distributed in the hope that it will be useful,
Packit Service 991b93
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 991b93
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Service 991b93
   GNU General Public License for more details.
Packit Service 991b93
Packit Service 991b93
   You should have received a copy of the GNU General Public License
Packit Service 991b93
   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
Packit Service 991b93
Packit Service 991b93
/* Written by Bruno Haible <bruno@clisp.org>, 2005.
Packit Service 991b93
   Based on GCC's gthr-win32.h.  */
Packit Service 991b93
Packit Service 991b93
#include <config.h>
Packit Service 991b93
Packit Service 991b93
/* Specification.  */
Packit Service 991b93
#include "windows-rwlock.h"
Packit Service 991b93
Packit Service 991b93
#include <errno.h>
Packit Service 991b93
#include <stdlib.h>
Packit Service 991b93
Packit Service 991b93
/* In this file, the waitqueues are implemented as circular arrays.  */
Packit Service 991b93
#define glwthread_waitqueue_t glwthread_carray_waitqueue_t
Packit Service 991b93
Packit Service 991b93
static void
Packit Service 991b93
glwthread_waitqueue_init (glwthread_waitqueue_t *wq)
Packit Service 991b93
{
Packit Service 991b93
  wq->array = NULL;
Packit Service 991b93
  wq->count = 0;
Packit Service 991b93
  wq->alloc = 0;
Packit Service 991b93
  wq->offset = 0;
Packit Service 991b93
}
Packit Service 991b93
Packit Service 991b93
/* Enqueues the current thread, represented by an event, in a wait queue.
Packit Service 991b93
   Returns INVALID_HANDLE_VALUE if an allocation failure occurs.  */
Packit Service 991b93
static HANDLE
Packit Service 991b93
glwthread_waitqueue_add (glwthread_waitqueue_t *wq)
Packit Service 991b93
{
Packit Service 991b93
  HANDLE event;
Packit Service 991b93
  unsigned int index;
Packit Service 991b93
Packit Service 991b93
  if (wq->count == wq->alloc)
Packit Service 991b93
    {
Packit Service 991b93
      unsigned int new_alloc = 2 * wq->alloc + 1;
Packit Service 991b93
      HANDLE *new_array =
Packit Service 991b93
        (HANDLE *) realloc (wq->array, new_alloc * sizeof (HANDLE));
Packit Service 991b93
      if (new_array == NULL)
Packit Service 991b93
        /* No more memory.  */
Packit Service 991b93
        return INVALID_HANDLE_VALUE;
Packit Service 991b93
      /* Now is a good opportunity to rotate the array so that its contents
Packit Service 991b93
         starts at offset 0.  */
Packit Service 991b93
      if (wq->offset > 0)
Packit Service 991b93
        {
Packit Service 991b93
          unsigned int old_count = wq->count;
Packit Service 991b93
          unsigned int old_alloc = wq->alloc;
Packit Service 991b93
          unsigned int old_offset = wq->offset;
Packit Service 991b93
          unsigned int i;
Packit Service 991b93
          if (old_offset + old_count > old_alloc)
Packit Service 991b93
            {
Packit Service 991b93
              unsigned int limit = old_offset + old_count - old_alloc;
Packit Service 991b93
              for (i = 0; i < limit; i++)
Packit Service 991b93
                new_array[old_alloc + i] = new_array[i];
Packit Service 991b93
            }
Packit Service 991b93
          for (i = 0; i < old_count; i++)
Packit Service 991b93
            new_array[i] = new_array[old_offset + i];
Packit Service 991b93
          wq->offset = 0;
Packit Service 991b93
        }
Packit Service 991b93
      wq->array = new_array;
Packit Service 991b93
      wq->alloc = new_alloc;
Packit Service 991b93
    }
Packit Service 991b93
  /* Whether the created event is a manual-reset one or an auto-reset one,
Packit Service 991b93
     does not matter, since we will wait on it only once.  */
Packit Service 991b93
  event = CreateEvent (NULL, TRUE, FALSE, NULL);
Packit Service 991b93
  if (event == INVALID_HANDLE_VALUE)
Packit Service 991b93
    /* No way to allocate an event.  */
Packit Service 991b93
    return INVALID_HANDLE_VALUE;
Packit Service 991b93
  index = wq->offset + wq->count;
Packit Service 991b93
  if (index >= wq->alloc)
Packit Service 991b93
    index -= wq->alloc;
Packit Service 991b93
  wq->array[index] = event;
Packit Service 991b93
  wq->count++;
Packit Service 991b93
  return event;
Packit Service 991b93
}
Packit Service 991b93
Packit Service 991b93
/* Notifies the first thread from a wait queue and dequeues it.  */
Packit Service 991b93
static void
Packit Service 991b93
glwthread_waitqueue_notify_first (glwthread_waitqueue_t *wq)
Packit Service 991b93
{
Packit Service 991b93
  SetEvent (wq->array[wq->offset + 0]);
Packit Service 991b93
  wq->offset++;
Packit Service 991b93
  wq->count--;
Packit Service 991b93
  if (wq->count == 0 || wq->offset == wq->alloc)
Packit Service 991b93
    wq->offset = 0;
Packit Service 991b93
}
Packit Service 991b93
Packit Service 991b93
/* Notifies all threads from a wait queue and dequeues them all.  */
Packit Service 991b93
static void
Packit Service 991b93
glwthread_waitqueue_notify_all (glwthread_waitqueue_t *wq)
Packit Service 991b93
{
Packit Service 991b93
  unsigned int i;
Packit Service 991b93
Packit Service 991b93
  for (i = 0; i < wq->count; i++)
Packit Service 991b93
    {
Packit Service 991b93
      unsigned int index = wq->offset + i;
Packit Service 991b93
      if (index >= wq->alloc)
Packit Service 991b93
        index -= wq->alloc;
Packit Service 991b93
      SetEvent (wq->array[index]);
Packit Service 991b93
    }
Packit Service 991b93
  wq->count = 0;
Packit Service 991b93
  wq->offset = 0;
Packit Service 991b93
}
Packit Service 991b93
Packit Service 991b93
void
Packit Service 991b93
glwthread_rwlock_init (glwthread_rwlock_t *lock)
Packit Service 991b93
{
Packit Service 991b93
  InitializeCriticalSection (&lock->lock);
Packit Service 991b93
  glwthread_waitqueue_init (&lock->waiting_readers);
Packit Service 991b93
  glwthread_waitqueue_init (&lock->waiting_writers);
Packit Service 991b93
  lock->runcount = 0;
Packit Service 991b93
  lock->guard.done = 1;
Packit Service 991b93
}
Packit Service 991b93
Packit Service 991b93
int
Packit Service 991b93
glwthread_rwlock_rdlock (glwthread_rwlock_t *lock)
Packit Service 991b93
{
Packit Service 991b93
  if (!lock->guard.done)
Packit Service 991b93
    {
Packit Service 991b93
      if (InterlockedIncrement (&lock->guard.started) == 0)
Packit Service 991b93
        /* This thread is the first one to need this lock.  Initialize it.  */
Packit Service 991b93
        glwthread_rwlock_init (lock);
Packit Service 991b93
      else
Packit Service 991b93
        {
Packit Service 991b93
          /* Don't let lock->guard.started grow and wrap around.  */
Packit Service 991b93
          InterlockedDecrement (&lock->guard.started);
Packit Service 991b93
          /* Yield the CPU while waiting for another thread to finish
Packit Service 991b93
             initializing this lock.  */
Packit Service 991b93
          while (!lock->guard.done)
Packit Service 991b93
            Sleep (0);
Packit Service 991b93
        }
Packit Service 991b93
    }
Packit Service 991b93
  EnterCriticalSection (&lock->lock);
Packit Service 991b93
  /* Test whether only readers are currently running, and whether the runcount
Packit Service 991b93
     field will not overflow, and whether no writer is waiting.  The latter
Packit Service 991b93
     condition is because POSIX recommends that "write locks shall take
Packit Service 991b93
     precedence over read locks", to avoid "writer starvation".  */
Packit Service 991b93
  if (!(lock->runcount + 1 > 0 && lock->waiting_writers.count == 0))
Packit Service 991b93
    {
Packit Service 991b93
      /* This thread has to wait for a while.  Enqueue it among the
Packit Service 991b93
         waiting_readers.  */
Packit Service 991b93
      HANDLE event = glwthread_waitqueue_add (&lock->waiting_readers);
Packit Service 991b93
      if (event != INVALID_HANDLE_VALUE)
Packit Service 991b93
        {
Packit Service 991b93
          DWORD result;
Packit Service 991b93
          LeaveCriticalSection (&lock->lock);
Packit Service 991b93
          /* Wait until another thread signals this event.  */
Packit Service 991b93
          result = WaitForSingleObject (event, INFINITE);
Packit Service 991b93
          if (result == WAIT_FAILED || result == WAIT_TIMEOUT)
Packit Service 991b93
            abort ();
Packit Service 991b93
          CloseHandle (event);
Packit Service 991b93
          /* The thread which signalled the event already did the bookkeeping:
Packit Service 991b93
             removed us from the waiting_readers, incremented lock->runcount.  */
Packit Service 991b93
          if (!(lock->runcount > 0))
Packit Service 991b93
            abort ();
Packit Service 991b93
          return 0;
Packit Service 991b93
        }
Packit Service 991b93
      else
Packit Service 991b93
        {
Packit Service 991b93
          /* Allocation failure.  Weird.  */
Packit Service 991b93
          do
Packit Service 991b93
            {
Packit Service 991b93
              LeaveCriticalSection (&lock->lock);
Packit Service 991b93
              Sleep (1);
Packit Service 991b93
              EnterCriticalSection (&lock->lock);
Packit Service 991b93
            }
Packit Service 991b93
          while (!(lock->runcount + 1 > 0));
Packit Service 991b93
        }
Packit Service 991b93
    }
Packit Service 991b93
  lock->runcount++;
Packit Service 991b93
  LeaveCriticalSection (&lock->lock);
Packit Service 991b93
  return 0;
Packit Service 991b93
}
Packit Service 991b93
Packit Service 991b93
int
Packit Service 991b93
glwthread_rwlock_wrlock (glwthread_rwlock_t *lock)
Packit Service 991b93
{
Packit Service 991b93
  if (!lock->guard.done)
Packit Service 991b93
    {
Packit Service 991b93
      if (InterlockedIncrement (&lock->guard.started) == 0)
Packit Service 991b93
        /* This thread is the first one to need this lock.  Initialize it.  */
Packit Service 991b93
        glwthread_rwlock_init (lock);
Packit Service 991b93
      else
Packit Service 991b93
        {
Packit Service 991b93
          /* Don't let lock->guard.started grow and wrap around.  */
Packit Service 991b93
          InterlockedDecrement (&lock->guard.started);
Packit Service 991b93
          /* Yield the CPU while waiting for another thread to finish
Packit Service 991b93
             initializing this lock.  */
Packit Service 991b93
          while (!lock->guard.done)
Packit Service 991b93
            Sleep (0);
Packit Service 991b93
        }
Packit Service 991b93
    }
Packit Service 991b93
  EnterCriticalSection (&lock->lock);
Packit Service 991b93
  /* Test whether no readers or writers are currently running.  */
Packit Service 991b93
  if (!(lock->runcount == 0))
Packit Service 991b93
    {
Packit Service 991b93
      /* This thread has to wait for a while.  Enqueue it among the
Packit Service 991b93
         waiting_writers.  */
Packit Service 991b93
      HANDLE event = glwthread_waitqueue_add (&lock->waiting_writers);
Packit Service 991b93
      if (event != INVALID_HANDLE_VALUE)
Packit Service 991b93
        {
Packit Service 991b93
          DWORD result;
Packit Service 991b93
          LeaveCriticalSection (&lock->lock);
Packit Service 991b93
          /* Wait until another thread signals this event.  */
Packit Service 991b93
          result = WaitForSingleObject (event, INFINITE);
Packit Service 991b93
          if (result == WAIT_FAILED || result == WAIT_TIMEOUT)
Packit Service 991b93
            abort ();
Packit Service 991b93
          CloseHandle (event);
Packit Service 991b93
          /* The thread which signalled the event already did the bookkeeping:
Packit Service 991b93
             removed us from the waiting_writers, set lock->runcount = -1.  */
Packit Service 991b93
          if (!(lock->runcount == -1))
Packit Service 991b93
            abort ();
Packit Service 991b93
          return 0;
Packit Service 991b93
        }
Packit Service 991b93
      else
Packit Service 991b93
        {
Packit Service 991b93
          /* Allocation failure.  Weird.  */
Packit Service 991b93
          do
Packit Service 991b93
            {
Packit Service 991b93
              LeaveCriticalSection (&lock->lock);
Packit Service 991b93
              Sleep (1);
Packit Service 991b93
              EnterCriticalSection (&lock->lock);
Packit Service 991b93
            }
Packit Service 991b93
          while (!(lock->runcount == 0));
Packit Service 991b93
        }
Packit Service 991b93
    }
Packit Service 991b93
  lock->runcount--; /* runcount becomes -1 */
Packit Service 991b93
  LeaveCriticalSection (&lock->lock);
Packit Service 991b93
  return 0;
Packit Service 991b93
}
Packit Service 991b93
Packit Service 991b93
int
Packit Service 991b93
glwthread_rwlock_tryrdlock (glwthread_rwlock_t *lock)
Packit Service 991b93
{
Packit Service 991b93
  if (!lock->guard.done)
Packit Service 991b93
    {
Packit Service 991b93
      if (InterlockedIncrement (&lock->guard.started) == 0)
Packit Service 991b93
        /* This thread is the first one to need this lock.  Initialize it.  */
Packit Service 991b93
        glwthread_rwlock_init (lock);
Packit Service 991b93
      else
Packit Service 991b93
        {
Packit Service 991b93
          /* Don't let lock->guard.started grow and wrap around.  */
Packit Service 991b93
          InterlockedDecrement (&lock->guard.started);
Packit Service 991b93
          /* Yield the CPU while waiting for another thread to finish
Packit Service 991b93
             initializing this lock.  */
Packit Service 991b93
          while (!lock->guard.done)
Packit Service 991b93
            Sleep (0);
Packit Service 991b93
        }
Packit Service 991b93
    }
Packit Service 991b93
  /* It's OK to wait for this critical section, because it is never taken for a
Packit Service 991b93
     long time.  */
Packit Service 991b93
  EnterCriticalSection (&lock->lock);
Packit Service 991b93
  /* Test whether only readers are currently running, and whether the runcount
Packit Service 991b93
     field will not overflow, and whether no writer is waiting.  The latter
Packit Service 991b93
     condition is because POSIX recommends that "write locks shall take
Packit Service 991b93
     precedence over read locks", to avoid "writer starvation".  */
Packit Service 991b93
  if (!(lock->runcount + 1 > 0 && lock->waiting_writers.count == 0))
Packit Service 991b93
    {
Packit Service 991b93
      /* This thread would have to wait for a while.  Return instead.  */
Packit Service 991b93
      LeaveCriticalSection (&lock->lock);
Packit Service 991b93
      return EBUSY;
Packit Service 991b93
    }
Packit Service 991b93
  lock->runcount++;
Packit Service 991b93
  LeaveCriticalSection (&lock->lock);
Packit Service 991b93
  return 0;
Packit Service 991b93
}
Packit Service 991b93
Packit Service 991b93
int
Packit Service 991b93
glwthread_rwlock_trywrlock (glwthread_rwlock_t *lock)
Packit Service 991b93
{
Packit Service 991b93
  if (!lock->guard.done)
Packit Service 991b93
    {
Packit Service 991b93
      if (InterlockedIncrement (&lock->guard.started) == 0)
Packit Service 991b93
        /* This thread is the first one to need this lock.  Initialize it.  */
Packit Service 991b93
        glwthread_rwlock_init (lock);
Packit Service 991b93
      else
Packit Service 991b93
        {
Packit Service 991b93
          /* Don't let lock->guard.started grow and wrap around.  */
Packit Service 991b93
          InterlockedDecrement (&lock->guard.started);
Packit Service 991b93
          /* Yield the CPU while waiting for another thread to finish
Packit Service 991b93
             initializing this lock.  */
Packit Service 991b93
          while (!lock->guard.done)
Packit Service 991b93
            Sleep (0);
Packit Service 991b93
        }
Packit Service 991b93
    }
Packit Service 991b93
  /* It's OK to wait for this critical section, because it is never taken for a
Packit Service 991b93
     long time.  */
Packit Service 991b93
  EnterCriticalSection (&lock->lock);
Packit Service 991b93
  /* Test whether no readers or writers are currently running.  */
Packit Service 991b93
  if (!(lock->runcount == 0))
Packit Service 991b93
    {
Packit Service 991b93
      /* This thread would have to wait for a while.  Return instead.  */
Packit Service 991b93
      LeaveCriticalSection (&lock->lock);
Packit Service 991b93
      return EBUSY;
Packit Service 991b93
    }
Packit Service 991b93
  lock->runcount--; /* runcount becomes -1 */
Packit Service 991b93
  LeaveCriticalSection (&lock->lock);
Packit Service 991b93
  return 0;
Packit Service 991b93
}
Packit Service 991b93
Packit Service 991b93
int
Packit Service 991b93
glwthread_rwlock_unlock (glwthread_rwlock_t *lock)
Packit Service 991b93
{
Packit Service 991b93
  if (!lock->guard.done)
Packit Service 991b93
    return EINVAL;
Packit Service 991b93
  EnterCriticalSection (&lock->lock);
Packit Service 991b93
  if (lock->runcount < 0)
Packit Service 991b93
    {
Packit Service 991b93
      /* Drop a writer lock.  */
Packit Service 991b93
      if (!(lock->runcount == -1))
Packit Service 991b93
        abort ();
Packit Service 991b93
      lock->runcount = 0;
Packit Service 991b93
    }
Packit Service 991b93
  else
Packit Service 991b93
    {
Packit Service 991b93
      /* Drop a reader lock.  */
Packit Service 991b93
      if (!(lock->runcount > 0))
Packit Service 991b93
        {
Packit Service 991b93
          LeaveCriticalSection (&lock->lock);
Packit Service 991b93
          return EPERM;
Packit Service 991b93
        }
Packit Service 991b93
      lock->runcount--;
Packit Service 991b93
    }
Packit Service 991b93
  if (lock->runcount == 0)
Packit Service 991b93
    {
Packit Service 991b93
      /* POSIX recommends that "write locks shall take precedence over read
Packit Service 991b93
         locks", to avoid "writer starvation".  */
Packit Service 991b93
      if (lock->waiting_writers.count > 0)
Packit Service 991b93
        {
Packit Service 991b93
          /* Wake up one of the waiting writers.  */
Packit Service 991b93
          lock->runcount--;
Packit Service 991b93
          glwthread_waitqueue_notify_first (&lock->waiting_writers);
Packit Service 991b93
        }
Packit Service 991b93
      else
Packit Service 991b93
        {
Packit Service 991b93
          /* Wake up all waiting readers.  */
Packit Service 991b93
          lock->runcount += lock->waiting_readers.count;
Packit Service 991b93
          glwthread_waitqueue_notify_all (&lock->waiting_readers);
Packit Service 991b93
        }
Packit Service 991b93
    }
Packit Service 991b93
  LeaveCriticalSection (&lock->lock);
Packit Service 991b93
  return 0;
Packit Service 991b93
}
Packit Service 991b93
Packit Service 991b93
int
Packit Service 991b93
glwthread_rwlock_destroy (glwthread_rwlock_t *lock)
Packit Service 991b93
{
Packit Service 991b93
  if (!lock->guard.done)
Packit Service 991b93
    return EINVAL;
Packit Service 991b93
  if (lock->runcount != 0)
Packit Service 991b93
    return EBUSY;
Packit Service 991b93
  DeleteCriticalSection (&lock->lock);
Packit Service 991b93
  if (lock->waiting_readers.array != NULL)
Packit Service 991b93
    free (lock->waiting_readers.array);
Packit Service 991b93
  if (lock->waiting_writers.array != NULL)
Packit Service 991b93
    free (lock->waiting_writers.array);
Packit Service 991b93
  lock->guard.done = 0;
Packit Service 991b93
  return 0;
Packit Service 991b93
}