kqueue: Fix missing wakeup
authorvisa <visa@openbsd.org>
Sun, 12 Jun 2022 10:34:36 +0000 (10:34 +0000)
committervisa <visa@openbsd.org>
Sun, 12 Jun 2022 10:34:36 +0000 (10:34 +0000)
While one thread is running kqueue_scan(), another thread can begin
scanning the same kqueue, observe that the event queue is empty, and
go to sleep. If the first thread re-inserts a knote for re-processing,
the second thread can miss the newly pending event. Wake up the kqueue
after a re-insert to correct this.

This fixes a Go test hang that jsing@ tracked down to kqueue.

Tested in snaps for a week.

OK jsing@ mpi@

sys/kern/kern_event.c

index 1d1b477..378e029 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: kern_event.c,v 1.188 2022/05/12 13:33:00 visa Exp $   */
+/*     $OpenBSD: kern_event.c,v 1.189 2022/06/12 10:34:36 visa Exp $   */
 
 /*-
  * Copyright (c) 1999,2000,2001 Jonathan Lemon <jlemon@FreeBSD.org>
@@ -1286,6 +1286,7 @@ kqueue_scan(struct kqueue_scan_state *scan, int maxevents,
        struct kqueue *kq = scan->kqs_kq;
        struct knote *kn;
        int error = 0, nkev = 0;
+       int reinserted;
 
        if (maxevents == 0)
                goto done;
@@ -1293,6 +1294,7 @@ retry:
        KASSERT(nkev == 0);
 
        error = 0;
+       reinserted = 0;
 
        /* msleep() with PCATCH requires kernel lock. */
        KERNEL_LOCK();
@@ -1453,6 +1455,8 @@ retry:
                                kq->kq_count++;
                                kn->kn_status |= KN_QUEUED;
                                TAILQ_INSERT_TAIL(&kq->kq_head, kn, kn_tqe);
+                               /* Wakeup is done after loop. */
+                               reinserted = 1;
                        }
                        knote_release(kn);
                }
@@ -1463,6 +1467,8 @@ retry:
                scan->kqs_nevent++;
        }
        TAILQ_REMOVE(&kq->kq_head, &scan->kqs_start, kn_tqe);
+       if (reinserted && kq->kq_count != 0)
+               kqueue_wakeup(kq);
        mtx_leave(&kq->kq_lock);
        if (scan->kqs_nevent == 0)
                goto retry;