util/eventbus: add a Done method to the Monitor type (#17263)

Some systems need to tell whether the monitored goroutine has finished
alongside other channel operations (notably in this case the relay server, but
there seem likely to be others similarly situated).

Updates #15160

Change-Id: I5f0f3fae827b07f9b7102a3b08f60cda9737fe28
Signed-off-by: M. J. Fromberger <fromberger@tailscale.com>
pull/17264/head
M. J. Fromberger 2 months ago committed by GitHub
parent b3e9a128af
commit df747f1c1b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -236,6 +236,17 @@ func TestMonitor(t *testing.T) {
}
})
t.Run("ZeroDone", func(t *testing.T) {
var zero eventbus.Monitor
select {
case <-zero.Done():
// OK
case <-time.After(time.Second):
t.Fatal("timeout waiting for zero monitor to be done")
}
})
t.Run("ZeroClose", func(t *testing.T) {
var zero eventbus.Monitor
@ -276,7 +287,13 @@ func TestMonitor(t *testing.T) {
// While the goroutine is running, Wait does not complete.
select {
case <-done:
t.Error("monitor is ready before its goroutine is finished")
t.Error("monitor is ready before its goroutine is finished (Wait)")
default:
// OK
}
select {
case <-m.Done():
t.Error("monitor is ready before its goroutine is finished (Done)")
default:
// OK
}
@ -286,7 +303,13 @@ func TestMonitor(t *testing.T) {
case <-done:
// OK
case <-time.After(time.Second):
t.Fatal("timeout waiting for monitor to complete")
t.Fatal("timeout waiting for monitor to complete (Wait)")
}
select {
case <-m.Done():
// OK
case <-time.After(time.Second):
t.Fatal("timeout waiting for monitor to complete (Done)")
}
}
}

@ -3,9 +3,12 @@
package eventbus
import "tailscale.com/syncs"
// A Monitor monitors the execution of a goroutine processing events from a
// [Client], allowing the caller to block until it is complete. The zero value
// of m is valid and its Close and Wait methods return immediately.
// of m is valid; its Close and Wait methods return immediately, and its Done
// method returns an already-closed channel.
type Monitor struct {
// These fields are immutable after initialization
cli *Client
@ -32,6 +35,15 @@ func (m Monitor) Wait() {
<-m.done
}
// Done returns a channel that is closed when the monitored goroutine has
// finished executing.
func (m Monitor) Done() <-chan struct{} {
if m.done == nil {
return syncs.ClosedChan()
}
return m.done
}
// Monitor executes f in a new goroutine attended by a [Monitor]. The caller
// is responsible for waiting for the goroutine to complete, by calling either
// [Monitor.Close] or [Monitor.Wait].

Loading…
Cancel
Save