From 2d68f1e88201d8c1efbd9517cc8c539e3cfb29a1 Mon Sep 17 00:00:00 2001 From: zackattack01 Date: Mon, 1 Jul 2024 16:49:04 -0400 Subject: [PATCH] add logic to integrate power_event_watcher, don't restart when in modern standby --- .../power_event_watcher_windows.go | 49 +++++++++++++ ee/watchdog/watchdog_service_windows.go | 70 +++++++++++++++++-- 2 files changed, 113 insertions(+), 6 deletions(-) diff --git a/ee/powereventwatcher/power_event_watcher_windows.go b/ee/powereventwatcher/power_event_watcher_windows.go index 83321396e..0a35fe16f 100644 --- a/ee/powereventwatcher/power_event_watcher_windows.go +++ b/ee/powereventwatcher/power_event_watcher_windows.go @@ -8,6 +8,7 @@ import ( "encoding/xml" "fmt" "log/slog" + "sync" "syscall" "unsafe" @@ -53,6 +54,14 @@ type ( knapsack types.Knapsack slogger *slog.Logger } + + // InMemorySleepStateUpdater implements the powerEventSubscriber interface. When passed as + // the powerEventSubscriber, it will expose the last seen ModernStandby state for the caller + InMemorySleepStateUpdater struct { + sync.Mutex + slogger *slog.Logger + inModernStandby bool + } ) const ( @@ -70,6 +79,46 @@ func NewKnapsackSleepStateUpdater(slogger *slog.Logger, k types.Knapsack) *knaps } } +func NewInMemorySleepStateUpdater(slogger *slog.Logger) *InMemorySleepStateUpdater { + return &InMemorySleepStateUpdater{ + slogger: slogger, + } +} + +func (ims *InMemorySleepStateUpdater) OnStartup() error { + ims.Lock() + defer ims.Unlock() + // this should essentially be a no-op for our inmemory store since it will default false + ims.inModernStandby = false + return nil +} + +func (ims *InMemorySleepStateUpdater) InModernStandby() bool { + ims.Lock() + defer ims.Unlock() + + return ims.inModernStandby +} + +func (ims *InMemorySleepStateUpdater) OnPowerEvent(eventID int) error { + ims.Lock() + defer ims.Unlock() + + switch eventID { + case eventIdEnteringModernStandby, eventIdEnteringSleep: + ims.inModernStandby = true + case eventIdExitingModernStandby: + ims.inModernStandby = false + default: + ims.slogger.Log(context.TODO(), slog.LevelWarn, + "received unexpected event ID in log", + "event_id", eventID, + ) + } + + return nil +} + func (ks *knapsackSleepStateUpdater) OnPowerEvent(eventID int) error { switch eventID { case eventIdEnteringModernStandby, eventIdEnteringSleep: diff --git a/ee/watchdog/watchdog_service_windows.go b/ee/watchdog/watchdog_service_windows.go index 733bd419a..31c3f828a 100644 --- a/ee/watchdog/watchdog_service_windows.go +++ b/ee/watchdog/watchdog_service_windows.go @@ -13,8 +13,10 @@ import ( "github.com/kolide/kit/version" agentsqlite "github.com/kolide/launcher/ee/agent/storage/sqlite" "github.com/kolide/launcher/ee/gowrapper" + "github.com/kolide/launcher/ee/powereventwatcher" "github.com/kolide/launcher/pkg/launcher" "github.com/kolide/launcher/pkg/log/multislogger" + "github.com/kolide/launcher/pkg/rungroup" "github.com/pkg/errors" "golang.org/x/sys/windows" "golang.org/x/sys/windows/svc" @@ -24,6 +26,11 @@ import ( type winWatchdogSvc struct { systemSlogger, slogger *multislogger.MultiSlogger opts *launcher.Options + sleepStateUpdater *powereventwatcher.InMemorySleepStateUpdater + // cachedInModernStandby is held for comparison against the current value + // from sleepStateUpdater, to allow us to trigger a healthcheck + // more frequently than the routine timer value when waking from modern standby + cachedInModernStandby bool } func RunWatchdogService(systemSlogger *multislogger.MultiSlogger, args []string) error { @@ -69,6 +76,8 @@ func RunWatchdogService(systemSlogger *multislogger.MultiSlogger, args []string) "version", version.Version().Version, ) + sleepStateUpdater := powereventwatcher.NewInMemorySleepStateUpdater(localSlogger.Logger) + // Log panics from the windows service defer func() { if r := recover(); r != nil { @@ -87,9 +96,10 @@ func RunWatchdogService(systemSlogger *multislogger.MultiSlogger, args []string) }() if err := svc.Run(launcherWatchdogServiceName, &winWatchdogSvc{ - systemSlogger: systemSlogger, - slogger: localSlogger, - opts: opts, + systemSlogger: systemSlogger, + slogger: localSlogger, + opts: opts, + sleepStateUpdater: sleepStateUpdater, }); err != nil { systemSlogger.Log(ctx, slog.LevelError, "error in service run", @@ -179,6 +189,10 @@ func (w *winWatchdogSvc) Execute(args []string, r <-chan svc.ChangeRequest, chan } func (w *winWatchdogSvc) checkLauncherStatus(ctx context.Context) error { + if w.sleepStateUpdater.InModernStandby() { + return nil + } + serviceManager, err := mgr.Connect() if err != nil { w.slogger.Log(ctx, slog.LevelError, @@ -212,19 +226,63 @@ func (w *winWatchdogSvc) checkLauncherStatus(ctx context.Context) error { } func runLauncherWatchdogService(ctx context.Context, w *winWatchdogSvc) error { - ticker := time.NewTicker(1 * time.Minute) + // create a rungroup for all the actors we create to allow for easy start/stop + runGroup := rungroup.NewRunGroup(w.slogger.Logger) + powerEventWatcher, err := powereventwatcher.New(ctx, w.slogger.Logger, w.sleepStateUpdater) + if err != nil { + w.slogger.Log(ctx, slog.LevelDebug, + "could not init power event watcher", + "err", err, + ) + } else { + runGroup.Add("powerEventWatcher", powerEventWatcher.Execute, powerEventWatcher.Interrupt) + } + + go runLauncherWatchdogStatusChecker(ctx, w) + + if err := runGroup.Run(); err != nil { + return fmt.Errorf("err from watchdog runGroup: %w", err) + } + + return nil +} + +func runLauncherWatchdogStatusChecker(ctx context.Context, w *winWatchdogSvc) error { + // to avoid constantly hitting windows service manager we run off of two timers: + // 1. a longer (routine) timer which always checks the current status + // 2. a shorter (sleepState) timer which will only trigger a check if we've recently + // woken up from modern standby + routineTicker := time.NewTicker(15 * time.Minute) + sleepStateTicker := time.NewTicker(1 * time.Minute) for { select { - case <-ticker.C: + case <-routineTicker.C: if err := w.checkLauncherStatus(ctx); err != nil { w.slogger.Log(ctx, slog.LevelError, "failure checking launcher health status", "err", err, ) } + case <-sleepStateTicker.C: + // if our last reading was in modern standby, but our current reading is awake, + // trigger the status check immediately + shouldCheckStatusNow := w.cachedInModernStandby && !w.sleepStateUpdater.InModernStandby() + // always persist the cached value for the next iteration, this must be done here before + // the checkLauncherStatus call to ensure we're operating off up to date sleep status there + w.cachedInModernStandby = w.sleepStateUpdater.InModernStandby() + if shouldCheckStatusNow { + if err := w.checkLauncherStatus(ctx); err != nil { + w.slogger.Log(ctx, slog.LevelError, + "failure checking launcher health status after detecting wake state", + "err", err, + ) + } + } + case <-ctx.Done(): - ticker.Stop() + routineTicker.Stop() + sleepStateTicker.Stop() return ctx.Err() } }