Source-Changes-HG archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

[src/trunk]: src/sys/kern pcq(9): Sketch correctness proof for some critical ...



details:   https://anonhg.NetBSD.org/src/rev/1facd5c8291d
branches:  trunk
changeset: 373660:1facd5c8291d
user:      riastradh <riastradh%NetBSD.org@localhost>
date:      Thu Feb 23 03:03:45 2023 +0000

description:
pcq(9): Sketch correctness proof for some critical properties.

No functional change intended.

diffstat:

 sys/kern/subr_pcq.c |  91 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 88 insertions(+), 3 deletions(-)

diffs (115 lines):

diff -r 316267834508 -r 1facd5c8291d sys/kern/subr_pcq.c
--- a/sys/kern/subr_pcq.c       Thu Feb 23 03:03:23 2023 +0000
+++ b/sys/kern/subr_pcq.c       Thu Feb 23 03:03:45 2023 +0000
@@ -1,4 +1,4 @@
-/*     $NetBSD: subr_pcq.c,v 1.17 2023/02/23 03:01:49 riastradh Exp $  */
+/*     $NetBSD: subr_pcq.c,v 1.18 2023/02/23 03:03:45 riastradh Exp $  */
 
 /*-
  * Copyright (c) 2009, 2019 The NetBSD Foundation, Inc.
@@ -31,10 +31,94 @@
 
 /*
  * Lockless producer/consumer queue.
+ *
+ * Summary of the producer algorithm in pcq_put (may run many in
+ * parallel with each other and with a consumer):
+ *
+ *     P1. initialize an item
+ *
+ *     P2. atomic_cas(&pcq->pcq_pc) loop to advance the producer
+ *         pointer, reserving a space at c (fails if not enough space)
+ *
+ *     P3. atomic_store_release(&pcq->pcq_items[c], item) to publish
+ *         the item in the space it reserved
+ *
+ * Summary of the consumer algorithm in pcq_get (must be serialized by
+ * caller with other consumers, may run in parallel with any number of
+ * producers):
+ *
+ *     C1. atomic_load_relaxed(&pcq->pcq_pc) to get the consumer
+ *         pointer and a snapshot of the producer pointer, which may
+ *         point to null items or point to initialized items (fails if
+ *         no space reserved for published items yet)
+ *
+ *     C2. atomic_load_consume(&pcq->pcq_items[c]) to get the next
+ *         unconsumed but potentially published item (fails if item
+ *         not published yet)
+ *
+ *     C3. pcq->pcq_items[c] = NULL to consume the next unconsumed but
+ *         published item
+ *
+ *     C4. membar_producer
+ *
+ *     C5. atomic_cas(&pcq->pcq_pc) loop to advance the consumer
+ *         pointer
+ *
+ *     C6. use the item
+ *
+ * Note that there is a weird bare membar_producer which is not matched
+ * by membar_consumer.  This is one of the rare cases of a memory
+ * barrier on one side that is not matched by a memory barrier on
+ * another side, but the ordering works out, with a somewhat more
+ * involved proof.
+ *
+ * Some properties that need to be proved:
+ *
+ *     Theorem 1.  For pcq_put call that leads into pcq_get:
+ *     Initializing item at P1 is dependency-ordered before usage of
+ *     item at C6, so items placed by pcq_put can be safely used by
+ *     the caller of pcq_get.
+ *
+ *     Proof sketch.
+ *
+ *             Assume load/store P2 synchronizes with load/store C1
+ *             (if not, pcq_get fails in `if (p == c) return NULL').
+ *
+ *             Assume store-release P3 synchronizes with load-consume
+ *             C2 (if not, pcq_get fails in `if (item == NULL) return
+ *             NULL').
+ *
+ *             Then:
+ *
+ *             - P1 is sequenced before store-release P3
+ *             - store-release P3 synchronizes with load-consume C2
+ *             - load-consume C2 is dependency-ordered before C6
+ *
+ *             Hence transitively, P1 is dependency-ordered before C6,
+ *             QED.
+ *
+ *     Theorem 2.  For pcq_get call followed by pcq_put: Nulling out
+ *     location at store C3 happens before placing a new item in the
+ *     same location at store P3, so items are not lost.
+ *
+ *     Proof sketch.
+ *
+ *             Assume load/store C5 synchronizes with load/store P2
+ *             (otherwise pcq_peek starts over the CAS loop or fails).
+ *
+ *             Then:
+ *
+ *             - store C3 is sequenced before membar_producer C4
+ *             - membar_producer C4 is sequenced before load/store C5
+ *             - load/store C5 synchronizes with load/store P2 at &pcq->pcq_pc
+ *             - P2 is sequenced before store-release P3
+ *
+ *             Hence transitively, store C3 happens before
+ *             store-release P3, QED.
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: subr_pcq.c,v 1.17 2023/02/23 03:01:49 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: subr_pcq.c,v 1.18 2023/02/23 03:03:45 riastradh Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -186,7 +270,8 @@
         * because the only load we need to ensure happens first is the
         * load of pcq->pcq_items[c], but that necessarily happens
         * before the store to pcq->pcq_items[c] to null it out because
-        * it is at the same memory location.
+        * it is at the same memory location.  Yes, this is a bare
+        * membar_producer with no matching membar_consumer.
         */
 #ifndef __HAVE_ATOMIC_AS_MEMBAR
        membar_producer();



Home | Main Index | Thread Index | Old Index