@evolu/common 8.0.0-next.4 → 8.0.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/Array.d.ts +279 -173
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +105 -101
- package/dist/src/Assert.d.ts +44 -44
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +44 -32
- package/dist/src/BigInt.d.ts +6 -3
- package/dist/src/BigInt.d.ts.map +1 -1
- package/dist/src/BigInt.js +5 -2
- package/dist/src/Brand.d.ts +27 -40
- package/dist/src/Brand.d.ts.map +1 -1
- package/dist/src/Buffer.d.ts +27 -11
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Cache.d.ts +9 -4
- package/dist/src/Cache.d.ts.map +1 -1
- package/dist/src/Cache.js +8 -3
- package/dist/src/Callbacks.d.ts +30 -19
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Console.d.ts +145 -66
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +91 -43
- package/dist/src/Crypto.d.ts +85 -42
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +44 -20
- package/dist/src/Eq.d.ts +87 -21
- package/dist/src/Eq.d.ts.map +1 -1
- package/dist/src/Eq.js +67 -19
- package/dist/src/Error.d.ts +5 -3
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +16 -0
- package/dist/src/Function.d.ts +119 -58
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +86 -43
- package/dist/src/Http.d.ts +394 -0
- package/dist/src/Http.d.ts.map +1 -0
- package/dist/src/Http.js +102 -0
- package/dist/src/Identicon.d.ts +16 -7
- package/dist/src/Identicon.d.ts.map +1 -1
- package/dist/src/Identicon.js +14 -5
- package/dist/src/LeakDetector.d.ts +88 -0
- package/dist/src/LeakDetector.d.ts.map +1 -0
- package/dist/src/LeakDetector.js +86 -0
- package/dist/src/LockManager.d.ts +18 -9
- package/dist/src/LockManager.d.ts.map +1 -1
- package/dist/src/LockManager.js +7 -8
- package/dist/src/Lookup.d.ts +43 -13
- package/dist/src/Lookup.d.ts.map +1 -1
- package/dist/src/Lookup.js +28 -8
- package/dist/src/Microtask.d.ts +1 -1
- package/dist/src/Microtask.d.ts.map +1 -1
- package/dist/src/Microtask.js +3 -7
- package/dist/src/Number.d.ts +49 -13
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +34 -5
- package/dist/src/Object.d.ts +160 -40
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +92 -46
- package/dist/src/Option.d.ts +10 -7
- package/dist/src/Option.d.ts.map +1 -1
- package/dist/src/Option.js +9 -6
- package/dist/src/Order.d.ts +21 -21
- package/dist/src/Order.d.ts.map +1 -1
- package/dist/src/Order.js +18 -21
- package/dist/src/Platform.d.ts +31 -0
- package/dist/src/Platform.d.ts.map +1 -1
- package/dist/src/Platform.js +98 -0
- package/dist/src/Random.d.ts +32 -17
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Random.js +12 -8
- package/dist/src/Redacted.d.ts +43 -29
- package/dist/src/Redacted.d.ts.map +1 -1
- package/dist/src/Redacted.js +14 -3
- package/dist/src/Ref.d.ts +11 -3
- package/dist/src/Ref.d.ts.map +1 -1
- package/dist/src/Ref.js +2 -2
- package/dist/src/RefCount.d.ts +2 -6
- package/dist/src/RefCount.d.ts.map +1 -1
- package/dist/src/RefCount.js +33 -90
- package/dist/src/Relation.d.ts +70 -5
- package/dist/src/Relation.d.ts.map +1 -1
- package/dist/src/Relation.js +77 -2
- package/dist/src/Resource.d.ts +590 -155
- package/dist/src/Resource.d.ts.map +1 -1
- package/dist/src/Resource.js +592 -343
- package/dist/src/Result.d.ts +838 -191
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +163 -102
- package/dist/src/Schedule.d.ts +684 -231
- package/dist/src/Schedule.d.ts.map +1 -1
- package/dist/src/Schedule.js +766 -295
- package/dist/src/Set.d.ts +89 -40
- package/dist/src/Set.d.ts.map +1 -1
- package/dist/src/Set.js +50 -29
- package/dist/src/Sqlite.d.ts +29 -72
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +41 -46
- package/dist/src/StackTrace.d.ts +40 -0
- package/dist/src/StackTrace.d.ts.map +1 -0
- package/dist/src/StackTrace.js +140 -0
- package/dist/src/Store.d.ts +5 -4
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +5 -4
- package/dist/src/String.d.ts +7 -1
- package/dist/src/String.d.ts.map +1 -1
- package/dist/src/String.js +20 -7
- package/dist/src/Task.d.ts +3572 -1770
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +1879 -1274
- package/dist/src/Test.d.ts +17 -125
- package/dist/src/Test.d.ts.map +1 -1
- package/dist/src/Test.js +17 -68
- package/dist/src/Time.d.ts +108 -49
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +172 -73
- package/dist/src/Type.d.ts +3542 -1926
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +3070 -1784
- package/dist/src/Types.d.ts +200 -108
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/Types.js +39 -8
- package/dist/src/WebSocket.d.ts +48 -23
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +15 -6
- package/dist/src/Worker.d.ts +94 -36
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +58 -24
- package/dist/src/index.d.ts +55 -52
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -1
- package/dist/src/intl/cs.d.ts +94 -0
- package/dist/src/intl/cs.d.ts.map +1 -0
- package/dist/src/intl/cs.js +220 -0
- package/dist/src/intl/index.d.ts +7 -0
- package/dist/src/intl/index.d.ts.map +1 -0
- package/dist/src/intl/index.js +6 -0
- package/dist/src/local-first/Db.d.ts +12 -8
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +109 -132
- package/dist/src/local-first/Error.d.ts +4 -4
- package/dist/src/local-first/Evolu.d.ts +338 -92
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +47 -55
- package/dist/src/local-first/LocalAuth.d.ts +4 -4
- package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
- package/dist/src/local-first/Owner.d.ts +61 -24
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +45 -8
- package/dist/src/local-first/Protocol.d.ts +44 -31
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +36 -13
- package/dist/src/local-first/Query.d.ts +147 -62
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Query.js +112 -58
- package/dist/src/local-first/Relay.d.ts +44 -14
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Schema.d.ts +92 -41
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +25 -8
- package/dist/src/local-first/Shared.d.ts +23 -21
- package/dist/src/local-first/Shared.d.ts.map +1 -1
- package/dist/src/local-first/Shared.js +100 -57
- package/dist/src/local-first/Storage.d.ts +53 -32
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +194 -234
- package/dist/src/local-first/Timestamp.d.ts +21 -21
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +2 -2
- package/dist/src/local-first/index.d.ts +11 -11
- package/package.json +14 -13
- package/src/Array.ts +306 -205
- package/src/Assert.ts +54 -69
- package/src/BigInt.ts +6 -3
- package/src/Brand.ts +27 -40
- package/src/Buffer.ts +28 -12
- package/src/Cache.ts +9 -4
- package/src/Callbacks.ts +30 -19
- package/src/Console.ts +156 -84
- package/src/Crypto.ts +83 -41
- package/src/Eq.ts +95 -22
- package/src/Error.ts +8 -4
- package/src/Function.ts +167 -59
- package/src/Http.ts +549 -0
- package/src/Identicon.ts +16 -7
- package/src/LeakDetector.ts +182 -0
- package/src/LockManager.ts +43 -39
- package/src/Lookup.ts +62 -28
- package/src/Microtask.ts +19 -23
- package/src/Number.ts +64 -12
- package/src/Object.ts +173 -49
- package/src/Option.ts +10 -7
- package/src/Order.ts +23 -21
- package/src/Platform.ts +136 -0
- package/src/Polyfills.ts +6 -12
- package/src/Random.ts +32 -19
- package/src/Redacted.ts +44 -30
- package/src/Ref.ts +14 -6
- package/src/RefCount.ts +38 -80
- package/src/Relation.ts +202 -10
- package/src/Resource.ts +1199 -591
- package/src/Result.ts +930 -271
- package/src/Schedule.ts +859 -343
- package/src/Set.ts +89 -40
- package/src/Sqlite.ts +144 -138
- package/src/StackTrace.ts +230 -0
- package/src/Store.ts +8 -7
- package/src/String.ts +22 -5
- package/src/Task.ts +5101 -2832
- package/src/Test.ts +20 -168
- package/src/Time.ts +293 -90
- package/src/Type.ts +10034 -4117
- package/src/Types.ts +227 -110
- package/src/WebSocket.ts +64 -36
- package/src/Worker.ts +180 -72
- package/src/index.ts +55 -52
- package/src/intl/cs.ts +388 -0
- package/src/intl/index.ts +7 -0
- package/src/local-first/Db.ts +152 -179
- package/src/local-first/Error.ts +4 -4
- package/src/local-first/Evolu.ts +495 -250
- package/src/local-first/LocalAuth.ts +6 -12
- package/src/local-first/Owner.ts +58 -21
- package/src/local-first/Protocol.ts +49 -31
- package/src/local-first/Query.ts +146 -61
- package/src/local-first/Relay.ts +51 -21
- package/src/local-first/Schema.ts +109 -51
- package/src/local-first/Shared.ts +266 -226
- package/src/local-first/Storage.ts +266 -264
- package/src/local-first/Timestamp.ts +25 -18
- package/src/local-first/index.ts +11 -11
- package/dist/src/Skiplist.d.ts +0 -28
- package/dist/src/Skiplist.d.ts.map +0 -1
- package/dist/src/Skiplist.js +0 -64
- package/dist/src/Tracer.d.ts +0 -48
- package/dist/src/Tracer.d.ts.map +0 -1
- package/dist/src/Tracer.js +0 -6
- package/src/Skiplist.ts +0 -108
- package/src/Tracer.ts +0 -54
package/dist/src/Resource.js
CHANGED
|
@@ -55,384 +55,633 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
55
55
|
var e = new Error(message);
|
|
56
56
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
57
57
|
});
|
|
58
|
-
import { assert,
|
|
58
|
+
import { assert, assertNonNullable } from "./Assert.js";
|
|
59
59
|
import { identity } from "./Function.js";
|
|
60
60
|
import { createLookupMap, createLookupSet, } from "./Lookup.js";
|
|
61
|
-
import {
|
|
62
|
-
import { createRelation } from "./Relation.js";
|
|
61
|
+
import { createRefCountedRelation } from "./Relation.js";
|
|
63
62
|
import { ok } from "./Result.js";
|
|
64
|
-
import { createMutex, createMutexByKey, sleep,
|
|
63
|
+
import { createMutex, createMutexByKey, sleep, } from "./Task.js";
|
|
65
64
|
import {} from "./Time.js";
|
|
66
|
-
import { NonNegativeInt
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
65
|
+
import { NonNegativeInt } from "./Type.js";
|
|
66
|
+
import {} from "./Types.js";
|
|
67
|
+
/**
|
|
68
|
+
* Creates {@link SharedResource}.
|
|
69
|
+
*
|
|
70
|
+
* The `create` Task must not fail. Creation establishes shared resource state
|
|
71
|
+
* and may serve multiple concurrent acquirers, so a recoverable failure does
|
|
72
|
+
* not belong to one lease. Handle recoverable failures inside `create`: retry
|
|
73
|
+
* until creation succeeds (for example with a Schedule), or return a resource
|
|
74
|
+
* whose state models connection and reconnection failures.
|
|
75
|
+
*
|
|
76
|
+
* A successfully returned resource must be live and independently owned. The
|
|
77
|
+
* `create` Task must not bind its disposal to that Task's {@link DisposableRun}
|
|
78
|
+
* with `defer`.
|
|
79
|
+
*
|
|
80
|
+
* Create resources with {@link disposable} when their shape supports it. Its
|
|
81
|
+
* disposal guard makes calls through a lease fail eagerly after resource
|
|
82
|
+
* disposal instead of operating on disposed state.
|
|
83
|
+
*
|
|
84
|
+
* The `create` Task runs while acquisition is locked and must not directly or
|
|
85
|
+
* transitively acquire from the same SharedResource. The lock is non-reentrant,
|
|
86
|
+
* so doing so deadlocks.
|
|
87
|
+
*
|
|
88
|
+
* Internal work runs on a Run created from the Run that executes this Task, so
|
|
89
|
+
* dependencies are captured at creation time. Deps provided to later acquire
|
|
90
|
+
* calls do not change what `create` observes.
|
|
91
|
+
*
|
|
92
|
+
* Lifecycle callbacks must not throw. A throwing
|
|
93
|
+
* {@link SharedResourceOptions.onDisposed | onDisposed} is a defect that panics
|
|
94
|
+
* the Run tree.
|
|
95
|
+
*/
|
|
96
|
+
export const createSharedResource = (create, { idleDisposeAfter, onDisposed } = {}) => (run) => {
|
|
97
|
+
const sharedResourceRun = run.create();
|
|
98
|
+
const { leakDetector } = run.deps;
|
|
99
|
+
let current;
|
|
100
|
+
let idleDisposeFiber;
|
|
101
|
+
const heldLeases = new Set();
|
|
102
|
+
const mutex = createMutex();
|
|
103
|
+
const sharedResourceHandle = {};
|
|
104
|
+
// Idle disposal calls this under the mutex. Owner finalization calls it only
|
|
105
|
+
// after child Tasks, including idle disposal, settle, so calls cannot
|
|
106
|
+
// overlap.
|
|
107
|
+
const disposeCurrent = async () => {
|
|
108
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
109
|
+
try {
|
|
110
|
+
if (!current)
|
|
111
|
+
return;
|
|
112
|
+
const resourceDisposer = __addDisposableResource(env_1, new AsyncDisposableStack(), true);
|
|
113
|
+
if (onDisposed)
|
|
114
|
+
resourceDisposer.defer(onDisposed);
|
|
115
|
+
resourceDisposer.use(current);
|
|
116
|
+
current = undefined;
|
|
117
|
+
}
|
|
118
|
+
catch (e_1) {
|
|
119
|
+
env_1.error = e_1;
|
|
120
|
+
env_1.hasError = true;
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
const result_1 = __disposeResources(env_1);
|
|
124
|
+
if (result_1)
|
|
125
|
+
await result_1;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
/** Disposes the current resource unless a new lease arrived meanwhile. */
|
|
129
|
+
const disposeCurrentWhenUnused = mutex.withLock(async () => {
|
|
130
|
+
// For delayed disposal, this is safe to clear unconditionally: a newer
|
|
131
|
+
// idle-dispose fiber would require a release after a completed acquire,
|
|
132
|
+
// but FIFO orders that acquire behind this task. Therefore this is the
|
|
133
|
+
// current fiber or already undefined, never newer. An acquire that got
|
|
134
|
+
// the mutex before this task queued aborted this fiber while it was
|
|
135
|
+
// still sleeping. If this fiber was already queued on the mutex, abort
|
|
136
|
+
// removes its semaphore waiter before grant, so this body cannot run
|
|
137
|
+
// stale.
|
|
138
|
+
idleDisposeFiber = undefined;
|
|
139
|
+
if (heldLeases.size === 0)
|
|
140
|
+
await disposeCurrent();
|
|
141
|
+
return ok();
|
|
142
|
+
});
|
|
143
|
+
// Execution order is LIFO: clear idle state, drain leases, dispose current,
|
|
144
|
+
// then untrack the owner. Separate entries preserve later cleanup when an
|
|
145
|
+
// earlier finalizer defects.
|
|
146
|
+
sharedResourceRun.defer(() => {
|
|
147
|
+
leakDetector.untrack(sharedResourceHandle);
|
|
148
|
+
});
|
|
149
|
+
sharedResourceRun.defer(disposeCurrent);
|
|
150
|
+
sharedResourceRun.defer(() => {
|
|
151
|
+
for (const handle of heldLeases)
|
|
152
|
+
leakDetector.untrack(handle);
|
|
153
|
+
heldLeases.clear();
|
|
154
|
+
});
|
|
155
|
+
sharedResourceRun.defer(() => {
|
|
156
|
+
idleDisposeFiber = undefined;
|
|
157
|
+
});
|
|
158
|
+
const createLease = (created) => {
|
|
159
|
+
const handle = {};
|
|
160
|
+
heldLeases.add(handle);
|
|
161
|
+
const release = () => {
|
|
162
|
+
if (!heldLeases.delete(handle))
|
|
163
|
+
return false;
|
|
164
|
+
leakDetector.untrack(handle);
|
|
165
|
+
if (heldLeases.size > 0)
|
|
166
|
+
return true;
|
|
167
|
+
// During shutdown, disposal owns the resource; release is accounting
|
|
168
|
+
// only.
|
|
169
|
+
if (sharedResourceRun.getState().type !== "Running")
|
|
170
|
+
return true;
|
|
171
|
+
if (idleDisposeAfter) {
|
|
172
|
+
idleDisposeFiber = sharedResourceRun.abortable(async (run) => {
|
|
173
|
+
await run.ok(sleep(idleDisposeAfter));
|
|
174
|
+
return run(disposeCurrentWhenUnused);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
// Immediate disposal is not retained for acquire to cancel. Starting
|
|
179
|
+
// it synchronously queues or acquires the mutex before release
|
|
180
|
+
// returns. The mutex orders it against acquires, and disposal
|
|
181
|
+
// rechecks the lease count under the lock.
|
|
182
|
+
void sharedResourceRun.abortable(disposeCurrentWhenUnused);
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
};
|
|
186
|
+
const lease = {
|
|
187
|
+
resource: current,
|
|
188
|
+
created,
|
|
189
|
+
release,
|
|
190
|
+
[Symbol.dispose]: release,
|
|
191
|
+
};
|
|
192
|
+
leakDetector.track(lease, { name: "Lease", isLeaked: () => heldLeases.has(handle) }, handle);
|
|
193
|
+
return lease;
|
|
194
|
+
};
|
|
195
|
+
const sharedResource = {
|
|
196
|
+
// Run on the resource-owned tree so caller abort cannot interrupt acquire
|
|
197
|
+
// and later acquire deps cannot replace the owner's captured deps.
|
|
198
|
+
acquire: () => sharedResourceRun(mutex.withLock(async (run) => {
|
|
199
|
+
if (idleDisposeFiber) {
|
|
200
|
+
idleDisposeFiber.abort();
|
|
201
|
+
idleDisposeFiber = undefined;
|
|
202
|
+
}
|
|
203
|
+
const created = current === undefined;
|
|
204
|
+
current ??= await run.ok(create);
|
|
205
|
+
// Publish ownership before the checkpoint so owner finalization
|
|
206
|
+
// disposes a resource returned after shutdown started.
|
|
207
|
+
run.signal.throwIfAborted();
|
|
208
|
+
return ok(createLease(created));
|
|
209
|
+
})),
|
|
210
|
+
acquireCurrent: () => sharedResourceRun(
|
|
211
|
+
// This body must stay synchronous so owner disposal cannot start
|
|
212
|
+
// between checking current and transferring its lease.
|
|
213
|
+
mutex.withLock(() => {
|
|
214
|
+
if (!current)
|
|
215
|
+
return ok(undefined);
|
|
216
|
+
if (idleDisposeFiber) {
|
|
217
|
+
idleDisposeFiber.abort();
|
|
218
|
+
idleDisposeFiber = undefined;
|
|
219
|
+
}
|
|
220
|
+
return ok(createLease(false));
|
|
221
|
+
})),
|
|
222
|
+
use: (callback) => async (run) => {
|
|
223
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
224
|
+
try {
|
|
225
|
+
const lease = __addDisposableResource(env_2, await run.ok(sharedResource.acquire), false);
|
|
226
|
+
return await run(callback(lease.resource, lease.created));
|
|
227
|
+
}
|
|
228
|
+
catch (e_2) {
|
|
229
|
+
env_2.error = e_2;
|
|
230
|
+
env_2.hasError = true;
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
__disposeResources(env_2);
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
snapshot: () => {
|
|
237
|
+
const mutexSnapshot = mutex.snapshot();
|
|
238
|
+
return {
|
|
239
|
+
isIdle: heldLeases.size === 0 &&
|
|
240
|
+
!current &&
|
|
241
|
+
!idleDisposeFiber &&
|
|
242
|
+
mutexSnapshot.isIdle,
|
|
243
|
+
leaseCount: NonNegativeInt.orThrow(heldLeases.size),
|
|
244
|
+
hasResource: current !== undefined,
|
|
245
|
+
idleDisposePending: idleDisposeFiber !== undefined,
|
|
246
|
+
mutex: mutexSnapshot,
|
|
247
|
+
};
|
|
248
|
+
},
|
|
249
|
+
[Symbol.asyncDispose]: () => sharedResourceRun[Symbol.asyncDispose](),
|
|
250
|
+
};
|
|
251
|
+
leakDetector.track(sharedResource, {
|
|
252
|
+
name: "SharedResource",
|
|
253
|
+
isLeaked: () => sharedResourceRun.getState().type === "Running",
|
|
254
|
+
}, sharedResourceHandle);
|
|
255
|
+
return ok(sharedResource);
|
|
256
|
+
};
|
|
257
|
+
export function createSharedResourceByKey(create, { idleDisposeAfter, lookup = identity, onDisposed, } = {}) {
|
|
258
|
+
return (run) => {
|
|
259
|
+
const sharedResourceByKeyRun = run.create();
|
|
260
|
+
const { leakDetector } = run.deps;
|
|
261
|
+
const sharedResourcesByKey = createLookupMap({
|
|
262
|
+
lookup,
|
|
96
263
|
});
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
264
|
+
const mutexByKey = createMutexByKey({ lookup });
|
|
265
|
+
const disposer = new AsyncDisposableStack();
|
|
266
|
+
const sharedResourceByKeyHandle = {};
|
|
267
|
+
const removeKey = (key, sharedResource) => {
|
|
268
|
+
// Callers must ensure no same-key operation can still use sharedResource.
|
|
269
|
+
// onDisposed checks that the key mutex is idle; acquireCurrent holds the
|
|
270
|
+
// key mutex after observing that the inner resource is empty.
|
|
271
|
+
// Explicit registry disposal owns every registered SharedResource. Keep
|
|
272
|
+
// the key visible until keyDisposer captures it.
|
|
273
|
+
if (disposer.disposed)
|
|
274
|
+
return;
|
|
275
|
+
sharedResourcesByKey.delete(key);
|
|
276
|
+
// During root shutdown, the inner Run is already being disposed.
|
|
277
|
+
if (sharedResourceByKeyRun.getState().type !== "Running")
|
|
278
|
+
return;
|
|
279
|
+
void sharedResourceByKeyRun.abortable(async () => {
|
|
280
|
+
await sharedResource[Symbol.asyncDispose]();
|
|
281
|
+
return ok();
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
// Disposed in reverse order: the Run first, so pending acquires and removed
|
|
285
|
+
// SharedResource disposals are aborted and awaited before the registered
|
|
286
|
+
// SharedResources are disposed.
|
|
287
|
+
disposer.defer(async () => {
|
|
119
288
|
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
120
289
|
try {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
disposer.defer(onDisposed);
|
|
126
|
-
disposer.use(current.stack);
|
|
127
|
-
current = undefined;
|
|
290
|
+
const keyDisposer = __addDisposableResource(env_3, new AsyncDisposableStack(), true);
|
|
291
|
+
for (const sharedResource of sharedResourcesByKey.values())
|
|
292
|
+
keyDisposer.use(sharedResource);
|
|
293
|
+
sharedResourcesByKey.clear();
|
|
128
294
|
}
|
|
129
295
|
catch (e_3) {
|
|
130
296
|
env_3.error = e_3;
|
|
131
297
|
env_3.hasError = true;
|
|
132
298
|
}
|
|
133
299
|
finally {
|
|
134
|
-
const
|
|
135
|
-
if (
|
|
136
|
-
await
|
|
300
|
+
const result_2 = __disposeResources(env_3);
|
|
301
|
+
if (result_2)
|
|
302
|
+
await result_2;
|
|
137
303
|
}
|
|
304
|
+
});
|
|
305
|
+
disposer.use(sharedResourceByKeyRun);
|
|
306
|
+
sharedResourceByKeyRun.defer(() => {
|
|
307
|
+
leakDetector.untrack(sharedResourceByKeyHandle);
|
|
308
|
+
});
|
|
309
|
+
const releaseLeaseIfAborted = (signal, lease) => {
|
|
310
|
+
if (!signal.aborted)
|
|
311
|
+
return;
|
|
312
|
+
lease?.release();
|
|
313
|
+
signal.throwIfAborted();
|
|
138
314
|
};
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
315
|
+
const sharedResourceByKey = {
|
|
316
|
+
acquire: (key) => () => sharedResourceByKeyRun(mutexByKey.withLock(key, async (run) => {
|
|
317
|
+
// The outer key mutex spans registration and the inner acquire so
|
|
318
|
+
// key removal cannot race resource creation or lease transfer.
|
|
319
|
+
let sharedResource = sharedResourcesByKey.get(key);
|
|
320
|
+
if (!sharedResource) {
|
|
321
|
+
sharedResource = await run.ok(createSharedResource(create(key), {
|
|
322
|
+
idleDisposeAfter,
|
|
323
|
+
onDisposed: () => {
|
|
324
|
+
assertNonNullable(sharedResource, "SharedResource must be assigned before its resource is disposed.");
|
|
325
|
+
if (mutexByKey.isIdle(key))
|
|
326
|
+
removeKey(key, sharedResource);
|
|
327
|
+
onDisposed?.(key);
|
|
328
|
+
},
|
|
329
|
+
}));
|
|
330
|
+
sharedResourcesByKey.set(key, sharedResource);
|
|
148
331
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
332
|
+
const lease = await run.ok(sharedResource.acquire);
|
|
333
|
+
releaseLeaseIfAborted(run.signal, lease);
|
|
334
|
+
return ok(lease);
|
|
335
|
+
})),
|
|
336
|
+
acquireCurrent: (key) => () => sharedResourceByKeyRun(mutexByKey.withLock(key, async (run) => {
|
|
337
|
+
const sharedResource = sharedResourcesByKey.get(key);
|
|
338
|
+
if (!sharedResource)
|
|
339
|
+
return ok(undefined);
|
|
340
|
+
const lease = await run.ok(sharedResource.acquireCurrent);
|
|
341
|
+
releaseLeaseIfAborted(run.signal, lease);
|
|
342
|
+
if (!lease)
|
|
343
|
+
removeKey(key, sharedResource);
|
|
344
|
+
return ok(lease);
|
|
345
|
+
})),
|
|
346
|
+
use: (key, callback) => async (run) => {
|
|
347
|
+
const env_4 = { stack: [], error: void 0, hasError: false };
|
|
348
|
+
try {
|
|
349
|
+
const lease = __addDisposableResource(env_4, await run.ok(sharedResourceByKey.acquire(key)), false);
|
|
350
|
+
return await run(callback(lease.resource, lease.created));
|
|
154
351
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
release: unabortable(() => sharedResourceRun(mutex.withLock(async () => {
|
|
159
|
-
if (refCount.decrement() > 0)
|
|
160
|
-
return ok();
|
|
161
|
-
if (!idleDisposeAfter) {
|
|
162
|
-
await disposeCurrent();
|
|
163
|
-
return ok();
|
|
352
|
+
catch (e_4) {
|
|
353
|
+
env_4.error = e_4;
|
|
354
|
+
env_4.hasError = true;
|
|
164
355
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
idleDisposeFiber = undefined;
|
|
171
|
-
await disposeCurrent();
|
|
172
|
-
return ok();
|
|
173
|
-
}));
|
|
174
|
-
});
|
|
175
|
-
return ok();
|
|
176
|
-
}))),
|
|
177
|
-
getCount: () => sharedResourceRun(mutex.withLock(() => ok(refCount.getCount()))),
|
|
178
|
-
get: () => current?.resource,
|
|
179
|
-
snapshot: () => ({
|
|
180
|
-
isIdle: refCount.getCount() === 0 && !current && !idleDisposeFiber,
|
|
181
|
-
mutex: mutex.snapshot(),
|
|
182
|
-
}),
|
|
183
|
-
[Symbol.asyncDispose]: () => disposables.disposeAsync(),
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
catch (e_2) {
|
|
187
|
-
env_2.error = e_2;
|
|
188
|
-
env_2.hasError = true;
|
|
189
|
-
}
|
|
190
|
-
finally {
|
|
191
|
-
const result_2 = __disposeResources(env_2);
|
|
192
|
-
if (result_2)
|
|
193
|
-
await result_2;
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
export function createSharedResourceByKey(create, { idleDisposeAfter, lookup = identity, onDisposed, } = {}) {
|
|
197
|
-
return unabortable(async (run) => {
|
|
198
|
-
const env_4 = { stack: [], error: void 0, hasError: false };
|
|
199
|
-
try {
|
|
200
|
-
const sharedResourceByKeyRun = run.create();
|
|
201
|
-
const sharedResourcesByKey = createLookupMap({
|
|
202
|
-
lookup,
|
|
203
|
-
});
|
|
204
|
-
const disposer = __addDisposableResource(env_4, new AsyncDisposableStack(), true);
|
|
205
|
-
const mutexByKey = disposer.use(createMutexByKey({ lookup }));
|
|
206
|
-
disposer.defer(async () => {
|
|
356
|
+
finally {
|
|
357
|
+
__disposeResources(env_4);
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
forEachCurrent: (callback) => async (run) => {
|
|
207
361
|
const env_5 = { stack: [], error: void 0, hasError: false };
|
|
208
362
|
try {
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
363
|
+
const leaseDisposer = __addDisposableResource(env_5, new DisposableStack(), false);
|
|
364
|
+
const leasesByKey = [];
|
|
365
|
+
for (const [key] of [...sharedResourcesByKey]) {
|
|
366
|
+
const lease = await run.ok(sharedResourceByKey.acquireCurrent(key));
|
|
367
|
+
if (lease)
|
|
368
|
+
leasesByKey.push([leaseDisposer.use(lease), key]);
|
|
369
|
+
run.signal.throwIfAborted();
|
|
370
|
+
}
|
|
371
|
+
for (const [lease, key] of leasesByKey)
|
|
372
|
+
callback(lease.resource, key);
|
|
373
|
+
return ok();
|
|
214
374
|
}
|
|
215
375
|
catch (e_5) {
|
|
216
376
|
env_5.error = e_5;
|
|
217
377
|
env_5.hasError = true;
|
|
218
378
|
}
|
|
219
379
|
finally {
|
|
220
|
-
|
|
221
|
-
if (result_5)
|
|
222
|
-
await result_5;
|
|
380
|
+
__disposeResources(env_5);
|
|
223
381
|
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
382
|
+
},
|
|
383
|
+
snapshot: () => {
|
|
384
|
+
const resourcesByKey = new Map();
|
|
385
|
+
for (const [key, sharedResource] of sharedResourcesByKey)
|
|
386
|
+
resourcesByKey.set(key, sharedResource.snapshot());
|
|
387
|
+
return { resourcesByKey };
|
|
388
|
+
},
|
|
389
|
+
[Symbol.asyncDispose]: () => disposer.disposeAsync(),
|
|
390
|
+
};
|
|
391
|
+
leakDetector.track(sharedResourceByKey, {
|
|
392
|
+
name: "SharedResourceByKey",
|
|
393
|
+
isLeaked: () => sharedResourceByKeyRun.getState().type === "Running",
|
|
394
|
+
}, sharedResourceByKeyHandle);
|
|
395
|
+
return ok(sharedResourceByKey);
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Creates {@link SharedResourceByKeyWithClaims}.
|
|
400
|
+
*
|
|
401
|
+
* The `create` callback is called with a resource key when that resource must
|
|
402
|
+
* be created. The returned Task must not fail. Its dependencies are captured
|
|
403
|
+
* when the registry is created; later claim calls do not require or replace
|
|
404
|
+
* them.
|
|
405
|
+
*
|
|
406
|
+
* Transition callbacks represent semantic claim-resource pair changes and are
|
|
407
|
+
* not called when this registry or its Run tree drains claims during disposal.
|
|
408
|
+
* They must not throw. If a first-claim callback defects after earlier
|
|
409
|
+
* callbacks in the same claim completed, matching last-claim callbacks
|
|
410
|
+
* compensate those completed transitions in reverse order before cleanup.
|
|
411
|
+
* Relation reads during compensation still include the failed claim. Callback
|
|
412
|
+
* defects panic the owner Run after claim metadata and resource ownership are
|
|
413
|
+
* cleaned up.
|
|
414
|
+
*
|
|
415
|
+
* Transition callbacks may synchronously read relation state. They must not
|
|
416
|
+
* call `claim`, release a {@link ClaimLease} owned by this registry, or dispose
|
|
417
|
+
* this registry.
|
|
418
|
+
*
|
|
419
|
+
* Transition callbacks do not report actual resource disposal. Disposal can
|
|
420
|
+
* happen later when `idleDisposeAfter` is configured.
|
|
421
|
+
*/
|
|
422
|
+
export const createSharedResourceByKeyWithClaims = (create, { idleDisposeAfter, resourceLookup = identity, claimLookup = identity, onFirstClaimAdded, onLastClaimRemoved, } = {}) => async (run) => {
|
|
423
|
+
const env_6 = { stack: [], error: void 0, hasError: false };
|
|
424
|
+
try {
|
|
425
|
+
const sharedResourceByKeyWithClaimsRun = run.create();
|
|
426
|
+
const { leakDetector } = run.deps;
|
|
427
|
+
const claimResourceRelation = createRefCountedRelation({
|
|
428
|
+
lookupA: claimLookup,
|
|
429
|
+
lookupB: resourceLookup,
|
|
430
|
+
});
|
|
431
|
+
// This map and the relation's resource-key index use the same lookup and
|
|
432
|
+
// are inserted and removed together, so they keep the same canonical keys.
|
|
433
|
+
const resourcesByKey = createLookupMap({
|
|
434
|
+
lookup: resourceLookup,
|
|
435
|
+
});
|
|
436
|
+
const heldClaimLeases = new Set();
|
|
437
|
+
const sharedResourceByKeyWithClaimsHandle = {};
|
|
438
|
+
const notifyLastClaimRemoved = (claim, resource, resourceKey) => {
|
|
439
|
+
try {
|
|
440
|
+
onLastClaimRemoved?.(claim, resource, resourceKey);
|
|
441
|
+
}
|
|
442
|
+
catch (defect) {
|
|
443
|
+
sharedResourceByKeyWithClaimsRun.panic(defect);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
const disposer = __addDisposableResource(env_6, new AsyncDisposableStack(), true);
|
|
447
|
+
// Explicit disposal stops claim operations and runs their finalizers before
|
|
448
|
+
// disposing the keyed registry. The nested stack is empty if keyed-registry
|
|
449
|
+
// creation fails. Both internal Runs attach to the root, whose disposal
|
|
450
|
+
// awaits them independently of this explicit-disposal stack.
|
|
451
|
+
const sharedResourceByKeyDisposer = disposer.use(new AsyncDisposableStack());
|
|
452
|
+
disposer.use(sharedResourceByKeyWithClaimsRun);
|
|
453
|
+
const releaseClaimLease = (heldClaimLease, notifyTransition = true) => {
|
|
454
|
+
const env_7 = { stack: [], error: void 0, hasError: false };
|
|
455
|
+
try {
|
|
456
|
+
if (!heldClaimLeases.delete(heldClaimLease))
|
|
457
|
+
return false;
|
|
458
|
+
leakDetector.untrack(heldClaimLease);
|
|
459
|
+
const _resourceLeases = __addDisposableResource(env_7, heldClaimLease.resourceLeases, false);
|
|
460
|
+
for (const resourceKey of heldClaimLease.resourceKeys) {
|
|
461
|
+
const decremented = claimResourceRelation.decrement(heldClaimLease.claim, resourceKey);
|
|
462
|
+
if (decremented.count > 0)
|
|
463
|
+
continue;
|
|
464
|
+
const resource = resourcesByKey.get(decremented.b);
|
|
465
|
+
assertNonNullable(resource, "Resource must exist while its claim-resource pair is retained.");
|
|
466
|
+
if (notifyTransition) {
|
|
467
|
+
notifyLastClaimRemoved(decremented.a, resource, decremented.b);
|
|
250
468
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
assert(sharedResource, "Release must not be called more times than acquire.");
|
|
256
|
-
return sharedResourceByKeyRun(sharedResource.release);
|
|
257
|
-
}))),
|
|
258
|
-
getCount: (key) => () => sharedResourceByKeyRun(mutexByKey.withLock(key, async (run) => {
|
|
259
|
-
const sharedResource = sharedResourcesByKey.get(key);
|
|
260
|
-
if (!sharedResource)
|
|
261
|
-
return ok(zeroNonNegativeInt);
|
|
262
|
-
return run(sharedResource.getCount);
|
|
263
|
-
})),
|
|
264
|
-
get: (key) => sharedResourcesByKey.get(key)?.get(),
|
|
265
|
-
snapshot: () => {
|
|
266
|
-
const resourcesByKey = new Map();
|
|
267
|
-
const mutexSnapshotsByKey = new Map();
|
|
268
|
-
for (const [key, sharedResource] of sharedResourcesByKey.entries()) {
|
|
269
|
-
const resource = sharedResource.get();
|
|
270
|
-
if (!resource)
|
|
271
|
-
continue;
|
|
272
|
-
resourcesByKey.set(key, resource);
|
|
273
|
-
mutexSnapshotsByKey.set(key, mutexByKey.snapshot(key));
|
|
469
|
+
if (!claimResourceRelation.hasB(decremented.b)) {
|
|
470
|
+
// Delete before _resourceLeases disposes at function exit. While this
|
|
471
|
+
// entry exists, its leases pin one resource generation.
|
|
472
|
+
resourcesByKey.delete(decremented.b);
|
|
274
473
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return unabortable(async (run) => {
|
|
296
|
-
const env_6 = { stack: [], error: void 0, hasError: false };
|
|
297
|
-
try {
|
|
298
|
-
const sharedResourceClaimsRun = run.create();
|
|
299
|
-
const disposer = __addDisposableResource(env_6, new AsyncDisposableStack(), true);
|
|
300
|
-
const keyByClaim = createRelation({
|
|
301
|
-
lookupA: claimLookup,
|
|
302
|
-
lookupB: resourceLookup,
|
|
303
|
-
});
|
|
304
|
-
const pairRefCountsByClaim = disposer.adopt(createLookupMap({ lookup: claimLookup }), (pairRefCountsByClaim) => {
|
|
305
|
-
const env_7 = { stack: [], error: void 0, hasError: false };
|
|
306
|
-
try {
|
|
307
|
-
const disposer = __addDisposableResource(env_7, new DisposableStack(), false);
|
|
474
|
+
}
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
catch (e_7) {
|
|
478
|
+
env_7.error = e_7;
|
|
479
|
+
env_7.hasError = true;
|
|
480
|
+
}
|
|
481
|
+
finally {
|
|
482
|
+
__disposeResources(env_7);
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
const drainClaimLeases = () => {
|
|
486
|
+
const env_8 = { stack: [], error: void 0, hasError: false };
|
|
487
|
+
try {
|
|
488
|
+
const disposer = __addDisposableResource(env_8, new DisposableStack(), false);
|
|
489
|
+
disposer.defer(() => {
|
|
490
|
+
claimResourceRelation.clear();
|
|
491
|
+
resourcesByKey.clear();
|
|
492
|
+
});
|
|
493
|
+
for (const heldClaimLease of heldClaimLeases) {
|
|
308
494
|
disposer.defer(() => {
|
|
309
|
-
|
|
495
|
+
releaseClaimLease(heldClaimLease, false);
|
|
310
496
|
});
|
|
311
|
-
for (const pairRefCountByKey of pairRefCountsByClaim.values()) {
|
|
312
|
-
disposer.use(pairRefCountByKey);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
catch (e_7) {
|
|
316
|
-
env_7.error = e_7;
|
|
317
|
-
env_7.hasError = true;
|
|
318
|
-
}
|
|
319
|
-
finally {
|
|
320
|
-
__disposeResources(env_7);
|
|
321
497
|
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
assert(keyByClaim.add(claim, resourceKey), "Claim-resource relation must be absent before first retain.");
|
|
367
|
-
pairRefCountByKey.increment(resourceKey);
|
|
368
|
-
if (firstResource) {
|
|
369
|
-
onFirstClaimAdded?.(claim, firstResource, resourceKey);
|
|
370
|
-
}
|
|
371
|
-
return ok();
|
|
372
|
-
}));
|
|
373
|
-
assertNotAborted(added);
|
|
498
|
+
}
|
|
499
|
+
catch (e_8) {
|
|
500
|
+
env_8.error = e_8;
|
|
501
|
+
env_8.hasError = true;
|
|
502
|
+
}
|
|
503
|
+
finally {
|
|
504
|
+
__disposeResources(env_8);
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
sharedResourceByKeyWithClaimsRun.defer(() => {
|
|
508
|
+
leakDetector.untrack(sharedResourceByKeyWithClaimsHandle);
|
|
509
|
+
});
|
|
510
|
+
sharedResourceByKeyWithClaimsRun.defer(drainClaimLeases);
|
|
511
|
+
const sharedResourceByKey = sharedResourceByKeyDisposer.use(await sharedResourceByKeyWithClaimsRun.ok(createSharedResourceByKey(create, {
|
|
512
|
+
idleDisposeAfter,
|
|
513
|
+
lookup: resourceLookup,
|
|
514
|
+
})));
|
|
515
|
+
run.signal.throwIfAborted();
|
|
516
|
+
const disposables = disposer.move();
|
|
517
|
+
const acquireClaim = (claim, resourceKeys, project) => () => {
|
|
518
|
+
const resourceKeysSnapshot = [
|
|
519
|
+
resourceKeys[0],
|
|
520
|
+
...resourceKeys.slice(1),
|
|
521
|
+
];
|
|
522
|
+
return sharedResourceByKeyWithClaimsRun(async (run) => {
|
|
523
|
+
const env_9 = { stack: [], error: void 0, hasError: false };
|
|
524
|
+
try {
|
|
525
|
+
assert(createLookupSet({
|
|
526
|
+
lookup: resourceLookup,
|
|
527
|
+
values: resourceKeysSnapshot,
|
|
528
|
+
}).size === resourceKeysSnapshot.length, "resourceKeys must not contain lookup duplicates.");
|
|
529
|
+
const resourceLeases = __addDisposableResource(env_9, new DisposableStack(), false);
|
|
530
|
+
const [firstResourceKey, ...remainingResourceKeys] = resourceKeysSnapshot;
|
|
531
|
+
// acquire releases a transferred lease before surfacing owner abort,
|
|
532
|
+
// so a throwing await cannot leave ownership uncollected.
|
|
533
|
+
const firstResourceLease = resourceLeases.use(await run.ok(sharedResourceByKey.acquire(firstResourceKey)));
|
|
534
|
+
const resources = [
|
|
535
|
+
[firstResourceLease.resource, firstResourceKey],
|
|
536
|
+
];
|
|
537
|
+
const firstClaimTransitions = [];
|
|
538
|
+
for (const resourceKey of remainingResourceKeys) {
|
|
539
|
+
const resourceLease = resourceLeases.use(await run.ok(sharedResourceByKey.acquire(resourceKey)));
|
|
540
|
+
resources.push([resourceLease.resource, resourceKey]);
|
|
374
541
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
542
|
+
run.signal.throwIfAborted();
|
|
543
|
+
for (const [resource, resourceKey] of resources) {
|
|
544
|
+
const incremented = claimResourceRelation.increment(claim, resourceKey);
|
|
545
|
+
resourcesByKey.getOrInsert(incremented.b, resource);
|
|
546
|
+
if (incremented.count === 1) {
|
|
547
|
+
firstClaimTransitions.push([
|
|
548
|
+
incremented.a,
|
|
549
|
+
resource,
|
|
550
|
+
incremented.b,
|
|
551
|
+
]);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
const heldClaimLease = {
|
|
555
|
+
claim,
|
|
556
|
+
resourceKeys: resourceKeysSnapshot,
|
|
557
|
+
resourceLeases: resourceLeases.move(),
|
|
558
|
+
};
|
|
559
|
+
heldClaimLeases.add(heldClaimLease);
|
|
560
|
+
const release = () => releaseClaimLease(heldClaimLease);
|
|
561
|
+
const claimLease = {
|
|
562
|
+
release,
|
|
563
|
+
[Symbol.dispose]: release,
|
|
564
|
+
};
|
|
565
|
+
leakDetector.track(claimLease, {
|
|
566
|
+
name: "ClaimLease",
|
|
567
|
+
isLeaked: () => heldClaimLeases.has(heldClaimLease),
|
|
568
|
+
}, heldClaimLease);
|
|
569
|
+
try {
|
|
570
|
+
const env_10 = { stack: [], error: void 0, hasError: false };
|
|
571
|
+
try {
|
|
572
|
+
let succeeded = false;
|
|
573
|
+
const compensations = __addDisposableResource(env_10, new DisposableStack(), false);
|
|
574
|
+
for (const [claim, resource, resourceKey,] of firstClaimTransitions) {
|
|
575
|
+
onFirstClaimAdded?.(claim, resource, resourceKey);
|
|
576
|
+
compensations.defer(() => {
|
|
577
|
+
if (!succeeded) {
|
|
578
|
+
notifyLastClaimRemoved(claim, resource, resourceKey);
|
|
579
|
+
}
|
|
580
|
+
});
|
|
393
581
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
582
|
+
succeeded = true;
|
|
583
|
+
}
|
|
584
|
+
catch (e_9) {
|
|
585
|
+
env_10.error = e_9;
|
|
586
|
+
env_10.hasError = true;
|
|
587
|
+
}
|
|
588
|
+
finally {
|
|
589
|
+
__disposeResources(env_10);
|
|
590
|
+
}
|
|
402
591
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
getClaimsForResource: (key) => new Set(keyByClaim.iterateA(key)),
|
|
407
|
-
getResourceKeysForClaim: (claim) => new Set(keyByClaim.iterateB(claim)),
|
|
408
|
-
getResourcesForClaim: (claim) => {
|
|
409
|
-
const resources = new Set();
|
|
410
|
-
for (const key of keyByClaim.iterateB(claim)) {
|
|
411
|
-
const resource = sharedResourcesByKey.get(key);
|
|
412
|
-
assert(resource, "Resource must exist for every retained claim-resource relation.");
|
|
413
|
-
resources.add(resource);
|
|
592
|
+
catch (defect) {
|
|
593
|
+
releaseClaimLease(heldClaimLease, false);
|
|
594
|
+
throw defect;
|
|
414
595
|
}
|
|
415
|
-
return resources;
|
|
416
|
-
}
|
|
417
|
-
|
|
596
|
+
return ok(project(claimLease, resources));
|
|
597
|
+
}
|
|
598
|
+
catch (e_10) {
|
|
599
|
+
env_9.error = e_10;
|
|
600
|
+
env_9.hasError = true;
|
|
601
|
+
}
|
|
602
|
+
finally {
|
|
603
|
+
__disposeResources(env_9);
|
|
604
|
+
}
|
|
418
605
|
});
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
606
|
+
};
|
|
607
|
+
const sharedResourceByKeyWithClaims = {
|
|
608
|
+
claim: (claim, resourceKeys) => acquireClaim(claim, resourceKeys, (claimLease) => claimLease),
|
|
609
|
+
use: (claim, resourceKeys, callback) => async (run) => {
|
|
610
|
+
const env_11 = { stack: [], error: void 0, hasError: false };
|
|
611
|
+
try {
|
|
612
|
+
const acquired = await run.ok(acquireClaim(claim, resourceKeys, (claimLease, resources) => ({
|
|
613
|
+
claimLease,
|
|
614
|
+
resources,
|
|
615
|
+
})));
|
|
616
|
+
const _claimLease = __addDisposableResource(env_11, acquired.claimLease, false);
|
|
617
|
+
return await run(callback(acquired.resources));
|
|
618
|
+
}
|
|
619
|
+
catch (e_11) {
|
|
620
|
+
env_11.error = e_11;
|
|
621
|
+
env_11.hasError = true;
|
|
622
|
+
}
|
|
623
|
+
finally {
|
|
624
|
+
__disposeResources(env_11);
|
|
625
|
+
}
|
|
626
|
+
},
|
|
627
|
+
getClaimsForResource: (resourceKey) => new Set(claimResourceRelation.getAs(resourceKey)),
|
|
628
|
+
getResourceKeysForClaim: (claim) => new Set(claimResourceRelation.getBs(claim)),
|
|
629
|
+
forEachResourceForClaim: (claim, callback) => {
|
|
630
|
+
const resources = claimResourceRelation
|
|
631
|
+
.getBs(claim)
|
|
632
|
+
.map((resourceKey) => {
|
|
633
|
+
const resource = resourcesByKey.get(resourceKey);
|
|
634
|
+
assertNonNullable(resource, "Resource must exist for every active claim-resource relation.");
|
|
635
|
+
return [resource, resourceKey];
|
|
636
|
+
});
|
|
637
|
+
// Releasing the final ClaimLease from a callback only schedules inner
|
|
638
|
+
// disposal: the mutex Task resumes after this synchronous frame, so all
|
|
639
|
+
// snapshotted resources remain live through the loop.
|
|
640
|
+
for (const [resource, resourceKey] of resources) {
|
|
641
|
+
callback(resource, resourceKey);
|
|
642
|
+
}
|
|
643
|
+
},
|
|
644
|
+
snapshot: () => {
|
|
645
|
+
const retainCountsByResourceKeyByClaimSnapshot = new Map();
|
|
646
|
+
for (const [claim, resourceKey, count,] of claimResourceRelation.getEntries()) {
|
|
647
|
+
retainCountsByResourceKeyByClaimSnapshot
|
|
648
|
+
.getOrInsertComputed(claim, () => new Map())
|
|
649
|
+
.set(resourceKey, count);
|
|
650
|
+
}
|
|
651
|
+
return {
|
|
652
|
+
claimLeaseCount: NonNegativeInt.orThrow(heldClaimLeases.size),
|
|
653
|
+
retainCountsByResourceKeyByClaim: retainCountsByResourceKeyByClaimSnapshot,
|
|
654
|
+
resourcesByKey: sharedResourceByKey.snapshot().resourcesByKey,
|
|
655
|
+
};
|
|
656
|
+
},
|
|
657
|
+
[Symbol.asyncDispose]: () => disposables.disposeAsync(),
|
|
658
|
+
};
|
|
659
|
+
leakDetector.track(sharedResourceByKeyWithClaims, {
|
|
660
|
+
name: "SharedResourceByKeyWithClaims",
|
|
661
|
+
isLeaked: () => sharedResourceByKeyWithClaimsRun.getState().type === "Running",
|
|
662
|
+
}, sharedResourceByKeyWithClaimsHandle);
|
|
663
|
+
return ok(sharedResourceByKeyWithClaims);
|
|
664
|
+
}
|
|
665
|
+
catch (e_6) {
|
|
666
|
+
env_6.error = e_6;
|
|
667
|
+
env_6.hasError = true;
|
|
668
|
+
}
|
|
669
|
+
finally {
|
|
670
|
+
const result_3 = __disposeResources(env_6);
|
|
671
|
+
if (result_3)
|
|
672
|
+
await result_3;
|
|
673
|
+
}
|
|
435
674
|
};
|
|
436
|
-
// TODO:
|
|
437
|
-
//
|
|
438
|
-
//
|
|
675
|
+
// TODO: Add a lease-based reloadable resource when a concrete use case needs
|
|
676
|
+
// resource swapping. A ResourceRef-style get/set API is unsafe because set can
|
|
677
|
+
// dispose a resource still held by a caller of get. Model resource generations
|
|
678
|
+
// explicitly and distinguish two replacement policies, probably as separate
|
|
679
|
+
// APIs rather than a boolean option:
|
|
680
|
+
// - Overlapping: create and validate the replacement, publish it to new leases,
|
|
681
|
+
// then dispose the old generation after its existing leases drain. This
|
|
682
|
+
// supports zero-downtime reload and keeps the old generation when creation
|
|
683
|
+
// fails, but both generations temporarily exist.
|
|
684
|
+
// - Exclusive: stop or queue new leases, drain and dispose the old generation,
|
|
685
|
+
// then create and publish the replacement. This guarantees at most one live
|
|
686
|
+
// resource, but introduces downtime and leaves no valid resource when creation
|
|
687
|
+
// fails unless the owner retries or recreates the old configuration.
|