Re: pthread_once fix memory leak

2021-05-02 Thread Otto Moerbeek
On Sun, May 02, 2021 at 02:07:21PM +0200, Mark Kettenis wrote:

> > From: Martijn van Duren 
> > Date: Sun, 02 May 2021 13:28:10 +0200
> > 
> > Found this while tracing a memory leak in filter-dkimsign, thanks to
> > libcrypto. The mutex in pthread_once_t is never destroyed, so the
> > memory allocated inside the mutex is never released.
> > 
> > The diff below was inspired by Ed Schouten and switches form mutex to
> > futex to prevent any memory allocation. I've run with it for about a
> > week without issues and tb@ has given it some beating on sparc64.
> > However I'm no expert in this area and scrutiny from people with more
> > experience in this area and testing in general would be appreciated.
> > 
> > This implementation has one shortcoming I can see, namely[0]:
> > The pthread_once() function is not a cancellation point. However, if
> > init_routine is a cancellation point and is canceled, the effect on
> > once_control shall be as if pthread_once() was never called.
> > It doesn't handle this situation by waking up the sleeping threads.
> > However, the current code doesn't handle this requirement either:
> > #include 
> > #include 
> > 
> > pthread_once_t once = PTHREAD_ONCE_INIT;
> > 
> > void
> > init(void)
> > {
> > printf("init\n");
> > pthread_exit(NULL);
> > }
> > 
> > void *
> > routine(void *arg)
> > {
> > pthread_once(, init);
> > printf("%s\n", __func__);
> > return NULL;
> > }
> > 
> > int
> > main(int argc, char *argv[])
> > {
> > pthread_t thread;
> > pthread_create(, NULL, routine, NULL);
> > pthread_once(, init);
> > printf("%s\n", __func__);
> > return 0;
> > }
> > 
> > Since our current code shows similar behaviour without real world
> > problems and all the solutions that I can come up with are racey I think 
> > this diff can stand on its own and some other brave soul can fix this
> > requirement at a later time. :-)
> > 
> > OK?
> 
> Sorry, no, this is an ABI break.  And a libpthreads major bump is a
> major flag day.
> 
> I don't think this is worth fixing on its own.  There are other
> instances where using a mutex will leak memory.  We need to change the
> mutex implementation such that it doesn't use malloc.  This is needed
> for process shared mutexes too.

Agreed. This is a one-time leak, since once_control must not be on
the stack. So not a big issue. I would love to see malloc-free mutexes
as well.

-Otto

