apache-iggy 0.10.0 → 0.10.1-edge.2
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/README.md +248 -28
- package/dist/client/client.config.d.ts +2 -0
- package/dist/client/client.config.js +13 -2
- package/dist/client/client.config.test.js +15 -2
- package/dist/client/client.connection-string.js +13 -1
- package/dist/client/client.connection-string.test.js +43 -1
- package/dist/client/client.connection.d.ts +33 -135
- package/dist/client/client.connection.js +190 -453
- package/dist/client/client.connection.test.js +244 -669
- package/dist/client/client.d.ts +4 -35
- package/dist/client/client.js +8 -101
- package/dist/client/client.operation.d.ts +120 -0
- package/dist/client/client.operation.js +428 -0
- package/dist/client/client.operation.test.d.ts +2 -0
- package/dist/client/client.operation.test.js +285 -0
- package/dist/client/client.socket.d.ts +92 -209
- package/dist/client/client.socket.js +1205 -981
- package/dist/client/client.socket.test.js +4865 -679
- package/dist/client/client.type.d.ts +65 -22
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/client.e2e.js +3 -5
- package/dist/e2e/resource-options.e2e.d.ts +2 -0
- package/dist/{wire/stream/purge-stream.command.js → e2e/resource-options.e2e.js} +12 -20
- package/dist/e2e/resource-options.suite.d.ts +15 -0
- package/dist/e2e/resource-options.suite.js +272 -0
- package/dist/e2e/tcp.cluster.e2e.js +1 -0
- package/dist/e2e/tcp.consumer-stream.e2e.js +4 -0
- package/dist/e2e/tcp.parallel.e2e.js +5 -6
- package/dist/e2e/tcp.stream.e2e.js +0 -3
- package/dist/e2e/tcp.system.e2e.js +10 -1
- package/dist/e2e/tcp.token.e2e.js +8 -2
- package/dist/e2e/tcp.topic.e2e.js +0 -3
- package/dist/e2e/tcp.user.e2e.js +46 -3
- package/dist/e2e/tls.system.e2e.js +21 -12
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/stream/consumer-stream.js +97 -12
- package/dist/wire/cluster/cluster.type.d.ts +2 -2
- package/dist/wire/cluster/cluster.utils.d.ts +1 -0
- package/dist/wire/cluster/cluster.utils.js +4 -1
- package/dist/wire/command-set.d.ts +8 -8
- package/dist/wire/command-set.js +18 -9
- package/dist/wire/command.code.d.ts +3 -10
- package/dist/wire/command.code.js +9 -11
- package/dist/wire/command.utils.d.ts +3 -2
- package/dist/wire/command.utils.js +3 -2
- package/dist/wire/error.code.js +17 -1
- package/dist/wire/error.code.test.js +28 -1
- package/dist/wire/error.utils.d.ts +4 -2
- package/dist/wire/error.utils.js +6 -3
- package/dist/wire/identifier.utils.d.ts +2 -0
- package/dist/wire/identifier.utils.js +2 -0
- package/dist/wire/index.d.ts +1 -0
- package/dist/wire/index.js +1 -0
- package/dist/wire/message/header.type.d.ts +1 -1
- package/dist/wire/message/message-batch.test.js +11 -5
- package/dist/wire/message/message.utils.d.ts +0 -10
- package/dist/wire/message/message.utils.js +1 -22
- package/dist/wire/message/poll-messages.command.d.ts +12 -1
- package/dist/wire/message/poll-messages.command.js +64 -16
- package/dist/wire/message/poll-messages.command.test.js +143 -5
- package/dist/wire/message/poll.utils.d.ts +4 -0
- package/dist/wire/message/poll.utils.js +6 -1
- package/dist/wire/message/send-messages.command.d.ts +3 -3
- package/dist/wire/message/send-messages.command.js +103 -3
- package/dist/wire/message/send-messages.command.test.js +113 -2
- package/dist/wire/offset/delete-offset.command.d.ts +11 -4
- package/dist/wire/offset/delete-offset.command.js +1 -1
- package/dist/wire/offset/get-offset.command.d.ts +1 -1
- package/dist/wire/offset/offset-write.command.test.d.ts +2 -0
- package/dist/wire/offset/offset-write.command.test.js +43 -0
- package/dist/wire/offset/offset.utils.js +10 -6
- package/dist/wire/offset/store-offset.command.d.ts +10 -0
- package/dist/wire/offset/store-offset.command.js +1 -1
- package/dist/wire/options.utils.d.ts +46 -0
- package/dist/wire/options.utils.js +57 -0
- package/dist/wire/partition/partition-route.d.ts +12 -0
- package/dist/wire/partition/partition-route.js +40 -0
- package/dist/wire/partition/partition-route.test.d.ts +2 -0
- package/dist/wire/partition/partition-route.test.js +42 -0
- package/dist/wire/session/login-with-token.command.d.ts +2 -2
- package/dist/wire/session/login-with-token.command.js +7 -2
- package/dist/wire/session/login.command.d.ts +2 -2
- package/dist/wire/session/login.command.js +7 -2
- package/dist/wire/stream/create-stream.command.d.ts +5 -2
- package/dist/wire/stream/create-stream.command.js +20 -2
- package/dist/wire/stream/index.d.ts +1 -1
- package/dist/wire/stream/index.js +1 -1
- package/dist/wire/stream/stream-options.d.ts +77 -0
- package/dist/wire/stream/stream-options.js +101 -0
- package/dist/wire/stream/stream-options.test.d.ts +2 -0
- package/dist/wire/stream/stream-options.test.js +103 -0
- package/dist/wire/stream/stream.utils.d.ts +9 -0
- package/dist/wire/stream/stream.utils.js +17 -3
- package/dist/wire/stream/update-stream.command.d.ts +23 -5
- package/dist/wire/stream/update-stream.command.js +10 -17
- package/dist/wire/system/describe-options.command.d.ts +23 -6
- package/dist/wire/system/describe-options.command.js +21 -2
- package/dist/wire/system/get-stats.command.d.ts +17 -0
- package/dist/wire/system/get-stats.command.js +69 -16
- package/dist/wire/system/get-stats.command.test.d.ts +2 -0
- package/dist/wire/system/get-stats.command.test.js +97 -0
- package/dist/wire/system/index.d.ts +2 -0
- package/dist/wire/system/index.js +2 -0
- package/dist/wire/system/snapshot.command.d.ts +64 -0
- package/dist/wire/system/snapshot.command.js +76 -0
- package/dist/wire/system/snapshot.command.test.d.ts +2 -0
- package/dist/wire/system/snapshot.command.test.js +86 -0
- package/dist/wire/system/update-resource.command.d.ts +53 -0
- package/dist/wire/system/update-resource.command.js +130 -0
- package/dist/wire/system/update-resource.command.test.d.ts +2 -0
- package/dist/wire/system/update-resource.command.test.js +497 -0
- package/dist/wire/token/create-token.command.d.ts +1 -1
- package/dist/wire/token/create-token.command.js +1 -1
- package/dist/wire/token/create-token.command.test.js +6 -0
- package/dist/wire/topic/create-topic.command.d.ts +20 -12
- package/dist/wire/topic/create-topic.command.js +11 -4
- package/dist/wire/topic/index.d.ts +1 -2
- package/dist/wire/topic/index.js +1 -2
- package/dist/wire/topic/topic.utils.d.ts +14 -5
- package/dist/wire/topic/topic.utils.js +26 -11
- package/dist/wire/topic/topic.utils.test.js +13 -13
- package/dist/wire/topic/update-topic.command.d.ts +22 -15
- package/dist/wire/topic/update-topic.command.js +12 -34
- package/dist/wire/user/create-user.command.d.ts +12 -2
- package/dist/wire/user/create-user.command.js +7 -3
- package/dist/wire/user/index.d.ts +1 -0
- package/dist/wire/user/index.js +1 -0
- package/dist/wire/user/update-user.command.d.ts +25 -7
- package/dist/wire/user/update-user.command.js +18 -31
- package/dist/wire/user/user.utils.d.ts +2 -0
- package/dist/wire/user/user.utils.js +5 -3
- package/dist/wire/vsr/header.d.ts +23 -8
- package/dist/wire/vsr/header.js +32 -10
- package/dist/wire/vsr/header.test.js +5 -1
- package/dist/wire/vsr/index.d.ts +8 -2
- package/dist/wire/vsr/index.js +44 -11
- package/dist/wire/vsr/operation.d.ts +18 -6
- package/dist/wire/vsr/operation.js +46 -16
- package/dist/wire/vsr/operation.test.js +32 -8
- package/dist/wire/vsr/register.d.ts +10 -6
- package/dist/wire/vsr/register.js +24 -7
- package/dist/wire/vsr/register.test.js +28 -8
- package/dist/wire/vsr/reply.d.ts +4 -1
- package/dist/wire/vsr/reply.js +42 -7
- package/dist/wire/vsr/reply.test.js +11 -1
- package/dist/wire/vsr/session.d.ts +9 -10
- package/dist/wire/vsr/session.js +32 -23
- package/dist/wire/vsr/session.test.js +33 -7
- package/dist/wire/vsr/vsr.test.js +57 -6
- package/package.json +6 -7
- package/dist/wire/message/flush-unsaved-buffers.command.d.ts +0 -26
- package/dist/wire/message/flush-unsaved-buffers.command.js +0 -36
- package/dist/wire/stream/purge-stream.command.d.ts +0 -22
- package/dist/wire/topic/purge-topic.command.d.ts +0 -24
- package/dist/wire/topic/purge-topic.command.js +0 -39
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
// Licensed to the Apache Software Foundation (ASF) under one
|
|
2
|
+
// or more contributor license agreements. See the NOTICE file
|
|
3
|
+
// distributed with this work for additional information
|
|
4
|
+
// regarding copyright ownership. The ASF licenses this file
|
|
5
|
+
// to you under the Apache License, Version 2.0 (the
|
|
6
|
+
// "License"); you may not use this file except in compliance
|
|
7
|
+
// with the License. You may obtain a copy of the License at
|
|
8
|
+
//
|
|
9
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
//
|
|
11
|
+
// Unless required by applicable law or agreed to in writing,
|
|
12
|
+
// software distributed under the License is distributed on an
|
|
13
|
+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
// KIND, either express or implied. See the License for the
|
|
15
|
+
// specific language governing permissions and limitations
|
|
16
|
+
// under the License.
|
|
17
|
+
import { HEADER_SIZE, REQUEST_OFFSET } from '../wire/vsr/header.js';
|
|
18
|
+
import { responseError } from '../wire/error.utils.js';
|
|
19
|
+
const INVALID_MESSAGE_LENGTH = 80;
|
|
20
|
+
const TRANSIENT_NOT_ACCEPTED = 58;
|
|
21
|
+
export const OPERATION_TIMEOUT_MS = 30_000;
|
|
22
|
+
export class OperationUncertainError extends Error {
|
|
23
|
+
operationId;
|
|
24
|
+
constructor(operationId, cause) {
|
|
25
|
+
super('The operation outcome is unknown.', { cause });
|
|
26
|
+
this.operationId = operationId;
|
|
27
|
+
this.name = 'OperationUncertainError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export class LaneBlockedError extends Error {
|
|
31
|
+
operationId;
|
|
32
|
+
constructor(operationId) {
|
|
33
|
+
super('A retained operation blocks this mutation lane.');
|
|
34
|
+
this.operationId = operationId;
|
|
35
|
+
this.name = 'LaneBlockedError';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The client has neither the operation nor a proved outcome for it, so the
|
|
40
|
+
* outcome can still be unknown. A session reset or an abandon came before a
|
|
41
|
+
* replay proved the outcome, or the proved outcome aged out.
|
|
42
|
+
*/
|
|
43
|
+
export class OperationNotRetainedError extends Error {
|
|
44
|
+
operationId;
|
|
45
|
+
constructor(operationId) {
|
|
46
|
+
super('The operation is not retained; its outcome may still be unknown.');
|
|
47
|
+
this.operationId = operationId;
|
|
48
|
+
this.name = 'OperationNotRetainedError';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export class VsrResponseTimeoutError extends Error {
|
|
52
|
+
constructor(timeout) {
|
|
53
|
+
super(`timed out after ${timeout} ms waiting for VSR response`);
|
|
54
|
+
this.name = 'VsrResponseTimeoutError';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Every replica link used its retry budget, or no endpoint accepted a dial.
|
|
59
|
+
* The client connects again on its own: each heartbeat restarts a link with a
|
|
60
|
+
* fresh budget, and the next call dials again.
|
|
61
|
+
*/
|
|
62
|
+
export class NotConnectedError extends Error {
|
|
63
|
+
constructor(cause) {
|
|
64
|
+
super('The client is not connected to any replica.', { cause });
|
|
65
|
+
this.name = 'NotConnectedError';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export const waitWithin = async (pending, deadline, signal) => {
|
|
69
|
+
void pending.catch(() => undefined);
|
|
70
|
+
signal?.throwIfAborted();
|
|
71
|
+
const remaining = deadline - Date.now();
|
|
72
|
+
if (remaining <= 0)
|
|
73
|
+
throw new VsrResponseTimeoutError(0);
|
|
74
|
+
let timer;
|
|
75
|
+
let abort;
|
|
76
|
+
try {
|
|
77
|
+
return await Promise.race([pending, new Promise((_resolve, reject) => {
|
|
78
|
+
if (remaining <= 2_147_483_647)
|
|
79
|
+
timer = setTimeout(() => reject(new VsrResponseTimeoutError(remaining)), remaining);
|
|
80
|
+
abort = () => reject(signal?.reason ?? new Error('operation aborted'));
|
|
81
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
82
|
+
})]);
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
if (abort)
|
|
87
|
+
signal?.removeEventListener('abort', abort);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
export const pause = (milliseconds, signal, background = false) => {
|
|
91
|
+
signal?.throwIfAborted();
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
const cleanup = () => signal?.removeEventListener('abort', abort);
|
|
94
|
+
const timer = setTimeout(() => { cleanup(); resolve(); }, milliseconds);
|
|
95
|
+
if (background)
|
|
96
|
+
timer.unref();
|
|
97
|
+
const abort = () => { clearTimeout(timer); cleanup(); reject(signal?.reason); };
|
|
98
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
export class ChangeSignal {
|
|
102
|
+
resolve;
|
|
103
|
+
promise = this.next();
|
|
104
|
+
next() {
|
|
105
|
+
return new Promise((resolve) => { this.resolve = resolve; });
|
|
106
|
+
}
|
|
107
|
+
pulse() {
|
|
108
|
+
const resolve = this.resolve;
|
|
109
|
+
this.promise = this.next();
|
|
110
|
+
resolve();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export class PermitPool {
|
|
114
|
+
capacity;
|
|
115
|
+
active = 0;
|
|
116
|
+
waiting = [];
|
|
117
|
+
constructor(capacity) {
|
|
118
|
+
this.capacity = capacity;
|
|
119
|
+
}
|
|
120
|
+
async acquire(deadline, signal) {
|
|
121
|
+
signal.throwIfAborted();
|
|
122
|
+
if (this.active < this.capacity && this.waiting.length === 0) {
|
|
123
|
+
this.active += 1;
|
|
124
|
+
return () => this.release();
|
|
125
|
+
}
|
|
126
|
+
let grant;
|
|
127
|
+
const pending = new Promise((resolve) => { grant = resolve; });
|
|
128
|
+
const waiter = { grant, granted: false };
|
|
129
|
+
this.waiting.push(waiter);
|
|
130
|
+
try {
|
|
131
|
+
await waitWithin(pending, deadline, signal);
|
|
132
|
+
return () => this.release();
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
if (waiter.granted)
|
|
136
|
+
this.release();
|
|
137
|
+
else
|
|
138
|
+
this.waiting.splice(this.waiting.indexOf(waiter), 1);
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
release() {
|
|
143
|
+
const waiter = this.waiting.shift();
|
|
144
|
+
if (waiter) {
|
|
145
|
+
waiter.granted = true;
|
|
146
|
+
waiter.grant();
|
|
147
|
+
}
|
|
148
|
+
else
|
|
149
|
+
this.active -= 1;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** Writer aliases pin physical bytes after logical completion or abandonment. */
|
|
153
|
+
export class FrozenFrame {
|
|
154
|
+
bytes;
|
|
155
|
+
released;
|
|
156
|
+
references = 1;
|
|
157
|
+
ownerReleased = false;
|
|
158
|
+
constructor(bytes, released) {
|
|
159
|
+
this.bytes = bytes;
|
|
160
|
+
this.released = released;
|
|
161
|
+
}
|
|
162
|
+
get buffer() {
|
|
163
|
+
if (!this.bytes)
|
|
164
|
+
throw new Error('request frame was released');
|
|
165
|
+
return this.bytes;
|
|
166
|
+
}
|
|
167
|
+
retain() {
|
|
168
|
+
if (!this.bytes)
|
|
169
|
+
throw new Error('request frame was released');
|
|
170
|
+
this.references += 1;
|
|
171
|
+
}
|
|
172
|
+
release() {
|
|
173
|
+
this.references -= 1;
|
|
174
|
+
if (this.references === 0) {
|
|
175
|
+
this.bytes?.fill(0);
|
|
176
|
+
this.bytes = undefined;
|
|
177
|
+
this.released();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
releaseOwner() {
|
|
181
|
+
if (!this.ownerReleased) {
|
|
182
|
+
this.ownerReleased = true;
|
|
183
|
+
this.release();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
export class MutationLanes {
|
|
188
|
+
maxOperations;
|
|
189
|
+
maxBytes;
|
|
190
|
+
lanes = new Map();
|
|
191
|
+
operations = new Map();
|
|
192
|
+
/**
|
|
193
|
+
* The outcomes that replays proved, oldest first: the committed rejection,
|
|
194
|
+
* or `undefined` for a committed success. A reset keeps them, because
|
|
195
|
+
* operation ids never repeat within a client.
|
|
196
|
+
*/
|
|
197
|
+
proved = new Map();
|
|
198
|
+
waiting = new Map();
|
|
199
|
+
capacityWaiting = new Set();
|
|
200
|
+
changed = new ChangeSignal();
|
|
201
|
+
count = 0;
|
|
202
|
+
bytes = 0;
|
|
203
|
+
constructor(maxOperations, maxBytes) {
|
|
204
|
+
this.maxOperations = maxOperations;
|
|
205
|
+
this.maxBytes = maxBytes;
|
|
206
|
+
}
|
|
207
|
+
async enter(lane, command, body, context, retainable, session, deadline, signal) {
|
|
208
|
+
const size = retainable ? HEADER_SIZE + body.length : 0;
|
|
209
|
+
if (!Number.isSafeInteger(size) || size > this.maxBytes)
|
|
210
|
+
throw responseError(command, INVALID_MESSAGE_LENGTH, 'request exceeds the mutation byte limit');
|
|
211
|
+
const waiting = { size, retainable, changed: new ChangeSignal() };
|
|
212
|
+
let queue = this.waiting.get(lane);
|
|
213
|
+
if (!queue) {
|
|
214
|
+
queue = {};
|
|
215
|
+
this.waiting.set(lane, queue);
|
|
216
|
+
}
|
|
217
|
+
waiting.previous = queue.tail;
|
|
218
|
+
if (queue.tail)
|
|
219
|
+
queue.tail.next = waiting;
|
|
220
|
+
else
|
|
221
|
+
queue.head = waiting;
|
|
222
|
+
queue.tail = waiting;
|
|
223
|
+
try {
|
|
224
|
+
for (;;) {
|
|
225
|
+
signal.throwIfAborted();
|
|
226
|
+
if (Date.now() >= deadline)
|
|
227
|
+
throw responseError(command, TRANSIENT_NOT_ACCEPTED);
|
|
228
|
+
this.throwIfRetained(lane);
|
|
229
|
+
const head = !this.lanes.has(lane) && queue.head === waiting;
|
|
230
|
+
if (head &&
|
|
231
|
+
(!retainable || this.count < this.maxOperations) && size <= this.maxBytes - this.bytes) {
|
|
232
|
+
if (retainable)
|
|
233
|
+
this.count += 1;
|
|
234
|
+
this.bytes += size;
|
|
235
|
+
let bytes;
|
|
236
|
+
try {
|
|
237
|
+
bytes = session.encode(command, body, context);
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
if (retainable)
|
|
241
|
+
this.count -= 1;
|
|
242
|
+
this.bytes -= size;
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
const operation = {
|
|
246
|
+
lane, command, context, retainable,
|
|
247
|
+
id: bytes.readBigUInt64LE(REQUEST_OFFSET.request),
|
|
248
|
+
frame: this.freeze(bytes, size),
|
|
249
|
+
lifetime: new AbortController(), resolving: false, retained: false, uncertain: false,
|
|
250
|
+
holdsSlot: retainable
|
|
251
|
+
};
|
|
252
|
+
this.lanes.set(lane, operation);
|
|
253
|
+
this.operations.set(operation.id, operation);
|
|
254
|
+
return operation;
|
|
255
|
+
}
|
|
256
|
+
if (head)
|
|
257
|
+
this.capacityWaiting.add(waiting);
|
|
258
|
+
await waitWithin(waiting.changed.promise, deadline, signal);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
if (!signal.aborted && error instanceof VsrResponseTimeoutError)
|
|
263
|
+
throw responseError(command, TRANSIENT_NOT_ACCEPTED);
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
finally {
|
|
267
|
+
if (waiting.previous)
|
|
268
|
+
waiting.previous.next = waiting.next;
|
|
269
|
+
else
|
|
270
|
+
queue.head = waiting.next;
|
|
271
|
+
if (waiting.next)
|
|
272
|
+
waiting.next.previous = waiting.previous;
|
|
273
|
+
else
|
|
274
|
+
queue.tail = waiting.previous;
|
|
275
|
+
this.capacityWaiting.delete(waiting);
|
|
276
|
+
if (!queue.head)
|
|
277
|
+
this.waiting.delete(lane);
|
|
278
|
+
this.wakeLane(lane);
|
|
279
|
+
this.wakeWaiting();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async restart(operation, body, context, session, deadline, signal) {
|
|
283
|
+
operation.frame.releaseOwner();
|
|
284
|
+
const size = operation.retainable ? HEADER_SIZE + body.length : 0;
|
|
285
|
+
for (;;) {
|
|
286
|
+
signal.throwIfAborted();
|
|
287
|
+
if (!this.owns(operation))
|
|
288
|
+
throw new OperationNotRetainedError(operation.id);
|
|
289
|
+
if (Date.now() >= deadline)
|
|
290
|
+
throw new VsrResponseTimeoutError(0);
|
|
291
|
+
if (size <= this.maxBytes - this.bytes)
|
|
292
|
+
break;
|
|
293
|
+
await waitWithin(this.changed.promise, deadline, signal);
|
|
294
|
+
}
|
|
295
|
+
const bytes = session.encode(operation.command, body, context);
|
|
296
|
+
this.bytes += size;
|
|
297
|
+
this.operations.delete(operation.id);
|
|
298
|
+
operation.id = bytes.readBigUInt64LE(REQUEST_OFFSET.request);
|
|
299
|
+
operation.context = context;
|
|
300
|
+
operation.frame = this.freeze(bytes, size);
|
|
301
|
+
this.operations.set(operation.id, operation);
|
|
302
|
+
}
|
|
303
|
+
freeze(bytes, size) {
|
|
304
|
+
return new FrozenFrame(bytes, () => {
|
|
305
|
+
this.bytes -= size;
|
|
306
|
+
this.wakeWaiting();
|
|
307
|
+
this.changed.pulse();
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
throwIfRetained(lane) {
|
|
311
|
+
const operation = this.lanes.get(lane);
|
|
312
|
+
if (operation?.retained)
|
|
313
|
+
throw new LaneBlockedError(operation.id);
|
|
314
|
+
}
|
|
315
|
+
retained(id) {
|
|
316
|
+
const operation = this.operations.get(id);
|
|
317
|
+
if (!operation?.retained)
|
|
318
|
+
throw new OperationNotRetainedError(id);
|
|
319
|
+
return operation;
|
|
320
|
+
}
|
|
321
|
+
owns(operation) {
|
|
322
|
+
return this.operations.get(operation.id) === operation;
|
|
323
|
+
}
|
|
324
|
+
retain(operation) {
|
|
325
|
+
// A replay keeps the state it has, which an abandon can have dropped.
|
|
326
|
+
if (operation.retainable && this.owns(operation) && !operation.resolving) {
|
|
327
|
+
operation.retained = true;
|
|
328
|
+
for (let waiter = this.waiting.get(operation.lane)?.head; waiter; waiter = waiter.next)
|
|
329
|
+
waiter.changed.pulse();
|
|
330
|
+
this.changed.pulse();
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/** Completes a call that proved its outcome. A replay keeps the outcome for a later resolve. */
|
|
334
|
+
prove(operation, rejection) {
|
|
335
|
+
if (operation.resolving) {
|
|
336
|
+
if (this.proved.size >= this.maxOperations)
|
|
337
|
+
this.proved.delete(this.proved.keys().next().value);
|
|
338
|
+
this.proved.set(operation.id, rejection);
|
|
339
|
+
}
|
|
340
|
+
this.complete(operation);
|
|
341
|
+
}
|
|
342
|
+
/** Returns or throws the outcome that a replay proved for `id`. */
|
|
343
|
+
provedOutcome(id) {
|
|
344
|
+
if (!this.proved.has(id))
|
|
345
|
+
throw new OperationNotRetainedError(id);
|
|
346
|
+
const rejection = this.proved.get(id);
|
|
347
|
+
if (rejection !== undefined)
|
|
348
|
+
throw rejection;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Drops the retained operation `id`, whose outcome stays unknown, and frees
|
|
352
|
+
* its slot. A running replay keeps the lane until it ends, so the lane stays
|
|
353
|
+
* FIFO.
|
|
354
|
+
*/
|
|
355
|
+
abandon(id) {
|
|
356
|
+
const operation = this.retained(id);
|
|
357
|
+
operation.retained = false;
|
|
358
|
+
if (!operation.resolving) {
|
|
359
|
+
this.complete(operation);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
this.releaseSlot(operation);
|
|
363
|
+
this.wakeWaiting();
|
|
364
|
+
}
|
|
365
|
+
complete(operation) {
|
|
366
|
+
if (!this.owns(operation))
|
|
367
|
+
return;
|
|
368
|
+
this.operations.delete(operation.id);
|
|
369
|
+
this.lanes.delete(operation.lane);
|
|
370
|
+
this.releaseSlot(operation);
|
|
371
|
+
operation.lifetime.abort(new OperationNotRetainedError(operation.id));
|
|
372
|
+
operation.frame.releaseOwner();
|
|
373
|
+
this.wakeLane(operation.lane);
|
|
374
|
+
this.wakeWaiting();
|
|
375
|
+
this.changed.pulse();
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Drops the retained operations and ends their replays, because no other
|
|
379
|
+
* session can replay their frames. A call keeps the lane it holds and starts
|
|
380
|
+
* again under the next session, so the calls in a lane keep their order.
|
|
381
|
+
*/
|
|
382
|
+
reset() {
|
|
383
|
+
for (const operation of this.operations.values())
|
|
384
|
+
if (operation.retained || operation.resolving)
|
|
385
|
+
this.complete(operation);
|
|
386
|
+
}
|
|
387
|
+
releaseSlot(operation) {
|
|
388
|
+
if (!operation.holdsSlot)
|
|
389
|
+
return;
|
|
390
|
+
operation.holdsSlot = false;
|
|
391
|
+
this.count -= 1;
|
|
392
|
+
}
|
|
393
|
+
wakeLane(lane) {
|
|
394
|
+
const head = this.waiting.get(lane)?.head;
|
|
395
|
+
if (head && !this.lanes.has(lane))
|
|
396
|
+
this.capacityWaiting.add(head);
|
|
397
|
+
}
|
|
398
|
+
wakeWaiting() {
|
|
399
|
+
for (const waiter of this.capacityWaiting) {
|
|
400
|
+
if ((waiter.retainable && this.count >= this.maxOperations) ||
|
|
401
|
+
waiter.size > this.maxBytes - this.bytes)
|
|
402
|
+
continue;
|
|
403
|
+
waiter.changed.pulse();
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
async acquireResolution(operation, deadline, signal) {
|
|
408
|
+
try {
|
|
409
|
+
while (operation.resolving) {
|
|
410
|
+
await waitWithin(this.changed.promise, deadline, signal);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
throw error instanceof VsrResponseTimeoutError ? new OperationUncertainError(operation.id, error) : error;
|
|
415
|
+
}
|
|
416
|
+
if (!this.owns(operation))
|
|
417
|
+
throw new OperationNotRetainedError(operation.id);
|
|
418
|
+
operation.resolving = true;
|
|
419
|
+
return () => {
|
|
420
|
+
operation.resolving = false;
|
|
421
|
+
// An abandon left the lane to this replay until now.
|
|
422
|
+
if (!operation.retained)
|
|
423
|
+
this.complete(operation);
|
|
424
|
+
this.changed.pulse();
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
//# sourceMappingURL=client.operation.js.map
|