Re: [RFC PATCH 03/12] rcu_txn: Skip the record lookup with a Bloom filter
From: Puranjay Mohan
Date: Fri Oct 23 2026 - 14:23:05 EST
On Fri, Oct 16, 2026 at 09:33:10AM -0400, Mathieu Desnoyers wrote:
[...]
> /* A consumed handle has no record left: it reads committed values. */
> WARN_ON_ONCE(t->state == RCU_TXN_DONE);
> if (!t->disjoint) {
> - r = __rcu_txn_scan(t, slot);
> + r = __rcu_txn_find_ryw(t, slot);
> if (r)
> return r->ptr[1]; /* our own pending write */
This patch adds t->bloom_live, but __rcu_txn_done() never clears it. So
on a committed or cancelled handle that had armed its filter,
rcu_txn_load() warns about the DONE state and then dereferences the
now-NULL t->desc in __rcu_txn_bloom(). Recording 16 slots through
rcu_txn_load() and rcu_txn_record_chain(), committing, and then calling
rcu_txn_load() again gives:
# rcu_txn_test_exp_consumed_bloom: after commit: desc 0000000000000000 bloom_live 1
Unable to handle kernel NULL pointer dereference at virtual address 0000000000000018
[...]
pc : rcu_txn_load.constprop.0+0xfc/0x284
Patch 7 says misuse of a handle warns in every build, so this should
only warn. Clearing the flag in __rcu_txn_done() does that:
--- a/include/linux/rcu_txn.h
+++ b/include/linux/rcu_txn.h
@@ -615,5 +615,6 @@ static inline void __rcu_txn_done(struct rcu_txn *t)
t->desc = NULL;
t->records = NULL;
t->nr = 0;
+ t->bloom_live = false;
t->state = RCU_TXN_DONE;
}
[...]
> - size = kmalloc_size_roundup(struct_size(desc, records, cap));
> + size = struct_size(desc, records, cap);
> + if (cap >= RCU_TXN_BLOOM_MIN)
> + size = size_add(size, RCU_TXN_BLOOM_SLOTS *
> + sizeof(struct rcu_txn_record));
The room for the filter costs more than the filter itself. ksize()
after rcu_txn_reserve(n) on arm64, against what patch 1 alone gives:
n this series patch 1
6, 7 512 bytes, cap 7 512 bytes, cap 10
8..10 1024 bytes, cap 17 512 bytes, cap 10
So transactions of 8 to 10 records now take 1024 bytes, even on a
handle declared disjoint that never arms the filter, and an unreserved
handle gets there on its fifth record.
[...]
> + for (i = 0; i < 64; i++)
> + rcu_txn_bloom_set(bloom, &bloom_slots[i]);
> + for (i = 64; i-- > 0; )
> + rcu_txn_bloom_set(rebuilt, &bloom_slots[i]);
> + KUNIT_EXPECT_MEMEQ(test, bloom, rebuilt, sizeof(bloom));
This can't fail, since OR doesn't care about order, and it doesn't
exercise __rcu_txn_bloom_arm(); rcu_txn_test_bloom_arming() already
does. I'd drop it.
Thanks,
Puranjay