@peerbit/shared-log 16.0.2 → 16.0.4
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/src/coordinate-persistence.d.ts +18 -1
- package/dist/src/coordinate-persistence.d.ts.map +1 -1
- package/dist/src/coordinate-persistence.js +61 -4
- package/dist/src/coordinate-persistence.js.map +1 -1
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +313 -201
- package/dist/src/index.js.map +1 -1
- package/package.json +13 -13
- package/src/coordinate-persistence.ts +66 -5
- package/src/index.ts +397 -258
package/src/index.ts
CHANGED
|
@@ -637,6 +637,13 @@ export type NativeBackboneCoordinateRollback<R extends "u32" | "u64"> = {
|
|
|
637
637
|
hashes: Set<string>;
|
|
638
638
|
entries: Map<string, ResidentCoordinateEntry<R>>;
|
|
639
639
|
generations: Map<string, number>;
|
|
640
|
+
/**
|
|
641
|
+
* Set by `settleResidentCoordinateSnapshot` once the token's holds on the
|
|
642
|
+
* mutation-generation map have been released. Load-bearing: it makes the
|
|
643
|
+
* settle idempotent, so a second settle of this token cannot consume a
|
|
644
|
+
* different token's hold on a shared hash.
|
|
645
|
+
*/
|
|
646
|
+
settled?: boolean;
|
|
640
647
|
};
|
|
641
648
|
|
|
642
649
|
export type RepairDispatchEntry<R extends "u32" | "u64"> =
|
|
@@ -2031,7 +2038,58 @@ export class SharedLog<
|
|
|
2031
2038
|
|
|
2032
2039
|
/* private _totalParticipation!: number; */
|
|
2033
2040
|
|
|
2034
|
-
// gid ->
|
|
2041
|
+
// gid -> set of publicKeyHashes known to hold that gid's entries.
|
|
2042
|
+
//
|
|
2043
|
+
// This is a suppression memo, not a source of truth. A present row lets the
|
|
2044
|
+
// rebalance and repair paths skip re-sending an entry to a peer that already
|
|
2045
|
+
// has it. Every read is `?.has(peer)` guarded, and a MISSING row always
|
|
2046
|
+
// means "assume nothing is known", which produces strictly MORE work --
|
|
2047
|
+
// redundant unchecked delivery in the rebalance loop, redundant queueing in
|
|
2048
|
+
// the repair planner -- and never a wrong prune, a wrong quorum, or data
|
|
2049
|
+
// loss. Losing a row costs bandwidth; keeping a stale row costs a little
|
|
2050
|
+
// memory. That asymmetry is what the rest of this note turns on.
|
|
2051
|
+
//
|
|
2052
|
+
// GROWTH SHAPE. Rows are released by `deleteGidPeerHistory` on the two prune
|
|
2053
|
+
// paths, by `removePeerFromGidPeerHistory` once a gid's last peer drops (the
|
|
2054
|
+
// routine disconnect outcome), by `rebalanceAll({ clearCache: true })`, and
|
|
2055
|
+
// wholesale on close/reset. Nothing on the TRIM path releases a row, so a
|
|
2056
|
+
// node that bounds its log with trim rather than prune accumulates one row
|
|
2057
|
+
// per distinct gid it has ever held. A gid names a graph, not an entry: an
|
|
2058
|
+
// entry with `meta.next` inherits `min(next.meta.gid)` (see
|
|
2059
|
+
// packages/log/src/entry-v0.ts), so document updates fold into the gid of
|
|
2060
|
+
// the first put and the row count tracks distinct chain roots -- distinct
|
|
2061
|
+
// document ids -- rather than entry count. Insert-only workloads mint a
|
|
2062
|
+
// fresh gid per append and so do grow one row per entry. Merges are a
|
|
2063
|
+
// smaller second source: when a join links two graphs the losing entries
|
|
2064
|
+
// keep their own `meta.gid` on disk, and shared-log does not subscribe to
|
|
2065
|
+
// the log's `onGidRemoved`, so the shadowed gid's row lingers too.
|
|
2066
|
+
//
|
|
2067
|
+
// WHY TRIM DOES NOT SIMPLY CALL `deleteGidPeerHistory` AS WELL. Both prune
|
|
2068
|
+
// callers delete a whole row from a single entry's gid, and under the
|
|
2069
|
+
// default hash domain that is correct by construction: the coordinate is a
|
|
2070
|
+
// pure function of the gid (replication-domain-hash.ts sha256s
|
|
2071
|
+
// `entry.meta.gid`), so identical gid => identical coordinates => identical
|
|
2072
|
+
// leader set => every local sibling of that gid is prune-eligible in the
|
|
2073
|
+
// same batch. The gid really is finished locally. Trim offers no such
|
|
2074
|
+
// guarantee. It walks oldest-first against a length/bytelength/age bound and
|
|
2075
|
+
// stops the instant the bound is met (packages/log/src/trim.ts); its only
|
|
2076
|
+
// use of gid is memoizing the caller's `canTrim` verdict, never grouping
|
|
2077
|
+
// deletes. So trim routinely removes the OLDEST entry of a gid while newer
|
|
2078
|
+
// siblings -- same gid, same coordinates, still local, still replicated --
|
|
2079
|
+
// remain. Copying the prune call onto trim would therefore delete a LIVE row
|
|
2080
|
+
// on the common path, paying for the freed memory in repeated re-delivery of
|
|
2081
|
+
// entries that are still here. That trade is not worth it.
|
|
2082
|
+
//
|
|
2083
|
+
// Bounding this correctly requires a per-gid count of locally held entries,
|
|
2084
|
+
// dropping the row only when it reaches zero -- a real reverse index, not a
|
|
2085
|
+
// one-line delete. Deliberately not built: the growth is bounded by distinct
|
|
2086
|
+
// gids, and the cheap version is a bandwidth regression.
|
|
2087
|
+
//
|
|
2088
|
+
// Existing, deliberate imprecision: under the time domain the coordinate is
|
|
2089
|
+
// `meta.clock.timestamp.wallTime` (replication-domain-time.ts) and is
|
|
2090
|
+
// gid-independent, so siblings of one gid can carry different leader sets
|
|
2091
|
+
// and prune's whole-row delete is already over-eager there. The cost is the
|
|
2092
|
+
// same bounded extra traffic, never a wrong prune.
|
|
2035
2093
|
_gidPeersHistory!: Map<string, Set<string>>;
|
|
2036
2094
|
|
|
2037
2095
|
private _onSubscriptionFn!: (arg: any) => any;
|
|
@@ -3114,9 +3172,23 @@ export class SharedLog<
|
|
|
3114
3172
|
};
|
|
3115
3173
|
for (const coordinate of intent.coordinates) {
|
|
3116
3174
|
rollback.hashes.add(coordinate.hash);
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3175
|
+
// Same hold-counted row shape the coordinator's own
|
|
3176
|
+
// snapshot writes: one hold per hash, released by the
|
|
3177
|
+
// settle after the replay consumes the token below.
|
|
3178
|
+
// RELIES ON `intent.coordinates` HOLDING UNIQUE HASHES —
|
|
3179
|
+
// it is built from the token's `hashes` Set (see
|
|
3180
|
+
// setNativeStrictDurableTransactionOperation). Holds are
|
|
3181
|
+
// taken per element here but released per unique hash by
|
|
3182
|
+
// the settle, so a duplicate would take two and release
|
|
3183
|
+
// one and retain that row forever. That is the safe
|
|
3184
|
+
// direction (a retained row, never a fail-open rollback),
|
|
3185
|
+
// but keep the source a Set.
|
|
3186
|
+
const row = mutationGenerations.get(coordinate.hash);
|
|
3187
|
+
const generation = (row?.generation ?? 0) + 1;
|
|
3188
|
+
mutationGenerations.set(coordinate.hash, {
|
|
3189
|
+
generation,
|
|
3190
|
+
holds: (row?.holds ?? 0) + 1,
|
|
3191
|
+
});
|
|
3120
3192
|
rollback.generations.set(coordinate.hash, generation);
|
|
3121
3193
|
if (coordinate.value) {
|
|
3122
3194
|
const number = (value: string) =>
|
|
@@ -3142,6 +3214,9 @@ export class SharedLog<
|
|
|
3142
3214
|
"",
|
|
3143
3215
|
rollback,
|
|
3144
3216
|
);
|
|
3217
|
+
// The replay fabricated these generations itself one turn
|
|
3218
|
+
// earlier and has now consumed them, so the rows are dead.
|
|
3219
|
+
this._coordinates.settleResidentCoordinateSnapshot(rollback);
|
|
3145
3220
|
}
|
|
3146
3221
|
for (const document of intent.documents) {
|
|
3147
3222
|
this.restoreNativeBackboneDocument({
|
|
@@ -10979,12 +11054,21 @@ export class SharedLog<
|
|
|
10979
11054
|
return rollbackLowerPublication(error);
|
|
10980
11055
|
}
|
|
10981
11056
|
if (!result) {
|
|
11057
|
+
// Abandon arm: the token was minted but nothing downstream can
|
|
11058
|
+
// roll it back from here.
|
|
11059
|
+
this._coordinates.settleResidentCoordinateSnapshot(
|
|
11060
|
+
lowerPublicationRollback?.coordinateEntries,
|
|
11061
|
+
);
|
|
10982
11062
|
return this.completeNativeStrictDurableTransaction(
|
|
10983
11063
|
nativeStrictTransaction,
|
|
10984
11064
|
).then(() => undefined);
|
|
10985
11065
|
}
|
|
10986
11066
|
return mapMaybePromise(result, async (prepared) => {
|
|
10987
11067
|
if (!prepared) {
|
|
11068
|
+
// Abandon arm: same shape as the `!result` arm above.
|
|
11069
|
+
this._coordinates.settleResidentCoordinateSnapshot(
|
|
11070
|
+
lowerPublicationRollback?.coordinateEntries,
|
|
11071
|
+
);
|
|
10988
11072
|
await this.completeNativeStrictDurableTransaction(
|
|
10989
11073
|
nativeStrictTransaction,
|
|
10990
11074
|
);
|
|
@@ -11018,6 +11102,11 @@ export class SharedLog<
|
|
|
11018
11102
|
prepared.appendFacts.hash,
|
|
11019
11103
|
lowerPublicationRollback?.coordinateEntries,
|
|
11020
11104
|
);
|
|
11105
|
+
// Terminal: this is the last rollback consumer for the
|
|
11106
|
+
// token. A throw above leaves the row, which is safe.
|
|
11107
|
+
this._coordinates.settleResidentCoordinateSnapshot(
|
|
11108
|
+
lowerPublicationRollback?.coordinateEntries,
|
|
11109
|
+
);
|
|
11021
11110
|
for (const document of lowerPublicationRollback?.documents ?? []) {
|
|
11022
11111
|
this.restoreNativeBackboneDocument(document);
|
|
11023
11112
|
}
|
|
@@ -11088,6 +11177,14 @@ export class SharedLog<
|
|
|
11088
11177
|
} catch (error) {
|
|
11089
11178
|
return rollback(error);
|
|
11090
11179
|
}
|
|
11180
|
+
// Success seam. The last await inside the protected try is the
|
|
11181
|
+
// finalizer acknowledge; `finish()` is synchronous, so no async
|
|
11182
|
+
// boundary separates the catch above from this statement and
|
|
11183
|
+
// `rollback` can no longer fire. Nothing downstream rolls back
|
|
11184
|
+
// (the retire below only warns), so the token is terminal here.
|
|
11185
|
+
this._coordinates.settleResidentCoordinateSnapshot(
|
|
11186
|
+
lowerPublicationRollback?.coordinateEntries,
|
|
11187
|
+
);
|
|
11091
11188
|
this.applyPreparedAppendFactsWithDeferredCoordinateDeletes(
|
|
11092
11189
|
prepared.appendFacts,
|
|
11093
11190
|
prepared.removed,
|
|
@@ -11544,6 +11641,12 @@ export class SharedLog<
|
|
|
11544
11641
|
}
|
|
11545
11642
|
return mapMaybePromise(result, async (prepared) => {
|
|
11546
11643
|
if (!prepared || !backboneAppend) {
|
|
11644
|
+
// Abandon arm: the token was minted inside
|
|
11645
|
+
// `prepareBackboneAppend` and nothing downstream of
|
|
11646
|
+
// this return can roll it back.
|
|
11647
|
+
this._coordinates.settleResidentCoordinateSnapshot(
|
|
11648
|
+
nativeCoordinateRollback,
|
|
11649
|
+
);
|
|
11547
11650
|
await this.completeNativeStrictDurableTransaction(
|
|
11548
11651
|
nativeStrictTransaction,
|
|
11549
11652
|
);
|
|
@@ -11635,6 +11738,10 @@ export class SharedLog<
|
|
|
11635
11738
|
prepared.appendFacts.hash,
|
|
11636
11739
|
rollbackCoordinateEntries,
|
|
11637
11740
|
);
|
|
11741
|
+
// Terminal: last rollback consumer for the token.
|
|
11742
|
+
this._coordinates.settleResidentCoordinateSnapshot(
|
|
11743
|
+
rollbackCoordinateEntries,
|
|
11744
|
+
);
|
|
11638
11745
|
} catch (rollbackError) {
|
|
11639
11746
|
rollbackFailures.push(rollbackError);
|
|
11640
11747
|
}
|
|
@@ -11711,6 +11818,12 @@ export class SharedLog<
|
|
|
11711
11818
|
} catch (error) {
|
|
11712
11819
|
return rollback(error);
|
|
11713
11820
|
}
|
|
11821
|
+
// Success seam: the finalizer acknowledge above is the last
|
|
11822
|
+
// await inside the protected try, so `rollback` can no
|
|
11823
|
+
// longer fire and the retire below only warns.
|
|
11824
|
+
this._coordinates.settleResidentCoordinateSnapshot(
|
|
11825
|
+
rollbackCoordinateEntries,
|
|
11826
|
+
);
|
|
11714
11827
|
this.applyPreparedAppendFactsWithDeferredCoordinateDeletes(
|
|
11715
11828
|
prepared.appendFacts,
|
|
11716
11829
|
prepared.removed,
|
|
@@ -12630,6 +12743,10 @@ export class SharedLog<
|
|
|
12630
12743
|
throw error;
|
|
12631
12744
|
}
|
|
12632
12745
|
if (!appended || !backboneAppends) {
|
|
12746
|
+
// Abandon arm: no consumer downstream of this return.
|
|
12747
|
+
this._coordinates.settleResidentCoordinateSnapshot(
|
|
12748
|
+
batchCoordinateRollback,
|
|
12749
|
+
);
|
|
12633
12750
|
await this.completeNativeStrictDurableTransaction(
|
|
12634
12751
|
nativeStrictTransaction,
|
|
12635
12752
|
);
|
|
@@ -12667,6 +12784,10 @@ export class SharedLog<
|
|
|
12667
12784
|
appended.appendFacts[0]?.hash ?? "",
|
|
12668
12785
|
batchCoordinateRollback,
|
|
12669
12786
|
);
|
|
12787
|
+
// Terminal: last rollback consumer for the batch token.
|
|
12788
|
+
this._coordinates.settleResidentCoordinateSnapshot(
|
|
12789
|
+
batchCoordinateRollback,
|
|
12790
|
+
);
|
|
12670
12791
|
} catch (rollbackError) {
|
|
12671
12792
|
rollbackFailures.push(rollbackError);
|
|
12672
12793
|
}
|
|
@@ -12798,6 +12919,11 @@ export class SharedLog<
|
|
|
12798
12919
|
} catch (error) {
|
|
12799
12920
|
return rollbackBatch(error);
|
|
12800
12921
|
}
|
|
12922
|
+
// Success seam: `rollbackBatch` has exactly one call site (the catch
|
|
12923
|
+
// above), and everything from here on escapes without any rollback.
|
|
12924
|
+
this._coordinates.settleResidentCoordinateSnapshot(
|
|
12925
|
+
batchCoordinateRollback,
|
|
12926
|
+
);
|
|
12801
12927
|
|
|
12802
12928
|
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
12803
12929
|
ownershipLifecycleController,
|
|
@@ -18907,292 +19033,305 @@ export class SharedLog<
|
|
|
18907
19033
|
reusableCoordinatePersistItems,
|
|
18908
19034
|
)
|
|
18909
19035
|
: undefined;
|
|
18910
|
-
|
|
18911
|
-
|
|
18912
|
-
|
|
18913
|
-
|
|
18914
|
-
|
|
18915
|
-
|
|
18916
|
-
|
|
18917
|
-
|
|
18918
|
-
|
|
18919
|
-
(committedHashes) => {
|
|
18920
|
-
nativePreparedCommittedHashes = new Set(committedHashes);
|
|
18921
|
-
},
|
|
18922
|
-
)
|
|
18923
|
-
: undefined;
|
|
18924
|
-
const finishNativePreparedCoordinates = async (properties: {
|
|
18925
|
-
nativePreparedCommitted: boolean;
|
|
18926
|
-
}) => {
|
|
18927
|
-
if (
|
|
18928
|
-
!properties.nativePreparedCommitted ||
|
|
18929
|
-
!nativePreparedCoordinateBatch
|
|
18930
|
-
) {
|
|
18931
|
-
return;
|
|
18932
|
-
}
|
|
18933
|
-
try {
|
|
18934
|
-
nativeBackboneOnlyPersistedHashes =
|
|
18935
|
-
await this._coordinates.finishBackboneOnlyReceiveCoordinateBatch(
|
|
18936
|
-
nativePreparedCoordinateBatch,
|
|
19036
|
+
try {
|
|
19037
|
+
const nativePreparedJoinCommit = canUsePreparedAppendFacts
|
|
19038
|
+
? this._coordinates.createNativeBackbonePreparedJoinCommit(
|
|
19039
|
+
nativeReceiveCoordinateBatch,
|
|
19040
|
+
(batch) => {
|
|
19041
|
+
nativePreparedCoordinateBatch = batch;
|
|
19042
|
+
},
|
|
19043
|
+
nativeCommitVerifyHashes,
|
|
19044
|
+
nativeCommitVerifyAllHashes,
|
|
18937
19045
|
syncProfile,
|
|
19046
|
+
(committedHashes) => {
|
|
19047
|
+
nativePreparedCommittedHashes = new Set(committedHashes);
|
|
19048
|
+
},
|
|
19049
|
+
)
|
|
19050
|
+
: undefined;
|
|
19051
|
+
const finishNativePreparedCoordinates = async (properties: {
|
|
19052
|
+
nativePreparedCommitted: boolean;
|
|
19053
|
+
}) => {
|
|
19054
|
+
if (
|
|
19055
|
+
!properties.nativePreparedCommitted ||
|
|
19056
|
+
!nativePreparedCoordinateBatch
|
|
19057
|
+
) {
|
|
19058
|
+
return;
|
|
19059
|
+
}
|
|
19060
|
+
try {
|
|
19061
|
+
nativeBackboneOnlyPersistedHashes =
|
|
19062
|
+
await this._coordinates.finishBackboneOnlyReceiveCoordinateBatch(
|
|
19063
|
+
nativePreparedCoordinateBatch,
|
|
19064
|
+
syncProfile,
|
|
19065
|
+
);
|
|
19066
|
+
nativePreparedCoordinatesFinished = true;
|
|
19067
|
+
} catch (error) {
|
|
19068
|
+
this._coordinates.rollbackBackboneOnlyReceiveCoordinateBatch(
|
|
19069
|
+
nativePreparedCoordinateBatch,
|
|
18938
19070
|
);
|
|
18939
|
-
|
|
18940
|
-
|
|
18941
|
-
|
|
18942
|
-
|
|
18943
|
-
|
|
18944
|
-
|
|
19071
|
+
throw error;
|
|
19072
|
+
}
|
|
19073
|
+
};
|
|
19074
|
+
const preparedAppendCanValidateAppend =
|
|
19075
|
+
canAppendAlreadyValidated ||
|
|
19076
|
+
(nativeCommitCanValidateAppend && !!nativePreparedJoinCommit);
|
|
19077
|
+
if (!preparedAppendCanValidateAppend) {
|
|
19078
|
+
canUsePreparedAppendFacts = false;
|
|
18945
19079
|
}
|
|
18946
|
-
|
|
18947
|
-
|
|
18948
|
-
|
|
18949
|
-
|
|
18950
|
-
|
|
18951
|
-
|
|
18952
|
-
|
|
18953
|
-
|
|
18954
|
-
|
|
18955
|
-
|
|
18956
|
-
? nativeCommitVerifyAllHashes
|
|
18957
|
-
? !!this._nativeBackbone?.graph
|
|
18958
|
-
.commitVerifiedAllPreparedRawReceiveJoinBatch ||
|
|
18959
|
-
!!this._nativeBackbone?.graph
|
|
18960
|
-
.commitVerifiedPreparedRawReceiveJoinBatch
|
|
19080
|
+
const nativePreparedJoinCommitValidatesPlan =
|
|
19081
|
+
!!nativePreparedJoinCommit &&
|
|
19082
|
+
(nativeCommitVerifyHashes && nativeCommitVerifyHashes.length > 0
|
|
19083
|
+
? nativeCommitVerifyAllHashes
|
|
19084
|
+
? !!this._nativeBackbone?.graph
|
|
19085
|
+
.commitVerifiedAllPreparedRawReceiveJoinBatch ||
|
|
19086
|
+
!!this._nativeBackbone?.graph
|
|
19087
|
+
.commitVerifiedPreparedRawReceiveJoinBatch
|
|
19088
|
+
: !!this._nativeBackbone?.graph
|
|
19089
|
+
.commitVerifiedPreparedRawReceiveJoinBatch
|
|
18961
19090
|
: !!this._nativeBackbone?.graph
|
|
18962
|
-
.
|
|
18963
|
-
|
|
18964
|
-
|
|
18965
|
-
|
|
18966
|
-
|
|
18967
|
-
|
|
18968
|
-
|
|
18969
|
-
|
|
18970
|
-
|
|
18971
|
-
|
|
18972
|
-
|
|
18973
|
-
|
|
18974
|
-
|
|
18975
|
-
|
|
18976
|
-
|
|
18977
|
-
|
|
19091
|
+
.commitPreparedRawReceiveJoinBatch);
|
|
19092
|
+
const trustedLowerLog = this.log as unknown as TrustedLowerLog<T>;
|
|
19093
|
+
// With a program-level onChange consumer the hash-only
|
|
19094
|
+
// sink is not used: the lower-log join dispatches the
|
|
19095
|
+
// change event (lazy entry views over the prepared raw
|
|
19096
|
+
// facts) so per-entry consumers observe every commit.
|
|
19097
|
+
const joinOnAppendHashes = programOnChange
|
|
19098
|
+
? undefined
|
|
19099
|
+
: onAppendHashes;
|
|
19100
|
+
const joinedPreparedFacts =
|
|
19101
|
+
canUsePreparedAppendFacts &&
|
|
19102
|
+
(await trustedLowerLog.joinPreparedAppendFactsBatch(
|
|
19103
|
+
preparedAppendFacts,
|
|
19104
|
+
{
|
|
19105
|
+
__peerbitEntriesAlreadyMissing: true,
|
|
19106
|
+
__peerbitCanAppendAlreadyValidated: true,
|
|
19107
|
+
__peerbitDeferIndexWrite: true,
|
|
19108
|
+
__peerbitOnAppendHashes: joinOnAppendHashes,
|
|
19109
|
+
__peerbitProfile: syncProfile,
|
|
19110
|
+
__peerbitNativePreparedJoinCommit: nativePreparedJoinCommit,
|
|
19111
|
+
__peerbitNativePreparedJoinCommitValidatesPlan:
|
|
19112
|
+
nativePreparedJoinCommitValidatesPlan,
|
|
19113
|
+
__peerbitOnPreparedJoinCommitted: nativePreparedJoinCommit
|
|
19114
|
+
? finishNativePreparedCoordinates
|
|
19115
|
+
: undefined,
|
|
19116
|
+
},
|
|
19117
|
+
));
|
|
19118
|
+
if (!joinedPreparedFacts) {
|
|
19119
|
+
await trustedLowerLog.join(materializeAllToMergeEntries(), {
|
|
19120
|
+
__peerbitBatchIndependent: true,
|
|
18978
19121
|
__peerbitEntriesAlreadyMissing: true,
|
|
18979
|
-
__peerbitCanAppendAlreadyValidated:
|
|
19122
|
+
__peerbitCanAppendAlreadyValidated:
|
|
19123
|
+
fallbackCanAppendAlreadyValidated,
|
|
18980
19124
|
__peerbitDeferIndexWrite: true,
|
|
18981
19125
|
__peerbitOnAppendHashes: joinOnAppendHashes,
|
|
18982
19126
|
__peerbitProfile: syncProfile,
|
|
18983
|
-
|
|
18984
|
-
|
|
18985
|
-
|
|
18986
|
-
|
|
18987
|
-
|
|
18988
|
-
|
|
18989
|
-
|
|
18990
|
-
|
|
18991
|
-
|
|
18992
|
-
|
|
18993
|
-
|
|
18994
|
-
|
|
18995
|
-
|
|
18996
|
-
|
|
18997
|
-
|
|
18998
|
-
|
|
18999
|
-
|
|
19000
|
-
|
|
19001
|
-
|
|
19002
|
-
|
|
19003
|
-
|
|
19004
|
-
|
|
19005
|
-
|
|
19006
|
-
|
|
19007
|
-
|
|
19008
|
-
const admittedHashes = joinedPreparedFacts
|
|
19009
|
-
? new Set(allToMergeHashes)
|
|
19010
|
-
: await this.log.hasMany(allToMergeHashes);
|
|
19011
|
-
admittedMergeHashes = admittedHashes;
|
|
19012
|
-
const admittedShallowEntries =
|
|
19013
|
-
admittedHashes.size === allToMergeShallowEntries.length
|
|
19014
|
-
? allToMergeShallowEntries
|
|
19015
|
-
: allToMergeShallowEntries.filter((entry) =>
|
|
19127
|
+
});
|
|
19128
|
+
}
|
|
19129
|
+
// A recursive lower-log join can resolve successfully while declining
|
|
19130
|
+
// an individual top-level entry (for example, when one of its parents
|
|
19131
|
+
// is temporarily unavailable). The public Log.join() API intentionally
|
|
19132
|
+
// does not expose that per-entry result, so make local index presence the
|
|
19133
|
+
// authority before publishing any SharedLog-side effects. A successful
|
|
19134
|
+
// prepared-facts batch is atomic and already proves every input hash.
|
|
19135
|
+
const admittedHashes = joinedPreparedFacts
|
|
19136
|
+
? new Set(allToMergeHashes)
|
|
19137
|
+
: await this.log.hasMany(allToMergeHashes);
|
|
19138
|
+
admittedMergeHashes = admittedHashes;
|
|
19139
|
+
const admittedShallowEntries =
|
|
19140
|
+
admittedHashes.size === allToMergeShallowEntries.length
|
|
19141
|
+
? allToMergeShallowEntries
|
|
19142
|
+
: allToMergeShallowEntries.filter((entry) =>
|
|
19143
|
+
admittedHashes.has(entry.hash),
|
|
19144
|
+
);
|
|
19145
|
+
if (!joinedPreparedFacts) {
|
|
19146
|
+
reusableCoordinatePersistItems =
|
|
19147
|
+
reusableCoordinatePersistItems.filter((item) =>
|
|
19148
|
+
admittedHashes.has(item.entry.hash),
|
|
19149
|
+
);
|
|
19150
|
+
coordinatePersistFallbackEntries =
|
|
19151
|
+
coordinatePersistFallbackEntries.filter((entry) =>
|
|
19016
19152
|
admittedHashes.has(entry.hash),
|
|
19017
19153
|
);
|
|
19018
|
-
|
|
19019
|
-
|
|
19020
|
-
reusableCoordinatePersistItems.
|
|
19021
|
-
|
|
19022
|
-
|
|
19023
|
-
|
|
19024
|
-
|
|
19025
|
-
|
|
19026
|
-
|
|
19027
|
-
|
|
19028
|
-
|
|
19029
|
-
|
|
19030
|
-
|
|
19031
|
-
|
|
19032
|
-
|
|
19033
|
-
|
|
19034
|
-
|
|
19035
|
-
|
|
19036
|
-
|
|
19037
|
-
|
|
19038
|
-
|
|
19039
|
-
|
|
19040
|
-
|
|
19041
|
-
|
|
19042
|
-
|
|
19043
|
-
|
|
19044
|
-
|
|
19045
|
-
|
|
19046
|
-
|
|
19047
|
-
|
|
19048
|
-
|
|
19049
|
-
|
|
19050
|
-
} else if (nativePreparedCoordinateBatch) {
|
|
19051
|
-
try {
|
|
19052
|
-
nativeBackboneOnlyPersistedHashes =
|
|
19053
|
-
await this._coordinates.finishBackboneOnlyReceiveCoordinateBatch(
|
|
19154
|
+
}
|
|
19155
|
+
const reusableCoordinatePersistItemCount =
|
|
19156
|
+
reusableCoordinatePersistItems.length;
|
|
19157
|
+
if (syncProfile) {
|
|
19158
|
+
emitSyncProfileDuration(syncProfile, lowerLogJoinStartedAt, {
|
|
19159
|
+
name: "sharedLog.receive.lowerLogJoin",
|
|
19160
|
+
component: "shared-log",
|
|
19161
|
+
entries: allToMerge.length,
|
|
19162
|
+
messages: 1,
|
|
19163
|
+
details: {
|
|
19164
|
+
hashOnlyEntryAdded,
|
|
19165
|
+
batchHashOnlyEntryAdded,
|
|
19166
|
+
programOnChange,
|
|
19167
|
+
joinedPreparedFacts,
|
|
19168
|
+
admittedEntries: admittedHashes.size,
|
|
19169
|
+
nativePreparedCoordinatesFinished,
|
|
19170
|
+
},
|
|
19171
|
+
});
|
|
19172
|
+
}
|
|
19173
|
+
const coordinatePersistStartedAt = syncProfileStart(syncProfile);
|
|
19174
|
+
if (nativePreparedCoordinatesFinished) {
|
|
19175
|
+
// The lower-log prepared receive transaction already finished
|
|
19176
|
+
// the native coordinate mirror/journal after entry-index commit.
|
|
19177
|
+
} else if (nativePreparedCoordinateBatch) {
|
|
19178
|
+
try {
|
|
19179
|
+
nativeBackboneOnlyPersistedHashes =
|
|
19180
|
+
await this._coordinates.finishBackboneOnlyReceiveCoordinateBatch(
|
|
19181
|
+
nativePreparedCoordinateBatch,
|
|
19182
|
+
syncProfile,
|
|
19183
|
+
);
|
|
19184
|
+
} catch (error) {
|
|
19185
|
+
this._coordinates.rollbackBackboneOnlyReceiveCoordinateBatch(
|
|
19054
19186
|
nativePreparedCoordinateBatch,
|
|
19055
|
-
syncProfile,
|
|
19056
19187
|
);
|
|
19057
|
-
|
|
19058
|
-
|
|
19059
|
-
|
|
19060
|
-
|
|
19061
|
-
|
|
19188
|
+
throw error;
|
|
19189
|
+
}
|
|
19190
|
+
} else {
|
|
19191
|
+
nativeBackboneOnlyPersistedHashes =
|
|
19192
|
+
await this._coordinates.persistBackboneOnlyReceiveCoordinateBatch(
|
|
19193
|
+
reusableCoordinatePersistItems,
|
|
19194
|
+
);
|
|
19062
19195
|
}
|
|
19063
|
-
|
|
19064
|
-
|
|
19065
|
-
|
|
19066
|
-
reusableCoordinatePersistItems,
|
|
19067
|
-
);
|
|
19068
|
-
}
|
|
19069
|
-
if (
|
|
19070
|
-
nativeBackboneOnlyPersistedHashes &&
|
|
19071
|
-
nativeBackboneOnlyPersistedHashes.size > 0
|
|
19072
|
-
) {
|
|
19073
|
-
for (
|
|
19074
|
-
let i = reusableCoordinatePersistItems.length - 1;
|
|
19075
|
-
i >= 0;
|
|
19076
|
-
i--
|
|
19196
|
+
if (
|
|
19197
|
+
nativeBackboneOnlyPersistedHashes &&
|
|
19198
|
+
nativeBackboneOnlyPersistedHashes.size > 0
|
|
19077
19199
|
) {
|
|
19078
|
-
|
|
19079
|
-
|
|
19080
|
-
|
|
19081
|
-
|
|
19200
|
+
for (
|
|
19201
|
+
let i = reusableCoordinatePersistItems.length - 1;
|
|
19202
|
+
i >= 0;
|
|
19203
|
+
i--
|
|
19082
19204
|
) {
|
|
19083
|
-
|
|
19205
|
+
if (
|
|
19206
|
+
nativeBackboneOnlyPersistedHashes.has(
|
|
19207
|
+
reusableCoordinatePersistItems[i]!.entry.hash,
|
|
19208
|
+
)
|
|
19209
|
+
) {
|
|
19210
|
+
reusableCoordinatePersistItems.splice(i, 1);
|
|
19211
|
+
}
|
|
19084
19212
|
}
|
|
19085
19213
|
}
|
|
19086
|
-
|
|
19087
|
-
|
|
19088
|
-
|
|
19089
|
-
|
|
19090
|
-
|
|
19091
|
-
|
|
19092
|
-
|
|
19093
|
-
|
|
19094
|
-
|
|
19095
|
-
|
|
19096
|
-
|
|
19097
|
-
|
|
19098
|
-
|
|
19099
|
-
|
|
19100
|
-
|
|
19101
|
-
|
|
19102
|
-
|
|
19103
|
-
|
|
19104
|
-
|
|
19105
|
-
|
|
19106
|
-
|
|
19107
|
-
|
|
19108
|
-
|
|
19109
|
-
|
|
19110
|
-
|
|
19111
|
-
|
|
19112
|
-
|
|
19113
|
-
}
|
|
19114
|
-
}
|
|
19115
|
-
|
|
19116
|
-
|
|
19117
|
-
|
|
19118
|
-
|
|
19119
|
-
|
|
19120
|
-
|
|
19121
|
-
|
|
19122
|
-
|
|
19123
|
-
|
|
19124
|
-
|
|
19125
|
-
|
|
19126
|
-
|
|
19127
|
-
|
|
19128
|
-
|
|
19214
|
+
if (reusableCoordinatePersistItems.length > 0) {
|
|
19215
|
+
await this._coordinates.persistCoordinatesBatch(
|
|
19216
|
+
reusableCoordinatePersistItems,
|
|
19217
|
+
);
|
|
19218
|
+
}
|
|
19219
|
+
if (coordinatePersistFallbackEntries.length > 0) {
|
|
19220
|
+
await this.planEntryLeaderBatch(
|
|
19221
|
+
coordinatePersistFallbackEntries.map((entry) => ({
|
|
19222
|
+
entry,
|
|
19223
|
+
replicas:
|
|
19224
|
+
receiveReplicaCounts.get(entry.hash) ??
|
|
19225
|
+
decodeReplicas(entry).getValue(this),
|
|
19226
|
+
options: { roleAge: 0, persist: {} },
|
|
19227
|
+
})),
|
|
19228
|
+
);
|
|
19229
|
+
}
|
|
19230
|
+
if (syncProfile) {
|
|
19231
|
+
emitSyncProfileDuration(syncProfile, coordinatePersistStartedAt, {
|
|
19232
|
+
name: "sharedLog.receive.coordinatePersist",
|
|
19233
|
+
component: "shared-log",
|
|
19234
|
+
entries: entriesToPersist.length,
|
|
19235
|
+
messages: 1,
|
|
19236
|
+
details: {
|
|
19237
|
+
reusedLeaderPlans: reusableCoordinatePersistItemCount,
|
|
19238
|
+
nativeBackboneOnly:
|
|
19239
|
+
nativeBackboneOnlyPersistedHashes?.size ?? 0,
|
|
19240
|
+
},
|
|
19241
|
+
});
|
|
19242
|
+
}
|
|
19243
|
+
for (const hash of admittedHashes) {
|
|
19244
|
+
confirmedHashes.add(hash);
|
|
19245
|
+
}
|
|
19246
|
+
const checkedPruneStartedAt = syncProfileStart(syncProfile);
|
|
19247
|
+
const ownershipChangedDuringReceive =
|
|
19248
|
+
!this.isReceiveOwnershipSnapshotStable(receiveOwnershipRevision);
|
|
19249
|
+
if (ownershipChangedDuringReceive) {
|
|
19250
|
+
const freshAuditRevision =
|
|
19251
|
+
this._instanceLifecycle?._receiveOwnershipRevision ?? 0;
|
|
19252
|
+
const armFreshAuditRetry = () => {
|
|
19253
|
+
for (const entry of admittedShallowEntries) {
|
|
19254
|
+
this.scheduleCheckedPruneRetry(
|
|
19255
|
+
{ entry, leaders: new Map() },
|
|
19256
|
+
receiveOwnershipLifecycleController,
|
|
19257
|
+
);
|
|
19258
|
+
}
|
|
19259
|
+
};
|
|
19260
|
+
try {
|
|
19261
|
+
await this.pruneJoinedEntriesNoLongerLed(
|
|
19262
|
+
admittedShallowEntries,
|
|
19263
|
+
{
|
|
19264
|
+
decodedReplicaCounts: receiveReplicaCounts,
|
|
19265
|
+
freshReceiveOwnerAudit: true,
|
|
19266
|
+
preserveExistingPruneOnLocalResult: true,
|
|
19267
|
+
profile: syncProfile,
|
|
19268
|
+
},
|
|
19129
19269
|
receiveOwnershipLifecycleController,
|
|
19130
19270
|
);
|
|
19271
|
+
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
19272
|
+
receiveOwnershipLifecycleController,
|
|
19273
|
+
);
|
|
19274
|
+
if (
|
|
19275
|
+
!this.isReceiveOwnershipSnapshotStable(freshAuditRevision)
|
|
19276
|
+
) {
|
|
19277
|
+
armFreshAuditRetry();
|
|
19278
|
+
}
|
|
19279
|
+
} catch {
|
|
19280
|
+
// The lower-log and coordinate commits are already durable. A
|
|
19281
|
+
// sender retry will filter these hashes as present, so retain a
|
|
19282
|
+
// bounded local obligation instead of failing the admitted receive.
|
|
19283
|
+
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
19284
|
+
receiveOwnershipLifecycleController,
|
|
19285
|
+
);
|
|
19286
|
+
armFreshAuditRetry();
|
|
19131
19287
|
}
|
|
19132
|
-
}
|
|
19133
|
-
try {
|
|
19288
|
+
} else {
|
|
19134
19289
|
await this.pruneJoinedEntriesNoLongerLed(
|
|
19135
19290
|
admittedShallowEntries,
|
|
19136
19291
|
{
|
|
19137
19292
|
decodedReplicaCounts: receiveReplicaCounts,
|
|
19138
|
-
freshReceiveOwnerAudit: true,
|
|
19139
19293
|
preserveExistingPruneOnLocalResult: true,
|
|
19294
|
+
reusableLeaderPlans: reusableCoordinatePlans,
|
|
19140
19295
|
profile: syncProfile,
|
|
19141
19296
|
},
|
|
19142
19297
|
receiveOwnershipLifecycleController,
|
|
19143
19298
|
);
|
|
19144
|
-
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
19145
|
-
receiveOwnershipLifecycleController,
|
|
19146
|
-
);
|
|
19147
|
-
if (
|
|
19148
|
-
!this.isReceiveOwnershipSnapshotStable(freshAuditRevision)
|
|
19149
|
-
) {
|
|
19150
|
-
armFreshAuditRetry();
|
|
19151
|
-
}
|
|
19152
|
-
} catch {
|
|
19153
|
-
// The lower-log and coordinate commits are already durable. A
|
|
19154
|
-
// sender retry will filter these hashes as present, so retain a
|
|
19155
|
-
// bounded local obligation instead of failing the admitted receive.
|
|
19156
|
-
this.throwIfReplicationOwnershipLifecycleInactive(
|
|
19157
|
-
receiveOwnershipLifecycleController,
|
|
19158
|
-
);
|
|
19159
|
-
armFreshAuditRetry();
|
|
19160
19299
|
}
|
|
19161
|
-
|
|
19162
|
-
|
|
19163
|
-
|
|
19164
|
-
|
|
19165
|
-
|
|
19166
|
-
|
|
19167
|
-
|
|
19168
|
-
|
|
19169
|
-
},
|
|
19170
|
-
receiveOwnershipLifecycleController,
|
|
19171
|
-
);
|
|
19172
|
-
}
|
|
19173
|
-
if (syncProfile) {
|
|
19174
|
-
emitSyncProfileDuration(syncProfile, checkedPruneStartedAt, {
|
|
19175
|
-
name: "sharedLog.receive.checkedPrune",
|
|
19176
|
-
component: "shared-log",
|
|
19177
|
-
entries: allToMerge.length,
|
|
19178
|
-
messages: 1,
|
|
19179
|
-
});
|
|
19180
|
-
}
|
|
19300
|
+
if (syncProfile) {
|
|
19301
|
+
emitSyncProfileDuration(syncProfile, checkedPruneStartedAt, {
|
|
19302
|
+
name: "sharedLog.receive.checkedPrune",
|
|
19303
|
+
component: "shared-log",
|
|
19304
|
+
entries: allToMerge.length,
|
|
19305
|
+
messages: 1,
|
|
19306
|
+
});
|
|
19307
|
+
}
|
|
19181
19308
|
|
|
19182
|
-
|
|
19183
|
-
|
|
19184
|
-
|
|
19185
|
-
|
|
19186
|
-
|
|
19187
|
-
|
|
19188
|
-
|
|
19189
|
-
|
|
19190
|
-
|
|
19191
|
-
|
|
19192
|
-
|
|
19193
|
-
|
|
19309
|
+
for (const plan of joinPlans) {
|
|
19310
|
+
plan.toDelete
|
|
19311
|
+
?.filter((entry) => admittedMergeHashes.has(entry.hash))
|
|
19312
|
+
.map((entry) =>
|
|
19313
|
+
this.pruneDebouncedFnAddIfNotKeeping({
|
|
19314
|
+
key: entry.hash,
|
|
19315
|
+
value: {
|
|
19316
|
+
entry,
|
|
19317
|
+
leaders: plan.leaders as Map<string, any>,
|
|
19318
|
+
},
|
|
19319
|
+
}),
|
|
19320
|
+
);
|
|
19321
|
+
}
|
|
19322
|
+
this.rebalanceParticipationDebounced?.call();
|
|
19323
|
+
} finally {
|
|
19324
|
+
// Settle seam for the receive token. Every consumer that can
|
|
19325
|
+
// roll it back runs inline before control leaves this block: the
|
|
19326
|
+
// prepared-join callback resolves during the join await, and the
|
|
19327
|
+
// late finish/rollback arm runs above. This `finally` is what
|
|
19328
|
+
// closes the abandon arms (no prepared-join commit, a declined
|
|
19329
|
+
// native commit, a downgrade to the plain join) without having
|
|
19330
|
+
// to enumerate them.
|
|
19331
|
+
this._coordinates.settleResidentCoordinateSnapshot(
|
|
19332
|
+
nativeReceiveCoordinateBatch?.rollbackCoordinateEntries,
|
|
19333
|
+
);
|
|
19194
19334
|
}
|
|
19195
|
-
this.rebalanceParticipationDebounced?.call();
|
|
19196
19335
|
}
|
|
19197
19336
|
|
|
19198
19337
|
for (const plan of joinPlans) {
|