summaryrefslogtreecommitdiffstats
path: root/gl/windows-once.c
diff options
context:
space:
mode:
Diffstat (limited to 'gl/windows-once.c')
-rw-r--r--gl/windows-once.c51
1 files changed, 47 insertions, 4 deletions
diff --git a/gl/windows-once.c b/gl/windows-once.c
index 17854f5c..bd9e672a 100644
--- a/gl/windows-once.c
+++ b/gl/windows-once.c
@@ -1,5 +1,5 @@
1/* Once-only control (native Windows implementation). 1/* Once-only control (native Windows implementation).
2 Copyright (C) 2005-2024 Free Software Foundation, Inc. 2 Copyright (C) 2005-2025 Free Software Foundation, Inc.
3 3
4 This file is free software: you can redistribute it and/or modify 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 5 it under the terms of the GNU Lesser General Public License as
@@ -29,7 +29,9 @@ glwthread_once (glwthread_once_t *once_control, void (*initfunction) (void))
29{ 29{
30 if (once_control->inited <= 0) 30 if (once_control->inited <= 0)
31 { 31 {
32 if (InterlockedIncrement (&once_control->started) == 0) 32 InterlockedIncrement (&once_control->num_threads);
33 /* If once_control->started is == -1, set it to 0. */
34 if (InterlockedCompareExchange (&once_control->started, 0, -1) < 0)
33 { 35 {
34 /* This thread is the first one to come to this once_control. */ 36 /* This thread is the first one to come to this once_control. */
35 InitializeCriticalSection (&once_control->lock); 37 InitializeCriticalSection (&once_control->lock);
@@ -41,8 +43,6 @@ glwthread_once (glwthread_once_t *once_control, void (*initfunction) (void))
41 } 43 }
42 else 44 else
43 { 45 {
44 /* Don't let once_control->started grow and wrap around. */
45 InterlockedDecrement (&once_control->started);
46 /* Some other thread has already started the initialization. 46 /* Some other thread has already started the initialization.
47 Yield the CPU while waiting for the other thread to finish 47 Yield the CPU while waiting for the other thread to finish
48 initializing and taking the lock. */ 48 initializing and taking the lock. */
@@ -58,5 +58,48 @@ glwthread_once (glwthread_once_t *once_control, void (*initfunction) (void))
58 abort (); 58 abort ();
59 } 59 }
60 } 60 }
61 /* Here once_control->started == 0 and once_control->inited > 0. */
62 if (InterlockedDecrement (&once_control->num_threads) == 0)
63 /* once_control->num_threads is now zero, and
64 once_control->started == 0 and once_control->inited > 0.
65 No other thread will need to use the lock.
66 We can therefore destroy the lock, to free resources. */
67 /* If once_control->inited is == 1, set it to 2. */
68 if (InterlockedCompareExchange (&once_control->inited, 2, 1) == 1)
69 DeleteCriticalSection (&once_control->lock);
61 } 70 }
71 /* Proof of correctness:
72 * num_threads is incremented and then decremented by some threads.
73 Therefore, num_threads always stays >= 0, and is == 0 at the end.
74 * The first thread to go through the once_control->started fence
75 initializes the lock and moves inited from <= 0 to > 0. The other
76 threads don't move inited from <= 0 to > 0.
77 * started, once == 0, stays == 0.
78 * inited, once > 0, stays > 0 (since at the place where it is assigned 0,
79 it cannot be > 0).
80 * inited does not change any more once it is 2.
81 Therefore, it can be changed from 1 to 2 only once.
82 * DeleteCriticalSection gets invoked right after inited has been changed
83 from 1 to 2. Therefore, DeleteCriticalSection gets invoked only once.
84 * After a moment where num_threads was 0 and started was 0 and
85 inited was > 0, no thread can reach an InitializeCriticalSection or
86 EnterCriticalSection invocation. Proof:
87 - At such a moment, no thread is in the code range between
88 InterlockedIncrement (&once_control->num_threads)
89 and
90 InterlockedDecrement (&once_control->num_threads)
91 - After such a moment, some thread can increment num_threads, but from
92 there they cannot reach the InitializeCriticalSection invocation,
93 because the once_control->started test prevents that, and they cannot
94 reach the EnterCriticalSection invocation in the other branch because
95 the
96 if (once_control->inited <= 0)
97 test prevents that.
98 * From this it follows that:
99 - DeleteCriticalSection cannot be executed while the lock is taken
100 (because DeleteCriticalSection is only executed after a moment where
101 num_threads was 0 and started was 0 and inited was > 0).
102 - Once DeleteCriticalSection has been executed, the lock is not used any
103 more.
104 */
62} 105}