Re: rpki-client: fix possible pthread deadlock on exit

David Gwynne <[email protected]> Wed, 22 Jul 2026 09:41:11 +1000
Newsgroups gmane.os.openbsd.tech
Message-ID <[email protected]>
I agree with Philip, it should be safe to pthread_cond_signal or 
pthread_cond_broadcast without holding the mutex, just like it's safe 
(and often desirable) to call wakeup(9) in the kernel without the lock 
the waiting thread is sleeping on.

On 22/07/2026 05:22, Philip Guenther wrote:
> Hmm, as long as the Boolean being checked (quit?) is both checked and 
> changed with the mutex held, there should be no race. How did the hung 
> thread see quit not true before waiting but the signaling thread set 
> or see it as true before signaling?  Smells like quit is being set 
> outside the mutex…
>
> (Holding the mutex while signaling is fine of course, but feels like 
> it should have been held even earlier)
>
> Philip
>
> On Tuesday, July 21, 2026, Claudio Jeker <[email protected]> wrote:
>
>     The interaction between quit and the pthread_cond_broadcast calls
>     in the
>     exit path are not properly sequenced. Calling the cond_broadcast
>     without
>     holding its mutex can lead to a case where the broadcast is lost
>     and so
>     that thread would not wake up. The process would then hang forever in
>     pthread_join().
>
>     Grab the mutex before sending the pthread_cond_broadcast() fixes this
>     issue since now the receiver is either properly parked or it will
>     see that quit is true and exit before calling pthread_cond_wait().
>
>     Also do an early exit from the event loop if quit is true. There is
>     no need work through the full event loop in that case.
>     -- 
>     :wq Claudio
>
>     Index: parser.c
>     ===================================================================
>     RCS file: /cvs/src/usr.sbin/rpki-client/parser.c,v
>     diff -u -p -r1.182 parser.c
>     --- parser.c    26 Jun 2026 08:05:22 -0000      1.182
>     +++ parser.c    9 Jul 2026 20:44:10 -0000
>     @@ -1072,6 +1072,12 @@ parse_worker(void *arg)
>                                     errx(1, "pthread_cond_wait: %s",
>                                         strerror(error));
>                     }
>     +               if (quit) {
>     +                       if ((error =
>     pthread_mutex_unlock(&globalq_mtx)) != 0)
>     +                               errx(1, "pthread_mutex_unlock: %s",
>     +                                   strerror(error));
>     +                       break;
>     +               }
>                     n = 0;
>                     while ((entp = TAILQ_FIRST(&globalq)) != NULL) {
>                             TAILQ_REMOVE(&globalq, entp, entries);
>     @@ -1141,6 +1147,8 @@ parse_writer(void *arg)
>                             if (error != 0)
>                                     errx(1, "pthread_mutex_lock: %s",
>                                         strerror(error));
>     +                       if (quit)
>     +                               break;
>                     }
>
>                     if (msgbuf_queuelen(myq) > 0) {
>     @@ -1279,13 +1287,19 @@ proc_parser(int fd, int nthreads)
>             }
>
>             /* signal all threads */
>     +       if ((error = pthread_mutex_lock(&globalq_mtx)) != 0)
>     +               errx(1, "pthread_mutex_lock: %s", strerror(error));
>     +       if ((error = pthread_mutex_lock(&globalmsgq_mtx)) != 0)
>     +               errx(1, "pthread_mutex_lock: %s", strerror(error));
>     +
>             if ((error = pthread_cond_broadcast(&globalq_cond)) != 0)
>                     errx(1, "pthread_cond_broadcast: %s",
>     strerror(error));
>             if ((error = pthread_cond_broadcast(&globalmsgq_cond)) != 0)
>                     errx(1, "pthread_cond_broadcast: %s",
>     strerror(error));
>
>     -       if ((error = pthread_mutex_lock(&globalq_mtx)) != 0)
>     -               errx(1, "pthread_mutex_lock: %s", strerror(error));
>     +       if ((error = pthread_mutex_unlock(&globalmsgq_mtx)) != 0)
>     +               errx(1, "pthread_mutex_unlock: %s", strerror(error));
>     +
>             while ((entp = TAILQ_FIRST(&globalq)) != NULL) {
>                     TAILQ_REMOVE(&globalq, entp, entries);
>                     entity_free(entp);
>