On 03/11/20 18:45 +0000, Jonathan Wakely wrote: >The current implementation of std::call_once uses pthread_once, which >only meets the C++ requirements when compiled with support for >exceptions. For most glibc targets and all non-glibc targets, >pthread_once does not work correctly if the init_routine exits via an >exception. The pthread_once_t object is left in the "active" state, and >any later attempts to run another init_routine will block forever. > >This change makes std::call_once work correctly for Linux targets, by >replacing the use of pthread_once with a futex, based on the code from >__cxa_guard_acquire. For both glibc and musl, the Linux implementation >of pthread_once is already based on futexes, and pthread_once_t is just >a typedef for int, so this change does not alter the layout of >std::once_flag. By choosing the values for the int appropriately, the >new code is even ABI compatible. Code that calls the old implementation >of std::call_once will use pthread_once to manipulate the int, while new >code will use the new std::once_flag members to manipulate it, but they >should interoperate correctly. In both cases, the int is initially zero, >has the lowest bit set when there is an active execution, and equals 2 >after a successful returning execution. The difference with the new code >is that exceptional exceptions are correctly detected and the int is >reset to zero. > >The __cxa_guard_acquire code (and musl's pthread_once) use an additional >state to say there are other threads waiting. This allows the futex wake >syscall to be skipped if there is no contention. Glibc doesn't use a >waiter bit, so we have to unconditionally issue the wake in order to be >compatible with code calling the old std::call_once that uses Glibc's >pthread_once. If we know that we're using musl (and musl's pthread_once >doesn't change) it would be possible to set a waiting state and check >for it in std::once_flag::_M_finish(bool), but this patch doesn't do >that. > >This doesn't fix the bug for non-linux targets. A similar approach could >be used for targets where we know the definition of pthread_once_t is a >mutex and an integer. We could make once_flag._M_activate() use >pthread_mutex_lock on the mutex member within the pthread_once_t, and >then only set the integer if the execution finishes, and then unlock the >mutex. That would require careful study of each target's pthread_once >implementation and that work is left for a later date. Something like the attached (completely untested) patch might work so we could stop using pthread_once for the BSDs and Solaris (and so fix the bug with exceptional executions). It's a kluge though (and fragile for Solaris because it assumes the pthread_once_t type will always have the same layout - in the public header it's just got lots of 64-bit integers for padding). I should probably test this some time. In theory something similar could be done for AIX, but its pthread_once_t is another struct full of padding integers, and unlike Solaris we can't look at the code to see how the OS uses those bits. I'm not sure if this can work for Darwin, as its pthread_once_t isn't just an aggregate of a pthread_mutex_t and a flag, I think it contains state used by GCD. That means we can't perform equivalent operations directly just in terms of Pthread APIs.