control/controlclient: introduce eventbus messages instead of callbacks (#16956)

This is a small introduction of the eventbus into controlclient that
communicates with mainly ipnlocal. While ipnlocal is a complicated part
of the codebase, the subscribers here are from the perspective of
ipnlocal already called async.

Updates #15160

Signed-off-by: Claus Lensbøl <claus@tailscale.com>
This commit is contained in:
Claus Lensbøl
2025-09-15 10:36:17 -04:00
committed by GitHub
parent 782c16c513
commit b816fd7117
15 changed files with 293 additions and 143 deletions
+41 -2
View File
@@ -6,12 +6,14 @@ package ipnlocal
import (
"time"
"tailscale.com/control/controlclient"
"tailscale.com/syncs"
"tailscale.com/tailcfg"
"tailscale.com/tstime"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/netmap"
"tailscale.com/util/eventbus"
)
// For extra defense-in-depth, when we're testing expired nodes we check
@@ -40,14 +42,46 @@ type expiryManager struct {
logf logger.Logf
clock tstime.Clock
eventClient *eventbus.Client
controlTimeSub *eventbus.Subscriber[controlclient.ControlTime]
subsDoneCh chan struct{} // closed when consumeEventbusTopics returns
}
func newExpiryManager(logf logger.Logf) *expiryManager {
return &expiryManager{
func newExpiryManager(logf logger.Logf, bus *eventbus.Bus) *expiryManager {
em := &expiryManager{
previouslyExpired: map[tailcfg.StableNodeID]bool{},
logf: logf,
clock: tstime.StdClock{},
}
em.eventClient = bus.Client("ipnlocal.expiryManager")
em.controlTimeSub = eventbus.Subscribe[controlclient.ControlTime](em.eventClient)
em.subsDoneCh = make(chan struct{})
go em.consumeEventbusTopics()
return em
}
// consumeEventbusTopics consumes events from all relevant
// [eventbus.Subscriber]'s and passes them to their related handler. Events are
// always handled in the order they are received, i.e. the next event is not
// read until the previous event's handler has returned. It returns when the
// [controlclient.ControlTime] subscriber is closed, which is interpreted to be the
// same as the [eventbus.Client] closing ([eventbus.Subscribers] are either
// all open or all closed).
func (em *expiryManager) consumeEventbusTopics() {
defer close(em.subsDoneCh)
for {
select {
case <-em.controlTimeSub.Done():
return
case time := <-em.controlTimeSub.Events():
em.onControlTime(time.Value)
}
}
}
// onControlTime is called whenever we receive a new timestamp from the control
@@ -218,6 +252,11 @@ func (em *expiryManager) nextPeerExpiry(nm *netmap.NetworkMap, localNow time.Tim
return nextExpiry
}
func (em *expiryManager) close() {
em.eventClient.Close()
<-em.subsDoneCh
}
// ControlNow estimates the current time on the control server, calculated as
// localNow + the delta between local and control server clocks as recorded
// when the LocalBackend last received a time message from the control server.