@fluidframework/container-runtime 2.41.0-338401 → 2.42.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 +8 -0
- package/container-runtime.test-files.tar +0 -0
- package/dist/channelCollection.d.ts +1 -1
- package/dist/channelCollection.d.ts.map +1 -1
- package/dist/channelCollection.js +4 -4
- package/dist/channelCollection.js.map +1 -1
- package/dist/compatUtils.d.ts +22 -1
- package/dist/compatUtils.d.ts.map +1 -1
- package/dist/compatUtils.js +109 -7
- package/dist/compatUtils.js.map +1 -1
- package/dist/containerRuntime.d.ts +67 -28
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +332 -186
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts.map +1 -1
- package/dist/dataStore.js +5 -0
- package/dist/dataStore.js.map +1 -1
- package/dist/gc/garbageCollection.d.ts.map +1 -1
- package/dist/gc/garbageCollection.js +2 -0
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +1 -1
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/messageTypes.d.ts +5 -4
- package/dist/messageTypes.d.ts.map +1 -1
- package/dist/messageTypes.js.map +1 -1
- package/dist/metadata.d.ts +1 -1
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts +4 -0
- package/dist/opLifecycle/batchManager.d.ts.map +1 -1
- package/dist/opLifecycle/batchManager.js +7 -0
- package/dist/opLifecycle/batchManager.js.map +1 -1
- package/dist/opLifecycle/definitions.d.ts +6 -5
- package/dist/opLifecycle/definitions.d.ts.map +1 -1
- package/dist/opLifecycle/definitions.js.map +1 -1
- package/dist/opLifecycle/index.d.ts +1 -1
- package/dist/opLifecycle/index.d.ts.map +1 -1
- package/dist/opLifecycle/index.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.d.ts +9 -0
- package/dist/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +6 -4
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/opSerialization.d.ts +2 -1
- package/dist/opLifecycle/opSerialization.d.ts.map +1 -1
- package/dist/opLifecycle/opSerialization.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +1 -0
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +6 -1
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts +22 -5
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +34 -11
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/runCounter.d.ts.map +1 -1
- package/dist/runCounter.js +1 -1
- package/dist/runCounter.js.map +1 -1
- package/dist/summary/documentSchema.d.ts +42 -18
- package/dist/summary/documentSchema.d.ts.map +1 -1
- package/dist/summary/documentSchema.js +62 -52
- package/dist/summary/documentSchema.js.map +1 -1
- package/dist/summary/index.d.ts +1 -1
- package/dist/summary/index.d.ts.map +1 -1
- package/dist/summary/index.js.map +1 -1
- package/lib/channelCollection.d.ts +1 -1
- package/lib/channelCollection.d.ts.map +1 -1
- package/lib/channelCollection.js +4 -4
- package/lib/channelCollection.js.map +1 -1
- package/lib/compatUtils.d.ts +22 -1
- package/lib/compatUtils.d.ts.map +1 -1
- package/lib/compatUtils.js +102 -3
- package/lib/compatUtils.js.map +1 -1
- package/lib/containerRuntime.d.ts +67 -28
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +333 -188
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts.map +1 -1
- package/lib/dataStore.js +5 -0
- package/lib/dataStore.js.map +1 -1
- package/lib/gc/garbageCollection.d.ts.map +1 -1
- package/lib/gc/garbageCollection.js +2 -0
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +1 -1
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/messageTypes.d.ts +5 -4
- package/lib/messageTypes.d.ts.map +1 -1
- package/lib/messageTypes.js.map +1 -1
- package/lib/metadata.d.ts +1 -1
- package/lib/metadata.d.ts.map +1 -1
- package/lib/metadata.js.map +1 -1
- package/lib/opLifecycle/batchManager.d.ts +4 -0
- package/lib/opLifecycle/batchManager.d.ts.map +1 -1
- package/lib/opLifecycle/batchManager.js +7 -0
- package/lib/opLifecycle/batchManager.js.map +1 -1
- package/lib/opLifecycle/definitions.d.ts +6 -5
- package/lib/opLifecycle/definitions.d.ts.map +1 -1
- package/lib/opLifecycle/definitions.js.map +1 -1
- package/lib/opLifecycle/index.d.ts +1 -1
- package/lib/opLifecycle/index.d.ts.map +1 -1
- package/lib/opLifecycle/index.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.d.ts +9 -0
- package/lib/opLifecycle/opGroupingManager.d.ts.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +6 -4
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/opSerialization.d.ts +2 -1
- package/lib/opLifecycle/opSerialization.d.ts.map +1 -1
- package/lib/opLifecycle/opSerialization.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +1 -0
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +6 -1
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts +22 -5
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +34 -11
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/runCounter.d.ts.map +1 -1
- package/lib/runCounter.js +1 -1
- package/lib/runCounter.js.map +1 -1
- package/lib/summary/documentSchema.d.ts +42 -18
- package/lib/summary/documentSchema.d.ts.map +1 -1
- package/lib/summary/documentSchema.js +62 -52
- package/lib/summary/documentSchema.js.map +1 -1
- package/lib/summary/index.d.ts +1 -1
- package/lib/summary/index.d.ts.map +1 -1
- package/lib/summary/index.js.map +1 -1
- package/package.json +19 -19
- package/src/channelCollection.ts +4 -4
- package/src/compatUtils.ts +145 -10
- package/src/containerRuntime.ts +472 -225
- package/src/dataStore.ts +7 -0
- package/src/gc/garbageCollection.ts +2 -0
- package/src/gc/gcDefinitions.ts +1 -1
- package/src/index.ts +2 -1
- package/src/messageTypes.ts +12 -5
- package/src/metadata.ts +1 -1
- package/src/opLifecycle/batchManager.ts +8 -0
- package/src/opLifecycle/definitions.ts +7 -3
- package/src/opLifecycle/index.ts +1 -0
- package/src/opLifecycle/opGroupingManager.ts +17 -4
- package/src/opLifecycle/opSerialization.ts +6 -1
- package/src/opLifecycle/outbox.ts +8 -1
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +64 -20
- package/src/runCounter.ts +4 -1
- package/src/summary/documentSchema.ts +111 -86
- package/src/summary/index.ts +2 -1
package/src/dataStore.ts
CHANGED
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
AliasResult,
|
|
12
12
|
IDataStore,
|
|
13
13
|
IFluidDataStoreChannel,
|
|
14
|
+
// eslint-disable-next-line import/no-deprecated
|
|
15
|
+
type IContainerRuntimeBaseExperimental,
|
|
14
16
|
} from "@fluidframework/runtime-definitions/internal";
|
|
15
17
|
import {
|
|
16
18
|
ITelemetryLoggerExt,
|
|
@@ -78,6 +80,11 @@ class DataStore implements IDataStore {
|
|
|
78
80
|
if (alias.includes("/")) {
|
|
79
81
|
throw new UsageError(`The alias cannot contain slashes: '${alias}'`);
|
|
80
82
|
}
|
|
83
|
+
// eslint-disable-next-line import/no-deprecated
|
|
84
|
+
const runtime = this.parentContext.containerRuntime as IContainerRuntimeBaseExperimental;
|
|
85
|
+
if (runtime.inStagingMode === true) {
|
|
86
|
+
throw new UsageError("Cannot set aliases while in staging mode");
|
|
87
|
+
}
|
|
81
88
|
|
|
82
89
|
switch (this.aliasState) {
|
|
83
90
|
// If we're already aliasing, check if it's for the same value and return
|
|
@@ -1045,6 +1045,8 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
1045
1045
|
|
|
1046
1046
|
// Any time we log a Tombstone Loaded error (via Telemetry Tracker),
|
|
1047
1047
|
// we want to also trigger autorecovery to avoid the object being deleted
|
|
1048
|
+
// i.e. this will be preceded by one of these telemetry events;
|
|
1049
|
+
// GC_Tombstone_DataStore_Requested, GC_Tombstone_SubDataStore_Requested, GC_Tombstone_Blob_Requested
|
|
1048
1050
|
// Note: We don't need to trigger on "Changed" because any change will cause the object
|
|
1049
1051
|
// to be loaded by the Summarizer, and auto-recovery will be triggered then.
|
|
1050
1052
|
if (isTombstoned && reason === "Loaded") {
|
package/src/gc/gcDefinitions.ts
CHANGED
|
@@ -448,7 +448,7 @@ export interface IGarbageCollector {
|
|
|
448
448
|
* Returns true if this node has been deleted by GC during sweep phase.
|
|
449
449
|
*/
|
|
450
450
|
isNodeDeleted(nodePath: string): boolean;
|
|
451
|
-
setConnectionState(
|
|
451
|
+
setConnectionState(canSendOps: boolean, clientId?: string): void;
|
|
452
452
|
dispose(): void;
|
|
453
453
|
}
|
|
454
454
|
|
package/src/index.ts
CHANGED
|
@@ -95,7 +95,8 @@ export {
|
|
|
95
95
|
IDocumentSchemaCurrent,
|
|
96
96
|
currentDocumentVersionSchema,
|
|
97
97
|
DocumentsSchemaController,
|
|
98
|
-
|
|
98
|
+
IDocumentSchemaChangeMessageIncoming,
|
|
99
|
+
IDocumentSchemaChangeMessageOutgoing,
|
|
99
100
|
IDocumentSchemaFeatures,
|
|
100
101
|
ReadFluidDataStoreAttributes,
|
|
101
102
|
IFluidDataStoreAttributes0,
|
package/src/messageTypes.ts
CHANGED
|
@@ -14,7 +14,10 @@ import {
|
|
|
14
14
|
import { IDataStoreAliasMessage } from "./dataStore.js";
|
|
15
15
|
import { GarbageCollectionMessage } from "./gc/index.js";
|
|
16
16
|
import { IChunkedOp } from "./opLifecycle/index.js";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
type IDocumentSchemaChangeMessageIncoming,
|
|
19
|
+
type IDocumentSchemaChangeMessageOutgoing,
|
|
20
|
+
} from "./summary/index.js";
|
|
18
21
|
|
|
19
22
|
/**
|
|
20
23
|
* @legacy
|
|
@@ -112,9 +115,13 @@ export type ContainerRuntimeGCMessage = TypedContainerRuntimeMessage<
|
|
|
112
115
|
ContainerMessageType.GC,
|
|
113
116
|
GarbageCollectionMessage
|
|
114
117
|
>;
|
|
115
|
-
export type
|
|
118
|
+
export type InboundContainerRuntimeDocumentSchemaMessage = TypedContainerRuntimeMessage<
|
|
119
|
+
ContainerMessageType.DocumentSchemaChange,
|
|
120
|
+
IDocumentSchemaChangeMessageIncoming
|
|
121
|
+
>;
|
|
122
|
+
export type OutboundContainerRuntimeDocumentSchemaMessage = TypedContainerRuntimeMessage<
|
|
116
123
|
ContainerMessageType.DocumentSchemaChange,
|
|
117
|
-
|
|
124
|
+
IDocumentSchemaChangeMessageOutgoing
|
|
118
125
|
>;
|
|
119
126
|
|
|
120
127
|
/**
|
|
@@ -147,7 +154,7 @@ export type InboundContainerRuntimeMessage =
|
|
|
147
154
|
| ContainerRuntimeAliasMessage
|
|
148
155
|
| ContainerRuntimeIdAllocationMessage
|
|
149
156
|
| ContainerRuntimeGCMessage
|
|
150
|
-
|
|
|
157
|
+
| InboundContainerRuntimeDocumentSchemaMessage
|
|
151
158
|
// Inbound messages may include unknown types from other clients, so we include that as a special case here
|
|
152
159
|
| UnknownContainerRuntimeMessage;
|
|
153
160
|
|
|
@@ -163,7 +170,7 @@ export type LocalContainerRuntimeMessage =
|
|
|
163
170
|
| ContainerRuntimeAliasMessage
|
|
164
171
|
| ContainerRuntimeIdAllocationMessage
|
|
165
172
|
| ContainerRuntimeGCMessage
|
|
166
|
-
|
|
|
173
|
+
| OutboundContainerRuntimeDocumentSchemaMessage
|
|
167
174
|
// In rare cases (e.g. related to stashed ops) we could have a local message of an unknown type
|
|
168
175
|
| UnknownContainerRuntimeMessage;
|
|
169
176
|
|
package/src/metadata.ts
CHANGED
|
@@ -26,7 +26,7 @@ export function asEmptyBatchLocalOpMetadata(
|
|
|
26
26
|
*/
|
|
27
27
|
export interface IEmptyBatchMetadata {
|
|
28
28
|
// Set to true on localOpMetadata for empty batches
|
|
29
|
-
emptyBatch?:
|
|
29
|
+
emptyBatch?: true;
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
32
|
* Properties put on the op metadata object for batch tracking
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "@fluidframework/telemetry-utils/internal";
|
|
12
12
|
|
|
13
13
|
import { ICompressionRuntimeOptions } from "../compressionDefinitions.js";
|
|
14
|
+
import { isContainerMessageDirtyable } from "../containerRuntime.js";
|
|
14
15
|
import { asBatchMetadata, type IBatchMetadata } from "../metadata.js";
|
|
15
16
|
import type { IPendingMessage } from "../pendingStateManager.js";
|
|
16
17
|
|
|
@@ -168,6 +169,13 @@ export class BatchManager {
|
|
|
168
169
|
},
|
|
169
170
|
};
|
|
170
171
|
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Does this batch current contain user changes ("dirtyable" ops)?
|
|
175
|
+
*/
|
|
176
|
+
public containsUserChanges(): boolean {
|
|
177
|
+
return this.pendingBatch.some((message) => isContainerMessageDirtyable(message.runtimeOp));
|
|
178
|
+
}
|
|
171
179
|
}
|
|
172
180
|
|
|
173
181
|
const addBatchMetadata = (batch: LocalBatch, batchId?: BatchId): LocalBatch => {
|
|
@@ -7,6 +7,9 @@ import type { IBatchMessage } from "@fluidframework/container-definitions/intern
|
|
|
7
7
|
|
|
8
8
|
import { CompressionAlgorithms } from "../compressionDefinitions.js";
|
|
9
9
|
import type { LocalContainerRuntimeMessage } from "../messageTypes.js";
|
|
10
|
+
import type { IEmptyBatchMetadata } from "../metadata.js";
|
|
11
|
+
|
|
12
|
+
import type { EmptyGroupedBatch } from "./opGroupingManager.js";
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* Local Batch message, before it is virtualized and sent to the ordering service
|
|
@@ -34,7 +37,7 @@ export interface LocalBatchMessage {
|
|
|
34
37
|
staged?: boolean;
|
|
35
38
|
|
|
36
39
|
/**
|
|
37
|
-
* @deprecated Use
|
|
40
|
+
* @deprecated Use runtimeOp
|
|
38
41
|
*/
|
|
39
42
|
contents?: never; // To ensure we don't leave this one when converting from OutboundBatchMessage
|
|
40
43
|
}
|
|
@@ -44,8 +47,9 @@ export interface LocalBatchMessage {
|
|
|
44
47
|
*/
|
|
45
48
|
export interface LocalEmptyBatchPlaceholder {
|
|
46
49
|
metadata?: Record<string, unknown>;
|
|
47
|
-
localOpMetadata:
|
|
50
|
+
localOpMetadata: Required<IEmptyBatchMetadata>;
|
|
48
51
|
referenceSequenceNumber: number;
|
|
52
|
+
runtimeOp: EmptyGroupedBatch;
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
/**
|
|
@@ -59,7 +63,7 @@ export type OutboundBatchMessage = IBatchMessage & {
|
|
|
59
63
|
/**
|
|
60
64
|
* @deprecated Use contents
|
|
61
65
|
*/
|
|
62
|
-
|
|
66
|
+
runtimeOp?: never; // To ensure we don't leave this one when converting from LocalBatchMessage
|
|
63
67
|
};
|
|
64
68
|
|
|
65
69
|
/**
|
package/src/opLifecycle/index.ts
CHANGED
|
@@ -46,6 +46,16 @@ export interface OpGroupingManagerConfig {
|
|
|
46
46
|
readonly groupedBatchingEnabled: boolean;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* This is the type of an empty grouped batch we send over the wire
|
|
51
|
+
* We also put this in the placeholder for an empty batch in the PendingStateManager.
|
|
52
|
+
* But most places throughout the ContainerRuntime, this will not be used (just as Grouped Batches in general don't appear outside opLifecycle dir)
|
|
53
|
+
*/
|
|
54
|
+
export interface EmptyGroupedBatch {
|
|
55
|
+
type: typeof OpGroupingManager.groupedBatchOp;
|
|
56
|
+
contents: readonly unknown[];
|
|
57
|
+
}
|
|
58
|
+
|
|
49
59
|
export class OpGroupingManager {
|
|
50
60
|
static readonly groupedBatchOp = "groupedBatch";
|
|
51
61
|
private readonly logger: ITelemetryLoggerExt;
|
|
@@ -75,19 +85,22 @@ export class OpGroupingManager {
|
|
|
75
85
|
this.config.groupedBatchingEnabled,
|
|
76
86
|
0xa00 /* cannot create empty grouped batch when grouped batching is disabled */,
|
|
77
87
|
);
|
|
78
|
-
|
|
79
|
-
|
|
88
|
+
|
|
89
|
+
const emptyGroupedBatch: EmptyGroupedBatch = {
|
|
90
|
+
type: "groupedBatch",
|
|
80
91
|
contents: [],
|
|
81
|
-
}
|
|
92
|
+
};
|
|
93
|
+
const serializedOp = JSON.stringify(emptyGroupedBatch);
|
|
82
94
|
|
|
83
95
|
const placeholderMessage: LocalEmptyBatchPlaceholder = {
|
|
84
96
|
metadata: { batchId: resubmittingBatchId },
|
|
85
97
|
localOpMetadata: { emptyBatch: true },
|
|
86
98
|
referenceSequenceNumber,
|
|
99
|
+
runtimeOp: emptyGroupedBatch,
|
|
87
100
|
};
|
|
88
101
|
const outboundBatch: OutboundSingletonBatch = {
|
|
89
102
|
contentSizeInBytes: 0,
|
|
90
|
-
messages: [{ ...placeholderMessage, contents: serializedOp }],
|
|
103
|
+
messages: [{ ...placeholderMessage, runtimeOp: undefined, contents: serializedOp }],
|
|
91
104
|
referenceSequenceNumber,
|
|
92
105
|
};
|
|
93
106
|
return { outboundBatch, placeholderMessage };
|
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
|
|
13
13
|
import type { LocalContainerRuntimeMessage } from "../messageTypes.js";
|
|
14
14
|
|
|
15
|
+
import type { EmptyGroupedBatch } from "./opGroupingManager.js";
|
|
16
|
+
|
|
15
17
|
/**
|
|
16
18
|
* Takes an incoming runtime message (outer type "op"), JSON.parses the message's contents in place,
|
|
17
19
|
* if needed (old Loader does this for us).
|
|
@@ -34,7 +36,10 @@ export function ensureContentsDeserialized(mutableMessage: ISequencedDocumentMes
|
|
|
34
36
|
* @param toSerialize - op message to serialize. Also supports an array of ops.
|
|
35
37
|
*/
|
|
36
38
|
export function serializeOp(
|
|
37
|
-
toSerialize:
|
|
39
|
+
toSerialize:
|
|
40
|
+
| EmptyGroupedBatch
|
|
41
|
+
| LocalContainerRuntimeMessage
|
|
42
|
+
| LocalContainerRuntimeMessage[],
|
|
38
43
|
): string {
|
|
39
44
|
return JSON.stringify(
|
|
40
45
|
toSerialize,
|
|
@@ -236,6 +236,13 @@ export class Outbox {
|
|
|
236
236
|
return this.messageCount === 0;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
public containsUserChanges(): boolean {
|
|
240
|
+
return (
|
|
241
|
+
this.mainBatch.containsUserChanges() || this.blobAttachBatch.containsUserChanges()
|
|
242
|
+
// ID Allocation ops are not user changes
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
239
246
|
/**
|
|
240
247
|
* Detect whether batching has been interrupted by an incoming message being processed. In this case,
|
|
241
248
|
* we will flush the accumulated messages to account for that (if allowed) and create a new batch with the new
|
|
@@ -440,7 +447,7 @@ export class Outbox {
|
|
|
440
447
|
const staged = rawBatch.staged === true;
|
|
441
448
|
assert(
|
|
442
449
|
resubmitInfo === undefined || resubmitInfo.staged === staged,
|
|
443
|
-
|
|
450
|
+
0xba3 /* Mismatch in staged state tracking */,
|
|
444
451
|
);
|
|
445
452
|
|
|
446
453
|
const groupingEnabled =
|
package/src/packageVersion.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import Deque from "double-ended-queue";
|
|
16
16
|
import { v4 as uuid } from "uuid";
|
|
17
17
|
|
|
18
|
+
import { isContainerMessageDirtyable } from "./containerRuntime.js";
|
|
18
19
|
import {
|
|
19
20
|
type InboundContainerRuntimeMessage,
|
|
20
21
|
type InboundSequencedContainerRuntimeMessage,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
} from "./messageTypes.js";
|
|
23
24
|
import { asBatchMetadata, asEmptyBatchLocalOpMetadata } from "./metadata.js";
|
|
24
25
|
import {
|
|
26
|
+
EmptyGroupedBatch,
|
|
25
27
|
LocalBatchMessage,
|
|
26
28
|
getEffectiveBatchId,
|
|
27
29
|
BatchStartInfo,
|
|
@@ -48,9 +50,20 @@ export interface IPendingMessage {
|
|
|
48
50
|
* The original runtime op that was submitted to the ContainerRuntime
|
|
49
51
|
* Unless this pending message came from stashed content, in which case this was roundtripped through string
|
|
50
52
|
*/
|
|
51
|
-
runtimeOp?: LocalContainerRuntimeMessage | undefined; // Undefined for
|
|
53
|
+
runtimeOp?: LocalContainerRuntimeMessage | EmptyGroupedBatch | undefined; // Undefined for initial messages before parsing
|
|
54
|
+
/**
|
|
55
|
+
* Local Op Metadata that was passed to the ContainerRuntime when the op was submitted.
|
|
56
|
+
* This contains state needed when processing the ack, or to resubmit or rollback the op.
|
|
57
|
+
*/
|
|
52
58
|
localOpMetadata: unknown;
|
|
59
|
+
/**
|
|
60
|
+
* Metadata that was passed to the ContainerRuntime when the op was submitted.
|
|
61
|
+
* This is rarely used, and may be inspected by the service (as opposed to op contents which is opaque)
|
|
62
|
+
*/
|
|
53
63
|
opMetadata: Record<string, unknown> | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* Populated upon processing the op's ack, before moving the pending message to savedOps.
|
|
66
|
+
*/
|
|
54
67
|
sequenceNumber?: number;
|
|
55
68
|
/**
|
|
56
69
|
* Info about the batch this pending message belongs to, for validation and for computing the batchId on reconnect
|
|
@@ -134,9 +147,7 @@ export interface IRuntimeStateHandler {
|
|
|
134
147
|
}
|
|
135
148
|
|
|
136
149
|
function isEmptyBatchPendingMessage(message: IPendingMessageFromStash): boolean {
|
|
137
|
-
|
|
138
|
-
const content = JSON.parse(message.content);
|
|
139
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
150
|
+
const content = JSON.parse(message.content) as Partial<EmptyGroupedBatch>;
|
|
140
151
|
return content.type === "groupedBatch" && content.contents?.length === 0;
|
|
141
152
|
}
|
|
142
153
|
|
|
@@ -282,6 +293,25 @@ export class PendingStateManager implements IDisposable {
|
|
|
282
293
|
return this.pendingMessages.length + this.initialMessages.length;
|
|
283
294
|
}
|
|
284
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Checks the pending messages to see if any of them represent user changes (aka "dirtyable" messages)
|
|
298
|
+
*/
|
|
299
|
+
public hasPendingUserChanges(): boolean {
|
|
300
|
+
for (let i = 0; i < this.pendingMessages.length; i++) {
|
|
301
|
+
const element = this.pendingMessages.get(i);
|
|
302
|
+
if (
|
|
303
|
+
element?.runtimeOp !== undefined &&
|
|
304
|
+
isNotEmptyGroupedBatch(element) &&
|
|
305
|
+
isContainerMessageDirtyable(element.runtimeOp)
|
|
306
|
+
) {
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// Consider any initial messages to be user changes
|
|
311
|
+
// (it's an approximation since we would have to parse them to know for sure)
|
|
312
|
+
return this.initialMessages.length > 0;
|
|
313
|
+
}
|
|
314
|
+
|
|
285
315
|
/**
|
|
286
316
|
* The minimumPendingMessageSequenceNumber is the minimum of the first pending message and the first initial message.
|
|
287
317
|
*
|
|
@@ -357,12 +387,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
357
387
|
clientSequenceNumber: number | undefined,
|
|
358
388
|
staged: boolean,
|
|
359
389
|
): void {
|
|
360
|
-
|
|
361
|
-
this.onFlushBatch(
|
|
362
|
-
[placeholder satisfies Omit<LocalBatchMessage, "runtimeOp"> as LocalBatchMessage],
|
|
363
|
-
clientSequenceNumber,
|
|
364
|
-
staged,
|
|
365
|
-
);
|
|
390
|
+
this.onFlushBatch([placeholder], clientSequenceNumber, staged);
|
|
366
391
|
}
|
|
367
392
|
|
|
368
393
|
/**
|
|
@@ -375,7 +400,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
375
400
|
* @param ignoreBatchId - Whether to ignore the batchId in the batchStartInfo
|
|
376
401
|
*/
|
|
377
402
|
public onFlushBatch(
|
|
378
|
-
batch: LocalBatchMessage[],
|
|
403
|
+
batch: LocalBatchMessage[] | [LocalEmptyBatchPlaceholder],
|
|
379
404
|
clientSequenceNumber: number | undefined,
|
|
380
405
|
staged: boolean,
|
|
381
406
|
ignoreBatchId?: boolean,
|
|
@@ -441,7 +466,9 @@ export class PendingStateManager implements IDisposable {
|
|
|
441
466
|
// We still need to track it for resubmission.
|
|
442
467
|
try {
|
|
443
468
|
if (isEmptyBatchPendingMessage(nextMessage)) {
|
|
444
|
-
nextMessage.localOpMetadata = {
|
|
469
|
+
nextMessage.localOpMetadata = {
|
|
470
|
+
emptyBatch: true,
|
|
471
|
+
} satisfies LocalEmptyBatchPlaceholder["localOpMetadata"]; // equivalent to applyStashedOp for empty batch
|
|
445
472
|
patchbatchInfo(nextMessage); // Back compat
|
|
446
473
|
this.pendingMessages.push(nextMessage);
|
|
447
474
|
continue;
|
|
@@ -659,7 +686,10 @@ export class PendingStateManager implements IDisposable {
|
|
|
659
686
|
pendingMessage !== undefined,
|
|
660
687
|
0xa21 /* No pending message found as we start processing this remote batch */,
|
|
661
688
|
);
|
|
662
|
-
assert(
|
|
689
|
+
assert(
|
|
690
|
+
!pendingMessage.batchInfo.staged,
|
|
691
|
+
0xb85 /* Pending state mismatch, ack came in but next pending message is staged */,
|
|
692
|
+
);
|
|
663
693
|
|
|
664
694
|
// If this batch became empty on resubmit, batch.messages will be empty (but keyMessage is always set)
|
|
665
695
|
// and the next pending message should be an empty batch marker.
|
|
@@ -764,7 +794,9 @@ export class PendingStateManager implements IDisposable {
|
|
|
764
794
|
assert(batchMetadataFlag !== false, 0x41b /* We cannot process batches in chunks */);
|
|
765
795
|
|
|
766
796
|
// The next message starts a batch (possibly single-message), and we'll need its batchId.
|
|
767
|
-
const batchId =
|
|
797
|
+
const batchId = pendingMessage.batchInfo.ignoreBatchId
|
|
798
|
+
? undefined
|
|
799
|
+
: getEffectiveBatchId(pendingMessage);
|
|
768
800
|
|
|
769
801
|
const staged = pendingMessage.batchInfo.staged;
|
|
770
802
|
|
|
@@ -775,7 +807,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
775
807
|
}
|
|
776
808
|
|
|
777
809
|
assert(
|
|
778
|
-
pendingMessage.runtimeOp !== undefined,
|
|
810
|
+
pendingMessage.runtimeOp !== undefined && isNotEmptyGroupedBatch(pendingMessage),
|
|
779
811
|
0xb87 /* viableOp is only undefined for empty batches */,
|
|
780
812
|
);
|
|
781
813
|
|
|
@@ -811,7 +843,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
811
843
|
// check is >= because batch end may be last pending message
|
|
812
844
|
while (remainingPendingMessagesCount >= 0) {
|
|
813
845
|
assert(
|
|
814
|
-
pendingMessage.runtimeOp !== undefined,
|
|
846
|
+
pendingMessage.runtimeOp !== undefined && isNotEmptyGroupedBatch(pendingMessage),
|
|
815
847
|
0xb88 /* viableOp is only undefined for empty batches */,
|
|
816
848
|
);
|
|
817
849
|
batch.push({
|
|
@@ -854,14 +886,20 @@ export class PendingStateManager implements IDisposable {
|
|
|
854
886
|
}
|
|
855
887
|
|
|
856
888
|
/**
|
|
857
|
-
* Pops all staged batches, invoking the callback on each
|
|
889
|
+
* Pops all staged batches, invoking the callback on each constituent op in order (LIFO)
|
|
858
890
|
*/
|
|
859
|
-
public popStagedBatches(
|
|
891
|
+
public popStagedBatches(
|
|
892
|
+
callback: (
|
|
893
|
+
stagedMessage: IPendingMessage & { runtimeOp?: LocalContainerRuntimeMessage }, // exclude empty grouped batches
|
|
894
|
+
) => void,
|
|
895
|
+
): void {
|
|
860
896
|
while (!this.pendingMessages.isEmpty()) {
|
|
861
897
|
const stagedMessage = this.pendingMessages.peekBack();
|
|
862
898
|
if (stagedMessage?.batchInfo.staged === true) {
|
|
863
|
-
|
|
864
|
-
|
|
899
|
+
if (isNotEmptyGroupedBatch(stagedMessage)) {
|
|
900
|
+
callback(stagedMessage);
|
|
901
|
+
this.pendingMessages.pop();
|
|
902
|
+
}
|
|
865
903
|
} else {
|
|
866
904
|
break; // no more staged messages
|
|
867
905
|
}
|
|
@@ -885,3 +923,9 @@ function patchbatchInfo(
|
|
|
885
923
|
message.batchInfo = { clientId: uuid(), batchStartCsn: -1, length: -1, staged: false };
|
|
886
924
|
}
|
|
887
925
|
}
|
|
926
|
+
|
|
927
|
+
function isNotEmptyGroupedBatch(
|
|
928
|
+
message: IPendingMessage,
|
|
929
|
+
): message is IPendingMessage & { runtimeOp: LocalContainerRuntimeMessage } {
|
|
930
|
+
return message.runtimeOp !== undefined && message.runtimeOp.type !== "groupedBatch";
|
|
931
|
+
}
|
package/src/runCounter.ts
CHANGED
|
@@ -45,7 +45,10 @@ export class BatchRunCounter extends RunCounter {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
public run<T>(act: () => T, resubmitInfo?: BatchResubmitInfo): T {
|
|
48
|
-
assert(
|
|
48
|
+
assert(
|
|
49
|
+
this.#resubmitInfo === undefined,
|
|
50
|
+
0xba2 /* Reentrancy not allowed in BatchRunCounter */,
|
|
51
|
+
);
|
|
49
52
|
this.#resubmitInfo = resubmitInfo;
|
|
50
53
|
try {
|
|
51
54
|
return super.run(act);
|