Page MenuHomeFreeBSD

epoch: Fix epoch_drain_callbacks() for skipped callback buckets
Needs ReviewPublic

Authored by rcm on Wed, Oct 14, 4:20 PM.
Tags
None
Referenced Files
F176280687: D60727.diff
Sat, Oct 17, 3:19 PM
F176253084: D60727.diff
Sat, Oct 17, 9:05 AM
Unknown Object (File)
Fri, Oct 16, 8:39 AM
Unknown Object (File)
Thu, Oct 15, 6:03 AM
Unknown Object (File)
Thu, Oct 15, 5:31 AM
Unknown Object (File)
Thu, Oct 15, 5:29 AM
Unknown Object (File)
Thu, Oct 15, 5:10 AM
Unknown Object (File)
Thu, Oct 15, 5:09 AM
Subscribers

Details

Reviewers
markj
glebius
Summary

The two-pass drain of 7bf11a2f0c9a assumes that once the first drain
callback has run, every callback pending at the time of the call has at
least been dispatched. ck_epoch's ring breaks that: a record's callbacks
sit in four buckets indexed by the global epoch, a poll at epoch G
dispatches bucket G-2 and bucket G-1 only on the CAS path, and the global
epoch is advanced by every other record's poll and by epoch_wait(). When
it moves by two or more between polls of one record, a bucket is passed
over for a full turn of the ring while younger buckets, including the
drain markers of both passes, are dispatched. epoch_drain_callbacks()
then returns with callbacks pending, and epoch_free() frees the record
with them in it.

A randomized stress hits this in about 1 % of drains on an 8 vCPU VM
and 3 % on a 4-core Netgate 4200.

Instead of a marker, have the callback task record which of its record's
buckets are non-empty when it sees the request and report the drain done
once each of them has been dispatched. A bucket only empties by the
task's own poll, which takes all of it and runs it before reporting, so
one pass suffices. A record with nothing pending finishes at the first
task run.

Fixes: 7bf11a2f0c9a ("epoch: Fix epoch_drain_callbacks()")
MFC after: 1 month
Sponsored by: Rubicon Communications, LLC ("Netgate")

Test Plan

Tested with two test-only sysctls that are not part of this change. The first
reproduces the bug with a controlled sequence of polls on one CPU while other
CPUs advance the global epoch: the unfixed kernel returns from the drain with a
callback still pending, the fixed kernel waits for it. The second drains in a
loop under random callback, reader and epoch_wait_preempt() churn and checks
after each drain that nothing queued before the request is still outstanding.

On an 8-vCPU arm64 VM the unfixed kernel failed that check about 1% of the
time; on a Netgate 4200, idle and while forwarding, about 3%. The fixed kernel
had no failures in 14000 drains across both and halved the average drain time.

Drain stress of pflow unload/load, epair create/destroy and vnet jail churn
under busy loops showed no change in throughput, no LORs and no sleep warnings,
and the epoch_free() module test passed.

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Skipped
Unit
Tests Skipped

Event Timeline

rcm requested review of this revision.Wed, Oct 14, 4:20 PM
sys/kern/subr_epoch.c
816–827

I have also pulled this out as an upstream PR: https://github.com/concurrencykit/ck/pull/294. If both sides here find this approach acceptable, we can drop our wrapper here.

873–875

and here

I will look at this more closely in a bit, but I'm a bit confused: is commit 7bf11a2f0c9a introducing a new problem? Or did it just not completely fix the problem it's supposed to fix?

sys/kern/subr_epoch.c
856

Why can't this er_draining flag be a local variable?

I will look at this more closely in a bit, but I'm a bit confused: is commit 7bf11a2f0c9a introducing a new problem? Or did it just not completely fix the problem it's supposed to fix?

No new problem; 7bf11a2f0c9a didn't completely fix it.

Drop er_draining: a non-zero er_drain_mask now means a drain is in progress on the record. When the request is seen and the snapshot is already empty, the drain is reported done in that task run.

rcm marked an inline comment as done.Wed, Oct 14, 8:05 PM
rcm added inline comments.
sys/kern/subr_epoch.c
856

The flag has to outlive a task run. Though we can just define a non-zero er_drain_mask as meaning a drain is in progress on the record. I have dropped the explicit flag in the latest revision.

rcm marked an inline comment as done.Thu, Oct 15, 3:19 PM
rcm added inline comments.
sys/kern/subr_epoch.c
812–827

https://github.com/concurrencykit/ck/pull/294 was accepted upstream, so a vendor update to ck would allow us to drop this.