EPOCH(9)
π NAME π
okay bestie, gather round, because this is the kernel epoch based reclamation squad and they're all iconic:
epoch, epoch_context, epoch_alloc, epoch_free, epoch_enter, epoch_exit, epoch_wait, epoch_call, epoch_drain_callbacks, in_epoch β yes all of them, no skipping, we're doing the whole lineup π
(this is from the Kernel Developer's Manual, EPOCH(9), in case you were wondering who invited us π)
β¨ SYNOPSIS β¨
first, the lil summoning circle of headers you gotta include:
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/epoch.h>
and here's every single function signature, exactly as written, no cap:
epoch_t
epoch_alloc(int flags);
void
epoch_enter(epoch_t epoch);
void
epoch_enter_preempt(epoch_t epoch, epoch_tracker_t et);
void
epoch_exit(epoch_t epoch);
void
epoch_exit_preempt(epoch_t epoch, epoch_tracker_t et);
void
epoch_wait(epoch_t epoch);
void
epoch_wait_preempt(epoch_t epoch);
void
epoch_call(epoch_t epoch, epoch_context_t ctx,
void (*callback) (epoch_context_t));
void
epoch_drain_callbacks(epoch_t epoch);
int
in_epoch(epoch_t epoch);
π¦ DESCRIPTION π¦
so what's the whole vibe? Epochs are used to guarantee liveness and immutability of data by deferring reclamation and mutation until a grace period has elapsed. and the best part?? Epochs do not have any lock ordering issues. literally none. Entering and leaving an epoch section will never block. not once. it's giving "the chillest synchronization primitive in the kernel" and I'm obsessed π
Epochs get allocated with epoch_alloc() and freed with epoch_free(). The flags you pass to epoch_alloc decide whether preemption is allowed during a section or not (not = the default), and that's specified by EPOCH_PREEMPT. π
Threads show they're starting an epoch critical section by calling epoch_enter(). They show they're done by calling epoch_exit(). The _preempt variants can be used around code which requires preemption β slay. A thread can wait until a grace period has elapsed since any threads have entered the epoch by calling epoch_wait() or epoch_wait_preempt(), depending on the epoch_type.
Now this next bit is lowkey the tea β β using a default epoch type lets you use epoch_wait(), which is guaranteed to have much shorter completion times since we know that none of the threads in an epoch section will be preempted before completing its section. iconic behavior honestly.
If the thread can't sleep, or is otherwise in a performance sensitive path, it can ensure a grace period has elapsed by calling epoch_call() with a callback containing any work that needs to wait for an epoch to elapse. β¨
Only non-sleepable locks can be acquired during a section protected byepoch_enter_preempt()andepoch_exit_preempt(). that's the rule, don't test it π
INVARIANTS can assert that a thread is in an epoch by using in_epoch().
okay and now the warnings, because I love you and I want you safe π«Ά:
The epoch API currently does not support sleeping inepoch_preemptsections. A caller should never callepoch_wait()in the middle of an epoch section for the same epoch as this will lead to a deadlock. deadlock. like, actually. do not.
By default, mutexes cannot be held across epoch_wait_preempt(). To permit this, the epoch must be allocated with EPOCH_LOCKED. But when you do this, you must be cautious of creating a situation where a deadlock is possible. π
Also, important: epochs are not a straight replacement for read locks. Callers must use safe list and tailq traversal routines in an epoch (see ck_queue). When modifying a list referenced from an epoch section, safe removal routines must be used, and the caller can no longer modify a list entry in place. An item to be modified must be handled with copy on write, and frees must be deferred until after a grace period has elapsed. periodt. π
The epoch_drain_callbacks() function is used to drain all pending callbacks which have been invoked by prior epoch_call() function calls on the same epoch. This function is useful when there are shared memory structure(s) referred to by the epoch callback(s) which are not refcounted and are rarely freed. The typical place for calling this function is right before freeing or invalidating the shared resource(s) used by the epoch callback(s). This function can sleep and is not optimized for performance. π
πΈ RETURN VALUES πΈ
in_epoch(curepoch) will return 1 if curthread is in curepoch, 0 otherwise. simple as that, bestie β¨
β οΈ CAVEATS β οΈ
One must be cautious when using epoch_wait_preempt() β threads are pinned during epoch sections, so if a thread in a section is then preempted by a higher priority compute bound thread on that CPU, it can be prevented from leaving the section. Thus the wait time for the waiter is potentially unbounded. yeah. potentially unbounded. not a vibe, just a fact π
π EXAMPLES π
async free example, let's gooo β this is the good stuff π
Thread 1:
int
in_pcbladdr(struct inpcb *inp, struct in_addr *faddr, struct in_laddr *laddr,
struct ucred *cred)
{
/* ... */
epoch_enter(net_epoch);
CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) {
sa = ifa->ifa_addr;
if (sa->sa_family != AF_INET)
continue;
sin = (struct sockaddr_in *)sa;
if (prison_check_ip4(cred, &sin->sin_addr) == 0) {
ia = (struct in_ifaddr *)ifa;
break;
}
}
epoch_exit(net_epoch);
/* ... */
}
Thread 2:
void
ifa_free(struct ifaddr *ifa)
{
if (refcount_release(&ifa->ifa_refcnt))
epoch_call(net_epoch, &ifa->ifa_epoch_ctx, ifa_destroy);
}
void
if_purgeaddrs(struct ifnet *ifp)
{
/* .... *
IF_ADDR_WLOCK(ifp);
CK_STAILQ_REMOVE(&ifp->if_addrhead, ifa, ifaddr, ifa_link);
IF_ADDR_WUNLOCK(ifp);
ifa_free(ifa);
}
so what's actually happening here? Thread 1 traverses the ifaddr list in an epoch. Thread 2 unlinks with the corresponding epoch safe macro, marks as logically free, and then defers deletion. More general mutation, or a synchronous free, would have to follow a call to epoch_wait(). π¦
π«Ά ERRORS π«Ά
None.
that's it. nothing. a clean slate. the silence is iconic π
π SEE ALSO π
go read these too, bestie, they're all part of the lore:
okay that's the whole page, you absolutely ate this π β¨ this little love letter came to you courtesy of Debian, dated June 28, 2019 β and with that, epoch bestie, I'm clocking out. go forth and defer your reclamation responsibly ππ
π¦ where this page comes from
- debian bookworm: freebsd-manpages 12.2-1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- debian trixie: freebsd-manpages 12.2-2 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- ubuntu jammy: freebsd-manpages 12.2-1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- ubuntu noble: freebsd-manpages 12.2-1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- ubuntu questing: freebsd-manpages 12.2-2
- ubuntu resolute: freebsd-manpages 12.2-2build1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- ubuntu stonking: freebsd-manpages 12.2-2build1 Β· BSD-2-Clause-FreeBSD AND BSD-4-Clause
- Copyright (C) 2018 Matthew Macy <mmacy@FreeBSD.org>
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms