@optimystic/db-p2p 1.0.0-beta.3 → 1.1.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/dist/src/cluster/block-transfer-service.d.ts +73 -1
- package/dist/src/cluster/block-transfer-service.d.ts.map +1 -1
- package/dist/src/cluster/block-transfer-service.js +70 -3
- package/dist/src/cluster/block-transfer-service.js.map +1 -1
- package/dist/src/cluster/block-transfer.d.ts +16 -1
- package/dist/src/cluster/block-transfer.d.ts.map +1 -1
- package/dist/src/cluster/block-transfer.js +73 -72
- package/dist/src/cluster/block-transfer.js.map +1 -1
- package/dist/src/cluster/client.d.ts +4 -6
- package/dist/src/cluster/client.d.ts.map +1 -1
- package/dist/src/cluster/client.js +8 -13
- package/dist/src/cluster/client.js.map +1 -1
- package/dist/src/cluster/cluster-policy.d.ts +2 -1
- package/dist/src/cluster/cluster-policy.d.ts.map +1 -1
- package/dist/src/cluster/cluster-policy.js.map +1 -1
- package/dist/src/cluster/cluster-repo.d.ts +155 -9
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +446 -58
- package/dist/src/cluster/cluster-repo.js.map +1 -1
- package/dist/src/cluster/commit-proof.js +2 -2
- package/dist/src/cluster/race-resolution.d.ts +51 -7
- package/dist/src/cluster/race-resolution.d.ts.map +1 -1
- package/dist/src/cluster/race-resolution.js +71 -7
- package/dist/src/cluster/race-resolution.js.map +1 -1
- package/dist/src/cluster/rebalance-monitor.d.ts +67 -5
- package/dist/src/cluster/rebalance-monitor.d.ts.map +1 -1
- package/dist/src/cluster/rebalance-monitor.js +120 -26
- package/dist/src/cluster/rebalance-monitor.js.map +1 -1
- package/dist/src/cluster/spread-on-churn.d.ts.map +1 -1
- package/dist/src/cluster/spread-on-churn.js +34 -47
- package/dist/src/cluster/spread-on-churn.js.map +1 -1
- package/dist/src/cohort-topic/host.d.ts.map +1 -1
- package/dist/src/cohort-topic/host.js +8 -6
- package/dist/src/cohort-topic/host.js.map +1 -1
- package/dist/src/cohort-topic/membership-source.js +1 -1
- package/dist/src/cohort-topic/membership-source.js.map +1 -1
- package/dist/src/cohort-topic/peer-codec.d.ts.map +1 -1
- package/dist/src/cohort-topic/peer-codec.js +5 -2
- package/dist/src/cohort-topic/peer-codec.js.map +1 -1
- package/dist/src/cohort-topic/stream-util.d.ts +24 -3
- package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
- package/dist/src/cohort-topic/stream-util.js +36 -3
- package/dist/src/cohort-topic/stream-util.js.map +1 -1
- package/dist/src/cohort-topic/topic-router.d.ts +9 -0
- package/dist/src/cohort-topic/topic-router.d.ts.map +1 -1
- package/dist/src/cohort-topic/topic-router.js +12 -2
- package/dist/src/cohort-topic/topic-router.js.map +1 -1
- package/dist/src/dispute/client.d.ts.map +1 -1
- package/dist/src/dispute/client.js +15 -3
- package/dist/src/dispute/client.js.map +1 -1
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/libp2p-key-network.d.ts +78 -17
- package/dist/src/libp2p-key-network.d.ts.map +1 -1
- package/dist/src/libp2p-key-network.js +234 -147
- package/dist/src/libp2p-key-network.js.map +1 -1
- package/dist/src/libp2p-node-base.d.ts +35 -1
- package/dist/src/libp2p-node-base.d.ts.map +1 -1
- package/dist/src/libp2p-node-base.js +151 -26
- package/dist/src/libp2p-node-base.js.map +1 -1
- package/dist/src/libp2p-node-rn.d.ts +2 -2
- package/dist/src/libp2p-node-rn.d.ts.map +1 -1
- package/dist/src/libp2p-node-rn.js.map +1 -1
- package/dist/src/libp2p-node.d.ts +2 -2
- package/dist/src/libp2p-node.d.ts.map +1 -1
- package/dist/src/libp2p-node.js.map +1 -1
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/logger.js +6 -1
- package/dist/src/logger.js.map +1 -1
- package/dist/src/matchmaking/query-transport.d.ts +3 -4
- package/dist/src/matchmaking/query-transport.d.ts.map +1 -1
- package/dist/src/matchmaking/query-transport.js +9 -9
- package/dist/src/matchmaking/query-transport.js.map +1 -1
- package/dist/src/network/network-manager-service.d.ts +10 -5
- package/dist/src/network/network-manager-service.d.ts.map +1 -1
- package/dist/src/network/network-manager-service.js +11 -2
- package/dist/src/network/network-manager-service.js.map +1 -1
- package/dist/src/network/relay-reservation.d.ts +170 -0
- package/dist/src/network/relay-reservation.d.ts.map +1 -0
- package/dist/src/network/relay-reservation.js +527 -0
- package/dist/src/network/relay-reservation.js.map +1 -0
- package/dist/src/pend-validation.d.ts +3 -2
- package/dist/src/pend-validation.d.ts.map +1 -1
- package/dist/src/pend-validation.js +3 -2
- package/dist/src/pend-validation.js.map +1 -1
- package/dist/src/protocol-client.d.ts +8 -1
- package/dist/src/protocol-client.d.ts.map +1 -1
- package/dist/src/protocol-client.js +11 -0
- package/dist/src/protocol-client.js.map +1 -1
- package/dist/src/reactivity/forwarder-host.d.ts +2 -1
- package/dist/src/reactivity/forwarder-host.d.ts.map +1 -1
- package/dist/src/reactivity/forwarder-host.js +2 -1
- package/dist/src/reactivity/forwarder-host.js.map +1 -1
- package/dist/src/reactivity/origination-manager.d.ts +3 -3
- package/dist/src/reactivity/origination-manager.js +3 -3
- package/dist/src/reactivity/recover-transport.d.ts +22 -14
- package/dist/src/reactivity/recover-transport.d.ts.map +1 -1
- package/dist/src/reactivity/recover-transport.js +32 -19
- package/dist/src/reactivity/recover-transport.js.map +1 -1
- package/dist/src/reactivity/subscription-manager.d.ts +2 -2
- package/dist/src/reactivity/topic-bytes.d.ts +6 -7
- package/dist/src/reactivity/topic-bytes.d.ts.map +1 -1
- package/dist/src/reactivity/topic-bytes.js +8 -9
- package/dist/src/reactivity/topic-bytes.js.map +1 -1
- package/dist/src/repo/client.d.ts +5 -2
- package/dist/src/repo/client.d.ts.map +1 -1
- package/dist/src/repo/client.js +49 -40
- package/dist/src/repo/client.js.map +1 -1
- package/dist/src/repo/cluster-coordinator.d.ts +96 -11
- package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
- package/dist/src/repo/cluster-coordinator.js +164 -25
- package/dist/src/repo/cluster-coordinator.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts +240 -122
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +627 -473
- package/dist/src/repo/coordinator-repo.js.map +1 -1
- package/dist/src/repo/i-under-replication-ledger.d.ts +87 -0
- package/dist/src/repo/i-under-replication-ledger.d.ts.map +1 -0
- package/dist/src/repo/i-under-replication-ledger.js +2 -0
- package/dist/src/repo/i-under-replication-ledger.js.map +1 -0
- package/dist/src/repo/kv-under-replication-ledger.d.ts +91 -0
- package/dist/src/repo/kv-under-replication-ledger.d.ts.map +1 -0
- package/dist/src/repo/kv-under-replication-ledger.js +287 -0
- package/dist/src/repo/kv-under-replication-ledger.js.map +1 -0
- package/dist/src/repo/responsibility.d.ts +37 -0
- package/dist/src/repo/responsibility.d.ts.map +1 -0
- package/dist/src/repo/responsibility.js +37 -0
- package/dist/src/repo/responsibility.js.map +1 -0
- package/dist/src/repo/service.d.ts +44 -15
- package/dist/src/repo/service.d.ts.map +1 -1
- package/dist/src/repo/service.js +64 -27
- package/dist/src/repo/service.js.map +1 -1
- package/dist/src/repo/stuck-reservation.d.ts +127 -0
- package/dist/src/repo/stuck-reservation.d.ts.map +1 -0
- package/dist/src/repo/stuck-reservation.js +179 -0
- package/dist/src/repo/stuck-reservation.js.map +1 -0
- package/dist/src/repo/under-replication-drain.d.ts +208 -0
- package/dist/src/repo/under-replication-drain.d.ts.map +1 -0
- package/dist/src/repo/under-replication-drain.js +461 -0
- package/dist/src/repo/under-replication-drain.js.map +1 -0
- package/dist/src/rn.d.ts +5 -0
- package/dist/src/rn.d.ts.map +1 -1
- package/dist/src/rn.js +5 -0
- package/dist/src/rn.js.map +1 -1
- package/dist/src/storage/block-latch.d.ts +3 -2
- package/dist/src/storage/block-latch.d.ts.map +1 -1
- package/dist/src/storage/block-latch.js +7 -4
- package/dist/src/storage/block-latch.js.map +1 -1
- package/dist/src/storage/block-storage.d.ts +45 -3
- package/dist/src/storage/block-storage.d.ts.map +1 -1
- package/dist/src/storage/block-storage.js +156 -9
- package/dist/src/storage/block-storage.js.map +1 -1
- package/dist/src/storage/i-block-storage.d.ts +57 -5
- package/dist/src/storage/i-block-storage.d.ts.map +1 -1
- package/dist/src/storage/i-block-storage.js.map +1 -1
- package/dist/src/storage/pending-claim.d.ts +167 -0
- package/dist/src/storage/pending-claim.d.ts.map +1 -0
- package/dist/src/storage/pending-claim.js +156 -0
- package/dist/src/storage/pending-claim.js.map +1 -0
- package/dist/src/storage/raw-store-codec.d.ts +13 -0
- package/dist/src/storage/raw-store-codec.d.ts.map +1 -1
- package/dist/src/storage/raw-store-codec.js +24 -3
- package/dist/src/storage/raw-store-codec.js.map +1 -1
- package/dist/src/storage/restoration-coordinator.d.ts.map +1 -1
- package/dist/src/storage/restoration-coordinator.js +2 -1
- package/dist/src/storage/restoration-coordinator.js.map +1 -1
- package/dist/src/storage/ring-shift-coordinator.d.ts.map +1 -1
- package/dist/src/storage/ring-shift-coordinator.js +3 -3
- package/dist/src/storage/ring-shift-coordinator.js.map +1 -1
- package/dist/src/storage/storage-repo.d.ts +122 -5
- package/dist/src/storage/storage-repo.d.ts.map +1 -1
- package/dist/src/storage/storage-repo.js +245 -68
- package/dist/src/storage/storage-repo.js.map +1 -1
- package/dist/src/storage/struct.d.ts +58 -0
- package/dist/src/storage/struct.d.ts.map +1 -1
- package/dist/src/testing/mesh-harness.d.ts +77 -11
- package/dist/src/testing/mesh-harness.d.ts.map +1 -1
- package/dist/src/testing/mesh-harness.js +226 -100
- package/dist/src/testing/mesh-harness.js.map +1 -1
- package/package.json +2 -2
- package/readme.md +27 -3
- package/src/cluster/block-transfer-service.ts +132 -4
- package/src/cluster/block-transfer.ts +79 -84
- package/src/cluster/client.ts +115 -119
- package/src/cluster/cluster-policy.ts +2 -1
- package/src/cluster/cluster-repo.ts +493 -65
- package/src/cluster/commit-proof.ts +2 -2
- package/src/cluster/race-resolution.ts +76 -7
- package/src/cluster/rebalance-monitor.ts +677 -539
- package/src/cluster/spread-on-churn.ts +34 -54
- package/src/cohort-topic/host.ts +8 -6
- package/src/cohort-topic/membership-source.ts +1 -1
- package/src/cohort-topic/peer-codec.ts +34 -31
- package/src/cohort-topic/stream-util.ts +39 -4
- package/src/cohort-topic/topic-router.ts +12 -2
- package/src/dispute/client.ts +17 -7
- package/src/index.ts +64 -59
- package/src/libp2p-key-network.ts +1377 -1260
- package/src/libp2p-node-base.ts +186 -27
- package/src/libp2p-node-rn.ts +31 -30
- package/src/libp2p-node.ts +37 -36
- package/src/logger.ts +6 -1
- package/src/matchmaking/query-transport.ts +9 -9
- package/src/network/network-manager-service.ts +16 -5
- package/src/network/relay-reservation.ts +636 -0
- package/src/pend-validation.ts +3 -2
- package/src/protocol-client.ts +13 -1
- package/src/reactivity/forwarder-host.ts +2 -1
- package/src/reactivity/origination-manager.ts +3 -3
- package/src/reactivity/recover-transport.ts +39 -22
- package/src/reactivity/subscription-manager.ts +2 -2
- package/src/reactivity/topic-bytes.ts +35 -37
- package/src/repo/client.ts +172 -173
- package/src/repo/cluster-coordinator.ts +183 -25
- package/src/repo/coordinator-repo.ts +656 -506
- package/src/repo/i-under-replication-ledger.ts +89 -0
- package/src/repo/kv-under-replication-ledger.ts +302 -0
- package/src/repo/responsibility.ts +48 -0
- package/src/repo/service.ts +76 -39
- package/src/repo/stuck-reservation.ts +224 -0
- package/src/repo/under-replication-drain.ts +582 -0
- package/src/rn.ts +74 -69
- package/src/storage/block-latch.ts +7 -4
- package/src/storage/block-storage.ts +161 -10
- package/src/storage/i-block-storage.ts +60 -5
- package/src/storage/pending-claim.ts +200 -0
- package/src/storage/raw-store-codec.ts +26 -3
- package/src/storage/restoration-coordinator.ts +212 -211
- package/src/storage/ring-shift-coordinator.ts +271 -271
- package/src/storage/storage-repo.ts +280 -72
- package/src/storage/struct.ts +58 -0
- package/src/testing/mesh-harness.ts +293 -112
package/dist/src/repo/service.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { pipe } from 'it-pipe';
|
|
2
2
|
import { decode as lpDecode, encode as lpEncode } from 'it-length-prefixed';
|
|
3
|
-
import { blockIdsForTransforms } from '@optimystic/db-core';
|
|
4
|
-
import {
|
|
3
|
+
import { LruMap, blockIdsForTransforms, routingKeyForBlock } from '@optimystic/db-core';
|
|
4
|
+
import { peerIdFromString } from '@libp2p/peer-id';
|
|
5
5
|
import { encodePeers } from './redirect.js';
|
|
6
6
|
import { MAX_BLOCK_MESSAGE_BYTES } from '../protocol-limits.js';
|
|
7
7
|
import { createLogger } from '../logger.js';
|
|
8
8
|
import { publishableAddrsForPeer } from '../peer-address-book.js';
|
|
9
9
|
import { createInboundStreamAuthorization } from '../inbound-authorization.js';
|
|
10
10
|
import { registerProtocolHandler } from '../network/register-protocol-handler.js';
|
|
11
|
+
import { RESPONSIBILITY_TTL_MS } from './responsibility.js';
|
|
11
12
|
export function repoService(init = {}) {
|
|
12
13
|
return (components) => new RepoService(components, init);
|
|
13
14
|
}
|
|
@@ -28,7 +29,7 @@ export class RepoService {
|
|
|
28
29
|
* The libp2p node, injected post-construction by the node wiring (see
|
|
29
30
|
* libp2p-node-base.ts, mirroring how `networkManager`/`fret` receive theirs).
|
|
30
31
|
* The libp2p `components.libp2p` proxy does NOT reliably resolve from inside a
|
|
31
|
-
* service at request time, so the redirect path resolves the network
|
|
32
|
+
* service at request time, so the redirect path resolves the key network,
|
|
32
33
|
* self identity, and connection addrs through this explicitly-set reference.
|
|
33
34
|
*/
|
|
34
35
|
libp2pRef;
|
|
@@ -40,6 +41,16 @@ export class RepoService {
|
|
|
40
41
|
* ingress point, instead of keeping it filterable as the single `peer-address-book` namespace.
|
|
41
42
|
*/
|
|
42
43
|
addressLog;
|
|
44
|
+
/**
|
|
45
|
+
* Per block key, the responsible peer ids the key network last named: one cohort lookup per block per
|
|
46
|
+
* minute on the inbound path, instead of one per request on top of the coordinator's own.
|
|
47
|
+
*
|
|
48
|
+
* NOTE: same TTL as `CoordinatorRepo`'s responsibility cache, deliberately not the same cache — the two
|
|
49
|
+
* sit on either side of the served-repo boundary. Their verdicts can therefore disagree about a block
|
|
50
|
+
* only inside the minute after its cohort changes; a request this check wrongly lets through is refused
|
|
51
|
+
* by the coordinator, and one it wrongly redirects is followed by the client to a member that serves it.
|
|
52
|
+
*/
|
|
53
|
+
responsibleIds = new LruMap(1000);
|
|
43
54
|
constructor(components, init = {}) {
|
|
44
55
|
this.components = components;
|
|
45
56
|
const computed = init.protocol ?? (init.protocolPrefix ?? '/db-p2p') + '/repo/1.0.0';
|
|
@@ -88,10 +99,15 @@ export class RepoService {
|
|
|
88
99
|
await this.components.registrar.unhandle(this.protocol);
|
|
89
100
|
this.running = false;
|
|
90
101
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
102
|
+
/**
|
|
103
|
+
* The key network the redirect check asks. Absent only before the node attaches it, which happens after
|
|
104
|
+
* `start()`; a request in that window is handled locally, where the served-repo proxy has no
|
|
105
|
+
* coordinator to hand it to yet either.
|
|
106
|
+
*/
|
|
107
|
+
getKeyNetwork() {
|
|
108
|
+
if (this.components.keyNetwork)
|
|
109
|
+
return this.components.keyNetwork;
|
|
110
|
+
return this.getLibp2p()?.keyNetwork;
|
|
95
111
|
}
|
|
96
112
|
getSelfId() {
|
|
97
113
|
if (this.components.peerId)
|
|
@@ -126,7 +142,7 @@ export class RepoService {
|
|
|
126
142
|
* - pend → blockIdsForTransforms(transforms)[0]
|
|
127
143
|
* - cancel → actionRef.blockIds[0]
|
|
128
144
|
* - commit → blockIds[0] (CoordinatorRepo.commit anchors consensus on
|
|
129
|
-
*
|
|
145
|
+
* resolveCohort(blockIds[0]) / executeClusterTransaction(blockIds[0]) and guards
|
|
130
146
|
* with verifyResponsibility(blockIds) — NOT tailId; for a per-block commit batch
|
|
131
147
|
* whose blockIds[0] !== tailId, keying on tailId redirected the commit to the
|
|
132
148
|
* collection tail's cluster, which then fails verifyResponsibility for the non-tail block.)
|
|
@@ -153,37 +169,58 @@ export class RepoService {
|
|
|
153
169
|
/**
|
|
154
170
|
* Check if this node should redirect the request for a given key.
|
|
155
171
|
* Returns a RedirectPayload if not responsible, null if should handle locally.
|
|
156
|
-
* Also attaches
|
|
172
|
+
* Also attaches the responsible peer ids to the message for downstream use.
|
|
173
|
+
*
|
|
174
|
+
* The responsible set comes from the node's own key network — `findCluster` on the block's routing
|
|
175
|
+
* key, the same call and the same bytes the writer's transactor and this node's `CoordinatorRepo` use —
|
|
176
|
+
* so a correctly routed request is never redirected, including on machines shared by several networks,
|
|
177
|
+
* where FRET's raw cohort would include peers that do not serve this one.
|
|
178
|
+
*
|
|
179
|
+
* A lookup that THROWS splits by operation: a `get` is handled locally (a read is best-effort, and the
|
|
180
|
+
* coordinator flags what it could not confirm); every other operation propagates the error, which
|
|
181
|
+
* aborts the stream so the writer excludes this peer and re-picks — the posture `CoordinatorRepo` takes
|
|
182
|
+
* on the same fault.
|
|
157
183
|
*/
|
|
158
184
|
async checkRedirect(blockKey, opName, message) {
|
|
159
|
-
const
|
|
160
|
-
if (!
|
|
185
|
+
const keyNetwork = this.getKeyNetwork();
|
|
186
|
+
if (!keyNetwork)
|
|
161
187
|
return null;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
188
|
+
let cluster;
|
|
189
|
+
try {
|
|
190
|
+
cluster = await this.responsiblePeerIds(keyNetwork, blockKey);
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
this.log.error('redirect lookup failed op=%s blockKey=%s - %e', opName, blockKey, err);
|
|
194
|
+
if (opName === 'get')
|
|
195
|
+
return null;
|
|
196
|
+
throw err;
|
|
197
|
+
}
|
|
198
|
+
;
|
|
199
|
+
message.cluster = [...cluster];
|
|
200
|
+
const selfId = this.getSelfId()?.toString();
|
|
173
201
|
if (!selfId)
|
|
174
202
|
return null;
|
|
175
|
-
const isMember = cluster.
|
|
203
|
+
const isMember = cluster.includes(selfId);
|
|
176
204
|
const smallMesh = cluster.length < this.responsibilityK;
|
|
177
205
|
if (!smallMesh && !isMember) {
|
|
178
|
-
const peers = cluster.filter((p) => !peersEqual(p, selfId));
|
|
179
206
|
this.log('redirect op=%s blockKey=%s cluster=%d', opName, blockKey, cluster.length);
|
|
180
|
-
return encodePeers(await Promise.all(
|
|
181
|
-
id
|
|
182
|
-
addrs: await this.getPeerAddrs(
|
|
207
|
+
return encodePeers(await Promise.all(cluster.map(async (id) => ({
|
|
208
|
+
id,
|
|
209
|
+
addrs: await this.getPeerAddrs(peerIdFromString(id))
|
|
183
210
|
}))));
|
|
184
211
|
}
|
|
185
212
|
return null;
|
|
186
213
|
}
|
|
214
|
+
/** The block's responsible peer ids, memoized per block key for {@link RESPONSIBILITY_TTL_MS}. A throw is never memoized. */
|
|
215
|
+
async responsiblePeerIds(keyNetwork, blockKey) {
|
|
216
|
+
const now = Date.now();
|
|
217
|
+
const memo = this.responsibleIds.get(blockKey);
|
|
218
|
+
if (memo && memo.expires > now)
|
|
219
|
+
return memo.peerIds;
|
|
220
|
+
const peerIds = Object.keys(await keyNetwork.findCluster(routingKeyForBlock(blockKey)));
|
|
221
|
+
this.responsibleIds.set(blockKey, { peerIds, expires: now + RESPONSIBILITY_TTL_MS });
|
|
222
|
+
return peerIds;
|
|
223
|
+
}
|
|
187
224
|
/**
|
|
188
225
|
* Handle incoming streams on the repo protocol
|
|
189
226
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../../src/repo/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAC9B,OAAO,EAAE,MAAM,IAAI,QAAQ,EAAE,MAAM,IAAI,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAG3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../../src/repo/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { type ActionId, type BlockId } from "@optimystic/db-core";
|
|
2
|
+
/**
|
|
3
|
+
* How many DISTINCT later actions one unchanged holder must refuse on a block before the refusals are
|
|
4
|
+
* named as a stuck reservation rather than as an ordinary lost race.
|
|
5
|
+
*
|
|
6
|
+
* **Why a count of distinct actions and not something else.** Elapsed time answers the wrong question
|
|
7
|
+
* — a slow writer is not a stuck one, and a holder legitimately keeps its reservation for as long as
|
|
8
|
+
* its own commit takes. A raw refusal count answers the wrong question too: a single writer retrying
|
|
9
|
+
* a lost race produces a run of refusals under ONE action id (see {@link StuckReservationWatch.refused}).
|
|
10
|
+
* What no healthy holder can produce is an unbounded stream of *different* writers all losing to it,
|
|
11
|
+
* because a healthy holder's reservation lasts one pend-to-commit window.
|
|
12
|
+
*
|
|
13
|
+
* **Why 8.** The bound to clear is how many distinct actions can honestly be refused inside one such
|
|
14
|
+
* window. Measured on the in-process mesh, in the healthy-contention arm of
|
|
15
|
+
* `test/stuck-reservation-named.spec.ts`: a holder that pends, is raced by other writers, and then
|
|
16
|
+
* commits refuses **2** distinct actions per episode — the two rivals — and the count resets on every
|
|
17
|
+
* holder change. `concurrent-diary-append-acknowledgement.spec.ts` races three writers at one diary
|
|
18
|
+
* and cannot exceed that either, for the same reason: at most (writers - 1) rivals can lose to one
|
|
19
|
+
* winner. 8 is four times the measured healthy figure, and it is a floor a genuinely stuck block
|
|
20
|
+
* clears trivially (the field instance refused hundreds).
|
|
21
|
+
*
|
|
22
|
+
* **The bound stated exactly.** It is distinct SYNC CYCLES, not distinct writers: one writer that
|
|
23
|
+
* exhausts a sync's retry budget and is re-driven by its caller mints a fresh id for the next cycle,
|
|
24
|
+
* so it can contribute more than one. That does not widen the window much — a cycle only ends in
|
|
25
|
+
* exhaustion after `DefaultMaxAttempts` (10) attempts of backoff, roughly 21s (see the exhaustion
|
|
26
|
+
* NOTE in `Collection.syncAttempts`), so a lone writer needs a holder to keep the block for upwards
|
|
27
|
+
* of two and a half minutes before it reaches 8 by itself, which is not a healthy holder.
|
|
28
|
+
*
|
|
29
|
+
* **What the margin does NOT cover, stated honestly.** A block with more than 8 distinct writers
|
|
30
|
+
* racing it inside a single pend-to-commit round trip could reach 8 with a perfectly healthy holder.
|
|
31
|
+
* That is a diagnostic false positive on a log line and nothing else — this counter never refuses,
|
|
32
|
+
* expires, or deletes anything (see {@link StuckReservationTracker}) — and the remedy if
|
|
33
|
+
* a deployment ever hits it is to raise this number, not to add a control path. Raising it costs
|
|
34
|
+
* detection latency on low-traffic blocks, which need this many distinct write ATTEMPTS before the
|
|
35
|
+
* condition can be named at all.
|
|
36
|
+
*/
|
|
37
|
+
export declare const STUCK_RESERVATION_DISTINCT_ACTIONS = 8;
|
|
38
|
+
/**
|
|
39
|
+
* The stuck-reservation wording: written for an operator reading logs, in the same register as
|
|
40
|
+
* `cohortTooSmallMessage` and `soleHolderMessage` in `coordinator-repo.ts` — what is stuck, what will
|
|
41
|
+
* and will not clear it, and what to do next.
|
|
42
|
+
*
|
|
43
|
+
* The claim is deliberately about the RESERVATION, not about the writer's intent: this node cannot
|
|
44
|
+
* see whether the holding process is alive, only that it has held the block across enough unrelated
|
|
45
|
+
* later actions that no retry is going to win. So the line says what is provable (the block accepts
|
|
46
|
+
* no writes while this record stands, and nothing on the node removes it) and points at the one check
|
|
47
|
+
* that settles the rest.
|
|
48
|
+
*/
|
|
49
|
+
export declare function stuckReservationMessage(holders: readonly ActionId[], refusedActions: number): string;
|
|
50
|
+
/**
|
|
51
|
+
* One episode that just crossed the threshold — what the caller logs, verbatim, under its own tag.
|
|
52
|
+
* The ids an operator needs to grep for and to cancel are kept as data beside the prose so a log
|
|
53
|
+
* search finds the block and the action without parsing English.
|
|
54
|
+
*/
|
|
55
|
+
export interface StuckReservationEpisode {
|
|
56
|
+
blockId: BlockId;
|
|
57
|
+
holdingActionIds: readonly ActionId[];
|
|
58
|
+
distinctRefusedActions: number;
|
|
59
|
+
message: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Count pending-conflict refusals against the holder(s) of each block they name, and say ONCE — in
|
|
63
|
+
* words, at the moment it becomes provable — when a block is wedged behind a reservation that is not
|
|
64
|
+
* going to clear.
|
|
65
|
+
*
|
|
66
|
+
* **Why this needs saying at all.** Every individual refusal is indistinguishable from an ordinary
|
|
67
|
+
* lost race, which is a normal and healthy event, so the logs of a permanently wedged block read
|
|
68
|
+
* exactly like the logs of a busy one. Finding the difference otherwise means noticing that the SAME
|
|
69
|
+
* rival action id keeps appearing across unrelated writers for as long as the process lives — a
|
|
70
|
+
* pattern nothing points at, and one that cost a downstream project several tickets and weeks to
|
|
71
|
+
* re-derive from raw traces. The node has the fact in hand at every refusal; this makes it sayable.
|
|
72
|
+
*
|
|
73
|
+
* **The signal, and the two things that are NOT the signal.** The discriminator is repetition
|
|
74
|
+
* against an unchanged holder — see {@link STUCK_RESERVATION_DISTINCT_ACTIONS} for why distinct
|
|
75
|
+
* refused actions is the right counter and for the measured threshold. Two cheaper-looking tests
|
|
76
|
+
* were tried and do not work: the members' in-memory reservation table
|
|
77
|
+
* (`ClusterMember.activeTransactions`) clears the moment a rival's pend reaches consensus, so a
|
|
78
|
+
* perfectly healthy rival inside its pend-to-commit window is absent from it too and absence there
|
|
79
|
+
* says nothing; and "the block already passed this pending record's revision" catches a different
|
|
80
|
+
* orphan class entirely — in the verified instance the wedged block sat at revision 1 while the
|
|
81
|
+
* orphaned record was for revision 2, still nominally promotable. (That second class is now handled
|
|
82
|
+
* at its own site — a record the incoming writer has built on no longer refuses at all, see
|
|
83
|
+
* `isReservationAgainst` — so what reaches this counter is a holder whose change the block has not
|
|
84
|
+
* taken: one whose slot every later writer still wants, or one past the base every later writer
|
|
85
|
+
* declares, such as an abandoned action's record on a block no later write touched.)
|
|
86
|
+
*
|
|
87
|
+
* **Never a control path.** This classifies and reports; it never refuses, expires, or deletes
|
|
88
|
+
* anything. Deciding when a durable pending record may be removed is precisely the hard problem
|
|
89
|
+
* backlog `debt-unpromotable-pending-records-need-a-sweep` exists for — deleting a live reservation
|
|
90
|
+
* is worse than the leak — and a counter accurate enough for a log line is not evidence enough to
|
|
91
|
+
* destroy state.
|
|
92
|
+
*
|
|
93
|
+
* **Two instances, two vantage points.** The coordinator keeps one (`CoordinatorRepo.noteStuckReservation`),
|
|
94
|
+
* fed by cohort-wide `held`-answered refusals whose holder its OWN storage corroborates, and every
|
|
95
|
+
* member keeps one (`ClusterMember.validatePendOperations`), fed by its own `held` votes — so a
|
|
96
|
+
* reservation only remote members hold is named by those members, where the record actually lives,
|
|
97
|
+
* rather than going unnamed at a coordinator that cannot see it.
|
|
98
|
+
*
|
|
99
|
+
* LRU-bounded: an eviction under more than `capacity` conflicted blocks loses an episode's say-once
|
|
100
|
+
* flag, so the line can repeat once for that block — bounded duplication, far cheaper than the
|
|
101
|
+
* unbounded silence it replaces.
|
|
102
|
+
*/
|
|
103
|
+
export declare class StuckReservationTracker {
|
|
104
|
+
private readonly watches;
|
|
105
|
+
constructor(capacity?: number);
|
|
106
|
+
/**
|
|
107
|
+
* Count one refusal of `refusedActionId` against the rival holder(s) of each block in `rivalsByBlock`.
|
|
108
|
+
* Returns the highest distinct-refusal count any of these blocks has now reached — for a
|
|
109
|
+
* classification line to carry, saturating at the threshold once an episode has been reported,
|
|
110
|
+
* since the ids are dropped at that point — and every episode that crossed the threshold on this
|
|
111
|
+
* refusal, which the caller must log.
|
|
112
|
+
*/
|
|
113
|
+
note(rivalsByBlock: ReadonlyMap<BlockId, readonly ActionId[]>, refusedActionId: ActionId): {
|
|
114
|
+
highest: number;
|
|
115
|
+
named: StuckReservationEpisode[];
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Forget any episode recorded for these blocks, optionally only when `holderActionId` is one of
|
|
119
|
+
* the actions that episode named. Called from the events the message names as the cures — a write
|
|
120
|
+
* the block accepted, a cancel for the holding action — and it is LRU hygiene rather than
|
|
121
|
+
* behaviour: a later wedge is named by a DIFFERENT holder, which {@link note}'s holder comparison
|
|
122
|
+
* already treats as a new episode whether the old entry is still there or not. Kept because a
|
|
123
|
+
* settled episode holding an LRU slot can only evict a live one.
|
|
124
|
+
*/
|
|
125
|
+
forget(blockIds: readonly BlockId[], holderActionId?: ActionId): void;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=stuck-reservation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stuck-reservation.d.ts","sourceRoot":"","sources":["../../../src/repo/stuck-reservation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,QAAQ,EAAE,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAgC1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,kCAAkC,IAAI,CAAC;AAUpD;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAiBpG;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,gBAAgB,EAAE,SAAS,QAAQ,EAAE,CAAC;IACtC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,qBAAa,uBAAuB;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwC;gBAEpD,QAAQ,SAAO;IAI3B;;;;;;OAMG;IACH,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,CAAC,EAAE,eAAe,EAAE,QAAQ,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,uBAAuB,EAAE,CAAA;KAAE;IAkChJ;;;;;;;OAOG;IACH,MAAM,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,EAAE,cAAc,CAAC,EAAE,QAAQ,GAAG,IAAI;CAMrE"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { LruMap } from "@optimystic/db-core";
|
|
2
|
+
/**
|
|
3
|
+
* How many DISTINCT later actions one unchanged holder must refuse on a block before the refusals are
|
|
4
|
+
* named as a stuck reservation rather than as an ordinary lost race.
|
|
5
|
+
*
|
|
6
|
+
* **Why a count of distinct actions and not something else.** Elapsed time answers the wrong question
|
|
7
|
+
* — a slow writer is not a stuck one, and a holder legitimately keeps its reservation for as long as
|
|
8
|
+
* its own commit takes. A raw refusal count answers the wrong question too: a single writer retrying
|
|
9
|
+
* a lost race produces a run of refusals under ONE action id (see {@link StuckReservationWatch.refused}).
|
|
10
|
+
* What no healthy holder can produce is an unbounded stream of *different* writers all losing to it,
|
|
11
|
+
* because a healthy holder's reservation lasts one pend-to-commit window.
|
|
12
|
+
*
|
|
13
|
+
* **Why 8.** The bound to clear is how many distinct actions can honestly be refused inside one such
|
|
14
|
+
* window. Measured on the in-process mesh, in the healthy-contention arm of
|
|
15
|
+
* `test/stuck-reservation-named.spec.ts`: a holder that pends, is raced by other writers, and then
|
|
16
|
+
* commits refuses **2** distinct actions per episode — the two rivals — and the count resets on every
|
|
17
|
+
* holder change. `concurrent-diary-append-acknowledgement.spec.ts` races three writers at one diary
|
|
18
|
+
* and cannot exceed that either, for the same reason: at most (writers - 1) rivals can lose to one
|
|
19
|
+
* winner. 8 is four times the measured healthy figure, and it is a floor a genuinely stuck block
|
|
20
|
+
* clears trivially (the field instance refused hundreds).
|
|
21
|
+
*
|
|
22
|
+
* **The bound stated exactly.** It is distinct SYNC CYCLES, not distinct writers: one writer that
|
|
23
|
+
* exhausts a sync's retry budget and is re-driven by its caller mints a fresh id for the next cycle,
|
|
24
|
+
* so it can contribute more than one. That does not widen the window much — a cycle only ends in
|
|
25
|
+
* exhaustion after `DefaultMaxAttempts` (10) attempts of backoff, roughly 21s (see the exhaustion
|
|
26
|
+
* NOTE in `Collection.syncAttempts`), so a lone writer needs a holder to keep the block for upwards
|
|
27
|
+
* of two and a half minutes before it reaches 8 by itself, which is not a healthy holder.
|
|
28
|
+
*
|
|
29
|
+
* **What the margin does NOT cover, stated honestly.** A block with more than 8 distinct writers
|
|
30
|
+
* racing it inside a single pend-to-commit round trip could reach 8 with a perfectly healthy holder.
|
|
31
|
+
* That is a diagnostic false positive on a log line and nothing else — this counter never refuses,
|
|
32
|
+
* expires, or deletes anything (see {@link StuckReservationTracker}) — and the remedy if
|
|
33
|
+
* a deployment ever hits it is to raise this number, not to add a control path. Raising it costs
|
|
34
|
+
* detection latency on low-traffic blocks, which need this many distinct write ATTEMPTS before the
|
|
35
|
+
* condition can be named at all.
|
|
36
|
+
*/
|
|
37
|
+
export const STUCK_RESERVATION_DISTINCT_ACTIONS = 8;
|
|
38
|
+
/** Whether two sorted holder lists name the same reservation — i.e. whether a refusal continues an
|
|
39
|
+
* existing episode or starts a new one. Both sides come from the same sort, so this is a plain
|
|
40
|
+
* element-wise comparison; a block normally has exactly one holder, since a member's own pend refuses
|
|
41
|
+
* a second one (`ClusterMember.validatePendOperations`). */
|
|
42
|
+
function sameHolders(a, b) {
|
|
43
|
+
return a.length === b.length && a.every((id, i) => id === b[i]);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The stuck-reservation wording: written for an operator reading logs, in the same register as
|
|
47
|
+
* `cohortTooSmallMessage` and `soleHolderMessage` in `coordinator-repo.ts` — what is stuck, what will
|
|
48
|
+
* and will not clear it, and what to do next.
|
|
49
|
+
*
|
|
50
|
+
* The claim is deliberately about the RESERVATION, not about the writer's intent: this node cannot
|
|
51
|
+
* see whether the holding process is alive, only that it has held the block across enough unrelated
|
|
52
|
+
* later actions that no retry is going to win. So the line says what is provable (the block accepts
|
|
53
|
+
* no writes while this record stands, and nothing on the node removes it) and points at the one check
|
|
54
|
+
* that settles the rest.
|
|
55
|
+
*/
|
|
56
|
+
export function stuckReservationMessage(holders, refusedActions) {
|
|
57
|
+
const held = holders.join(', ');
|
|
58
|
+
return `This block is WEDGED BEHIND A PENDING WRITE THAT IS NOT COMPLETING, and retrying will never ` +
|
|
59
|
+
`clear it: action(s) ${held} reserved the block and have now refused ${refusedActions} DISTINCT, ` +
|
|
60
|
+
`unrelated later actions. Each of those refusals on its own looks exactly like an ordinary ` +
|
|
61
|
+
`optimistic-concurrency loss, which is normal and healthy — the repetition is what is not. A ` +
|
|
62
|
+
`healthy rival holds a block only for its own pend-to-commit window and then releases it by ` +
|
|
63
|
+
`committing or cancelling; a reservation that keeps refusing NEW writers is holding the block ` +
|
|
64
|
+
`against every writer on every machine, and each of them loses again identically. EXACTLY TWO ` +
|
|
65
|
+
`THINGS CLEAR IT: a cancel for action(s) ${held} on this block (route it through the cohort so ` +
|
|
66
|
+
`every member drops the record), or that same action's own commit landing. Nothing on the node ` +
|
|
67
|
+
`expires it — there is no sweep for abandoned pending records — so until one of those two happens ` +
|
|
68
|
+
`the block takes NO writes while continuing to serve reads and to look healthy in every other ` +
|
|
69
|
+
`respect. The usual cause is a writer that went away between a failed or half-applied commit and ` +
|
|
70
|
+
`the cancel it owed, so check whether whatever ran ${held} still exists before cancelling on its ` +
|
|
71
|
+
`behalf. This line is a diagnosis and nothing more: this node does not expire, refuse, or delete ` +
|
|
72
|
+
`the record on the strength of it.`;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Count pending-conflict refusals against the holder(s) of each block they name, and say ONCE — in
|
|
76
|
+
* words, at the moment it becomes provable — when a block is wedged behind a reservation that is not
|
|
77
|
+
* going to clear.
|
|
78
|
+
*
|
|
79
|
+
* **Why this needs saying at all.** Every individual refusal is indistinguishable from an ordinary
|
|
80
|
+
* lost race, which is a normal and healthy event, so the logs of a permanently wedged block read
|
|
81
|
+
* exactly like the logs of a busy one. Finding the difference otherwise means noticing that the SAME
|
|
82
|
+
* rival action id keeps appearing across unrelated writers for as long as the process lives — a
|
|
83
|
+
* pattern nothing points at, and one that cost a downstream project several tickets and weeks to
|
|
84
|
+
* re-derive from raw traces. The node has the fact in hand at every refusal; this makes it sayable.
|
|
85
|
+
*
|
|
86
|
+
* **The signal, and the two things that are NOT the signal.** The discriminator is repetition
|
|
87
|
+
* against an unchanged holder — see {@link STUCK_RESERVATION_DISTINCT_ACTIONS} for why distinct
|
|
88
|
+
* refused actions is the right counter and for the measured threshold. Two cheaper-looking tests
|
|
89
|
+
* were tried and do not work: the members' in-memory reservation table
|
|
90
|
+
* (`ClusterMember.activeTransactions`) clears the moment a rival's pend reaches consensus, so a
|
|
91
|
+
* perfectly healthy rival inside its pend-to-commit window is absent from it too and absence there
|
|
92
|
+
* says nothing; and "the block already passed this pending record's revision" catches a different
|
|
93
|
+
* orphan class entirely — in the verified instance the wedged block sat at revision 1 while the
|
|
94
|
+
* orphaned record was for revision 2, still nominally promotable. (That second class is now handled
|
|
95
|
+
* at its own site — a record the incoming writer has built on no longer refuses at all, see
|
|
96
|
+
* `isReservationAgainst` — so what reaches this counter is a holder whose change the block has not
|
|
97
|
+
* taken: one whose slot every later writer still wants, or one past the base every later writer
|
|
98
|
+
* declares, such as an abandoned action's record on a block no later write touched.)
|
|
99
|
+
*
|
|
100
|
+
* **Never a control path.** This classifies and reports; it never refuses, expires, or deletes
|
|
101
|
+
* anything. Deciding when a durable pending record may be removed is precisely the hard problem
|
|
102
|
+
* backlog `debt-unpromotable-pending-records-need-a-sweep` exists for — deleting a live reservation
|
|
103
|
+
* is worse than the leak — and a counter accurate enough for a log line is not evidence enough to
|
|
104
|
+
* destroy state.
|
|
105
|
+
*
|
|
106
|
+
* **Two instances, two vantage points.** The coordinator keeps one (`CoordinatorRepo.noteStuckReservation`),
|
|
107
|
+
* fed by cohort-wide `held`-answered refusals whose holder its OWN storage corroborates, and every
|
|
108
|
+
* member keeps one (`ClusterMember.validatePendOperations`), fed by its own `held` votes — so a
|
|
109
|
+
* reservation only remote members hold is named by those members, where the record actually lives,
|
|
110
|
+
* rather than going unnamed at a coordinator that cannot see it.
|
|
111
|
+
*
|
|
112
|
+
* LRU-bounded: an eviction under more than `capacity` conflicted blocks loses an episode's say-once
|
|
113
|
+
* flag, so the line can repeat once for that block — bounded duplication, far cheaper than the
|
|
114
|
+
* unbounded silence it replaces.
|
|
115
|
+
*/
|
|
116
|
+
export class StuckReservationTracker {
|
|
117
|
+
watches;
|
|
118
|
+
constructor(capacity = 1000) {
|
|
119
|
+
this.watches = new LruMap(capacity);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Count one refusal of `refusedActionId` against the rival holder(s) of each block in `rivalsByBlock`.
|
|
123
|
+
* Returns the highest distinct-refusal count any of these blocks has now reached — for a
|
|
124
|
+
* classification line to carry, saturating at the threshold once an episode has been reported,
|
|
125
|
+
* since the ids are dropped at that point — and every episode that crossed the threshold on this
|
|
126
|
+
* refusal, which the caller must log.
|
|
127
|
+
*/
|
|
128
|
+
note(rivalsByBlock, refusedActionId) {
|
|
129
|
+
let highest = 0;
|
|
130
|
+
const named = [];
|
|
131
|
+
for (const [blockId, rivals] of rivalsByBlock) {
|
|
132
|
+
const holders = [...new Set(rivals)].sort();
|
|
133
|
+
const prior = this.watches.get(blockId);
|
|
134
|
+
// A different holder set is a DIFFERENT episode — the block changed hands, which is the
|
|
135
|
+
// healthy cycle — so the count starts over and the new holder gets its own chance to speak.
|
|
136
|
+
const watch = prior !== undefined && sameHolders(prior.holders, holders)
|
|
137
|
+
? prior
|
|
138
|
+
: { holders, refused: new Set(), reported: false };
|
|
139
|
+
if (watch !== prior)
|
|
140
|
+
this.watches.set(blockId, watch);
|
|
141
|
+
if (watch.reported) {
|
|
142
|
+
highest = Math.max(highest, STUCK_RESERVATION_DISTINCT_ACTIONS);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
watch.refused.add(refusedActionId);
|
|
146
|
+
highest = Math.max(highest, watch.refused.size);
|
|
147
|
+
if (watch.refused.size < STUCK_RESERVATION_DISTINCT_ACTIONS)
|
|
148
|
+
continue;
|
|
149
|
+
named.push({
|
|
150
|
+
blockId,
|
|
151
|
+
holdingActionIds: holders,
|
|
152
|
+
distinctRefusedActions: watch.refused.size,
|
|
153
|
+
message: stuckReservationMessage(holders, watch.refused.size)
|
|
154
|
+
});
|
|
155
|
+
watch.reported = true;
|
|
156
|
+
// Said once per episode: from here the flag alone suppresses, and the ids have done their
|
|
157
|
+
// work (their count is in the line above), so drop them rather than growing a set for the
|
|
158
|
+
// unbounded remainder of a permanent condition.
|
|
159
|
+
watch.refused.clear();
|
|
160
|
+
}
|
|
161
|
+
return { highest, named };
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Forget any episode recorded for these blocks, optionally only when `holderActionId` is one of
|
|
165
|
+
* the actions that episode named. Called from the events the message names as the cures — a write
|
|
166
|
+
* the block accepted, a cancel for the holding action — and it is LRU hygiene rather than
|
|
167
|
+
* behaviour: a later wedge is named by a DIFFERENT holder, which {@link note}'s holder comparison
|
|
168
|
+
* already treats as a new episode whether the old entry is still there or not. Kept because a
|
|
169
|
+
* settled episode holding an LRU slot can only evict a live one.
|
|
170
|
+
*/
|
|
171
|
+
forget(blockIds, holderActionId) {
|
|
172
|
+
for (const blockId of blockIds) {
|
|
173
|
+
if (holderActionId !== undefined && !this.watches.peek(blockId)?.holders.includes(holderActionId))
|
|
174
|
+
continue;
|
|
175
|
+
this.watches.delete(blockId);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=stuck-reservation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stuck-reservation.js","sourceRoot":"","sources":["../../../src/repo/stuck-reservation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAA+B,MAAM,qBAAqB,CAAC;AAgC1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,CAAC,MAAM,kCAAkC,GAAG,CAAC,CAAC;AAEpD;;;6DAG6D;AAC7D,SAAS,WAAW,CAAC,CAAsB,EAAE,CAAsB;IAClE,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACjE,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAA4B,EAAE,cAAsB;IAC3F,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChC,OAAO,8FAA8F;QACpG,uBAAuB,IAAI,4CAA4C,cAAc,aAAa;QAClG,4FAA4F;QAC5F,8FAA8F;QAC9F,6FAA6F;QAC7F,+FAA+F;QAC/F,+FAA+F;QAC/F,2CAA2C,IAAI,iDAAiD;QAChG,gGAAgG;QAChG,mGAAmG;QACnG,+FAA+F;QAC/F,kGAAkG;QAClG,qDAAqD,IAAI,yCAAyC;QAClG,kGAAkG;QAClG,mCAAmC,CAAC;AACtC,CAAC;AAcD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,MAAM,OAAO,uBAAuB;IAClB,OAAO,CAAwC;IAEhE,YAAY,QAAQ,GAAG,IAAI;QAC1B,IAAI,CAAC,OAAO,GAAG,IAAI,MAAM,CAAgC,QAAQ,CAAC,CAAC;IACpE,CAAC;IAED;;;;;;OAMG;IACH,IAAI,CAAC,aAAwD,EAAE,eAAyB;QACvF,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,MAAM,KAAK,GAA8B,EAAE,CAAC;QAC5C,KAAK,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,aAAa,EAAE,CAAC;YAC/C,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACxC,wFAAwF;YACxF,4FAA4F;YAC5F,MAAM,KAAK,GAA0B,KAAK,KAAK,SAAS,IAAI,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC;gBAC9F,CAAC,CAAC,KAAK;gBACP,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,GAAG,EAAY,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;YAC9D,IAAI,KAAK,KAAK,KAAK;gBAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YACtD,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACpB,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,kCAAkC,CAAC,CAAC;gBAChE,SAAS;YACV,CAAC;YACD,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;YACnC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAChD,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,GAAG,kCAAkC;gBAAE,SAAS;YACtE,KAAK,CAAC,IAAI,CAAC;gBACV,OAAO;gBACP,gBAAgB,EAAE,OAAO;gBACzB,sBAAsB,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI;gBAC1C,OAAO,EAAE,uBAAuB,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;aAC7D,CAAC,CAAC;YACH,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC;YACtB,0FAA0F;YAC1F,0FAA0F;YAC1F,gDAAgD;YAChD,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC3B,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,QAA4B,EAAE,cAAyB;QAC7D,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAChC,IAAI,cAAc,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;gBAAE,SAAS;YAC5G,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC;IACF,CAAC;CACD"}
|