-
Notifications
You must be signed in to change notification settings - Fork 926
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
internal/task: implement atomic primitives for preemptive scheduling
- Loading branch information
Showing
9 changed files
with
139 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
//go:build scheduler.threads | ||
|
||
package task | ||
|
||
// Atomics implementation for non-cooperative systems (multithreaded, etc). | ||
// These atomic types use real atomic instructions. | ||
|
||
import "sync/atomic" | ||
|
||
type ( | ||
Uintptr = atomic.Uintptr | ||
Uint32 = atomic.Uint32 | ||
Uint64 = atomic.Uint64 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
//go:build scheduler.threads | ||
|
||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
//go:build none | ||
|
||
// This file is manually included, to avoid CGo which would cause a circular | ||
// import. | ||
|
||
#include <stdint.h> | ||
#include <sys/syscall.h> | ||
#include <unistd.h> | ||
|
||
#define FUTEX_WAIT 0 | ||
#define FUTEX_WAKE 1 | ||
#define FUTEX_PRIVATE 128 | ||
|
||
void tinygo_futex_wait(uint32_t *addr, uint32_t cmp) { | ||
syscall(SYS_futex, addr, FUTEX_WAIT|FUTEX_PRIVATE, cmp, NULL, NULL, 0); | ||
} | ||
|
||
void tinygo_futex_wake(uint32_t *addr, uint32_t num) { | ||
syscall(SYS_futex, addr, FUTEX_WAKE|FUTEX_PRIVATE, num, NULL, NULL, 0); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
//go:build scheduler.threads | ||
|
||
package task | ||
|
||
import ( | ||
"unsafe" | ||
) | ||
|
||
// 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) bool { | ||
tinygo_futex_wait((*uint32)(unsafe.Pointer(&f.Uint32)), cmp) | ||
|
||
// We *could* detect a zero return value from the futex system call which | ||
// would indicate we got awoken by a Wake or WakeAll call. However, this is | ||
// what the manual page has to say: | ||
// | ||
// > Note that a wake-up can also be caused by common futex usage patterns | ||
// > in unrelated code that happened to have previously used the futex | ||
// > word's memory location (e.g., typical futex-based implementations of | ||
// > Pthreads mutexes can cause this under some conditions). Therefore, | ||
// > callers should always conservatively assume that a return value of 0 | ||
// > can mean a spurious wake-up, and use the futex word's value (i.e., the | ||
// > user-space synchronization scheme) to decide whether to continue to | ||
// > block or not. | ||
// | ||
// I'm not sure whether we do anything like pthread does, so to be on the | ||
// safe side we say we don't know whether the wakeup was spurious or not and | ||
// return false. | ||
return false | ||
} | ||
|
||
// Wake a single waiter. | ||
func (f *Futex) Wake() { | ||
tinygo_futex_wake((*uint32)(unsafe.Pointer(&f.Uint32)), 1) | ||
} | ||
|
||
// Wake all waiters. | ||
func (f *Futex) WakeAll() { | ||
const maxInt32 = 0x7fff_ffff | ||
tinygo_futex_wake((*uint32)(unsafe.Pointer(&f.Uint32)), maxInt32) | ||
} | ||
|
||
//export tinygo_futex_wait | ||
func tinygo_futex_wait(addr *uint32, cmp uint32) | ||
|
||
//export tinygo_futex_wake | ||
func tinygo_futex_wake(addr *uint32, num uint32) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
//go:build scheduler.threads | ||
|
||
package task | ||
|
||
// PMutex is a real mutex on systems that can be either preemptive or threaded, | ||
// and a dummy lock on other (purely cooperative) systems. | ||
// | ||
// It is mainly useful for short operations that need a lock when threading may | ||
// be involved, but which do not need a lock with a purely cooperative | ||
// scheduler. | ||
type PMutex struct { | ||
futex Futex | ||
} | ||
|
||
func (m *PMutex) Lock() { | ||
// Fast path: try to take an uncontended lock. | ||
if m.futex.CompareAndSwap(0, 1) { | ||
// We obtained the mutex. | ||
return | ||
} | ||
|
||
// Try to lock the mutex. If it changed from 0 to 2, we took a contended | ||
// lock. | ||
for m.futex.Swap(2) != 0 { | ||
// Wait until we get resumed in Unlock. | ||
m.futex.Wait(2) | ||
} | ||
} | ||
|
||
func (m *PMutex) Unlock() { | ||
if old := m.futex.Swap(0); old == 2 { | ||
// Mutex was a contended lock, so we need to wake the next waiter. | ||
m.futex.Wake() | ||
} | ||
// Note: this implementation doesn't check for an unlock of an unlocked | ||
// mutex to keep it as lightweight as possible. | ||
} |