Home | History | Annotate | Line # | Download | only in import
      1      1.1  christos /* Read-write locks (native Windows implementation).
      2  1.1.1.2  christos    Copyright (C) 2005-2022 Free Software Foundation, Inc.
      3      1.1  christos 
      4  1.1.1.2  christos    This file is free software: you can redistribute it and/or modify
      5  1.1.1.2  christos    it under the terms of the GNU Lesser General Public License as
      6  1.1.1.2  christos    published by the Free Software Foundation; either version 2.1 of the
      7  1.1.1.2  christos    License, or (at your option) any later version.
      8      1.1  christos 
      9  1.1.1.2  christos    This file is distributed in the hope that it will be useful,
     10      1.1  christos    but WITHOUT ANY WARRANTY; without even the implied warranty of
     11      1.1  christos    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12  1.1.1.2  christos    GNU Lesser General Public License for more details.
     13      1.1  christos 
     14  1.1.1.2  christos    You should have received a copy of the GNU Lesser General Public License
     15  1.1.1.2  christos    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
     16      1.1  christos 
     17      1.1  christos /* Written by Bruno Haible <bruno (at) clisp.org>, 2005.
     18      1.1  christos    Based on GCC's gthr-win32.h.  */
     19      1.1  christos 
     20      1.1  christos #include <config.h>
     21      1.1  christos 
     22      1.1  christos /* Specification.  */
     23      1.1  christos #include "windows-rwlock.h"
     24      1.1  christos 
     25      1.1  christos #include <errno.h>
     26      1.1  christos #include <stdlib.h>
     27      1.1  christos 
     28      1.1  christos /* Don't assume that UNICODE is not defined.  */
     29      1.1  christos #undef CreateEvent
     30      1.1  christos #define CreateEvent CreateEventA
     31      1.1  christos 
     32      1.1  christos /* In this file, the waitqueues are implemented as circular arrays.  */
     33      1.1  christos #define glwthread_waitqueue_t glwthread_carray_waitqueue_t
     34      1.1  christos 
     35      1.1  christos static void
     36      1.1  christos glwthread_waitqueue_init (glwthread_waitqueue_t *wq)
     37      1.1  christos {
     38      1.1  christos   wq->array = NULL;
     39      1.1  christos   wq->count = 0;
     40      1.1  christos   wq->alloc = 0;
     41      1.1  christos   wq->offset = 0;
     42      1.1  christos }
     43      1.1  christos 
     44      1.1  christos /* Enqueues the current thread, represented by an event, in a wait queue.
     45      1.1  christos    Returns INVALID_HANDLE_VALUE if an allocation failure occurs.  */
     46      1.1  christos static HANDLE
     47      1.1  christos glwthread_waitqueue_add (glwthread_waitqueue_t *wq)
     48      1.1  christos {
     49      1.1  christos   HANDLE event;
     50      1.1  christos   unsigned int index;
     51      1.1  christos 
     52      1.1  christos   if (wq->count == wq->alloc)
     53      1.1  christos     {
     54      1.1  christos       unsigned int new_alloc = 2 * wq->alloc + 1;
     55      1.1  christos       HANDLE *new_array =
     56      1.1  christos         (HANDLE *) realloc (wq->array, new_alloc * sizeof (HANDLE));
     57      1.1  christos       if (new_array == NULL)
     58      1.1  christos         /* No more memory.  */
     59      1.1  christos         return INVALID_HANDLE_VALUE;
     60      1.1  christos       /* Now is a good opportunity to rotate the array so that its contents
     61      1.1  christos          starts at offset 0.  */
     62      1.1  christos       if (wq->offset > 0)
     63      1.1  christos         {
     64      1.1  christos           unsigned int old_count = wq->count;
     65      1.1  christos           unsigned int old_alloc = wq->alloc;
     66      1.1  christos           unsigned int old_offset = wq->offset;
     67      1.1  christos           unsigned int i;
     68      1.1  christos           if (old_offset + old_count > old_alloc)
     69      1.1  christos             {
     70      1.1  christos               unsigned int limit = old_offset + old_count - old_alloc;
     71      1.1  christos               for (i = 0; i < limit; i++)
     72      1.1  christos                 new_array[old_alloc + i] = new_array[i];
     73      1.1  christos             }
     74      1.1  christos           for (i = 0; i < old_count; i++)
     75      1.1  christos             new_array[i] = new_array[old_offset + i];
     76      1.1  christos           wq->offset = 0;
     77      1.1  christos         }
     78      1.1  christos       wq->array = new_array;
     79      1.1  christos       wq->alloc = new_alloc;
     80      1.1  christos     }
     81      1.1  christos   /* Whether the created event is a manual-reset one or an auto-reset one,
     82      1.1  christos      does not matter, since we will wait on it only once.  */
     83      1.1  christos   event = CreateEvent (NULL, TRUE, FALSE, NULL);
     84      1.1  christos   if (event == INVALID_HANDLE_VALUE)
     85      1.1  christos     /* No way to allocate an event.  */
     86      1.1  christos     return INVALID_HANDLE_VALUE;
     87      1.1  christos   index = wq->offset + wq->count;
     88      1.1  christos   if (index >= wq->alloc)
     89      1.1  christos     index -= wq->alloc;
     90      1.1  christos   wq->array[index] = event;
     91      1.1  christos   wq->count++;
     92      1.1  christos   return event;
     93      1.1  christos }
     94      1.1  christos 
     95      1.1  christos /* Notifies the first thread from a wait queue and dequeues it.  */
     96      1.1  christos static void
     97      1.1  christos glwthread_waitqueue_notify_first (glwthread_waitqueue_t *wq)
     98      1.1  christos {
     99      1.1  christos   SetEvent (wq->array[wq->offset + 0]);
    100      1.1  christos   wq->offset++;
    101      1.1  christos   wq->count--;
    102      1.1  christos   if (wq->count == 0 || wq->offset == wq->alloc)
    103      1.1  christos     wq->offset = 0;
    104      1.1  christos }
    105      1.1  christos 
    106      1.1  christos /* Notifies all threads from a wait queue and dequeues them all.  */
    107      1.1  christos static void
    108      1.1  christos glwthread_waitqueue_notify_all (glwthread_waitqueue_t *wq)
    109      1.1  christos {
    110      1.1  christos   unsigned int i;
    111      1.1  christos 
    112      1.1  christos   for (i = 0; i < wq->count; i++)
    113      1.1  christos     {
    114      1.1  christos       unsigned int index = wq->offset + i;
    115      1.1  christos       if (index >= wq->alloc)
    116      1.1  christos         index -= wq->alloc;
    117      1.1  christos       SetEvent (wq->array[index]);
    118      1.1  christos     }
    119      1.1  christos   wq->count = 0;
    120      1.1  christos   wq->offset = 0;
    121      1.1  christos }
    122      1.1  christos 
    123      1.1  christos void
    124      1.1  christos glwthread_rwlock_init (glwthread_rwlock_t *lock)
    125      1.1  christos {
    126      1.1  christos   InitializeCriticalSection (&lock->lock);
    127      1.1  christos   glwthread_waitqueue_init (&lock->waiting_readers);
    128      1.1  christos   glwthread_waitqueue_init (&lock->waiting_writers);
    129      1.1  christos   lock->runcount = 0;
    130      1.1  christos   lock->guard.done = 1;
    131      1.1  christos }
    132      1.1  christos 
    133      1.1  christos int
    134      1.1  christos glwthread_rwlock_rdlock (glwthread_rwlock_t *lock)
    135      1.1  christos {
    136      1.1  christos   if (!lock->guard.done)
    137      1.1  christos     {
    138      1.1  christos       if (InterlockedIncrement (&lock->guard.started) == 0)
    139      1.1  christos         /* This thread is the first one to need this lock.  Initialize it.  */
    140      1.1  christos         glwthread_rwlock_init (lock);
    141      1.1  christos       else
    142      1.1  christos         {
    143      1.1  christos           /* Don't let lock->guard.started grow and wrap around.  */
    144      1.1  christos           InterlockedDecrement (&lock->guard.started);
    145      1.1  christos           /* Yield the CPU while waiting for another thread to finish
    146      1.1  christos              initializing this lock.  */
    147      1.1  christos           while (!lock->guard.done)
    148      1.1  christos             Sleep (0);
    149      1.1  christos         }
    150      1.1  christos     }
    151      1.1  christos   EnterCriticalSection (&lock->lock);
    152      1.1  christos   /* Test whether only readers are currently running, and whether the runcount
    153      1.1  christos      field will not overflow, and whether no writer is waiting.  The latter
    154      1.1  christos      condition is because POSIX recommends that "write locks shall take
    155      1.1  christos      precedence over read locks", to avoid "writer starvation".  */
    156      1.1  christos   if (!(lock->runcount + 1 > 0 && lock->waiting_writers.count == 0))
    157      1.1  christos     {
    158      1.1  christos       /* This thread has to wait for a while.  Enqueue it among the
    159      1.1  christos          waiting_readers.  */
    160      1.1  christos       HANDLE event = glwthread_waitqueue_add (&lock->waiting_readers);
    161      1.1  christos       if (event != INVALID_HANDLE_VALUE)
    162      1.1  christos         {
    163      1.1  christos           DWORD result;
    164      1.1  christos           LeaveCriticalSection (&lock->lock);
    165      1.1  christos           /* Wait until another thread signals this event.  */
    166      1.1  christos           result = WaitForSingleObject (event, INFINITE);
    167      1.1  christos           if (result == WAIT_FAILED || result == WAIT_TIMEOUT)
    168      1.1  christos             abort ();
    169      1.1  christos           CloseHandle (event);
    170      1.1  christos           /* The thread which signalled the event already did the bookkeeping:
    171      1.1  christos              removed us from the waiting_readers, incremented lock->runcount.  */
    172      1.1  christos           if (!(lock->runcount > 0))
    173      1.1  christos             abort ();
    174      1.1  christos           return 0;
    175      1.1  christos         }
    176      1.1  christos       else
    177      1.1  christos         {
    178      1.1  christos           /* Allocation failure.  Weird.  */
    179      1.1  christos           do
    180      1.1  christos             {
    181      1.1  christos               LeaveCriticalSection (&lock->lock);
    182      1.1  christos               Sleep (1);
    183      1.1  christos               EnterCriticalSection (&lock->lock);
    184      1.1  christos             }
    185      1.1  christos           while (!(lock->runcount + 1 > 0));
    186      1.1  christos         }
    187      1.1  christos     }
    188      1.1  christos   lock->runcount++;
    189      1.1  christos   LeaveCriticalSection (&lock->lock);
    190      1.1  christos   return 0;
    191      1.1  christos }
    192      1.1  christos 
    193      1.1  christos int
    194      1.1  christos glwthread_rwlock_wrlock (glwthread_rwlock_t *lock)
    195      1.1  christos {
    196      1.1  christos   if (!lock->guard.done)
    197      1.1  christos     {
    198      1.1  christos       if (InterlockedIncrement (&lock->guard.started) == 0)
    199      1.1  christos         /* This thread is the first one to need this lock.  Initialize it.  */
    200      1.1  christos         glwthread_rwlock_init (lock);
    201      1.1  christos       else
    202      1.1  christos         {
    203      1.1  christos           /* Don't let lock->guard.started grow and wrap around.  */
    204      1.1  christos           InterlockedDecrement (&lock->guard.started);
    205      1.1  christos           /* Yield the CPU while waiting for another thread to finish
    206      1.1  christos              initializing this lock.  */
    207      1.1  christos           while (!lock->guard.done)
    208      1.1  christos             Sleep (0);
    209      1.1  christos         }
    210      1.1  christos     }
    211      1.1  christos   EnterCriticalSection (&lock->lock);
    212      1.1  christos   /* Test whether no readers or writers are currently running.  */
    213      1.1  christos   if (!(lock->runcount == 0))
    214      1.1  christos     {
    215      1.1  christos       /* This thread has to wait for a while.  Enqueue it among the
    216      1.1  christos          waiting_writers.  */
    217      1.1  christos       HANDLE event = glwthread_waitqueue_add (&lock->waiting_writers);
    218      1.1  christos       if (event != INVALID_HANDLE_VALUE)
    219      1.1  christos         {
    220      1.1  christos           DWORD result;
    221      1.1  christos           LeaveCriticalSection (&lock->lock);
    222      1.1  christos           /* Wait until another thread signals this event.  */
    223      1.1  christos           result = WaitForSingleObject (event, INFINITE);
    224      1.1  christos           if (result == WAIT_FAILED || result == WAIT_TIMEOUT)
    225      1.1  christos             abort ();
    226      1.1  christos           CloseHandle (event);
    227      1.1  christos           /* The thread which signalled the event already did the bookkeeping:
    228      1.1  christos              removed us from the waiting_writers, set lock->runcount = -1.  */
    229      1.1  christos           if (!(lock->runcount == -1))
    230      1.1  christos             abort ();
    231      1.1  christos           return 0;
    232      1.1  christos         }
    233      1.1  christos       else
    234      1.1  christos         {
    235      1.1  christos           /* Allocation failure.  Weird.  */
    236      1.1  christos           do
    237      1.1  christos             {
    238      1.1  christos               LeaveCriticalSection (&lock->lock);
    239      1.1  christos               Sleep (1);
    240      1.1  christos               EnterCriticalSection (&lock->lock);
    241      1.1  christos             }
    242      1.1  christos           while (!(lock->runcount == 0));
    243      1.1  christos         }
    244      1.1  christos     }
    245      1.1  christos   lock->runcount--; /* runcount becomes -1 */
    246      1.1  christos   LeaveCriticalSection (&lock->lock);
    247      1.1  christos   return 0;
    248      1.1  christos }
    249      1.1  christos 
    250      1.1  christos int
    251      1.1  christos glwthread_rwlock_tryrdlock (glwthread_rwlock_t *lock)
    252      1.1  christos {
    253      1.1  christos   if (!lock->guard.done)
    254      1.1  christos     {
    255      1.1  christos       if (InterlockedIncrement (&lock->guard.started) == 0)
    256      1.1  christos         /* This thread is the first one to need this lock.  Initialize it.  */
    257      1.1  christos         glwthread_rwlock_init (lock);
    258      1.1  christos       else
    259      1.1  christos         {
    260      1.1  christos           /* Don't let lock->guard.started grow and wrap around.  */
    261      1.1  christos           InterlockedDecrement (&lock->guard.started);
    262      1.1  christos           /* Yield the CPU while waiting for another thread to finish
    263      1.1  christos              initializing this lock.  */
    264      1.1  christos           while (!lock->guard.done)
    265      1.1  christos             Sleep (0);
    266      1.1  christos         }
    267      1.1  christos     }
    268      1.1  christos   /* It's OK to wait for this critical section, because it is never taken for a
    269      1.1  christos      long time.  */
    270      1.1  christos   EnterCriticalSection (&lock->lock);
    271      1.1  christos   /* Test whether only readers are currently running, and whether the runcount
    272      1.1  christos      field will not overflow, and whether no writer is waiting.  The latter
    273      1.1  christos      condition is because POSIX recommends that "write locks shall take
    274      1.1  christos      precedence over read locks", to avoid "writer starvation".  */
    275      1.1  christos   if (!(lock->runcount + 1 > 0 && lock->waiting_writers.count == 0))
    276      1.1  christos     {
    277      1.1  christos       /* This thread would have to wait for a while.  Return instead.  */
    278      1.1  christos       LeaveCriticalSection (&lock->lock);
    279      1.1  christos       return EBUSY;
    280      1.1  christos     }
    281      1.1  christos   lock->runcount++;
    282      1.1  christos   LeaveCriticalSection (&lock->lock);
    283      1.1  christos   return 0;
    284      1.1  christos }
    285      1.1  christos 
    286      1.1  christos int
    287      1.1  christos glwthread_rwlock_trywrlock (glwthread_rwlock_t *lock)
    288      1.1  christos {
    289      1.1  christos   if (!lock->guard.done)
    290      1.1  christos     {
    291      1.1  christos       if (InterlockedIncrement (&lock->guard.started) == 0)
    292      1.1  christos         /* This thread is the first one to need this lock.  Initialize it.  */
    293      1.1  christos         glwthread_rwlock_init (lock);
    294      1.1  christos       else
    295      1.1  christos         {
    296      1.1  christos           /* Don't let lock->guard.started grow and wrap around.  */
    297      1.1  christos           InterlockedDecrement (&lock->guard.started);
    298      1.1  christos           /* Yield the CPU while waiting for another thread to finish
    299      1.1  christos              initializing this lock.  */
    300      1.1  christos           while (!lock->guard.done)
    301      1.1  christos             Sleep (0);
    302      1.1  christos         }
    303      1.1  christos     }
    304      1.1  christos   /* It's OK to wait for this critical section, because it is never taken for a
    305      1.1  christos      long time.  */
    306      1.1  christos   EnterCriticalSection (&lock->lock);
    307      1.1  christos   /* Test whether no readers or writers are currently running.  */
    308      1.1  christos   if (!(lock->runcount == 0))
    309      1.1  christos     {
    310      1.1  christos       /* This thread would have to wait for a while.  Return instead.  */
    311      1.1  christos       LeaveCriticalSection (&lock->lock);
    312      1.1  christos       return EBUSY;
    313      1.1  christos     }
    314      1.1  christos   lock->runcount--; /* runcount becomes -1 */
    315      1.1  christos   LeaveCriticalSection (&lock->lock);
    316      1.1  christos   return 0;
    317      1.1  christos }
    318      1.1  christos 
    319      1.1  christos int
    320      1.1  christos glwthread_rwlock_unlock (glwthread_rwlock_t *lock)
    321      1.1  christos {
    322      1.1  christos   if (!lock->guard.done)
    323      1.1  christos     return EINVAL;
    324      1.1  christos   EnterCriticalSection (&lock->lock);
    325      1.1  christos   if (lock->runcount < 0)
    326      1.1  christos     {
    327      1.1  christos       /* Drop a writer lock.  */
    328      1.1  christos       if (!(lock->runcount == -1))
    329      1.1  christos         abort ();
    330      1.1  christos       lock->runcount = 0;
    331      1.1  christos     }
    332      1.1  christos   else
    333      1.1  christos     {
    334      1.1  christos       /* Drop a reader lock.  */
    335      1.1  christos       if (!(lock->runcount > 0))
    336      1.1  christos         {
    337      1.1  christos           LeaveCriticalSection (&lock->lock);
    338      1.1  christos           return EPERM;
    339      1.1  christos         }
    340      1.1  christos       lock->runcount--;
    341      1.1  christos     }
    342      1.1  christos   if (lock->runcount == 0)
    343      1.1  christos     {
    344      1.1  christos       /* POSIX recommends that "write locks shall take precedence over read
    345      1.1  christos          locks", to avoid "writer starvation".  */
    346      1.1  christos       if (lock->waiting_writers.count > 0)
    347      1.1  christos         {
    348      1.1  christos           /* Wake up one of the waiting writers.  */
    349      1.1  christos           lock->runcount--;
    350      1.1  christos           glwthread_waitqueue_notify_first (&lock->waiting_writers);
    351      1.1  christos         }
    352      1.1  christos       else
    353      1.1  christos         {
    354      1.1  christos           /* Wake up all waiting readers.  */
    355      1.1  christos           lock->runcount += lock->waiting_readers.count;
    356      1.1  christos           glwthread_waitqueue_notify_all (&lock->waiting_readers);
    357      1.1  christos         }
    358      1.1  christos     }
    359      1.1  christos   LeaveCriticalSection (&lock->lock);
    360      1.1  christos   return 0;
    361      1.1  christos }
    362      1.1  christos 
    363      1.1  christos int
    364      1.1  christos glwthread_rwlock_destroy (glwthread_rwlock_t *lock)
    365      1.1  christos {
    366      1.1  christos   if (!lock->guard.done)
    367      1.1  christos     return EINVAL;
    368      1.1  christos   if (lock->runcount != 0)
    369      1.1  christos     return EBUSY;
    370      1.1  christos   DeleteCriticalSection (&lock->lock);
    371      1.1  christos   if (lock->waiting_readers.array != NULL)
    372      1.1  christos     free (lock->waiting_readers.array);
    373      1.1  christos   if (lock->waiting_writers.array != NULL)
    374      1.1  christos     free (lock->waiting_writers.array);
    375      1.1  christos   lock->guard.done = 0;
    376      1.1  christos   return 0;
    377      1.1  christos }
    378