fix: Fix availability checks not stopped on extension stop (#20093)

* fix: availability checks not stopped on extension stop

* Add test

* Clarify comments

* Simplify test

* Simplify code
This commit is contained in:
Mario Guggenberger
2023-12-10 21:20:59 +01:00
committed by GitHub
parent 56589dccf1
commit 5aeec130b1
2 changed files with 38 additions and 1 deletions
+13 -1
View File
@@ -17,6 +17,7 @@ export default class Availability extends Extension {
private retrieveStateDebouncers: {[s: string]: () => void} = {};
private pingQueue: Device[] = [];
private pingQueueExecuting = false;
private stopped = false;
private getTimeout(device: Device): number {
if (typeof device.options.availability === 'object' && device.options.availability?.timeout != null) {
@@ -92,6 +93,11 @@ export default class Availability extends Extension {
}
}
if (this.stopped) {
// Exit here to avoid triggering any follow-up activity (e.g., re-queuing another ping attempt).
return;
}
this.publishAvailability(device, !pingedSuccessfully);
this.resetTimer(device);
this.removeFromPingQueue(device);
@@ -103,6 +109,10 @@ export default class Availability extends Extension {
}
override async start(): Promise<void> {
if (this.stopped) {
throw new Error('This extension cannot be restarted.');
}
this.eventBus.onEntityRenamed(this, (data) => {
if (utils.isAvailabilityEnabledForEntity(data.entity, settings.get())) {
this.mqtt.publish(`${data.from}/availability`, null, {retain: true, qos: 1});
@@ -183,8 +193,10 @@ export default class Availability extends Extension {
}
override async stop(): Promise<void> {
this.stopped = true;
this.pingQueue = [];
Object.values(this.timers).forEach((t) => clearTimeout(t));
super.stop();
await super.stop();
}
private retrieveState(device: Device): void {
+25
View File
@@ -6,6 +6,7 @@ import zigbeeHerdsman from './stub/zigbeeHerdsman';
import utils from '../lib/util/utils';
import * as settings from '../lib/util/settings';
import Controller from '../lib/controller';
import Availability from '../lib/extension/availability';
import flushPromises from './lib/flushPromises';
import stringify from 'json-stable-stringify-without-jsonify';
@@ -344,4 +345,28 @@ describe('Availability', () => {
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/group_tradfri_remote/availability',
'online', {retain: true, qos: 1}, expect.any(Function));
});
it('Should clear the ping queue on stop', async () => {
const availability = controller.extensions.find((extension) => extension instanceof Availability);
const publishAvailabilitySpy = jest.spyOn(availability, 'publishAvailability');
devices.bulb_color.zh = { ping: jest.fn().mockImplementation(() => new Promise((resolve) => setTimeout(resolve, 1000)))};
availability.addToPingQueue(devices.bulb_color);
availability.addToPingQueue(devices.bulb_color_2);
await availability.stop();
await advancedTime(utils.minutes(1));
expect(availability.pingQueue).toEqual([]);
// Validate the stop-interrupt implicitly by checking that it prevents further function invocations
expect(publishAvailabilitySpy).not.toHaveBeenCalled();
});
it('Should prevent instance restart', async () => {
const availability = controller.extensions.find((extension) => extension instanceof Availability);
await availability.stop();
await expect(() => availability.start()).rejects.toThrowError();
});
});