> 
> > Index: include/pthread.h
> > ===
> > RCS file: /cvs/src/include/pthread.h,v
> > retrieving revision 1.4
> > diff -u -p -r1.4 pthread.h
> > --- include/pthread.h   5 Mar 2018 01:15:26 -   1.4
> > +++ include/pthread.h   2 May 2021 11:24:17 -
> > @@ -136,20 +136,13 @@ typedef void  *(*pthread_startroutine_t)(
> >   * Once definitions.
> >   */
> >  struct pthread_once {
> > -   int state;
> > -   pthread_mutex_t mutex;
> > +   volatile unsigned int   state;
> >  };
> >  
> >  /*
> > - * Flags for once initialization.
> > - */
> > -#define PTHREAD_NEEDS_INIT  0
> > -#define PTHREAD_DONE_INIT   1
> > -
> > -/*
> >   * Static once initialization values. 
> >   */
> > -#define PTHREAD_ONCE_INIT   { PTHREAD_NEEDS_INIT, 
> > PTHREAD_MUTEX_INITIALIZER }
> > +#define PTHREAD_ONCE_INIT   { 0 }
> >  
> >  /*
> >   * Static initialization values. 
> > Index: lib/libc/thread/rthread_once.c
> > ===
> > RCS file: /cvs/src/lib/libc/thread/rthread_once.c,v
> > retrieving revision 1.3
> > diff -u -p -r1.3 rthread_once.c
> > --- lib/libc/thread/rthread_once.c  4 Nov 2017 22:53:57 -   1.3
> > +++ lib/libc/thread/rthread_once.c  2 May 2021 11:24:17 -
> > @@ -18,15 +18,25 @@
> >  
> >  #include 
> >  
> > +#include "synch.h"
> > +
> >  int
> >  pthread_once(pthread_once_t *once_control, void (*init_routine)(void))
> >  {
> > -   pthread_mutex_lock(_control->mutex);
> > -   if (once_control->state == PTHREAD_NEEDS_INIT) {
> > +   switch (atomic_cas_uint(&(once_control->state), 0, 1)) {
> > +   case 0:
> > init_routine();
> > -   once_control->state = PTHREAD_DONE_INIT;
> > +   atomic_inc_int(_control->state);
> > +   _wake(_control->state, INT_MAX);
> > +   break;
> > +   case 1:
> > +   do {
> > +   _twait(_control->state, 1, 0, NULL);
> > +   } while (once_control->state != 2);
> > +   break;
> > +   default:
> > +   break;
> > }
> > -   pthread_mutex_unlock(_control->mutex);
> >  
> > -   return (0);
> > +   return 0;
> >  }
> > 
> > 
> > 
> 



Re: pthread_once fix memory leak

2021-05-02 Thread Mark Kettenis
> From: Martijn van Duren 
> Date: Sun, 02 May 2021 13:28:10 +0200
> 
> Found this while tracing a memory leak in filter-dkimsign, thanks to
> libcrypto. The mutex in pthread_once_t is never destroyed, so the
> memory allocated inside the mutex is never released.
> 
> The diff below was inspired by Ed Schouten and switches form mutex to
> futex to prevent any memory allocation. I've run with it for about a
> week without issues and tb@ has given it some beating on sparc64.
> However I'm no expert in this area and scrutiny from people with more
> experience in this area and testing in general would be appreciated.
> 
> This implementation has one shortcoming I can see, namely[0]:
> The pthread_once() function is not a cancellation point. However, if
> init_routine is a cancellation point and is canceled, the effect on
> once_control shall be as if pthread_once() was never called.
> It doesn't handle this situation by waking up the sleeping threads.
> However, the current code doesn't handle this requirement either:
> #include 
> #include 
> 
> pthread_once_t once = PTHREAD_ONCE_INIT;
> 
> void
> init(void)
> {
>   printf("init\n");
>   pthread_exit(NULL);
> }
> 
> void *
> routine(void *arg)
> {
>   pthread_once(, init);
>   printf("%s\n", __func__);
>   return NULL;
> }
> 
> int
> main(int argc, char *argv[])
> {
>   pthread_t thread;
>   pthread_create(, NULL, routine, NULL);
>   pthread_once(, init);
>   printf("%s\n", __func__);
>   return 0;
> }
> 
> Since our current code shows similar behaviour without real world
> problems and all the solutions that I can come up with are racey I think 
> this diff can stand on its own and some other brave soul can fix this
> requirement at a later time. :-)
> 
> OK?

Sorry, no, this is an ABI break.  And a libpthreads major bump is a
major flag day.

I don't think this is worth fixing on its own.  There are other
instances where using a mutex will leak memory.  We need to change the
mutex implementation such that it doesn't use malloc.  This is needed
for process shared mutexes too.

> Index: include/pthread.h
> ===
> RCS file: /cvs/src/include/pthread.h,v
> retrieving revision 1.4
> diff -u -p -r1.4 pthread.h
> --- include/pthread.h 5 Mar 2018 01:15:26 -   1.4
> +++ include/pthread.h 2 May 2021 11:24:17 -
> @@ -136,20 +136,13 @@ typedef void*(*pthread_startroutine_t)(
>   * Once definitions.
>   */
>  struct pthread_once {
> - int state;
> - pthread_mutex_t mutex;
> + volatile unsigned int   state;
>  };
>  
>  /*
> - * Flags for once initialization.
> - */
> -#define PTHREAD_NEEDS_INIT  0
> -#define PTHREAD_DONE_INIT   1
> -
> -/*
>   * Static once initialization values. 
>   */
> -#define PTHREAD_ONCE_INIT   { PTHREAD_NEEDS_INIT, PTHREAD_MUTEX_INITIALIZER }
> +#define PTHREAD_ONCE_INIT   { 0 }
>  
>  /*
>   * Static initialization values. 
> Index: lib/libc/thread/rthread_once.c
> ===
> RCS file: /cvs/src/lib/libc/thread/rthread_once.c,v
> retrieving revision 1.3
> diff -u -p -r1.3 rthread_once.c
> --- lib/libc/thread/rthread_once.c4 Nov 2017 22:53:57 -   1.3
> +++ lib/libc/thread/rthread_once.c2 May 2021 11:24:17 -
> @@ -18,15 +18,25 @@
>  
>  #include 
>  
> +#include "synch.h"
> +
>  int
>  pthread_once(pthread_once_t *once_control, void (*init_routine)(void))
>  {
> - pthread_mutex_lock(_control->mutex);
> - if (once_control->state == PTHREAD_NEEDS_INIT) {
> + switch (atomic_cas_uint(&(once_control->state), 0, 1)) {
> + case 0:
>   init_routine();
> - once_control->state = PTHREAD_DONE_INIT;
> + atomic_inc_int(_control->state);
> + _wake(_control->state, INT_MAX);
> + break;
> + case 1:
> + do {
> + _twait(_control->state, 1, 0, NULL);
> + } while (once_control->state != 2);
> + break;
> + default:
> + break;
>   }
> - pthread_mutex_unlock(_control->mutex);
>  
> - return (0);
> + return 0;
>  }
> 
> 
>