Skip to content

Commit 869366b

Browse files
committed
internal/task: add Waiter for waiting for a flag from interrupts
This provides an abstraction to allow goroutines to wait for an event from an interrupt, and for interrupts to send such an event and know whether the goroutine is still working on the previous event.
1 parent 3869f76 commit 869366b

File tree

1 file changed

+99
-0
lines changed

1 file changed

+99
-0
lines changed

src/internal/task/waiter.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package task
2+
3+
import (
4+
"runtime/interrupt"
5+
"unsafe"
6+
)
7+
8+
// A waiter waits for an interrupt to fire. Call Wait() from a goroutine to
9+
// pause it, and call Resume() from an interrupt handler to resume the
10+
// goroutine.
11+
type Waiter struct {
12+
waiting *Task
13+
lock PMutex
14+
}
15+
16+
var resumeTaskSentinel = (*Task)(unsafe.Pointer(uintptr(2)))
17+
var workingTaskSentinel = (*Task)(unsafe.Pointer(uintptr(1)))
18+
19+
// Wait from a main goroutine until an interrupt calls Resume(). It will also
20+
// return if the interrupt called Resume() before the Wait() call (to avoid a
21+
// race condition).
22+
func (w *Waiter) Wait() {
23+
if interrupt.In() {
24+
runtimePanic("Waiter: called Wait from interrupt")
25+
}
26+
27+
mask := interrupt.Disable()
28+
w.lock.Lock()
29+
switch w.waiting {
30+
case nil, workingTaskSentinel:
31+
w.waiting = Current()
32+
w.lock.Unlock()
33+
interrupt.Restore(mask)
34+
Pause()
35+
case resumeTaskSentinel:
36+
// Marked as 'resume now', can return immediately.
37+
w.waiting = workingTaskSentinel
38+
w.lock.Unlock()
39+
interrupt.Restore(mask)
40+
default:
41+
w.lock.Unlock()
42+
interrupt.Restore(mask)
43+
runtimePanic("Waiter: task is waiting already")
44+
}
45+
}
46+
47+
// Resume a waiting goroutine from an interrupt handler. If there is a goroutine
48+
// waiting, it will resume. If not, the next one that calls Wait() will
49+
// immediately resume.
50+
func (w *Waiter) Resume() {
51+
if !interrupt.In() {
52+
runtimePanic("Waiter: called Resume outside interrupt")
53+
}
54+
55+
waiting := w.waiting
56+
switch waiting {
57+
case nil, workingTaskSentinel:
58+
w.waiting = resumeTaskSentinel
59+
case resumeTaskSentinel:
60+
// Called Resume() twice in a row, this may indicate a bug at the caller
61+
// site.
62+
default:
63+
// Schedule the given task to resume.
64+
// If it's not yet paused, it will immediately resume on the next call
65+
// to Pause().
66+
w.waiting = workingTaskSentinel
67+
scheduleTask(waiting)
68+
}
69+
}
70+
71+
// Return true if Done has not been called after a Resume call.
72+
// This is typically used to indicate the task outside the interrupt is still
73+
// being worked on.
74+
func (w *Waiter) Working() bool {
75+
switch w.waiting {
76+
case workingTaskSentinel, resumeTaskSentinel:
77+
return true
78+
default: // nil, <*task.Task>
79+
return false
80+
}
81+
}
82+
83+
// Done can be called outside interrupt context to indicate the task this waiter
84+
// was waiting for is done, and Working() can return false. It is entirely
85+
// optional, if not called Working will continue to return true until it is
86+
// blocked in Wait() but the waiter will function normally otherwise.
87+
func (w *Waiter) Done() {
88+
if interrupt.In() {
89+
runtimePanic("Waiter: called Done from interrupt")
90+
}
91+
92+
mask := interrupt.Disable()
93+
w.lock.Lock()
94+
if w.waiting == workingTaskSentinel {
95+
w.waiting = nil
96+
}
97+
w.lock.Unlock()
98+
interrupt.Restore(mask)
99+
}

0 commit comments

Comments
 (0)