diff --git a/packages/node/src/behavior/system/software-update/SoftwareUpdateManager.ts b/packages/node/src/behavior/system/software-update/SoftwareUpdateManager.ts index 75f4578009..0f2cfbceb2 100644 --- a/packages/node/src/behavior/system/software-update/SoftwareUpdateManager.ts +++ b/packages/node/src/behavior/system/software-update/SoftwareUpdateManager.ts @@ -165,6 +165,7 @@ export class SoftwareUpdateManager extends Behavior { await this.internal.otaService.construction; this.reactTo(rootNode.lifecycle.online, this.#nodeOnline); + this.reactTo(rootNode.lifecycle.goingOffline, this.#nodeGoingOffline); if (rootNode.lifecycle.isOnline) { await this.#nodeOnline(); } @@ -174,6 +175,7 @@ export class SoftwareUpdateManager extends Behavior { } async #nodeOnline() { + this.internal.suppressUpdates = false; if (this.internal.announcements !== undefined) { await this.internal.announcements.close(); this.internal.announcements = undefined; @@ -204,6 +206,16 @@ export class SoftwareUpdateManager extends Behavior { ).start(); } + async #nodeGoingOffline() { + this.internal.suppressUpdates = true; + this.internal.checkForUpdateTimer?.stop(); + + // Announcements run on their own timers and write to peers, so the suppression flag alone does not stop them. + // #nodeOnline installs a fresh instance. + await this.internal.announcements?.close(); + this.internal.announcements = undefined; + } + #updateAnnouncementSettings() { if (this.internal.announcements === undefined) { return; @@ -481,7 +493,7 @@ export class SoftwareUpdateManager extends Behavior { // Collect all client nodes and their versions, so we only need to check each version once const updateDetails = new Map(); for (const peer of rootNode.peers) { - if (this.internal.closed) { + if (this.internal.suppressUpdates) { return []; } if (peerToCheck !== undefined && peerToCheck !== peer) { @@ -512,7 +524,7 @@ export class SoftwareUpdateManager extends Behavior { const peersWithUpdates = new Array<{ peerAddress: PeerAddress; info: SoftwareUpdateInfo }>(); for (const infos of updateDetails.values()) { - if (this.internal.closed) { + if (this.internal.suppressUpdates) { return []; } try { @@ -550,7 +562,7 @@ export class SoftwareUpdateManager extends Behavior { includeStoredUpdates, isProduction: this.state.allowTestOtaImages ? undefined : true, }); - if (!updateDetails || this.internal.closed) { + if (!updateDetails || this.internal.suppressUpdates) { return []; } const fd = await this.internal.otaService.downloadUpdate(updateDetails); @@ -589,7 +601,7 @@ export class SoftwareUpdateManager extends Behavior { consent.peerAddress.fabricIndex === peerAddress.fabricIndex && consent.peerAddress.nodeId === peerAddress.nodeId, ); - if (this.internal.closed) { + if (this.internal.suppressUpdates) { break; } if (hasConsent) { @@ -791,7 +803,7 @@ export class SoftwareUpdateManager extends Behavior { * monitor for stalled updates. */ #triggerQueuedUpdate() { - if (this.internal.closed) { + if (this.internal.suppressUpdates) { return; } const now = Time.nowMs; @@ -850,7 +862,7 @@ export class SoftwareUpdateManager extends Behavior { * The node usually calls queryImage as a result of this when it processes the announcement. */ async #triggerUpdateOnNode(entry: UpdateQueueEntry) { - if (this.internal.announcements == undefined) { + if (this.internal.announcements === undefined) { logger.info(`Not yet initialized with peers, can not trigger update on node`, entry.peerAddress); return; } @@ -1082,7 +1094,7 @@ export class SoftwareUpdateManager extends Behavior { * messages, and triggers the necessary events. */ onOtaStatusChange(peerAddress: PeerAddress, status: OtaUpdateStatus, toVersion?: number) { - if (this.internal.closed) { + if (this.internal.suppressUpdates) { // A post-dispose Applying would otherwise re-arm the already-disposed armer whose observers are // closed, leaving an entry that never gets a grace timer nor self-cleans. return; @@ -1145,7 +1157,7 @@ export class SoftwareUpdateManager extends Behavior { } override async [Symbol.asyncDispose]() { - this.internal.closed = true; + this.internal.suppressUpdates = true; this.internal.checkForUpdateTimer?.stop(); this.internal.updateQueueTimer?.stop(); await this.internal.announcements?.close(); @@ -1200,7 +1212,8 @@ export namespace SoftwareUpdateManager { rebootResubscribeArmer?: RebootResubscribeArmer; - closed = false; + /** Raised whenever the node is not online, so update work cannot start before the first online transition. */ + suppressUpdates = true; } export class Events extends EventEmitter {