diff options
Diffstat (limited to 'gl/windows-once.c')
| -rw-r--r-- | gl/windows-once.c | 51 |
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 | } |
