@fluidframework/datastore 2.4.0 → 2.5.0
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/CHANGELOG.md +4 -0
- package/api-report/datastore.legacy.alpha.api.md +1 -1
- package/dist/channelContext.d.ts +7 -3
- package/dist/channelContext.d.ts.map +1 -1
- package/dist/channelContext.js.map +1 -1
- package/dist/channelDeltaConnection.d.ts +2 -2
- package/dist/channelDeltaConnection.d.ts.map +1 -1
- package/dist/channelDeltaConnection.js +39 -4
- package/dist/channelDeltaConnection.js.map +1 -1
- package/dist/dataStoreRuntime.d.ts +18 -2
- package/dist/dataStoreRuntime.d.ts.map +1 -1
- package/dist/dataStoreRuntime.js +118 -44
- package/dist/dataStoreRuntime.js.map +1 -1
- package/dist/localChannelContext.d.ts +9 -4
- package/dist/localChannelContext.d.ts.map +1 -1
- package/dist/localChannelContext.js +19 -7
- package/dist/localChannelContext.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/remoteChannelContext.d.ts +9 -4
- package/dist/remoteChannelContext.d.ts.map +1 -1
- package/dist/remoteChannelContext.js +25 -13
- package/dist/remoteChannelContext.js.map +1 -1
- package/lib/channelContext.d.ts +7 -3
- package/lib/channelContext.d.ts.map +1 -1
- package/lib/channelContext.js.map +1 -1
- package/lib/channelDeltaConnection.d.ts +2 -2
- package/lib/channelDeltaConnection.d.ts.map +1 -1
- package/lib/channelDeltaConnection.js +39 -4
- package/lib/channelDeltaConnection.js.map +1 -1
- package/lib/dataStoreRuntime.d.ts +18 -2
- package/lib/dataStoreRuntime.d.ts.map +1 -1
- package/lib/dataStoreRuntime.js +118 -44
- package/lib/dataStoreRuntime.js.map +1 -1
- package/lib/localChannelContext.d.ts +9 -4
- package/lib/localChannelContext.d.ts.map +1 -1
- package/lib/localChannelContext.js +19 -7
- package/lib/localChannelContext.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/remoteChannelContext.d.ts +9 -4
- package/lib/remoteChannelContext.d.ts.map +1 -1
- package/lib/remoteChannelContext.js +25 -13
- package/lib/remoteChannelContext.js.map +1 -1
- package/package.json +23 -19
- package/src/channelContext.ts +6 -6
- package/src/channelDeltaConnection.ts +46 -15
- package/src/dataStoreRuntime.ts +146 -68
- package/src/localChannelContext.ts +21 -16
- package/src/packageVersion.ts +1 -1
- package/src/remoteChannelContext.ts +39 -18
package/src/dataStoreRuntime.ts
CHANGED
|
@@ -54,6 +54,8 @@ import {
|
|
|
54
54
|
VisibilityState,
|
|
55
55
|
gcDataBlobKey,
|
|
56
56
|
IInboundSignalMessage,
|
|
57
|
+
type IRuntimeMessageCollection,
|
|
58
|
+
type IRuntimeMessagesContent,
|
|
57
59
|
} from "@fluidframework/runtime-definitions/internal";
|
|
58
60
|
import {
|
|
59
61
|
GCDataBuilder,
|
|
@@ -356,6 +358,18 @@ export class FluidDataStoreRuntime
|
|
|
356
358
|
public async request(request: IRequest): Promise<IResponse> {
|
|
357
359
|
try {
|
|
358
360
|
const parser = RequestParser.create(request);
|
|
361
|
+
// If there are not path parts, and the request is via a handle
|
|
362
|
+
// then we should return the entrypoint object for this runtime.
|
|
363
|
+
// This allows the entrypoint handle to be resolved without the need
|
|
364
|
+
// for the entrypoint object to know anything about requests or handles.
|
|
365
|
+
//
|
|
366
|
+
// This works because the entrypoint handle is an object handle,
|
|
367
|
+
// which always has a real reference to the object itself.
|
|
368
|
+
// Those get serialized and then deserialized into a plain handle, which really just has a path,
|
|
369
|
+
// resolution walks to the runtime, which calls this, and get the true object off the internal object handle
|
|
370
|
+
if (parser.pathParts.length === 0 && request.headers?.viaHandle === true) {
|
|
371
|
+
return { mimeType: "fluid/object", status: 200, value: await this.entryPoint.get() };
|
|
372
|
+
}
|
|
359
373
|
const id = parser.pathParts[0];
|
|
360
374
|
|
|
361
375
|
if (id === "_channels" || id === "_custom") {
|
|
@@ -650,66 +664,151 @@ export class FluidDataStoreRuntime
|
|
|
650
664
|
);
|
|
651
665
|
}
|
|
652
666
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
667
|
+
/**
|
|
668
|
+
* Process channel messages. The messages here are contiguous channel types messages in a batch for this data
|
|
669
|
+
* store.
|
|
670
|
+
* @param messageCollection - The collection of messages to process.
|
|
671
|
+
*/
|
|
672
|
+
private processChannelMessages(messageCollection: IRuntimeMessageCollection) {
|
|
658
673
|
this.verifyNotClosed();
|
|
659
674
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
675
|
+
/*
|
|
676
|
+
* Bunch contiguous messages for the same channel and send them together.
|
|
677
|
+
* This is an optimization where DDSes can process a bunch of ops together. DDSes
|
|
678
|
+
* like merge tree or shared tree can process ops more efficiently when they are bunched together.
|
|
679
|
+
*/
|
|
680
|
+
let currentAddress: string | undefined;
|
|
681
|
+
let currentMessagesContent: IRuntimeMessagesContent[] = [];
|
|
682
|
+
const { messagesContent, local } = messageCollection;
|
|
683
|
+
|
|
684
|
+
const sendBunchedMessages = () => {
|
|
685
|
+
// Current address will be undefined for the first message in the list.
|
|
686
|
+
if (currentAddress === undefined) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// process the last set of channel ops
|
|
691
|
+
const channelContext = this.contexts.get(currentAddress);
|
|
692
|
+
assert(!!channelContext, 0xa6b /* Channel context not found */);
|
|
693
|
+
|
|
694
|
+
channelContext.processMessages({
|
|
695
|
+
envelope: messageCollection.envelope,
|
|
696
|
+
messagesContent: currentMessagesContent,
|
|
697
|
+
local,
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
currentMessagesContent = [];
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
for (const { contents, ...restOfMessagesContent } of messagesContent) {
|
|
704
|
+
const contentsEnvelope = contents as IEnvelope;
|
|
705
|
+
|
|
706
|
+
// If the address of the message changes while processing the batch, send the current bunch.
|
|
707
|
+
if (currentAddress !== contentsEnvelope.address) {
|
|
708
|
+
sendBunchedMessages();
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
currentMessagesContent.push({
|
|
712
|
+
contents: contentsEnvelope.contents,
|
|
713
|
+
...restOfMessagesContent,
|
|
714
|
+
});
|
|
715
|
+
currentAddress = contentsEnvelope.address;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Process the last bunch of messages.
|
|
719
|
+
sendBunchedMessages();
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
private processAttachMessages(messageCollection: IRuntimeMessageCollection) {
|
|
723
|
+
const { envelope, messagesContent, local } = messageCollection;
|
|
724
|
+
for (const { contents } of messagesContent) {
|
|
725
|
+
const attachMessage = contents as IAttachMessage;
|
|
726
|
+
const id = attachMessage.id;
|
|
727
|
+
|
|
728
|
+
// We need to process the GC Data for both local and remote attach messages
|
|
729
|
+
processAttachMessageGCData(attachMessage.snapshot, (nodeId, toPath) => {
|
|
730
|
+
// Note: nodeId will be "/" unless and until we support sub-DDS GC Nodes
|
|
731
|
+
const fromPath = `/${this.id}/${id}${nodeId === "/" ? "" : nodeId}`;
|
|
732
|
+
this.dataStoreContext.addedGCOutboundRoute(fromPath, toPath, envelope.timestamp);
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
// If a non-local operation then go and create the object
|
|
736
|
+
// Otherwise mark it as officially attached.
|
|
737
|
+
if (local) {
|
|
738
|
+
assert(
|
|
739
|
+
this.pendingAttach.delete(id),
|
|
740
|
+
0x17c /* "Unexpected attach (local) channel OP" */,
|
|
741
|
+
);
|
|
742
|
+
} else {
|
|
743
|
+
assert(!this.contexts.has(id), 0x17d /* "Unexpected attach channel OP" */);
|
|
744
|
+
|
|
745
|
+
const summarizerNodeParams = {
|
|
746
|
+
type: CreateSummarizerNodeSource.FromAttach,
|
|
747
|
+
sequenceNumber: envelope.sequenceNumber,
|
|
748
|
+
snapshot: attachMessage.snapshot,
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
const remoteChannelContext = this.createRemoteChannelContext(
|
|
752
|
+
attachMessage,
|
|
753
|
+
summarizerNodeParams,
|
|
754
|
+
);
|
|
755
|
+
this.contexts.set(id, remoteChannelContext);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Process messages for this data store. The messages here are contiguous messages in a batch.
|
|
762
|
+
* @param messageCollection - The collection of messages to process.
|
|
763
|
+
*/
|
|
764
|
+
public processMessages(messageCollection: IRuntimeMessageCollection): void {
|
|
765
|
+
this.verifyNotClosed();
|
|
698
766
|
|
|
767
|
+
const { envelope, messagesContent } = messageCollection;
|
|
768
|
+
try {
|
|
769
|
+
switch (envelope.type) {
|
|
699
770
|
case DataStoreMessageType.ChannelOp:
|
|
700
|
-
this.
|
|
771
|
+
this.processChannelMessages(messageCollection);
|
|
772
|
+
break;
|
|
773
|
+
case DataStoreMessageType.Attach:
|
|
774
|
+
this.processAttachMessages(messageCollection);
|
|
701
775
|
break;
|
|
702
776
|
default:
|
|
703
777
|
}
|
|
704
|
-
|
|
705
|
-
this.emit("op", message);
|
|
706
778
|
} catch (error) {
|
|
707
779
|
throw DataProcessingError.wrapIfUnrecognized(
|
|
708
780
|
error,
|
|
709
781
|
"fluidDataStoreRuntimeFailedToProcessMessage",
|
|
710
|
-
|
|
782
|
+
envelope,
|
|
711
783
|
);
|
|
712
784
|
}
|
|
785
|
+
|
|
786
|
+
for (const { contents, clientSequenceNumber } of messagesContent) {
|
|
787
|
+
this.emit("op", { ...envelope, contents, clientSequenceNumber });
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* back-compat ADO 21575.
|
|
793
|
+
* This is still here for back-compat purposes because it exists on IFluidDataStoreChannel. Once it is removed from
|
|
794
|
+
* the interface, this method can be removed.
|
|
795
|
+
*/
|
|
796
|
+
public process(
|
|
797
|
+
message: ISequencedDocumentMessage,
|
|
798
|
+
local: boolean,
|
|
799
|
+
localOpMetadata: unknown,
|
|
800
|
+
) {
|
|
801
|
+
this.processMessages({
|
|
802
|
+
envelope: message,
|
|
803
|
+
messagesContent: [
|
|
804
|
+
{
|
|
805
|
+
contents: message.contents,
|
|
806
|
+
localOpMetadata,
|
|
807
|
+
clientSequenceNumber: message.clientSequenceNumber,
|
|
808
|
+
},
|
|
809
|
+
],
|
|
810
|
+
local,
|
|
811
|
+
});
|
|
713
812
|
}
|
|
714
813
|
|
|
715
814
|
public processSignal(message: IInboundSignalMessage, local: boolean) {
|
|
@@ -1126,27 +1225,6 @@ export class FluidDataStoreRuntime
|
|
|
1126
1225
|
this.dataStoreContext.setChannelDirty(address);
|
|
1127
1226
|
}
|
|
1128
1227
|
|
|
1129
|
-
private processChannelOp(
|
|
1130
|
-
message: ISequencedDocumentMessage,
|
|
1131
|
-
local: boolean,
|
|
1132
|
-
localOpMetadata: unknown,
|
|
1133
|
-
) {
|
|
1134
|
-
this.verifyNotClosed();
|
|
1135
|
-
|
|
1136
|
-
const envelope = message.contents as IEnvelope;
|
|
1137
|
-
|
|
1138
|
-
const transformed: ISequencedDocumentMessage = {
|
|
1139
|
-
...message,
|
|
1140
|
-
contents: envelope.contents,
|
|
1141
|
-
};
|
|
1142
|
-
|
|
1143
|
-
const channelContext = this.contexts.get(envelope.address);
|
|
1144
|
-
assert(!!channelContext, 0x185 /* "Channel not found" */);
|
|
1145
|
-
channelContext.processOp(transformed, local, localOpMetadata);
|
|
1146
|
-
|
|
1147
|
-
return channelContext;
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
1228
|
private attachListener() {
|
|
1151
1229
|
this.setMaxListeners(Number.MAX_SAFE_INTEGER);
|
|
1152
1230
|
|
|
@@ -12,13 +12,14 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
IDocumentStorageService,
|
|
14
14
|
ISnapshotTree,
|
|
15
|
-
ISequencedDocumentMessage,
|
|
16
15
|
} from "@fluidframework/driver-definitions/internal";
|
|
17
16
|
import {
|
|
18
17
|
ITelemetryContext,
|
|
19
18
|
IFluidDataStoreContext,
|
|
20
19
|
IGarbageCollectionData,
|
|
21
20
|
ISummarizeResult,
|
|
21
|
+
type IPendingMessagesState,
|
|
22
|
+
type IRuntimeMessageCollection,
|
|
22
23
|
} from "@fluidframework/runtime-definitions/internal";
|
|
23
24
|
import {
|
|
24
25
|
ITelemetryLoggerExt,
|
|
@@ -41,7 +42,11 @@ import { ISharedObjectRegistry } from "./dataStoreRuntime.js";
|
|
|
41
42
|
*/
|
|
42
43
|
export abstract class LocalChannelContextBase implements IChannelContext {
|
|
43
44
|
private globallyVisible = false;
|
|
44
|
-
|
|
45
|
+
/** Tracks the messages for this channel that are sent while it's not loaded */
|
|
46
|
+
protected pendingMessagesState: IPendingMessagesState = {
|
|
47
|
+
messageCollections: [],
|
|
48
|
+
pendingCount: 0,
|
|
49
|
+
};
|
|
45
50
|
constructor(
|
|
46
51
|
protected readonly id: string,
|
|
47
52
|
protected readonly runtime: IFluidDataStoreRuntime,
|
|
@@ -74,11 +79,11 @@ export abstract class LocalChannelContextBase implements IChannelContext {
|
|
|
74
79
|
}
|
|
75
80
|
}
|
|
76
81
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
): void {
|
|
82
|
+
/**
|
|
83
|
+
* Process messages for this channel context. The messages here are contiguous messages for this context in a batch.
|
|
84
|
+
* @param messageCollection - The collection of messages to process.
|
|
85
|
+
*/
|
|
86
|
+
processMessages(messageCollection: IRuntimeMessageCollection): void {
|
|
82
87
|
assert(
|
|
83
88
|
this.globallyVisible,
|
|
84
89
|
0x2d3 /* "Local channel must be globally visible when processing op" */,
|
|
@@ -88,13 +93,17 @@ export abstract class LocalChannelContextBase implements IChannelContext {
|
|
|
88
93
|
// delay loading. So after the container is attached and some other client joins which start generating
|
|
89
94
|
// ops for this channel. So not loaded local channel can still receive ops and we store them to process later.
|
|
90
95
|
if (this.isLoaded) {
|
|
91
|
-
this.services.value.deltaConnection.
|
|
96
|
+
this.services.value.deltaConnection.processMessages(messageCollection);
|
|
92
97
|
} else {
|
|
93
98
|
assert(
|
|
94
|
-
local
|
|
99
|
+
!messageCollection.local,
|
|
95
100
|
0x189 /* "Should always be remote because a local dds shouldn't generate ops before loading" */,
|
|
96
101
|
);
|
|
97
|
-
|
|
102
|
+
const propsCopy = {
|
|
103
|
+
...messageCollection,
|
|
104
|
+
messagesContent: Array.from(messageCollection.messagesContent),
|
|
105
|
+
};
|
|
106
|
+
this.pendingMessagesState.messageCollections.push(propsCopy);
|
|
98
107
|
}
|
|
99
108
|
}
|
|
100
109
|
|
|
@@ -254,12 +263,8 @@ export class RehydratedLocalChannelContext extends LocalChannelContextBase {
|
|
|
254
263
|
this.id,
|
|
255
264
|
);
|
|
256
265
|
// Send all pending messages to the channel
|
|
257
|
-
for (const
|
|
258
|
-
this.services.value.deltaConnection.
|
|
259
|
-
message,
|
|
260
|
-
false,
|
|
261
|
-
undefined /* localOpMetadata */,
|
|
262
|
-
);
|
|
266
|
+
for (const messageCollection of this.pendingMessagesState.messageCollections) {
|
|
267
|
+
this.services.value.deltaConnection.processMessages(messageCollection);
|
|
263
268
|
}
|
|
264
269
|
return channel;
|
|
265
270
|
} catch (err) {
|
package/src/packageVersion.ts
CHANGED
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
IDocumentStorageService,
|
|
14
14
|
ISnapshotTree,
|
|
15
|
-
ISequencedDocumentMessage,
|
|
16
15
|
} from "@fluidframework/driver-definitions/internal";
|
|
17
16
|
import {
|
|
18
17
|
IExperimentalIncrementalSummaryContext,
|
|
@@ -23,6 +22,8 @@ import {
|
|
|
23
22
|
ISummarizeInternalResult,
|
|
24
23
|
ISummarizeResult,
|
|
25
24
|
ISummarizerNodeWithGC,
|
|
25
|
+
type IPendingMessagesState,
|
|
26
|
+
type IRuntimeMessageCollection,
|
|
26
27
|
} from "@fluidframework/runtime-definitions/internal";
|
|
27
28
|
import {
|
|
28
29
|
ITelemetryLoggerExt,
|
|
@@ -42,7 +43,11 @@ import { ISharedObjectRegistry } from "./dataStoreRuntime.js";
|
|
|
42
43
|
|
|
43
44
|
export class RemoteChannelContext implements IChannelContext {
|
|
44
45
|
private isLoaded = false;
|
|
45
|
-
|
|
46
|
+
/** Tracks the messages for this channel that are sent while it's not loaded */
|
|
47
|
+
private pendingMessagesState: IPendingMessagesState | undefined = {
|
|
48
|
+
messageCollections: [],
|
|
49
|
+
pendingCount: 0,
|
|
50
|
+
};
|
|
46
51
|
private readonly channelP: Promise<IChannel>;
|
|
47
52
|
private channel: IChannel | undefined;
|
|
48
53
|
private readonly services: ChannelServiceEndpoints;
|
|
@@ -100,16 +105,21 @@ export class RemoteChannelContext implements IChannelContext {
|
|
|
100
105
|
this.id,
|
|
101
106
|
);
|
|
102
107
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
assert(
|
|
109
|
+
this.pendingMessagesState !== undefined,
|
|
110
|
+
0xa6c /* pending messages state is undefined */,
|
|
111
|
+
);
|
|
112
|
+
for (const messageCollection of this.pendingMessagesState.messageCollections) {
|
|
113
|
+
this.services.deltaConnection.processMessages(messageCollection);
|
|
107
114
|
}
|
|
108
|
-
this.thresholdOpsCounter.send(
|
|
115
|
+
this.thresholdOpsCounter.send(
|
|
116
|
+
"ProcessPendingOps",
|
|
117
|
+
this.pendingMessagesState.pendingCount,
|
|
118
|
+
);
|
|
109
119
|
|
|
110
120
|
// Commit changes.
|
|
111
121
|
this.channel = channel;
|
|
112
|
-
this.
|
|
122
|
+
this.pendingMessagesState = undefined;
|
|
113
123
|
this.isLoaded = true;
|
|
114
124
|
|
|
115
125
|
// Because have some await between we created the service and here, the connection state might have changed
|
|
@@ -161,20 +171,31 @@ export class RemoteChannelContext implements IChannelContext {
|
|
|
161
171
|
return this.services.deltaConnection.applyStashedOp(content);
|
|
162
172
|
}
|
|
163
173
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
): void {
|
|
169
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Process messages for this channel context. The messages here are contiguous messages for this context in a batch.
|
|
176
|
+
* @param messageCollection - The collection of messages to process.
|
|
177
|
+
*/
|
|
178
|
+
public processMessages(messageCollection: IRuntimeMessageCollection): void {
|
|
179
|
+
const { envelope, messagesContent, local } = messageCollection;
|
|
180
|
+
this.summarizerNode.invalidate(envelope.sequenceNumber);
|
|
170
181
|
|
|
171
182
|
if (this.isLoaded) {
|
|
172
|
-
this.services.deltaConnection.
|
|
183
|
+
this.services.deltaConnection.processMessages(messageCollection);
|
|
173
184
|
} else {
|
|
174
185
|
assert(!local, 0x195 /* "Remote channel must not be local when processing op" */);
|
|
175
|
-
assert(
|
|
176
|
-
|
|
177
|
-
|
|
186
|
+
assert(
|
|
187
|
+
this.pendingMessagesState !== undefined,
|
|
188
|
+
0xa6d /* pending messages queue is undefined */,
|
|
189
|
+
);
|
|
190
|
+
this.pendingMessagesState.messageCollections.push({
|
|
191
|
+
...messageCollection,
|
|
192
|
+
messagesContent: Array.from(messagesContent),
|
|
193
|
+
});
|
|
194
|
+
this.pendingMessagesState.pendingCount += messagesContent.length;
|
|
195
|
+
this.thresholdOpsCounter.sendIfMultiple(
|
|
196
|
+
"StorePendingOps",
|
|
197
|
+
this.pendingMessagesState.pendingCount,
|
|
198
|
+
);
|
|
178
199
|
}
|
|
179
200
|
}
|
|
180
201
|
|