Refactor resume worker to handle unexpected wall-clock changes and improve SPN auto-resume logic

This commit is contained in:
Alexandr Stelnykovych
2025-11-27 16:27:59 +02:00
parent 1208783f34
commit b43905aac5

View File

@@ -133,50 +133,65 @@ func (c *Control) stopResumeWorker() {
// startResumeWorker starts a worker that will resume normal operation after the specified duration. // startResumeWorker starts a worker that will resume normal operation after the specified duration.
// No thread safety, caller must hold c.locker. // No thread safety, caller must hold c.locker.
func (c *Control) startResumeWorker(duration time.Duration) { func (c *Control) startResumeWorker(duration time.Duration) {
c.pauseInfo.TillTime = time.Now().Add(duration) deadline := time.Now().Add(duration)
c.pauseInfo.TillTime = deadline
resumerWorkerFunc := func(wc *mgr.WorkerCtx) error { resumerWorkerFunc := func(wc *mgr.WorkerCtx) error {
wc.Info(fmt.Sprintf("Scheduling resume in %v", duration)) wc.Info(fmt.Sprintf("Scheduling resume in %v", duration))
// Subscribe to config changes to detect SPN enable. // Subscribe to config changes to detect SPN enable.
cfgChangeEvt := c.instance.Config().EventConfigChange.Subscribe("control: spn enable check", 10) cfgChangeEvt := c.instance.Config().EventConfigChange.Subscribe("control: spn enable check", 10)
// Make sure to cancel subscription when worker stops.
defer cfgChangeEvt.Cancel() defer cfgChangeEvt.Cancel()
for { // Timer for the deadline.
timer := time.NewTimer(time.Until(deadline))
defer timer.Stop()
// Periodically check resume time to handle unexpected wall-clock changes.
ticker := time.NewTicker(15 * time.Second)
defer ticker.Stop()
// Wait until duration elapses or SPN is enabled by user.
needToAutoResume := false
for !needToAutoResume {
select { select {
case <-wc.Ctx().Done(): case <-wc.Ctx().Done():
return nil return nil
case <-cfgChangeEvt.Events(): case <-cfgChangeEvt.Events():
spnEnabled := config.GetAsBool("spn/enable", false) spnEnabled := config.GetAsBool("spn/enable", false)
if spnEnabled() { if spnEnabled() {
wc.Info("SPN enabled by user, resuming...") wc.Info("SPN enabled by user. Auto-resume initiated.")
return c.resume() needToAutoResume = true
} }
case <-time.After(duration): case <-ticker.C:
wc.Info("Resuming...") if time.Now().After(deadline) {
needToAutoResume = true
err := c.resume()
if err == nil {
n := &notifications.Notification{
EventID: "control:resumed",
Type: notifications.Info,
Title: "Resumed",
Message: "Automatically resumed from pause state",
ShowOnSystem: true,
Expires: time.Now().Add(15 * time.Second).Unix(),
AvailableActions: []*notifications.Action{
{
ID: "ack",
Text: "OK",
},
},
}
notifications.Notify(n)
} }
return err case <-timer.C:
needToAutoResume = true
} }
} }
// Time to resume
wc.Info("Resuming...")
err := c.resume()
if err == nil {
n := &notifications.Notification{
EventID: "control:resumed",
Type: notifications.Info,
Title: "Resumed",
Message: "Automatically resumed from pause state",
ShowOnSystem: true,
Expires: time.Now().Add(15 * time.Second).Unix(),
AvailableActions: []*notifications.Action{
{
ID: "ack",
Text: "OK",
},
},
}
notifications.Notify(n)
}
return err
} }
c.resumeWorker = c.mgr.NewWorkerMgr("resumer", resumerWorkerFunc, nil) c.resumeWorker = c.mgr.NewWorkerMgr("resumer", resumerWorkerFunc, nil)