@fluidframework/container-runtime 2.0.0-internal.2.2.1 → 2.0.0-internal.2.3.1
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/.eslintrc.js +19 -8
- package/dist/batchTracker.d.ts +1 -2
- package/dist/batchTracker.d.ts.map +1 -1
- package/dist/batchTracker.js.map +1 -1
- package/dist/blobManager.d.ts +45 -34
- package/dist/blobManager.d.ts.map +1 -1
- package/dist/blobManager.js +135 -102
- package/dist/blobManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +54 -8
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +143 -72
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +1 -1
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +6 -8
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +12 -9
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +41 -35
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +41 -20
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +205 -150
- package/dist/garbageCollection.js.map +1 -1
- package/dist/garbageCollectionConstants.d.ts +7 -3
- package/dist/garbageCollectionConstants.d.ts.map +1 -1
- package/dist/garbageCollectionConstants.js +10 -8
- package/dist/garbageCollectionConstants.js.map +1 -1
- package/dist/garbageCollectionTombstoneUtils.d.ts +14 -0
- package/dist/garbageCollectionTombstoneUtils.d.ts.map +1 -0
- package/dist/garbageCollectionTombstoneUtils.js +23 -0
- package/dist/garbageCollectionTombstoneUtils.js.map +1 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -5
- package/dist/index.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts +13 -1
- package/dist/opLifecycle/batchManager.d.ts.map +1 -1
- package/dist/opLifecycle/batchManager.js +35 -1
- package/dist/opLifecycle/batchManager.js.map +1 -1
- package/dist/opLifecycle/definitions.d.ts +25 -1
- package/dist/opLifecycle/definitions.d.ts.map +1 -1
- package/dist/opLifecycle/definitions.js.map +1 -1
- package/dist/opLifecycle/index.d.ts +2 -2
- package/dist/opLifecycle/index.d.ts.map +1 -1
- package/dist/opLifecycle/index.js +2 -1
- package/dist/opLifecycle/index.js.map +1 -1
- package/dist/opLifecycle/opCompressor.d.ts +1 -1
- package/dist/opLifecycle/opCompressor.d.ts.map +1 -1
- package/dist/opLifecycle/opCompressor.js +24 -10
- package/dist/opLifecycle/opCompressor.js.map +1 -1
- package/dist/opLifecycle/opDecompressor.d.ts +2 -1
- package/dist/opLifecycle/opDecompressor.d.ts.map +1 -1
- package/dist/opLifecycle/opDecompressor.js +30 -17
- package/dist/opLifecycle/opDecompressor.js.map +1 -1
- package/dist/opLifecycle/opSplitter.d.ts +34 -2
- package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
- package/dist/opLifecycle/opSplitter.js +114 -5
- package/dist/opLifecycle/opSplitter.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +5 -0
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +24 -14
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +17 -2
- package/dist/opLifecycle/remoteMessageProcessor.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/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +0 -1
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/scheduleManager.d.ts +0 -1
- package/dist/scheduleManager.d.ts.map +1 -1
- package/dist/scheduleManager.js +9 -20
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summarizer.d.ts +0 -1
- package/dist/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +2 -1
- package/dist/summarizer.js.map +1 -1
- package/dist/summarizerTypes.d.ts +1 -0
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryFormat.d.ts.map +1 -1
- package/dist/summaryFormat.js +1 -2
- package/dist/summaryFormat.js.map +1 -1
- package/lib/batchTracker.d.ts +1 -2
- package/lib/batchTracker.d.ts.map +1 -1
- package/lib/batchTracker.js.map +1 -1
- package/lib/blobManager.d.ts +45 -34
- package/lib/blobManager.d.ts.map +1 -1
- package/lib/blobManager.js +137 -104
- package/lib/blobManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +54 -8
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +140 -69
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +1 -1
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +7 -9
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +12 -9
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +44 -38
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +41 -20
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +201 -146
- package/lib/garbageCollection.js.map +1 -1
- package/lib/garbageCollectionConstants.d.ts +7 -3
- package/lib/garbageCollectionConstants.d.ts.map +1 -1
- package/lib/garbageCollectionConstants.js +9 -7
- package/lib/garbageCollectionConstants.js.map +1 -1
- package/lib/garbageCollectionTombstoneUtils.d.ts +14 -0
- package/lib/garbageCollectionTombstoneUtils.d.ts.map +1 -0
- package/lib/garbageCollectionTombstoneUtils.js +19 -0
- package/lib/garbageCollectionTombstoneUtils.js.map +1 -0
- package/lib/index.d.ts +1 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -2
- package/lib/index.js.map +1 -1
- package/lib/opLifecycle/batchManager.d.ts +13 -1
- package/lib/opLifecycle/batchManager.d.ts.map +1 -1
- package/lib/opLifecycle/batchManager.js +35 -1
- package/lib/opLifecycle/batchManager.js.map +1 -1
- package/lib/opLifecycle/definitions.d.ts +25 -1
- package/lib/opLifecycle/definitions.d.ts.map +1 -1
- package/lib/opLifecycle/definitions.js.map +1 -1
- package/lib/opLifecycle/index.d.ts +2 -2
- package/lib/opLifecycle/index.d.ts.map +1 -1
- package/lib/opLifecycle/index.js +1 -1
- package/lib/opLifecycle/index.js.map +1 -1
- package/lib/opLifecycle/opCompressor.d.ts +1 -1
- package/lib/opLifecycle/opCompressor.d.ts.map +1 -1
- package/lib/opLifecycle/opCompressor.js +24 -10
- package/lib/opLifecycle/opCompressor.js.map +1 -1
- package/lib/opLifecycle/opDecompressor.d.ts +2 -1
- package/lib/opLifecycle/opDecompressor.d.ts.map +1 -1
- package/lib/opLifecycle/opDecompressor.js +30 -17
- package/lib/opLifecycle/opDecompressor.js.map +1 -1
- package/lib/opLifecycle/opSplitter.d.ts +34 -2
- package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
- package/lib/opLifecycle/opSplitter.js +112 -4
- package/lib/opLifecycle/opSplitter.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +5 -0
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +24 -14
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +17 -2
- package/lib/opLifecycle/remoteMessageProcessor.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/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +0 -1
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/scheduleManager.d.ts +0 -1
- package/lib/scheduleManager.d.ts.map +1 -1
- package/lib/scheduleManager.js +9 -20
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summarizer.d.ts +0 -1
- package/lib/summarizer.d.ts.map +1 -1
- package/lib/summarizer.js +2 -1
- package/lib/summarizer.js.map +1 -1
- package/lib/summarizerTypes.d.ts +1 -0
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryFormat.d.ts.map +1 -1
- package/lib/summaryFormat.js +1 -2
- package/lib/summaryFormat.js.map +1 -1
- package/package.json +20 -19
- package/src/batchTracker.ts +1 -1
- package/src/blobManager.ts +159 -111
- package/src/containerRuntime.ts +202 -73
- package/src/dataStoreContext.ts +15 -16
- package/src/dataStores.ts +61 -45
- package/src/garbageCollection.ts +258 -183
- package/src/garbageCollectionConstants.ts +10 -7
- package/src/garbageCollectionTombstoneUtils.ts +28 -0
- package/src/index.ts +2 -5
- package/src/opLifecycle/batchManager.ts +59 -1
- package/src/opLifecycle/definitions.ts +27 -1
- package/src/opLifecycle/index.ts +2 -1
- package/src/opLifecycle/opCompressor.ts +29 -12
- package/src/opLifecycle/opDecompressor.ts +39 -18
- package/src/opLifecycle/opSplitter.ts +141 -7
- package/src/opLifecycle/outbox.ts +32 -16
- package/src/opLifecycle/remoteMessageProcessor.ts +19 -3
- package/src/packageVersion.ts +1 -1
- package/src/runningSummarizer.ts +0 -1
- package/src/scheduleManager.ts +19 -30
- package/src/summarizer.ts +1 -1
- package/src/summarizerTypes.ts +1 -0
- package/src/summaryFormat.ts +1 -2
package/src/dataStores.ts
CHANGED
|
@@ -34,7 +34,6 @@ import {
|
|
|
34
34
|
create404Response,
|
|
35
35
|
createResponseError,
|
|
36
36
|
responseToException,
|
|
37
|
-
packagePathToTelemetryProperty,
|
|
38
37
|
SummaryTreeBuilder,
|
|
39
38
|
} from "@fluidframework/runtime-utils";
|
|
40
39
|
import { ChildLogger, loggerToMonitoringContext, LoggingError, MonitoringContext, TelemetryDataTag } from "@fluidframework/telemetry-utils";
|
|
@@ -42,9 +41,9 @@ import { AttachState } from "@fluidframework/container-definitions";
|
|
|
42
41
|
import { BlobCacheStorageService, buildSnapshotTree } from "@fluidframework/driver-utils";
|
|
43
42
|
import { assert, Lazy, LazyPromise } from "@fluidframework/common-utils";
|
|
44
43
|
import { v4 as uuid } from "uuid";
|
|
45
|
-
import { GCDataBuilder, unpackChildNodesUsedRoutes } from "@fluidframework/garbage-collector";
|
|
44
|
+
import { GCDataBuilder, unpackChildNodesGCDetails, unpackChildNodesUsedRoutes } from "@fluidframework/garbage-collector";
|
|
46
45
|
import { DataStoreContexts } from "./dataStoreContexts";
|
|
47
|
-
import { ContainerRuntime } from "./containerRuntime";
|
|
46
|
+
import { ContainerRuntime, defaultRuntimeHeaderData, RuntimeHeaderData, TombstoneResponseHeaderKey } from "./containerRuntime";
|
|
48
47
|
import {
|
|
49
48
|
FluidDataStoreContext,
|
|
50
49
|
RemoteFluidDataStoreContext,
|
|
@@ -55,8 +54,9 @@ import {
|
|
|
55
54
|
import { IContainerRuntimeMetadata, nonDataStorePaths, rootHasIsolatedChannels } from "./summaryFormat";
|
|
56
55
|
import { IDataStoreAliasMessage, isDataStoreAliasMessage } from "./dataStore";
|
|
57
56
|
import { GCNodeType } from "./garbageCollection";
|
|
58
|
-
import {
|
|
57
|
+
import { throwOnTombstoneLoadKey } from "./garbageCollectionConstants";
|
|
59
58
|
import { summarizerClientType } from "./summarizerClientElection";
|
|
59
|
+
import { sendGCTombstoneEvent } from "./garbageCollectionTombstoneUtils";
|
|
60
60
|
|
|
61
61
|
type PendingAliasResolve = (success: boolean) => void;
|
|
62
62
|
|
|
@@ -85,7 +85,7 @@ export class DataStores implements IDisposable {
|
|
|
85
85
|
// root data stores that are added.
|
|
86
86
|
private dataStoresSinceLastGC: string[] = [];
|
|
87
87
|
/** If true, throw an error when a tombstone data store is retrieved. */
|
|
88
|
-
private readonly
|
|
88
|
+
private readonly throwOnTombstoneLoad: boolean;
|
|
89
89
|
// The handle to the container runtime. This is used mainly for GC purposes to represent outbound reference from
|
|
90
90
|
// the container runtime to other nodes.
|
|
91
91
|
private readonly containerRuntimeHandle: IFluidHandle;
|
|
@@ -99,7 +99,7 @@ export class DataStores implements IDisposable {
|
|
|
99
99
|
(id: string, createParam: CreateChildSummarizerNodeParam) => CreateChildSummarizerNodeFn,
|
|
100
100
|
private readonly deleteChildSummarizerNodeFn: (id: string) => void,
|
|
101
101
|
baseLogger: ITelemetryBaseLogger,
|
|
102
|
-
getBaseGCDetails: () => Promise<
|
|
102
|
+
getBaseGCDetails: () => Promise<IGarbageCollectionDetailsBase>,
|
|
103
103
|
private readonly gcNodeUpdated: (
|
|
104
104
|
nodePath: string, timestampMs: number, packagePath?: readonly string[]) => void,
|
|
105
105
|
private readonly aliasMap: Map<string, string>,
|
|
@@ -109,7 +109,8 @@ export class DataStores implements IDisposable {
|
|
|
109
109
|
this.containerRuntimeHandle = new FluidObjectHandle(this.runtime, "/", this.runtime.IFluidHandleContext);
|
|
110
110
|
|
|
111
111
|
const baseGCDetailsP = new LazyPromise(async () => {
|
|
112
|
-
|
|
112
|
+
const baseGCDetails = await getBaseGCDetails();
|
|
113
|
+
return unpackChildNodesGCDetails(baseGCDetails);
|
|
113
114
|
});
|
|
114
115
|
// Returns the base GC details for the data store with the given id.
|
|
115
116
|
const dataStoreBaseGCDetails = async (dataStoreId: string) => {
|
|
@@ -117,8 +118,8 @@ export class DataStores implements IDisposable {
|
|
|
117
118
|
return baseGCDetails.get(dataStoreId);
|
|
118
119
|
};
|
|
119
120
|
// Tombstone should only throw when the feature flag is enabled and the client isn't a summarizer
|
|
120
|
-
this.
|
|
121
|
-
this.mc.config.getBoolean(
|
|
121
|
+
this.throwOnTombstoneLoad =
|
|
122
|
+
this.mc.config.getBoolean(throwOnTombstoneLoadKey) === true &&
|
|
122
123
|
this.runtime.clientDetails.type !== summarizerClientType;
|
|
123
124
|
|
|
124
125
|
// Extract stores stored inside the snapshot
|
|
@@ -429,8 +430,10 @@ export class DataStores implements IDisposable {
|
|
|
429
430
|
);
|
|
430
431
|
}
|
|
431
432
|
|
|
432
|
-
public async getDataStore(id: string,
|
|
433
|
-
const
|
|
433
|
+
public async getDataStore(id: string, requestHeaderData: RuntimeHeaderData): Promise<FluidDataStoreContext> {
|
|
434
|
+
const headerData = { ...defaultRuntimeHeaderData, ...requestHeaderData };
|
|
435
|
+
|
|
436
|
+
const context = await this.contexts.getBoundOrRemoted(id, headerData.wait);
|
|
434
437
|
const request = { url: id };
|
|
435
438
|
if (context === undefined) {
|
|
436
439
|
// The requested data store does not exits. Throw a 404 response exception.
|
|
@@ -438,18 +441,28 @@ export class DataStores implements IDisposable {
|
|
|
438
441
|
}
|
|
439
442
|
|
|
440
443
|
if (context.tombstoned) {
|
|
444
|
+
const shouldFail = this.throwOnTombstoneLoad && !headerData.allowTombstone;
|
|
445
|
+
|
|
441
446
|
// The requested data store is removed by gc. Create a 404 gc response exception.
|
|
442
|
-
const error = responseToException(createResponseError(
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
447
|
+
const error = responseToException(createResponseError(
|
|
448
|
+
404,
|
|
449
|
+
"DataStore was deleted",
|
|
450
|
+
request,
|
|
451
|
+
{ [TombstoneResponseHeaderKey]: true },
|
|
452
|
+
), request);
|
|
453
|
+
sendGCTombstoneEvent(
|
|
454
|
+
this.mc,
|
|
455
|
+
{
|
|
456
|
+
eventName: "GC_Tombstone_DataStore_Requested",
|
|
457
|
+
category: shouldFail ? "error" : "generic",
|
|
458
|
+
isSummarizerClient: this.runtime.clientDetails.type === summarizerClientType,
|
|
459
|
+
headers: JSON.stringify(requestHeaderData),
|
|
460
|
+
},
|
|
461
|
+
context.isLoaded ? context.packagePath : undefined,
|
|
462
|
+
error,
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
if (shouldFail) {
|
|
453
466
|
throw error;
|
|
454
467
|
}
|
|
455
468
|
}
|
|
@@ -624,11 +637,6 @@ export class DataStores implements IDisposable {
|
|
|
624
637
|
// Verify that the used routes are correct.
|
|
625
638
|
for (const [id] of usedDataStoreRoutes) {
|
|
626
639
|
assert(this.contexts.has(id), 0x167 /* "Used route does not belong to any known data store" */);
|
|
627
|
-
|
|
628
|
-
// Revive datastores regardless of whether or not tombstone the tombstone flag is flipped
|
|
629
|
-
const dataStore = this.contexts.get(id);
|
|
630
|
-
assert(dataStore !== undefined, 0x46e /* No data store retrieved with specified id */);
|
|
631
|
-
dataStore.setTombstone(false /* tombstone */);
|
|
632
640
|
}
|
|
633
641
|
|
|
634
642
|
// Update the used routes in each data store. Used routes is empty for unused data stores.
|
|
@@ -638,13 +646,10 @@ export class DataStores implements IDisposable {
|
|
|
638
646
|
}
|
|
639
647
|
|
|
640
648
|
/**
|
|
641
|
-
* This is called to update objects whose routes are unused. The unused objects are
|
|
642
|
-
* tombstones.
|
|
649
|
+
* This is called to update objects whose routes are unused. The unused objects are deleted.
|
|
643
650
|
* @param unusedRoutes - The routes that are unused in all data stores in this Container.
|
|
644
|
-
* @param tombstone - if true, the objects corresponding to unused routes are marked tombstones. Otherwise, they
|
|
645
|
-
* are deleted.
|
|
646
651
|
*/
|
|
647
|
-
public updateUnusedRoutes(unusedRoutes: string[]
|
|
652
|
+
public updateUnusedRoutes(unusedRoutes: string[]) {
|
|
648
653
|
for (const route of unusedRoutes) {
|
|
649
654
|
const pathParts = route.split("/");
|
|
650
655
|
// Delete data store only if its route (/datastoreId) is in unusedRoutes. We don't want to delete a data
|
|
@@ -654,19 +659,6 @@ export class DataStores implements IDisposable {
|
|
|
654
659
|
}
|
|
655
660
|
const dataStoreId = pathParts[1];
|
|
656
661
|
assert(this.contexts.has(dataStoreId), 0x2d7 /* No data store with specified id */);
|
|
657
|
-
|
|
658
|
-
/**
|
|
659
|
-
* When running GC in tombstone mode, datastore contexts are tombstoned. Tombstoned datastore contexts
|
|
660
|
-
* enable testing scenarios with accessing deleted content without actually deleting content from
|
|
661
|
-
* summaries.
|
|
662
|
-
*/
|
|
663
|
-
if (tombstone) {
|
|
664
|
-
const dataStore = this.contexts.get(dataStoreId);
|
|
665
|
-
assert(dataStore !== undefined, 0x442 /* No data store retrieved with specified id */);
|
|
666
|
-
dataStore.setTombstone(true /* tombstone */);
|
|
667
|
-
continue;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
662
|
// Delete the contexts of unused data stores.
|
|
671
663
|
this.contexts.delete(dataStoreId);
|
|
672
664
|
// Delete the summarizer node of the unused data stores.
|
|
@@ -674,6 +666,30 @@ export class DataStores implements IDisposable {
|
|
|
674
666
|
}
|
|
675
667
|
}
|
|
676
668
|
|
|
669
|
+
/**
|
|
670
|
+
* This is called to update objects whose routes are tombstones. Tombstoned datastore contexts enable testing
|
|
671
|
+
* scenarios with accessing deleted content without actually deleting content from summaries.
|
|
672
|
+
* @param tombstonedRoutes - The routes that are tombstones in all data stores in this Container.
|
|
673
|
+
*/
|
|
674
|
+
public updateTombstonedRoutes(tombstonedRoutes: string[]) {
|
|
675
|
+
const tombstonedDataStoresSet: Set<string> = new Set();
|
|
676
|
+
for (const route of tombstonedRoutes) {
|
|
677
|
+
const pathParts = route.split("/");
|
|
678
|
+
// Tombstone data store only if its route (/datastoreId) is directly in tombstoneRoutes.
|
|
679
|
+
if (pathParts.length > 2) {
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
const dataStoreId = pathParts[1];
|
|
683
|
+
assert(this.contexts.has(dataStoreId), 0x510 /* No data store with specified id */);
|
|
684
|
+
tombstonedDataStoresSet.add(dataStoreId);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Update the used routes in each data store. Used routes is empty for unused data stores.
|
|
688
|
+
for (const [contextId, context] of this.contexts) {
|
|
689
|
+
context.setTombstone(tombstonedDataStoresSet.has(contextId));
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
677
693
|
/**
|
|
678
694
|
* Returns the outbound routes of this channel. Only root data stores are considered referenced and their paths are
|
|
679
695
|
* part of outbound routes.
|