ipn/ipnlocal: fix deadlock in ipn bus watcher

fixes tailscale/corp#40401

From an internal stack trace.  We witnessed a slow bus error and
a complete lock up of the ipn bus watcher.

Stack showed a b.mu->t.mu and t.mu->b.mu lock ordering
issue when setting up the HealthTracker's initial state and its
timer simultaneously fires.

Signed-off-by: Jonathan Nobels <jonathan@tailscale.com>
This commit is contained in:
Jonathan Nobels 2026-04-10 13:00:34 -04:00
parent 399f048332
commit c18f8516c4

View File

@ -3233,9 +3233,6 @@ func (b *LocalBackend) WatchNotificationsAs(ctx context.Context, actor ipnauth.A
if mask&ipn.NotifyInitialDriveShares != 0 && b.DriveSharingEnabled() {
ini.DriveShares = b.pm.prefs.DriveShares()
}
if mask&ipn.NotifyInitialHealthState != 0 {
ini.Health = b.HealthTracker().CurrentState()
}
if mask&ipn.NotifyInitialSuggestedExitNode != 0 {
if en, err := b.suggestExitNodeLocked(); err == nil {
ini.SuggestedExitNode = &en.ID
@ -3261,6 +3258,10 @@ func (b *LocalBackend) WatchNotificationsAs(ctx context.Context, actor ipnauth.A
mak.Set(&b.notifyWatchers, sessionID, session)
b.mu.Unlock()
if mask&ipn.NotifyInitialHealthState != 0 && ini != nil {
ini.Health = b.HealthTracker().CurrentState()
}
metricCurrentWatchIPNBus.Add(1)
defer metricCurrentWatchIPNBus.Add(-1)