From 2588bf7fa047ad3d4fa02ef3e23eb21b1bb45677 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 28 Oct 2024 10:55:37 +0100 Subject: [PATCH] internal/task: add cooperative implementation of Futex See the code comments for details. But in short, this implements a futex for the cooperative scheduler (that is single threaded and non-reentrant). Similar implementations can be made for basically every other operating system, and even WebAssembly with the threading (actually: atomics) proposal. Using this basic futex implementation means we can use the same implementation for synchronisation primitives on cooperative and multicore systems. For more information on futex across operating systems: https://outerproduct.net/futex-dictionary.html --- src/internal/task/futex-cooperative.go | 44 ++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/internal/task/futex-cooperative.go diff --git a/src/internal/task/futex-cooperative.go b/src/internal/task/futex-cooperative.go new file mode 100644 index 000000000..8351f8877 --- /dev/null +++ b/src/internal/task/futex-cooperative.go @@ -0,0 +1,44 @@ +package task + +// A futex is a way for userspace to wait with the pointer as the key, and for +// another thread to wake one or all waiting threads keyed on the same pointer. +// +// A futex does not change the underlying value, it only reads it before to prevent +// lost wake-ups. +type Futex struct { + Uint32 + waiters Stack +} + +// Atomically check for cmp to still be equal to the futex value and if so, go +// to sleep. Return true if we were definitely awoken by a call to Wake or +// WakeAll, and false if we can't be sure of that. +func (f *Futex) Wait(cmp uint32) (awoken bool) { + if f.Uint32.v != cmp { + return false + } + + // Push the current goroutine onto the waiter stack. + f.waiters.Push(Current()) + + // Pause until the waiters are awoken by Wake/WakeAll. + Pause() + + // We were awoken by a call to Wake or WakeAll. There is no chance for + // spurious wakeups. + return true +} + +// Wake a single waiter. +func (f *Futex) Wake() { + if t := f.waiters.Pop(); t != nil { + scheduleTask(t) + } +} + +// Wake all waiters. +func (f *Futex) WakeAll() { + for t := f.waiters.Pop(); t != nil; t = f.waiters.Pop() { + scheduleTask(t) + } +}