fix: Fix leaky Frigate subscriptions/unsubscriptions (#2513)
This commit is contained in:
committed by
dermotduffy
parent
0a36358394
commit
37df382aa2
@@ -473,8 +473,10 @@ export class FrigateCamera extends Camera {
|
||||
event.after.camera === config.frigate.camera_name,
|
||||
};
|
||||
|
||||
await frigateEventWatcher.subscribe(hass, request);
|
||||
this._onDestroy(() => frigateEventWatcher.unsubscribe(request));
|
||||
await this._setupSubscription(
|
||||
() => frigateEventWatcher.subscribe(hass, request),
|
||||
() => frigateEventWatcher.unsubscribe(request),
|
||||
);
|
||||
}
|
||||
|
||||
private _frigateEventHandler = (ev: FrigateEventChange): void => {
|
||||
@@ -548,8 +550,10 @@ export class FrigateCamera extends Camera {
|
||||
review.after.camera === config.frigate.camera_name,
|
||||
};
|
||||
|
||||
await frigateReviewWatcher.subscribe(hass, request);
|
||||
this._onDestroy(() => frigateReviewWatcher.unsubscribe(request));
|
||||
await this._setupSubscription(
|
||||
() => frigateReviewWatcher.subscribe(hass, request),
|
||||
() => frigateReviewWatcher.unsubscribe(request),
|
||||
);
|
||||
}
|
||||
|
||||
private _frigateReviewHandler = (review: FrigateReviewChange): void => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
import { HomeAssistant, SubscriptionUnsubscribe } from '../../ha/types';
|
||||
import { HomeAssistant } from '../../ha/types';
|
||||
import { KeyedSubscriptionManager } from '../../utils/keyed-subscription-manager';
|
||||
import {
|
||||
FrigateEventChange,
|
||||
FrigateReviewChange,
|
||||
@@ -17,53 +18,39 @@ export interface FrigateWatcherRequest<T> {
|
||||
// Generic subscription interface
|
||||
export interface FrigateWatcherSubscriptionInterface<T> {
|
||||
subscribe(hass: HomeAssistant, request: FrigateWatcherRequest<T>): Promise<void>;
|
||||
unsubscribe(request: FrigateWatcherRequest<T>): void;
|
||||
unsubscribe(request: FrigateWatcherRequest<T>): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for Frigate WebSocket watchers.
|
||||
* Handles subscription management and message routing to callbacks.
|
||||
* Base class for Frigate WebSocket watchers. Counted per `instanceID`: the
|
||||
* first subscriber for an instance opens the WS subscription, the last to
|
||||
* unsubscribe tears it down. Each message is parsed, schema-validated, and
|
||||
* fanned out to every registered request whose `instanceID` matches and whose
|
||||
* `matcher` accepts the payload.
|
||||
*/
|
||||
abstract class FrigateWatcher<T> implements FrigateWatcherSubscriptionInterface<T> {
|
||||
protected abstract _type: string;
|
||||
protected abstract _schema: z.ZodType<T>;
|
||||
|
||||
protected _requests: FrigateWatcherRequest<T>[] = [];
|
||||
protected _unsubscribeCallback: Record<string, SubscriptionUnsubscribe> = {};
|
||||
private _subscriptions = new KeyedSubscriptionManager<
|
||||
string,
|
||||
FrigateWatcherRequest<T>
|
||||
>((request) => request.instanceID);
|
||||
|
||||
public async subscribe(
|
||||
hass: HomeAssistant,
|
||||
request: FrigateWatcherRequest<T>,
|
||||
): Promise<void> {
|
||||
const shouldSubscribe = !this._hasSubscribers(request.instanceID);
|
||||
this._requests.push(request);
|
||||
if (shouldSubscribe) {
|
||||
this._unsubscribeCallback[request.instanceID] =
|
||||
await hass.connection.subscribeMessage<string>(
|
||||
(data) => this._receiveHandler(request.instanceID, data),
|
||||
{ type: this._type, instance_id: request.instanceID },
|
||||
);
|
||||
}
|
||||
await this._subscriptions.subscribe(request, () =>
|
||||
hass.connection.subscribeMessage<string>(
|
||||
(data) => this._receiveHandler(request.instanceID, data),
|
||||
{ type: this._type, instance_id: request.instanceID },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public async unsubscribe(request: FrigateWatcherRequest<T>): Promise<void> {
|
||||
this._requests = this._requests.filter(
|
||||
(existingRequest) => existingRequest !== request,
|
||||
);
|
||||
|
||||
if (!this._hasSubscribers(request.instanceID)) {
|
||||
const callback = this._unsubscribeCallback[request.instanceID];
|
||||
delete this._unsubscribeCallback[request.instanceID];
|
||||
|
||||
// Callback may be undefined if unsubscribe is called while subscribe is
|
||||
// still awaiting the Home Assistant connection.
|
||||
await callback?.();
|
||||
}
|
||||
}
|
||||
|
||||
protected _hasSubscribers(instanceID: string): boolean {
|
||||
return !!this._requests.filter((request) => request.instanceID === instanceID)
|
||||
.length;
|
||||
await this._subscriptions.unsubscribe(request);
|
||||
}
|
||||
|
||||
protected _receiveHandler(instanceID: string, data: string): void {
|
||||
@@ -82,11 +69,8 @@ abstract class FrigateWatcher<T> implements FrigateWatcherSubscriptionInterface<
|
||||
return;
|
||||
}
|
||||
|
||||
for (const request of this._requests) {
|
||||
if (
|
||||
request.instanceID === instanceID &&
|
||||
(!request.matcher || request.matcher(parseResult.data))
|
||||
) {
|
||||
for (const request of this._subscriptions.getRequestsForKey(instanceID)) {
|
||||
if (!request.matcher || request.matcher(parseResult.data)) {
|
||||
request.callback(parseResult.data);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user