Skip to content

Commit 35f6c2d

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 35f6c2d

File tree

1 file changed

+98
-0
lines changed

1 file changed

+98
-0
lines changed

src/internal/task/waiter.go

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

0 commit comments

Comments
 (0)