• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Read-write locks (native Windows implementation).
2    Copyright (C) 2005-2020 Free Software Foundation, Inc.
3 
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU Lesser General Public License as published by
6    the Free Software Foundation; either version 2.1 of the License, or
7    (at your option) any later version.
8 
9    This program 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 
35 static void
glwthread_waitqueue_init(glwthread_waitqueue_t * wq)36 glwthread_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.  */
46 static HANDLE
glwthread_waitqueue_add(glwthread_waitqueue_t * wq)47 glwthread_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.  */
96 static void
glwthread_waitqueue_notify_first(glwthread_waitqueue_t * wq)97 glwthread_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.  */
107 static void
glwthread_waitqueue_notify_all(glwthread_waitqueue_t * wq)108 glwthread_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 
123 void
glwthread_rwlock_init(glwthread_rwlock_t * lock)124 glwthread_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 
133 int
glwthread_rwlock_rdlock(glwthread_rwlock_t * lock)134 glwthread_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 
193 int
glwthread_rwlock_wrlock(glwthread_rwlock_t * lock)194 glwthread_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 
250 int
glwthread_rwlock_tryrdlock(glwthread_rwlock_t * lock)251 glwthread_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 
286 int
glwthread_rwlock_trywrlock(glwthread_rwlock_t * lock)287 glwthread_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 
319 int
glwthread_rwlock_unlock(glwthread_rwlock_t * lock)320 glwthread_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 
363 int
glwthread_rwlock_destroy(glwthread_rwlock_t * lock)364 glwthread_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