fix: Fix initialization race condition that occurs in certain circumstances (#1545)
* fix: Fix initialization race condition in certain circumstances * Minor improvements and camera iris logo
This commit is contained in:
@@ -111,7 +111,6 @@ export class CameraManager {
|
||||
protected _api: CardCameraAPI;
|
||||
protected _engineFactory: CameraManagerEngineFactory;
|
||||
protected _store: CameraManagerStore;
|
||||
protected _initializationLimit = new PQueue({ concurrency: 1 });
|
||||
protected _requestLimit = new PQueue();
|
||||
|
||||
constructor(
|
||||
@@ -147,16 +146,8 @@ export class CameraManager {
|
||||
recursivelyMergeObjectsNotArrays({}, cloneDeep(config?.cameras_global), camera),
|
||||
);
|
||||
|
||||
const resetAndInitialize = async () => {
|
||||
await this._reset();
|
||||
await this._initializeCameras(cameras);
|
||||
};
|
||||
|
||||
try {
|
||||
// This concurrency limit prevents multiple rapidly arriving configs from
|
||||
// generating reset-n-initialize race conditions (e.g. changing values
|
||||
// rapidly in the config editor).
|
||||
await this._initializationLimit.add(resetAndInitialize);
|
||||
await this._initializeCameras(cameras);
|
||||
} catch (e: unknown) {
|
||||
this._api
|
||||
.getMessageManager()
|
||||
@@ -166,7 +157,7 @@ export class CameraManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async _reset(): Promise<void> {
|
||||
public async reset(): Promise<void> {
|
||||
await this._store.reset();
|
||||
}
|
||||
|
||||
@@ -246,6 +237,8 @@ export class CameraManager {
|
||||
async ([cameraConfig, engine]) => await engine.createCamera(hass, cameraConfig),
|
||||
);
|
||||
|
||||
const cameraIDs: Set<string> = new Set();
|
||||
|
||||
// Do the additions based off the result-order, to ensure the map order is
|
||||
// preserved.
|
||||
cameras.forEach((camera) => {
|
||||
@@ -258,7 +251,7 @@ export class CameraManager {
|
||||
);
|
||||
}
|
||||
|
||||
if (this._store.hasCameraID(cameraID)) {
|
||||
if (cameraIDs.has(cameraID)) {
|
||||
throw new CameraInitializationError(
|
||||
localize('error.duplicate_camera_id'),
|
||||
camera.getConfig(),
|
||||
@@ -267,9 +260,11 @@ export class CameraManager {
|
||||
|
||||
// Always ensure the actual ID used in the card is in the configuration itself.
|
||||
camera.setID(cameraID);
|
||||
this._store.addCamera(camera);
|
||||
cameraIDs.add(cameraID);
|
||||
});
|
||||
|
||||
await this._store.setCameras(cameras);
|
||||
|
||||
log(
|
||||
this._api.getConfigManager().getCardWideConfig(),
|
||||
'Frigate Card CameraManager initialized (Cameras: ',
|
||||
|
||||
@@ -44,6 +44,30 @@ export class CameraManagerStore implements CameraManagerReadOnlyConfigStore {
|
||||
this._enginesByType.set(camera.getEngine().getEngineType(), camera.getEngine());
|
||||
}
|
||||
|
||||
public async setCameras(cameras: Camera[]): Promise<void> {
|
||||
// In setting the store cameras, take great care to replace/add first before
|
||||
// remove. Otherwise, there may be race conditions where the card attempts
|
||||
// to render a view with (momentarily) no camera.
|
||||
// See: https://github.com/dermotduffy/frigate-hass-card/issues/1533
|
||||
|
||||
// Replace/Add the new cameras.
|
||||
for (const camera of cameras) {
|
||||
const oldCamera = this._cameras.get(camera.getID());
|
||||
if (oldCamera !== camera) {
|
||||
this.addCamera(camera);
|
||||
await oldCamera?.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the old cameras.
|
||||
for (const camera of this._cameras.values()) {
|
||||
if (!cameras.includes(camera)) {
|
||||
await camera.destroy();
|
||||
this._cameras.delete(camera.getID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async reset(): Promise<void> {
|
||||
await allPromises(this._cameras.values(), (camera) => camera.destroy());
|
||||
this._cameras.clear();
|
||||
|
||||
Reference in New Issue
Block a user