@matter/node 0.17.7 → 0.17.8-alpha.0-20260801-92169d0aa
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/behavior/system/software-update/SoftwareUpdateManager.d.ts +2 -1
- package/dist/cjs/behavior/system/software-update/SoftwareUpdateManager.d.ts.map +1 -1
- package/dist/cjs/behavior/system/software-update/SoftwareUpdateManager.js +18 -9
- package/dist/cjs/behavior/system/software-update/SoftwareUpdateManager.js.map +1 -1
- package/dist/cjs/behavior/system/subscriptions/SubscriptionsServer.d.ts.map +1 -1
- package/dist/cjs/behavior/system/subscriptions/SubscriptionsServer.js +4 -1
- package/dist/cjs/behavior/system/subscriptions/SubscriptionsServer.js.map +1 -1
- package/dist/cjs/node/server/InteractionServer.d.ts.map +1 -1
- package/dist/cjs/node/server/InteractionServer.js +52 -35
- package/dist/cjs/node/server/InteractionServer.js.map +1 -1
- package/dist/cjs/node/server/ServerSubscription.d.ts +2 -2
- package/dist/cjs/node/server/ServerSubscription.d.ts.map +1 -1
- package/dist/cjs/node/server/ServerSubscription.js +41 -22
- package/dist/cjs/node/server/ServerSubscription.js.map +1 -1
- package/dist/esm/behavior/system/software-update/SoftwareUpdateManager.d.ts +2 -1
- package/dist/esm/behavior/system/software-update/SoftwareUpdateManager.d.ts.map +1 -1
- package/dist/esm/behavior/system/software-update/SoftwareUpdateManager.js +18 -9
- package/dist/esm/behavior/system/software-update/SoftwareUpdateManager.js.map +1 -1
- package/dist/esm/behavior/system/subscriptions/SubscriptionsServer.d.ts.map +1 -1
- package/dist/esm/behavior/system/subscriptions/SubscriptionsServer.js +16 -2
- package/dist/esm/behavior/system/subscriptions/SubscriptionsServer.js.map +1 -1
- package/dist/esm/node/server/InteractionServer.d.ts.map +1 -1
- package/dist/esm/node/server/InteractionServer.js +52 -35
- package/dist/esm/node/server/InteractionServer.js.map +1 -1
- package/dist/esm/node/server/ServerSubscription.d.ts +2 -2
- package/dist/esm/node/server/ServerSubscription.d.ts.map +1 -1
- package/dist/esm/node/server/ServerSubscription.js +44 -23
- package/dist/esm/node/server/ServerSubscription.js.map +1 -1
- package/package.json +6 -6
- package/src/behavior/system/software-update/SoftwareUpdateManager.ts +22 -9
- package/src/behavior/system/subscriptions/SubscriptionsServer.ts +22 -2
- package/src/node/server/InteractionServer.ts +59 -38
- package/src/node/server/ServerSubscription.ts +48 -26
|
@@ -7,16 +7,27 @@
|
|
|
7
7
|
import { InteractionServer, PeerSubscription } from "#node/server/InteractionServer.js";
|
|
8
8
|
import { ServerSubscription } from "#node/server/ServerSubscription.js";
|
|
9
9
|
import {
|
|
10
|
+
causedBy,
|
|
10
11
|
ChannelType,
|
|
11
12
|
deepCopy,
|
|
12
13
|
Logger,
|
|
13
14
|
MatterAggregateError,
|
|
14
15
|
MatterError,
|
|
15
16
|
MaybePromise,
|
|
17
|
+
NetworkError,
|
|
18
|
+
NoResponseTimeoutError,
|
|
16
19
|
Seconds,
|
|
17
20
|
} from "@matter/general";
|
|
18
21
|
import { DatatypeModel, FieldElement } from "@matter/model";
|
|
19
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
GroupSession,
|
|
24
|
+
PeerAddress,
|
|
25
|
+
PeerAddressMap,
|
|
26
|
+
PeerAddressSet,
|
|
27
|
+
PeerSet,
|
|
28
|
+
Subscription,
|
|
29
|
+
TransientPeerCommunicationError,
|
|
30
|
+
} from "@matter/protocol";
|
|
20
31
|
import { Status, StatusResponseError } from "@matter/types";
|
|
21
32
|
import { Behavior } from "../../Behavior.js";
|
|
22
33
|
import { SessionsBehavior } from "../sessions/SessionsBehavior.js";
|
|
@@ -162,7 +173,7 @@ export class SubscriptionsServer extends Behavior {
|
|
|
162
173
|
}
|
|
163
174
|
|
|
164
175
|
#subscriptionCancelled(subscription: Subscription): MaybePromise {
|
|
165
|
-
if (subscription.
|
|
176
|
+
if (subscription.isTerminated && this.state.persistenceEnabled !== false) {
|
|
166
177
|
const { subscriptionId: id } = subscription;
|
|
167
178
|
const subscriptionIndex = this.state.subscriptions.findIndex(({ subscriptionId }) => id === subscriptionId);
|
|
168
179
|
if (subscriptionIndex !== -1) {
|
|
@@ -273,7 +284,16 @@ export class SubscriptionsServer extends Behavior {
|
|
|
273
284
|
: sre.message
|
|
274
285
|
: error,
|
|
275
286
|
);
|
|
287
|
+
if (
|
|
288
|
+
causedBy(error, TransientPeerCommunicationError, NoResponseTimeoutError, NetworkError)
|
|
289
|
+
) {
|
|
290
|
+
// Report sends do not declare peer loss, so nothing else stops this loop from
|
|
291
|
+
// spending a full MRP window on each of the peer's remaining subscriptions
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
276
294
|
if (isInvalidSubscription) {
|
|
295
|
+
// The peer dropped its state for us; too unlikely another of its subscriptions
|
|
296
|
+
// survived to spend an initial report finding out
|
|
277
297
|
break;
|
|
278
298
|
}
|
|
279
299
|
continue;
|
|
@@ -79,6 +79,9 @@ const logger = Logger.get("InteractionServer");
|
|
|
79
79
|
const MAX_READ_PATHS = 10_000;
|
|
80
80
|
const MAX_SUBSCRIBE_PATHS = 10_000;
|
|
81
81
|
|
|
82
|
+
// A controller decides for itself when its sessions are gone; a report we cannot push does not prove they are
|
|
83
|
+
const SUBSCRIPTION_EXCHANGE_OPTIONS: MessageExchange.Options = { suppressPeerLoss: true };
|
|
84
|
+
|
|
82
85
|
export interface PeerSubscription {
|
|
83
86
|
subscriptionId: number;
|
|
84
87
|
peerAddress: PeerAddress;
|
|
@@ -745,8 +748,16 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
|
|
|
745
748
|
#initiateSubscriptionExchange(addressOrSession: PeerAddress | Session, protocolId: number) {
|
|
746
749
|
const exchange =
|
|
747
750
|
addressOrSession instanceof Session
|
|
748
|
-
? this.#context.exchangeManager.initiateExchangeForSession(
|
|
749
|
-
|
|
751
|
+
? this.#context.exchangeManager.initiateExchangeForSession(
|
|
752
|
+
addressOrSession,
|
|
753
|
+
protocolId,
|
|
754
|
+
SUBSCRIPTION_EXCHANGE_OPTIONS,
|
|
755
|
+
)
|
|
756
|
+
: this.#context.exchangeManager.initiateExchange(
|
|
757
|
+
addressOrSession,
|
|
758
|
+
protocolId,
|
|
759
|
+
SUBSCRIPTION_EXCHANGE_OPTIONS,
|
|
760
|
+
);
|
|
750
761
|
|
|
751
762
|
// Count subscription report messages we push and the acks we receive in response
|
|
752
763
|
this.#countExchangeMessages(exchange);
|
|
@@ -813,47 +824,50 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
|
|
|
813
824
|
}: PeerSubscription,
|
|
814
825
|
session: NodeSession,
|
|
815
826
|
) {
|
|
816
|
-
const exchange = this.#
|
|
827
|
+
const exchange = this.#initiateSubscriptionExchange(session, INTERACTION_PROTOCOL_ID);
|
|
828
|
+
const messenger = new InteractionServerMessenger(exchange);
|
|
817
829
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
830
|
+
let subscription: ServerSubscription | undefined;
|
|
831
|
+
try {
|
|
832
|
+
logger.info(
|
|
833
|
+
`Reestablish subscription`,
|
|
834
|
+
Mark.OUTBOUND,
|
|
835
|
+
exchange.via,
|
|
836
|
+
Diagnostic.dict({
|
|
837
|
+
...Subscription.diagnosticOf(subscriptionId),
|
|
838
|
+
isFabricFiltered,
|
|
839
|
+
maxInterval: Duration.format(maxInterval),
|
|
840
|
+
sendInterval: Duration.format(sendInterval),
|
|
841
|
+
}),
|
|
842
|
+
);
|
|
829
843
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
844
|
+
const context: ServerSubscriptionContext = {
|
|
845
|
+
session,
|
|
846
|
+
node: this.#node,
|
|
847
|
+
initiateExchange: (addressOrSession, protocolId) =>
|
|
848
|
+
this.#initiateSubscriptionExchange(addressOrSession, protocolId),
|
|
849
|
+
};
|
|
836
850
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
+
subscription = new ServerSubscription({
|
|
852
|
+
id: subscriptionId,
|
|
853
|
+
context,
|
|
854
|
+
request: {
|
|
855
|
+
attributeRequests,
|
|
856
|
+
eventRequests,
|
|
857
|
+
isFabricFiltered,
|
|
858
|
+
minIntervalFloorSeconds: Seconds.of(minIntervalFloor),
|
|
859
|
+
maxIntervalCeilingSeconds: Seconds.of(maxIntervalCeiling),
|
|
860
|
+
},
|
|
861
|
+
subscriptionOptions: this.#subscriptionConfig,
|
|
862
|
+
useAsMaxInterval: maxInterval,
|
|
863
|
+
useAsSendInterval: sendInterval,
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
const readContext = this.#prepareOnlineContext(exchange, undefined, isFabricFiltered);
|
|
851
867
|
|
|
852
|
-
const readContext = this.#prepareOnlineContext(exchange, undefined, isFabricFiltered);
|
|
853
|
-
try {
|
|
854
868
|
// Send initial data report to prime the subscription with initial data
|
|
855
869
|
await subscription.sendInitialReport(
|
|
856
|
-
|
|
870
|
+
messenger,
|
|
857
871
|
readContext,
|
|
858
872
|
true, // Do not send status responses because we simulate that the subscription is still established
|
|
859
873
|
);
|
|
@@ -873,8 +887,15 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
|
|
|
873
887
|
);
|
|
874
888
|
}
|
|
875
889
|
} catch (error) {
|
|
876
|
-
await subscription
|
|
890
|
+
await subscription?.close(); // Cleanup
|
|
877
891
|
throw error;
|
|
892
|
+
} finally {
|
|
893
|
+
// Reports use their own exchanges; leaving this one open blocks the session from ever closing gracefully
|
|
894
|
+
try {
|
|
895
|
+
await messenger.close();
|
|
896
|
+
} catch (error) {
|
|
897
|
+
logger.warn("Error closing subscription re-establishment exchange", error);
|
|
898
|
+
}
|
|
878
899
|
}
|
|
879
900
|
return subscription;
|
|
880
901
|
}
|
|
@@ -10,6 +10,7 @@ import { IcdManagementServer } from "#behaviors/icd-management";
|
|
|
10
10
|
import type { ServerNode } from "#node/ServerNode.js";
|
|
11
11
|
import {
|
|
12
12
|
AsyncObservable,
|
|
13
|
+
causedBy,
|
|
13
14
|
ClosedError,
|
|
14
15
|
Diagnostic,
|
|
15
16
|
Duration,
|
|
@@ -42,6 +43,7 @@ import {
|
|
|
42
43
|
ReadResult,
|
|
43
44
|
SessionClosedError,
|
|
44
45
|
Subscription,
|
|
46
|
+
TransientPeerCommunicationError,
|
|
45
47
|
} from "@matter/protocol";
|
|
46
48
|
import {
|
|
47
49
|
AttributeId,
|
|
@@ -130,7 +132,7 @@ export class ServerSubscription implements Subscription {
|
|
|
130
132
|
|
|
131
133
|
#id: SubscriptionId;
|
|
132
134
|
#isClosed = false;
|
|
133
|
-
#
|
|
135
|
+
#isTerminated = false;
|
|
134
136
|
#request: Omit<SubscribeRequest, "interactionModelRevision" | "keepSubscriptions">;
|
|
135
137
|
#cancelled = AsyncObservable<[subscription: Subscription]>();
|
|
136
138
|
#maxInterval?: Duration;
|
|
@@ -206,8 +208,8 @@ export class ServerSubscription implements Subscription {
|
|
|
206
208
|
return this.#context.session;
|
|
207
209
|
}
|
|
208
210
|
|
|
209
|
-
get
|
|
210
|
-
return this.#
|
|
211
|
+
get isTerminated() {
|
|
212
|
+
return this.#isTerminated;
|
|
211
213
|
}
|
|
212
214
|
|
|
213
215
|
get request() {
|
|
@@ -245,7 +247,8 @@ export class ServerSubscription implements Subscription {
|
|
|
245
247
|
}
|
|
246
248
|
|
|
247
249
|
async handlePeerCancel() {
|
|
248
|
-
this
|
|
250
|
+
logger.notice(`Subscription ${this.idStr} cancelled by peer`);
|
|
251
|
+
this.#isTerminated = true;
|
|
249
252
|
// Force-close any in-flight send exchange so MRP retransmissions stop immediately.
|
|
250
253
|
// Use try/finally so this.close() always runs even if the exchange close throws.
|
|
251
254
|
try {
|
|
@@ -508,7 +511,7 @@ export class ServerSubscription implements Subscription {
|
|
|
508
511
|
this.#sendUpdateErrorCounter = 0;
|
|
509
512
|
}
|
|
510
513
|
} catch (error) {
|
|
511
|
-
if (this.#isClosed || this.#
|
|
514
|
+
if (this.#isClosed || this.#isTerminated) {
|
|
512
515
|
// No need to care about resubmissions when the server is closing or peer cancelled us
|
|
513
516
|
return;
|
|
514
517
|
}
|
|
@@ -535,20 +538,24 @@ export class ServerSubscription implements Subscription {
|
|
|
535
538
|
this.#outstandingEventsMinNumber = eventsMinNumber; // newer number are always higher, so we can just set it
|
|
536
539
|
}
|
|
537
540
|
} else {
|
|
538
|
-
logger.
|
|
539
|
-
`
|
|
541
|
+
logger.notice(
|
|
542
|
+
`Giving up on subscription ${this.idStr} after 3 failed updates; the controller must subscribe again`,
|
|
540
543
|
);
|
|
541
544
|
this.#sendNextUpdateImmediately = false;
|
|
542
545
|
if (
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
+
causedBy(
|
|
547
|
+
error,
|
|
548
|
+
TransientPeerCommunicationError,
|
|
549
|
+
NoResponseTimeoutError,
|
|
550
|
+
NetworkError,
|
|
551
|
+
SessionClosedError,
|
|
552
|
+
)
|
|
546
553
|
) {
|
|
547
|
-
//
|
|
548
|
-
// controller
|
|
549
|
-
using _messaging = updating?.join("
|
|
550
|
-
this.#
|
|
551
|
-
await this.#
|
|
554
|
+
// The session is left alone: failing to push reports says nothing about whether the
|
|
555
|
+
// controller can still reach us, and recovery is its call regardless
|
|
556
|
+
using _messaging = updating?.join("abandoning");
|
|
557
|
+
this.#isTerminated = true;
|
|
558
|
+
await this.#closeFromUpdate();
|
|
552
559
|
break;
|
|
553
560
|
} else {
|
|
554
561
|
throw error;
|
|
@@ -693,12 +700,12 @@ export class ServerSubscription implements Subscription {
|
|
|
693
700
|
});
|
|
694
701
|
}
|
|
695
702
|
|
|
696
|
-
async #flush(flushViaSession?: Session) {
|
|
703
|
+
async #flush(flushViaSession?: Session, currentExchange?: MessageExchange) {
|
|
697
704
|
this.#sendDelayTimer.stop();
|
|
698
705
|
if (this.#outstandingAttributeUpdates !== undefined || this.#outstandingEventsMinNumber !== undefined) {
|
|
699
706
|
logger.debug(`Flushing subscription ${this.idStr}${this.#isClosed ? " (for closing)" : ""}`);
|
|
700
707
|
this.#triggerSendUpdate(true, flushViaSession);
|
|
701
|
-
if (this.#currentUpdatePromise) {
|
|
708
|
+
if (this.#currentUpdatePromise && !this.#isSendingOn(currentExchange)) {
|
|
702
709
|
using _waiting = this.#lifetime?.join("waiting on flush");
|
|
703
710
|
await this.#currentUpdatePromise;
|
|
704
711
|
}
|
|
@@ -708,21 +715,35 @@ export class ServerSubscription implements Subscription {
|
|
|
708
715
|
/**
|
|
709
716
|
* Closes the subscription and flushes all outstanding data updates if requested.
|
|
710
717
|
*/
|
|
711
|
-
async close(flushViaSession?: Session) {
|
|
718
|
+
async close(flushViaSession?: Session, currentExchange?: MessageExchange) {
|
|
712
719
|
if (this.#isClosed) {
|
|
713
720
|
return;
|
|
714
721
|
}
|
|
715
722
|
this.#isClosed = true;
|
|
716
723
|
|
|
717
|
-
await this.#cancel(flushViaSession);
|
|
724
|
+
await this.#cancel(flushViaSession, currentExchange);
|
|
718
725
|
|
|
719
|
-
if (this.#currentUpdatePromise) {
|
|
726
|
+
if (this.#currentUpdatePromise && !this.#isSendingOn(currentExchange)) {
|
|
720
727
|
using _waiting = this.#lifetime?.closing()?.join("waiting on update");
|
|
721
728
|
await this.#currentUpdatePromise;
|
|
722
729
|
}
|
|
723
730
|
}
|
|
724
731
|
|
|
725
|
-
|
|
732
|
+
/** Close from within the update loop, which {@link close} would wait on -- it is our own caller. */
|
|
733
|
+
async #closeFromUpdate() {
|
|
734
|
+
if (this.#isClosed) {
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
this.#isClosed = true;
|
|
738
|
+
await this.#cancel();
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/** Whether our in-flight update is sending on this exchange, making it this close's own caller. */
|
|
742
|
+
#isSendingOn(currentExchange?: MessageExchange) {
|
|
743
|
+
return currentExchange !== undefined && currentExchange === this.#currentSendExchange;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
async #cancel(flushViaSession?: Session, currentExchange?: MessageExchange) {
|
|
726
747
|
const closing = this.#lifetime?.closing();
|
|
727
748
|
|
|
728
749
|
this.#sendUpdatesActivated = false;
|
|
@@ -731,7 +752,7 @@ export class ServerSubscription implements Subscription {
|
|
|
731
752
|
|
|
732
753
|
if (flushViaSession !== undefined) {
|
|
733
754
|
using _flushing = closing?.join("flushing");
|
|
734
|
-
await this.#flush(flushViaSession);
|
|
755
|
+
await this.#flush(flushViaSession, currentExchange);
|
|
735
756
|
}
|
|
736
757
|
|
|
737
758
|
this.#updateTimer.stop();
|
|
@@ -845,19 +866,20 @@ export class ServerSubscription implements Subscription {
|
|
|
845
866
|
}
|
|
846
867
|
} catch (error) {
|
|
847
868
|
if (StatusResponseError.is(error, Status.InvalidSubscription, Status.Failure)) {
|
|
848
|
-
logger.notice(`Subscription ${this.idStr}
|
|
849
|
-
this.#
|
|
869
|
+
logger.notice(`Subscription ${this.idStr} reported invalid by peer`);
|
|
870
|
+
this.#isTerminated = true;
|
|
850
871
|
} else {
|
|
851
872
|
StatusResponseError.accept(error);
|
|
852
873
|
logger.info(`Subscription ${this.idStr} update failed:`, error);
|
|
853
874
|
}
|
|
854
875
|
|
|
855
876
|
using _canceling = lifetime?.join("canceling");
|
|
856
|
-
await this.#
|
|
877
|
+
await this.#closeFromUpdate();
|
|
857
878
|
} finally {
|
|
858
|
-
this.#currentSendExchange = undefined;
|
|
859
879
|
using _closing = lifetime?.join("closing messenger");
|
|
880
|
+
// Reset only after the close: it can still send, and #isSendingOn must recognise this exchange until then
|
|
860
881
|
await messenger.close();
|
|
882
|
+
this.#currentSendExchange = undefined;
|
|
861
883
|
}
|
|
862
884
|
return true;
|
|
863
885
|
}
|