diff --git a/packages/adapter/src/lib/_Types.ts b/packages/adapter/src/lib/_Types.ts index b32d9d2dd..816a53a6f 100644 --- a/packages/adapter/src/lib/_Types.ts +++ b/packages/adapter/src/lib/_Types.ts @@ -494,6 +494,22 @@ export interface InternalDeleteStateFromEnumOptions { callback?: ioBroker.ErrorCallback; } +export interface StopParameters { + /** Specify an optional exit code */ + exitCode?: number; + /** Specify an optional reason for stoppage */ + reason?: string; +} + +export interface InternalStopParameters extends StopParameters { + /** If mode is schedule or once */ + isPause?: boolean; + /** If it has a restart schedule running */ + isScheduled?: boolean; + /** If alive state should be updated, if undefined defaults to true */ + updateAliveState?: boolean; +} + /** * The internal adapter config type should only be used to access config properties which are set by the adapter developers. * Only use it like `this.config as InternalAdapterConfig` diff --git a/packages/adapter/src/lib/adapter/adapter.ts b/packages/adapter/src/lib/adapter/adapter.ts index 7fbd9c0bd..d115b5a35 100644 --- a/packages/adapter/src/lib/adapter/adapter.ts +++ b/packages/adapter/src/lib/adapter/adapter.ts @@ -119,7 +119,9 @@ import type { IoPackageInstanceObject, AliasTargetEntry, InstallNodeModuleOptions, - InternalInstallNodeModuleOptions + InternalInstallNodeModuleOptions, + StopParameters, + InternalStopParameters } from '@/lib/_Types.js'; import { UserInterfaceMessagingController } from '@/lib/adapter/userInterfaceMessagingController.js'; import { SYSTEM_ADAPTER_PREFIX } from '@iobroker/js-controller-common/constants'; @@ -675,7 +677,7 @@ export class AdapterClass extends EventEmitter { common?: ioBroker.InstanceCommon; private mboxSubscribed?: boolean; /** Stop the adapter */ - stop?: () => Promise; + stop?: (params?: StopParameters) => Promise; version?: string; /** Stop the adapter only defined in compact, not for external usage */ protected kill?: () => Promise; @@ -2135,12 +2137,15 @@ export class AdapterClass extends EventEmitter { }); } - private async _stop( - isPause?: boolean, - isScheduled?: boolean, - exitCode?: number, - updateAliveState?: boolean - ): Promise { + /** + * Stop an instance gracefully + * + * @param options information about the stoppage + */ + private async _stop(options: InternalStopParameters = {}): Promise { + const { isPause, isScheduled, reason } = options; + let { exitCode, updateAliveState } = options; + exitCode = exitCode || (isScheduled ? EXIT_CODES.START_IMMEDIATELY_AFTER_STOP : 0); if (updateAliveState === undefined) { updateAliveState = true; @@ -2153,7 +2158,7 @@ export class AdapterClass extends EventEmitter { this._reportInterval = null; const id = `system.adapter.${this.namespace}`; - const finishUnload = (): void => { + const finishUnload = async (): Promise => { if (this._timers.size) { this._timers.forEach(timer => clearTimeout(timer)); this._timers.clear(); @@ -2176,19 +2181,18 @@ export class AdapterClass extends EventEmitter { if (this.#states && updateAliveState) { this.outputCount++; - this.#states.setState(`${id}.alive`, { val: false, ack: true, from: id }, () => { - if (!isPause) { - this._logger.info(`${this.namespaceLog} terminating`); - } + await this.#states.setState(`${id}.alive`, { val: false, ack: true, from: id }); + if (!isPause) { + this._logger.info(`${this.namespaceLog} terminating`); + } - // To this moment, the class could be destroyed - this.terminate(exitCode); - }); + // To this moment, the class could be destroyed + this.terminate(reason, exitCode); } else { if (!isPause) { this._logger.info(`${this.namespaceLog} terminating`); } - this.terminate(exitCode); + this.terminate(reason, exitCode); } }; @@ -2251,7 +2255,7 @@ export class AdapterClass extends EventEmitter { * Reads the file certificate from a given path and adds a file watcher to restart adapter on cert changes * if a cert is passed it is returned as it is * - * @param cert + * @param cert cert or path to cert */ private _readFileCertificate(cert: string): string { if (typeof cert === 'string') { @@ -2265,7 +2269,7 @@ export class AdapterClass extends EventEmitter { this._logger.warn( `${this.namespaceLog} New certificate "${filename}" detected. Restart adapter` ); - setTimeout(() => this._stop(false, true), 2000); + setTimeout(() => this._stop({ isPause: false, isScheduled: true }), 2_000); }); } } catch (e) { @@ -2859,8 +2863,13 @@ export class AdapterClass extends EventEmitter { * * @param id of the object * @param obj The object to set +<<<<<<< HEAD + * @param [options] additional options + * @param [callback] optional callback function +======= * @param options optional user context * @param callback optional callback +>>>>>>> 0e706f4213e588a7efda7be7732e25c7157e0b2c */ private async _setObjectWithDefaultValue( id: string, @@ -3679,7 +3688,7 @@ export class AdapterClass extends EventEmitter { * @param design name of the design * @param search name of the view * @param params object containing startkey: first id to include in result; endkey: last id to include in result - * @param options + * @param options additional objects, e.g. for permissions * @param callback return result * ```js * function (err, doc) { @@ -3788,9 +3797,9 @@ export class AdapterClass extends EventEmitter { * It is required, that ID consists namespace in startkey and endkey. E.g. `{startkey: 'hm-rpc.' + adapter.instance + '.', endkey: 'hm-rpc.' + adapter.instance + '.\u9999'}` * to get all objects of the instance. * - * @param params - * @param options - * @param callback + * @param params startkey and endkey information + * @param options additional options, e.g. for permissions + * @param callback optional callback * ```js * function (err, res) { * if (res && res.rows) { @@ -7108,10 +7117,10 @@ export class AdapterClass extends EventEmitter { * Async version of sendTo * As we have a special case (first arg can be error or result, we need to promisify manually) * - * @param instanceName - * @param command - * @param message - * @param options + * @param instanceName name of the instance + * @param command command to send + * @param message message to send + * @param options additional options, e.g. for permissions */ sendToAsync(instanceName: unknown, command: unknown, message?: unknown, options?: unknown): any { return new Promise((resolve, reject) => { @@ -9816,8 +9825,7 @@ export class AdapterClass extends EventEmitter { * ``` * * @param pattern string in form 'adapter.0.*'. Must be the same as subscribe. - * @param options]optional argument to describe the user context - * @param options + * @param options optional argument to describe the user context * @param callback return result * ```js * function (err) {} @@ -9966,7 +9974,7 @@ export class AdapterClass extends EventEmitter { * * @param pattern string in form 'adapter.0.*' or like this. Only string allowed * @param options optional argument to describe the user context - * @param callback + * @param callback optional callback */ subscribeStates(pattern: unknown, options: unknown, callback?: unknown): any { if (typeof options === 'function') { @@ -10002,7 +10010,7 @@ export class AdapterClass extends EventEmitter { * * @param pattern string in form 'adapter.0.*'. Must be the same as subscribe. * @param options optional argument to describe the user context - * @param callback + * @param callback optional callback */ unsubscribeStates(pattern: unknown, options: unknown, callback?: unknown): any { if (typeof options === 'function') { @@ -10664,7 +10672,12 @@ export class AdapterClass extends EventEmitter { } // by deletion of state, stop this instance if (sigKillVal !== process.pid && !this._config.forceIfDisabled) { - this._stop(false, false, EXIT_CODES.ADAPTER_REQUESTED_TERMINATION, false); + this._stop({ + isPause: false, + isScheduled: false, + exitCode: EXIT_CODES.ADAPTER_REQUESTED_TERMINATION, + updateAliveState: false + }); setTimeout(() => this.terminate(EXIT_CODES.ADAPTER_REQUESTED_TERMINATION), 4000); } } @@ -11347,12 +11360,12 @@ export class AdapterClass extends EventEmitter { // @ts-expect-error adapterConfig.common.mode === 'once' ) { - this.stop = () => this._stop(true); + this.stop = params => this._stop({ ...params, isPause: true }); } else if (this.startedInCompactMode) { - this.stop = () => this._stop(false); + this.stop = params => this._stop({ ...params, isPause: false }); this.kill = this.stop; } else { - this.stop = () => this._stop(false); + this.stop = params => this._stop({ ...params, isPause: false }); } // Monitor logging state @@ -11519,7 +11532,7 @@ export class AdapterClass extends EventEmitter { ); this._restartScheduleJob = this._schedule.scheduleJob(adapterConfig.common.restartSchedule, () => { this._logger.info(`${this.namespaceLog} Scheduled restart.`); - this._stop(false, true); + this._stop({ isPause: false, isScheduled: true }); }); } } @@ -11620,7 +11633,12 @@ export class AdapterClass extends EventEmitter { } try { - this._stop(false, false, EXIT_CODES.UNCAUGHT_EXCEPTION, false); + this._stop({ + isPause: false, + isScheduled: false, + exitCode: EXIT_CODES.UNCAUGHT_EXCEPTION, + updateAliveState: false + }); setTimeout(() => this.terminate(EXIT_CODES.UNCAUGHT_EXCEPTION), 1_000); } catch (err) { this._logger.error(`${this.namespaceLog} exception by stop: ${err ? err.message : err}`);