Skip to content

Commit

Permalink
add logic to integrate power_event_watcher, don't restart when in mod…
Browse files Browse the repository at this point in the history
…ern standby
  • Loading branch information
zackattack01 committed Jul 1, 2024
1 parent 1be4237 commit 2d68f1e
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 6 deletions.
49 changes: 49 additions & 0 deletions ee/powereventwatcher/power_event_watcher_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"encoding/xml"
"fmt"
"log/slog"
"sync"
"syscall"
"unsafe"

Expand Down Expand Up @@ -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 (
Expand All @@ -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:
Expand Down
70 changes: 64 additions & 6 deletions ee/watchdog/watchdog_service_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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",
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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()
}
}
Expand Down

0 comments on commit 2d68f1e

Please sign in to comment.