Adds restart for MDM

This commit is contained in:
riccardom
2026-06-18 19:23:41 +02:00
parent ec18b07959
commit daf5026192
3 changed files with 51 additions and 15 deletions

View File

@@ -111,6 +111,14 @@ func (c *ConnectClient) RunAsync(config *profilemanager.Config, md metadata.MD)
c.sup.startAsync(config, md, c.mobileDependency(config), "", nil)
}
// Restart atomically stops any in-flight run and starts a fresh one with the
// given config. The stop+start happens as a single supervisor operation, so no
// other lifecycle request can interleave between them — used for explicit
// restarts (e.g. an MDM policy change) that must not expose a "stopped" window.
func (c *ConnectClient) Restart(config *profilemanager.Config, md metadata.MD) {
c.sup.restartAsync(config, md, c.mobileDependency(config), "")
}
// WaitEstablishedOrDone blocks until the in-flight run becomes established (nil),
// ends before that (the run error, or a sentinel on a clean stop), or ctx is
// cancelled. Returns errNoRunInFlight if no run is in flight. Wraps the wait on

View File

@@ -26,6 +26,7 @@ type lifecycleOp int
const (
opStart lifecycleOp = iota
opStop
opRestart
opStatus
opWaitEstablished
)
@@ -119,6 +120,8 @@ func (s *supervisor) loop() {
s.handleStart(cmd)
case opStop:
s.handleStop(cmd)
case opRestart:
s.handleRestart(cmd)
case opStatus:
cmd.reply <- (s.isRunningInternal())
case opWaitEstablished:
@@ -159,14 +162,30 @@ func (s *supervisor) handleStop(cmd lifecycleCmd) {
notify(cmd.done, nil)
return
}
s.stopCurrentRun()
notify(cmd.done, nil)
}
// Cancel the in-flight run and block the supervisor until it has fully
// unwound, so the next queued command (e.g. a fresh start) starts from a
// clean slate. The run goroutine reports completion via runEnded.
// handleRestart tears down any in-flight run and starts a fresh one in a single
// loop turn. No other command can interleave between the stop and the start
// (the loop is single-threaded), so the swap is atomic without relying on any
// daemon-side lock — that is what an explicit restart (e.g. MDM config change)
// needs to avoid a window where the client is observably stopped.
func (s *supervisor) handleRestart(cmd lifecycleCmd) {
if s.curStart != nil {
s.stopCurrentRun()
}
s.handleStart(cmd)
}
// stopCurrentRun cancels the in-flight run and blocks the supervisor until it
// has fully unwound, so the next action starts from a clean slate. The run
// goroutine reports completion via runEnded. Caller must hold an in-flight run
// (curStart != nil).
func (s *supervisor) stopCurrentRun() {
s.runCancel()
res := <-s.runEnded
s.finishRun(res.err)
notify(cmd.done, nil)
}
// finishRun resets lifecycle state after a run terminates and hands the run
@@ -243,6 +262,18 @@ func (s *supervisor) startAsync(config *profilemanager.Config, md metadata.MD, m
}
}
// restartAsync enqueues an atomic stop+start without blocking. The supervisor
// tears down any in-flight run and starts a fresh one with the supplied config
// in a single loop turn (see handleRestart). Fire-and-forget: the new run owns
// its lifecycle channels, observed via waitEstablishedOrDone.
func (s *supervisor) restartAsync(config *profilemanager.Config, md metadata.MD, mobileDep MobileDependency, logPath string) {
cmd := lifecycleCmd{op: opRestart, config: config, md: md, mobileDep: mobileDep, logPath: logPath}
select {
case s.cmdCh <- cmd:
case <-s.ctx.Done():
}
}
// start enqueues a start and blocks until the run terminates, preserving the
// blocking contract of the legacy Run entry points.
func (s *supervisor) start(config *profilemanager.Config, md metadata.MD, mobileDep MobileDependency, logPath string) error {

View File

@@ -61,13 +61,9 @@ func (s *Server) onMDMPolicyChange(_, _ *mdm.Policy) error {
return nil
}
// End the in-flight run through the supervisor. Stop blocks until the run
// has fully unwound (the supervisor is the single place a run is stopped),
// so by the time it returns we can safely start a fresh run with the new
// config — no separate quiescence wait needed.
if err := s.connectClient.Stop(); err != nil {
log.Warnf("MDM restart: failed to stop current run: %v", err)
}
// Cancel daemon-side login/status activities tied to the old run; the run
// itself is torn down atomically by the supervisor inside Restart (see
// restartEngineForMDMLocked), which stops and re-starts in one operation.
if s.actCancel != nil {
s.actCancel()
}
@@ -142,10 +138,11 @@ func (s *Server) restartEngineForMDMLocked() error {
_, cancel := context.WithCancel(s.rootCtx)
s.actCancel = cancel
log.Info("MDM restart: starting a fresh run with re-resolved config")
// MDM restart has no incoming RPC metadata; fire and forget (the run owns
// its established/done channels, the supervisor reconnects internally).
s.connectClient.RunAsync(config, nil)
log.Info("MDM restart: atomically restarting the run with re-resolved config")
// MDM restart has no incoming RPC metadata; fire and forget. Restart is a
// single supervisor op (atomic stop+start), so there is no observable
// "stopped" window between tearing down the old run and starting the new.
s.connectClient.Restart(config, nil)
s.publishConfigChangedEvent("mdm")
return nil
}