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