The pthread_once_t type, used by pthread_once in POSIX,
and the once_flag type, used by ISO C 11 call_once,
have no destructor. But a Windows implementation of these types
necessarily includes a CRITICAL_SECTION object.

Takashi Yano noted that not destroying such a CRITICAL_SECTION object
produces a HANDLE leak. This was in the context of the Cygwin implementation
of pthread_once, but the same thing holds w.r.t. the Gnulib implementation.

This patch fixes it for Gnulib. The native Windows implementation of
pthread_once and call_once are backed by the 'windows-once' module.


2024-05-30  Bruno Haible  <br...@clisp.org>

        windows-once: Free allocated resources when done.
        Based on an observation regarding Cygwin's pthread_once implementation
        by Takashi Yano <takashi.y...@nifty.ne.jp> at
        <https://cygwin.com/pipermail/cygwin/2024-January/255182.html> and
        <https://cygwin.com/pipermail/cygwin-patches/2024q1/012600.html>
        * lib/windows-once.h (glwthread_once_t): Add field 'num_threads'.
        (GLWTHREAD_ONCE_INIT): Initialize it to zero.
        * lib/windows-once.c (glwthread_once): Increment num_threads while the
        thread uses the lock. Let the last thread that uses the lock destroy it.

diff --git a/lib/windows-once.c b/lib/windows-once.c
index 17854f5c09..b6e1c77566 100644
--- a/lib/windows-once.c
+++ b/lib/windows-once.c
@@ -29,6 +29,7 @@ glwthread_once (glwthread_once_t *once_control, void 
(*initfunction) (void))
 {
   if (once_control->inited <= 0)
     {
+      InterlockedIncrement (&once_control->num_threads);
       if (InterlockedIncrement (&once_control->started) == 0)
         {
           /* This thread is the first one to come to this once_control.  */
@@ -58,5 +59,11 @@ glwthread_once (glwthread_once_t *once_control, void 
(*initfunction) (void))
                 abort ();
             }
         }
+      /* Here once_control->inited > 0.  */
+      if (InterlockedDecrement (&once_control->num_threads) == 0)
+        /* once_control->num_threads is now zero, and once_control->inited is 
1.
+           No other thread will need to use the lock.
+           We can therefore destroy the lock, to free resources.  */
+        DeleteCriticalSection (&once_control->lock);
     }
 }
diff --git a/lib/windows-once.h b/lib/windows-once.h
index c5bbcd573c..13579b5807 100644
--- a/lib/windows-once.h
+++ b/lib/windows-once.h
@@ -26,12 +26,13 @@
 typedef struct
         {
           volatile int inited;
+          volatile LONG num_threads;
           volatile LONG started;
           CRITICAL_SECTION lock;
         }
         glwthread_once_t;
 
-#define GLWTHREAD_ONCE_INIT { -1, -1 }
+#define GLWTHREAD_ONCE_INIT { -1, 0, -1 }
 
 #ifdef __cplusplus
 extern "C" {




Reply via email to