effect 4.0.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Cache.d.ts.map +1 -1
- package/dist/Cache.js +20 -16
- package/dist/Cache.js.map +1 -1
- package/dist/Cause.d.ts.map +1 -1
- package/dist/Cause.js +1 -1
- package/dist/Cause.js.map +1 -1
- package/dist/Effect.d.ts +150 -22
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +11 -5
- package/dist/Effect.js.map +1 -1
- package/dist/HashRing.d.ts.map +1 -1
- package/dist/HashRing.js +13 -5
- package/dist/HashRing.js.map +1 -1
- package/dist/Pool.d.ts +9 -5
- package/dist/Pool.d.ts.map +1 -1
- package/dist/Pool.js +34 -41
- package/dist/Pool.js.map +1 -1
- package/dist/RcMap.d.ts.map +1 -1
- package/dist/RcMap.js +16 -1
- package/dist/RcMap.js.map +1 -1
- package/dist/ScopedCache.d.ts.map +1 -1
- package/dist/ScopedCache.js +24 -16
- package/dist/ScopedCache.js.map +1 -1
- package/dist/Stream.d.ts +45 -6
- package/dist/Stream.d.ts.map +1 -1
- package/dist/Stream.js +55 -44
- package/dist/Stream.js.map +1 -1
- package/dist/ai/LanguageModel.d.ts +8 -0
- package/dist/ai/LanguageModel.d.ts.map +1 -1
- package/dist/ai/LanguageModel.js +13 -2
- package/dist/ai/LanguageModel.js.map +1 -1
- package/dist/ai/McpSchema.d.ts +508 -508
- package/dist/ai/McpSchema.d.ts.map +1 -1
- package/dist/ai/McpSchema.js +1 -1
- package/dist/ai/McpSchema.js.map +1 -1
- package/dist/ai/McpServer.d.ts.map +1 -1
- package/dist/ai/McpServer.js +11 -3
- package/dist/ai/McpServer.js.map +1 -1
- package/dist/ai/Response.d.ts.map +1 -1
- package/dist/ai/Response.js +22 -12
- package/dist/ai/Response.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2024_11_05.js +1 -0
- package/dist/ai/internal/mcpProtocol/v2024_11_05.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_03_26.js +1 -0
- package/dist/ai/internal/mcpProtocol/v2025_03_26.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_06_18.js +1 -0
- package/dist/ai/internal/mcpProtocol/v2025_06_18.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_11_25.js +1 -0
- package/dist/ai/internal/mcpProtocol/v2025_11_25.js.map +1 -1
- package/dist/ai/internal/mcpSchema/v2024_11_05.js +3 -1
- package/dist/ai/internal/mcpSchema/v2024_11_05.js.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.js +11 -2
- package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
- package/dist/cluster/Sharding.d.ts.map +1 -1
- package/dist/cluster/Sharding.js +14 -7
- package/dist/cluster/Sharding.js.map +1 -1
- package/dist/cluster/SqlMessageStorage.d.ts.map +1 -1
- package/dist/cluster/SqlMessageStorage.js +21 -12
- package/dist/cluster/SqlMessageStorage.js.map +1 -1
- package/dist/cluster/SqlRunnerStorage.d.ts.map +1 -1
- package/dist/cluster/SqlRunnerStorage.js +30 -44
- package/dist/cluster/SqlRunnerStorage.js.map +1 -1
- package/dist/cluster/internal/entityManager.js +63 -62
- package/dist/cluster/internal/entityManager.js.map +1 -1
- package/dist/cluster/internal/interruptors.js +8 -0
- package/dist/cluster/internal/interruptors.js.map +1 -1
- package/dist/cluster/internal/resourceMap.js +39 -14
- package/dist/cluster/internal/resourceMap.js.map +1 -1
- package/dist/cluster/internal/resourceRef.js +168 -87
- package/dist/cluster/internal/resourceRef.js.map +1 -1
- package/dist/http/HttpMiddleware.d.ts.map +1 -1
- package/dist/http/HttpMiddleware.js +3 -2
- package/dist/http/HttpMiddleware.js.map +1 -1
- package/dist/http/HttpStaticServer.d.ts.map +1 -1
- package/dist/http/HttpStaticServer.js +14 -0
- package/dist/http/HttpStaticServer.js.map +1 -1
- package/dist/http/HttpTraceContext.d.ts +1 -1
- package/dist/http/HttpTraceContext.d.ts.map +1 -1
- package/dist/http/HttpTraceContext.js +4 -4
- package/dist/http/HttpTraceContext.js.map +1 -1
- package/dist/http-api/HttpApiError.d.ts +1 -0
- package/dist/http-api/HttpApiError.d.ts.map +1 -1
- package/dist/http-api/HttpApiError.js +1 -0
- package/dist/http-api/HttpApiError.js.map +1 -1
- package/dist/internal/core.js +43 -2
- package/dist/internal/core.js.map +1 -1
- package/dist/internal/dateTime.js +1 -1
- package/dist/internal/dateTime.js.map +1 -1
- package/dist/internal/effect.js +63 -81
- package/dist/internal/effect.js.map +1 -1
- package/dist/internal/rcRef.js +59 -23
- package/dist/internal/rcRef.js.map +1 -1
- package/dist/internal/schema/fromJsonSchemaDocument.js +11 -8
- package/dist/internal/schema/fromJsonSchemaDocument.js.map +1 -1
- package/dist/rpc/RpcClient.d.ts.map +1 -1
- package/dist/rpc/RpcClient.js +20 -14
- package/dist/rpc/RpcClient.js.map +1 -1
- package/dist/rpc/RpcServer.d.ts.map +1 -1
- package/dist/rpc/RpcServer.js +2 -2
- package/dist/rpc/RpcServer.js.map +1 -1
- package/dist/rpc/Utils.d.ts +2 -3
- package/dist/rpc/Utils.d.ts.map +1 -1
- package/dist/rpc/Utils.js +3 -4
- package/dist/rpc/Utils.js.map +1 -1
- package/dist/workflow/WorkflowEngine.d.ts +2 -0
- package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
- package/dist/workflow/WorkflowEngine.js +1 -0
- package/dist/workflow/WorkflowEngine.js.map +1 -1
- package/package.json +1 -1
- package/src/Cache.ts +22 -16
- package/src/Cause.ts +1 -1
- package/src/Effect.ts +153 -22
- package/src/HashRing.ts +14 -5
- package/src/Pool.ts +28 -36
- package/src/RcMap.ts +26 -2
- package/src/ScopedCache.ts +27 -16
- package/src/Stream.ts +158 -88
- package/src/ai/LanguageModel.ts +27 -2
- package/src/ai/McpSchema.ts +3 -3
- package/src/ai/McpServer.ts +12 -3
- package/src/ai/Response.ts +27 -12
- package/src/ai/internal/mcpProtocol/v2024_11_05.ts +1 -0
- package/src/ai/internal/mcpProtocol/v2025_03_26.ts +1 -0
- package/src/ai/internal/mcpProtocol/v2025_06_18.ts +1 -0
- package/src/ai/internal/mcpProtocol/v2025_11_25.ts +1 -0
- package/src/ai/internal/mcpSchema/v2024_11_05.ts +4 -2
- package/src/cluster/ClusterWorkflowEngine.ts +18 -4
- package/src/cluster/Sharding.ts +24 -24
- package/src/cluster/SqlMessageStorage.ts +26 -13
- package/src/cluster/SqlRunnerStorage.ts +85 -113
- package/src/cluster/internal/entityManager.ts +68 -80
- package/src/cluster/internal/interruptors.ts +13 -0
- package/src/cluster/internal/resourceMap.ts +55 -17
- package/src/cluster/internal/resourceRef.ts +189 -96
- package/src/http/HttpMiddleware.ts +8 -2
- package/src/http/HttpStaticServer.ts +16 -0
- package/src/http/HttpTraceContext.ts +8 -4
- package/src/http-api/HttpApiError.ts +1 -0
- package/src/internal/core.ts +56 -2
- package/src/internal/dateTime.ts +3 -1
- package/src/internal/effect.ts +109 -111
- package/src/internal/rcRef.ts +83 -33
- package/src/internal/schema/fromJsonSchemaDocument.ts +5 -3
- package/src/rpc/RpcClient.ts +26 -22
- package/src/rpc/RpcServer.ts +2 -1
- package/src/rpc/Utils.ts +4 -10
- package/src/workflow/WorkflowEngine.ts +4 -0
|
@@ -97,6 +97,8 @@ export type EntityState = {
|
|
|
97
97
|
readonly rpc: Rpc.AnyWithProps
|
|
98
98
|
readonly message: Message.IncomingRequestLocal<any>
|
|
99
99
|
sentReply: boolean
|
|
100
|
+
/** Excludes requests awaiting their first dispatch from replay. */
|
|
101
|
+
delivered: boolean
|
|
100
102
|
sentExit: boolean
|
|
101
103
|
lastSentChunk: Option.Option<Reply.Chunk<Rpc.Any>>
|
|
102
104
|
sequence: number
|
|
@@ -200,11 +202,19 @@ export const make = Effect.fnUntraced(function*<
|
|
|
200
202
|
)
|
|
201
203
|
|
|
202
204
|
const activeRequests: EntityState["activeRequests"] = new Map()
|
|
203
|
-
|
|
204
|
-
|
|
205
|
+
const retired = Latch.makeUnsafe()
|
|
206
|
+
const isActive = () => !retired.isOpen()
|
|
207
|
+
|
|
208
|
+
// Replay previously dispatched requests before admitting new work.
|
|
209
|
+
const replay = Effect.fnUntraced(function*(write: EntityState["write"]) {
|
|
210
|
+
if (!isActive()) return
|
|
211
|
+
for (const request of activeRequests.values()) {
|
|
212
|
+
if (!request.delivered) continue
|
|
213
|
+
request.sentExit = false
|
|
214
|
+
yield* write(0, requestEnvelope(request), requestWriteOptions(request))
|
|
215
|
+
}
|
|
216
|
+
})
|
|
205
217
|
|
|
206
|
-
// the server is stored in a ref, so if there is a defect, we can
|
|
207
|
-
// swap the server without losing the active requests
|
|
208
218
|
const writeRef = yield* ResourceRef.from(
|
|
209
219
|
scope,
|
|
210
220
|
Effect.fnUntraced(function*(handlerScope) {
|
|
@@ -245,11 +255,8 @@ export const make = Effect.fnUntraced(function*<
|
|
|
245
255
|
request.sentReply = true
|
|
246
256
|
request.sentExit = true
|
|
247
257
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
Exit.hasInterrupts(response.exit) &&
|
|
251
|
-
defectRequestIds.has(Snowflake.Snowflake(response.requestId))
|
|
252
|
-
) {
|
|
258
|
+
// Rebuild interrupts are not replies; replacement handlers replay the request.
|
|
259
|
+
if (isShuttingDown && Exit.hasInterrupts(response.exit) && isActive()) {
|
|
253
260
|
return Effect.void
|
|
254
261
|
}
|
|
255
262
|
|
|
@@ -265,19 +272,7 @@ export const make = Effect.fnUntraced(function*<
|
|
|
265
272
|
) {
|
|
266
273
|
if (!isShuttingDown) {
|
|
267
274
|
request.sentExit = false
|
|
268
|
-
return server.write(
|
|
269
|
-
0,
|
|
270
|
-
{
|
|
271
|
-
...request.message.envelope,
|
|
272
|
-
id: request.message.envelope.requestId as any,
|
|
273
|
-
tag: request.message.envelope.tag as any,
|
|
274
|
-
payload: new Request({
|
|
275
|
-
...request.message.envelope,
|
|
276
|
-
lastSentChunk: request.lastSentChunk
|
|
277
|
-
} as any) as any
|
|
278
|
-
},
|
|
279
|
-
requestWriteOptions(request)
|
|
280
|
-
).pipe(
|
|
275
|
+
return server.write(0, requestEnvelope(request), requestWriteOptions(request)).pipe(
|
|
281
276
|
Effect.forkIn(handlerScope)
|
|
282
277
|
)
|
|
283
278
|
}
|
|
@@ -335,7 +330,10 @@ export const make = Effect.fnUntraced(function*<
|
|
|
335
330
|
))
|
|
336
331
|
}
|
|
337
332
|
case "Defect": {
|
|
338
|
-
|
|
333
|
+
if (!isActive()) return endLatch.open
|
|
334
|
+
const rebuild = writeRef.rebuildUnsafe({ from: server.write, prepare: replay })
|
|
335
|
+
if (!rebuild) return Effect.void
|
|
336
|
+
return Effect.forkIn(restart(Cause.die(response.defect), rebuild), managerScope)
|
|
339
337
|
}
|
|
340
338
|
case "ClientEnd": {
|
|
341
339
|
return endLatch.open
|
|
@@ -354,56 +352,25 @@ export const make = Effect.fnUntraced(function*<
|
|
|
354
352
|
})
|
|
355
353
|
)
|
|
356
354
|
|
|
357
|
-
if (defectRequestIds.size > 0) {
|
|
358
|
-
for (const id of defectRequestIds) {
|
|
359
|
-
const request = activeRequests.get(id)
|
|
360
|
-
if (!request) continue
|
|
361
|
-
const { lastSentChunk, message } = request
|
|
362
|
-
request.sentExit = false
|
|
363
|
-
yield* server.write(
|
|
364
|
-
0,
|
|
365
|
-
{
|
|
366
|
-
...message.envelope,
|
|
367
|
-
id: message.envelope.requestId as any,
|
|
368
|
-
tag: message.envelope.tag as any,
|
|
369
|
-
payload: new Request({
|
|
370
|
-
...message.envelope,
|
|
371
|
-
lastSentChunk
|
|
372
|
-
} as any) as any
|
|
373
|
-
},
|
|
374
|
-
requestWriteOptions(request)
|
|
375
|
-
)
|
|
376
|
-
}
|
|
377
|
-
defectRequestIds.clear()
|
|
378
|
-
}
|
|
379
|
-
|
|
380
355
|
return server.write
|
|
381
356
|
}),
|
|
382
357
|
address
|
|
383
358
|
)
|
|
384
359
|
|
|
385
|
-
function
|
|
386
|
-
if (!activeServers.has(address.entityId)) {
|
|
387
|
-
return endLatch.open
|
|
388
|
-
}
|
|
389
|
-
if (isRestartingDueToDefect) {
|
|
390
|
-
return Effect.void
|
|
391
|
-
}
|
|
392
|
-
defectRequestIds = new Set(activeRequests.keys())
|
|
393
|
-
isRestartingDueToDefect = true
|
|
394
|
-
const effect = writeRef.rebuildUnsafe()
|
|
360
|
+
function restart(cause: Cause.Cause<unknown>, rebuild: Effect.Effect<void>): Effect.Effect<void> {
|
|
395
361
|
return Effect.logError("Defect in entity, restarting", cause).pipe(
|
|
396
362
|
Effect.andThen(Effect.ignore(retryDriver(void 0))),
|
|
397
|
-
Effect.flatMap(() =>
|
|
398
|
-
Effect.ensuring(Effect.sync(() => {
|
|
399
|
-
isRestartingDueToDefect = false
|
|
400
|
-
})),
|
|
363
|
+
Effect.flatMap(() => isActive() ? rebuild : endLatch.open),
|
|
401
364
|
Effect.annotateLogs({
|
|
402
365
|
module: "EntityManager",
|
|
403
366
|
address,
|
|
404
367
|
runner: options.runnerAddress
|
|
405
368
|
}),
|
|
406
|
-
Effect.catchCause(
|
|
369
|
+
Effect.catchCause((cause) => {
|
|
370
|
+
if (Cause.hasInterruptsOnly(cause)) return Effect.void
|
|
371
|
+
const retry = isActive() ? writeRef.rebuildUnsafe({ prepare: replay }) : undefined
|
|
372
|
+
return retry ? restart(cause, retry) : Effect.void
|
|
373
|
+
})
|
|
407
374
|
)
|
|
408
375
|
}
|
|
409
376
|
|
|
@@ -411,10 +378,27 @@ export const make = Effect.fnUntraced(function*<
|
|
|
411
378
|
scope,
|
|
412
379
|
address,
|
|
413
380
|
write(clientId, message, writeOptions) {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
381
|
+
return Effect.suspend(() => {
|
|
382
|
+
if (message._tag !== "Request") {
|
|
383
|
+
// Interrupts, acks and EOF reach the handlers even during shutdown.
|
|
384
|
+
const write = writeRef.getUnsafe()
|
|
385
|
+
return write
|
|
386
|
+
? write(clientId, message, writeOptions)
|
|
387
|
+
: Effect.flatMap(writeRef.await, (write) => write(clientId, message, writeOptions))
|
|
388
|
+
}
|
|
389
|
+
if (!isActive()) return Effect.interrupt
|
|
390
|
+
const write = writeRef.getUnsafe()
|
|
391
|
+
if (write === undefined) {
|
|
392
|
+
return Effect.flatMap(
|
|
393
|
+
Effect.raceFirst(writeRef.await, retired.await),
|
|
394
|
+
() => state.write(clientId, message, writeOptions)
|
|
395
|
+
)
|
|
396
|
+
}
|
|
397
|
+
const request = activeRequests.get(Snowflake.Snowflake(message.id))
|
|
398
|
+
if (!request) return Effect.void
|
|
399
|
+
request.delivered = true
|
|
400
|
+
return write(clientId, message, writeOptions)
|
|
401
|
+
})
|
|
418
402
|
},
|
|
419
403
|
activeRequests,
|
|
420
404
|
lastActiveCheck: clock.currentTimeMillisUnsafe(),
|
|
@@ -430,6 +414,7 @@ export const make = Effect.fnUntraced(function*<
|
|
|
430
414
|
scope,
|
|
431
415
|
Effect.suspend(() => {
|
|
432
416
|
activeServers.delete(address.entityId)
|
|
417
|
+
retired.openUnsafe()
|
|
433
418
|
acquireEntity(address)
|
|
434
419
|
return Effect.raceFirst(
|
|
435
420
|
state.write(0, { _tag: "Eof" }).pipe(
|
|
@@ -441,6 +426,9 @@ export const make = Effect.fnUntraced(function*<
|
|
|
441
426
|
)
|
|
442
427
|
})
|
|
443
428
|
)
|
|
429
|
+
if (!options.sharding.hasShardId(address.shardId)) {
|
|
430
|
+
return yield* new EntityNotAssignedToRunner({ address })
|
|
431
|
+
}
|
|
444
432
|
// Do not make shard interruption wait for an entity that is still building.
|
|
445
433
|
serverCloseLatches.set(address, closeLatches)
|
|
446
434
|
activeServers.set(address.entityId, state)
|
|
@@ -544,6 +532,7 @@ export const make = Effect.fnUntraced(function*<
|
|
|
544
532
|
rpc,
|
|
545
533
|
message,
|
|
546
534
|
sentReply: false,
|
|
535
|
+
delivered: false,
|
|
547
536
|
sentExit: false,
|
|
548
537
|
lastSentChunk: Option.filter(
|
|
549
538
|
message.lastSentReply,
|
|
@@ -565,21 +554,7 @@ export const make = Effect.fnUntraced(function*<
|
|
|
565
554
|
}
|
|
566
555
|
}))
|
|
567
556
|
}
|
|
568
|
-
return server.write(
|
|
569
|
-
0,
|
|
570
|
-
{
|
|
571
|
-
...message.envelope,
|
|
572
|
-
id: message.envelope.requestId as any,
|
|
573
|
-
payload: new Request({
|
|
574
|
-
...message.envelope,
|
|
575
|
-
lastSentChunk: Option.filter(
|
|
576
|
-
message.lastSentReply,
|
|
577
|
-
(reply): reply is Reply.Chunk<R> => reply._tag === "Chunk"
|
|
578
|
-
)
|
|
579
|
-
})
|
|
580
|
-
},
|
|
581
|
-
requestWriteOptions(entry)
|
|
582
|
-
)
|
|
557
|
+
return server.write(0, requestEnvelope(entry), requestWriteOptions(entry))
|
|
583
558
|
}
|
|
584
559
|
case "IncomingEnvelope": {
|
|
585
560
|
const entry = server.activeRequests.get(message.envelope.requestId)
|
|
@@ -795,6 +770,19 @@ const makeMessageDecode = <Rpcs extends Rpc.Any>(entityRpcs: Map<string, Rpcs>)
|
|
|
795
770
|
}
|
|
796
771
|
}
|
|
797
772
|
|
|
773
|
+
const requestEnvelope = (entry: {
|
|
774
|
+
readonly message: Message.IncomingRequestLocal<any>
|
|
775
|
+
readonly lastSentChunk: Option.Option<Reply.Chunk<Rpc.Any>>
|
|
776
|
+
}): Parameters<EntityState["write"]>[1] => ({
|
|
777
|
+
...entry.message.envelope,
|
|
778
|
+
id: entry.message.envelope.requestId as any,
|
|
779
|
+
tag: entry.message.envelope.tag as any,
|
|
780
|
+
payload: new Request({
|
|
781
|
+
...entry.message.envelope,
|
|
782
|
+
lastSentChunk: entry.lastSentChunk
|
|
783
|
+
} as any) as any
|
|
784
|
+
})
|
|
785
|
+
|
|
798
786
|
const retryRespond = <A, E, R>(times: number, effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
|
|
799
787
|
times === 0 ?
|
|
800
788
|
effect :
|
|
@@ -48,6 +48,14 @@ export const acquireEntityType = (entityType: string): void => acquire(entityTyp
|
|
|
48
48
|
*/
|
|
49
49
|
export const releaseEntityType = (entityType: string): void => release(entityTypeKey(entityType))
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
export const aroundEntity = <A, E, R>(
|
|
55
|
+
address: EntityAddress,
|
|
56
|
+
effect: Effect.Effect<A, E, R>
|
|
57
|
+
): Effect.Effect<A, E, R> => around(entityKey(address), effect)
|
|
58
|
+
|
|
51
59
|
/**
|
|
52
60
|
* @internal
|
|
53
61
|
*/
|
|
@@ -71,3 +79,8 @@ export const isActive = (address: EntityAddress): boolean =>
|
|
|
71
79
|
counts.has(entityKey(address)) ||
|
|
72
80
|
counts.has(shardKey(address.shardId)) ||
|
|
73
81
|
counts.has(entityTypeKey(address.entityType))
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @internal
|
|
85
|
+
*/
|
|
86
|
+
export const isShardActive = (shardId: ShardId): boolean => counts.has(shardKey(shardId))
|
|
@@ -2,6 +2,7 @@ import * as Context from "../../Context.ts"
|
|
|
2
2
|
import * as Deferred from "../../Deferred.ts"
|
|
3
3
|
import * as Effect from "../../Effect.ts"
|
|
4
4
|
import * as Exit from "../../Exit.ts"
|
|
5
|
+
import * as Fiber from "../../Fiber.ts"
|
|
5
6
|
import * as MutableHashMap from "../../MutableHashMap.ts"
|
|
6
7
|
import * as MutableRef from "../../MutableRef.ts"
|
|
7
8
|
import * as Scope from "../../Scope.ts"
|
|
@@ -44,9 +45,9 @@ export class ResourceMap<K, A, E> {
|
|
|
44
45
|
scope,
|
|
45
46
|
(exit) => {
|
|
46
47
|
MutableRef.set(isClosed, true)
|
|
47
|
-
return Effect.forEach(entries.map, ([key, { scope }]) => {
|
|
48
|
+
return Effect.forEach(entries.map, ([key, { fiber, scope }]) => {
|
|
48
49
|
backingDelete(entries, key)
|
|
49
|
-
return Effect.exit(Scope.close(scope, exit))
|
|
50
|
+
return Effect.exit(Effect.andThen(Fiber.interrupt(fiber), Scope.close(scope, exit)))
|
|
50
51
|
}, { concurrency: "unbounded", discard: true })
|
|
51
52
|
}
|
|
52
53
|
)
|
|
@@ -67,30 +68,64 @@ export class ResourceMap<K, A, E> {
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
get(key: K): Effect.Effect<A, E> {
|
|
70
|
-
return Effect.
|
|
71
|
+
return Effect.uninterruptibleMask((restore) => {
|
|
71
72
|
if (MutableRef.get(this.isClosed)) {
|
|
72
73
|
return Effect.interrupt
|
|
73
74
|
}
|
|
74
75
|
const existing = backingGet(this.entries, key)
|
|
75
76
|
if (existing) {
|
|
76
|
-
return
|
|
77
|
+
return this.awaitEntry(key, existing, restore)
|
|
78
|
+
}
|
|
79
|
+
const entry: Entry<A, E> = {
|
|
80
|
+
scope: Scope.makeUnsafe(),
|
|
81
|
+
deferred: Deferred.makeUnsafe<A, E>(),
|
|
82
|
+
fiber: undefined as any,
|
|
83
|
+
awaiters: 0
|
|
84
|
+
}
|
|
85
|
+
backingSet(this.entries, key, entry)
|
|
86
|
+
// Run the lookup on a detached fiber so it is shared by every caller and
|
|
87
|
+
// only interrupted once all of them have been interrupted.
|
|
88
|
+
entry.fiber = Effect.runForkWith(Fiber.getCurrent()!.context)(
|
|
89
|
+
Effect.onExit(this.lookup(key, entry.scope), (exit) => {
|
|
90
|
+
if (exit._tag === "Success") {
|
|
91
|
+
return Deferred.done(entry.deferred, exit)
|
|
92
|
+
}
|
|
93
|
+
this.deleteEntry(key, entry)
|
|
94
|
+
return Effect.andThen(
|
|
95
|
+
Deferred.done(entry.deferred, exit),
|
|
96
|
+
Scope.close(entry.scope, exit)
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
)
|
|
100
|
+
return this.awaitEntry(key, entry, restore)
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private awaitEntry(
|
|
105
|
+
key: K,
|
|
106
|
+
entry: Entry<A, E>,
|
|
107
|
+
restore: <AX, EX, RX>(effect: Effect.Effect<AX, EX, RX>) => Effect.Effect<AX, EX, RX>
|
|
108
|
+
): Effect.Effect<A, E> {
|
|
109
|
+
if (Deferred.isDoneUnsafe(entry.deferred)) return Deferred.await(entry.deferred)
|
|
110
|
+
entry.awaiters++
|
|
111
|
+
return Effect.onExit(restore(Deferred.await(entry.deferred)), () => {
|
|
112
|
+
if (--entry.awaiters > 0 || Deferred.isDoneUnsafe(entry.deferred)) {
|
|
113
|
+
return Effect.void
|
|
77
114
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return
|
|
82
|
-
if (exit._tag === "Success") {
|
|
83
|
-
return Deferred.done(deferred, exit)
|
|
84
|
-
}
|
|
85
|
-
backingDelete(this.entries, key)
|
|
86
|
-
return Effect.andThen(
|
|
87
|
-
Deferred.done(deferred, exit),
|
|
88
|
-
Scope.close(scope, exit)
|
|
89
|
-
)
|
|
90
|
-
})
|
|
115
|
+
// Detach the abandoned entry first so new callers start a fresh lookup
|
|
116
|
+
// instead of joining one that is being interrupted.
|
|
117
|
+
this.deleteEntry(key, entry)
|
|
118
|
+
return Fiber.interrupt(entry.fiber)
|
|
91
119
|
})
|
|
92
120
|
}
|
|
93
121
|
|
|
122
|
+
private deleteEntry(key: K, entry: Entry<A, E>): void {
|
|
123
|
+
// Never delete a replacement entry created after this one was removed.
|
|
124
|
+
if (backingGet(this.entries, key) === entry) {
|
|
125
|
+
backingDelete(this.entries, key)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
94
129
|
remove(key: K): Effect.Effect<void> {
|
|
95
130
|
return Effect.suspend(() => {
|
|
96
131
|
const entry = backingGet(this.entries, key)
|
|
@@ -123,6 +158,9 @@ type BackingMap<K, A, E> = {
|
|
|
123
158
|
type Entry<A, E> = {
|
|
124
159
|
readonly scope: Scope.Closeable
|
|
125
160
|
readonly deferred: Deferred.Deferred<A, E>
|
|
161
|
+
// Assigned as soon as the lookup is forked.
|
|
162
|
+
fiber: Fiber.Fiber<unknown, unknown>
|
|
163
|
+
awaiters: number
|
|
126
164
|
}
|
|
127
165
|
|
|
128
166
|
const backingGet = <K, A, E>(map: BackingMap<K, A, E>, key: K): Entry<A, E> | undefined => {
|