@effect/cluster 0.52.11 → 0.53.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/cjs/ClusterWorkflowEngine.js +99 -105
- package/dist/cjs/ClusterWorkflowEngine.js.map +1 -1
- package/dist/cjs/Entity.js +58 -2
- package/dist/cjs/Entity.js.map +1 -1
- package/dist/cjs/internal/entityManager.js +55 -24
- package/dist/cjs/internal/entityManager.js.map +1 -1
- package/dist/dts/ClusterWorkflowEngine.d.ts +15 -22
- package/dist/dts/ClusterWorkflowEngine.d.ts.map +1 -1
- package/dist/dts/Entity.d.ts +18 -4
- package/dist/dts/Entity.d.ts.map +1 -1
- package/dist/esm/ClusterWorkflowEngine.js +100 -106
- package/dist/esm/ClusterWorkflowEngine.js.map +1 -1
- package/dist/esm/Entity.js +54 -0
- package/dist/esm/Entity.js.map +1 -1
- package/dist/esm/internal/entityManager.js +56 -25
- package/dist/esm/internal/entityManager.js.map +1 -1
- package/package.json +4 -4
- package/src/ClusterWorkflowEngine.ts +35 -29
- package/src/Entity.ts +67 -1
- package/src/internal/entityManager.ts +105 -46
|
@@ -15,6 +15,7 @@ import { identity } from "effect/Function"
|
|
|
15
15
|
import * as HashMap from "effect/HashMap"
|
|
16
16
|
import * as Metric from "effect/Metric"
|
|
17
17
|
import * as Option from "effect/Option"
|
|
18
|
+
import * as ParseResult from "effect/ParseResult"
|
|
18
19
|
import * as Runtime from "effect/Runtime"
|
|
19
20
|
import * as Schedule from "effect/Schedule"
|
|
20
21
|
import * as Schema from "effect/Schema"
|
|
@@ -23,10 +24,10 @@ import { AlreadyProcessingMessage, EntityNotAssignedToRunner, MailboxFull, Malfo
|
|
|
23
24
|
import * as ClusterMetrics from "../ClusterMetrics.js"
|
|
24
25
|
import { Persisted, Uninterruptible } from "../ClusterSchema.js"
|
|
25
26
|
import type { Entity, HandlersFrom } from "../Entity.js"
|
|
26
|
-
import { CurrentAddress, CurrentRunnerAddress, Request } from "../Entity.js"
|
|
27
|
+
import { CurrentAddress, CurrentRunnerAddress, KeepAliveLatch, KeepAliveRpc, Request } from "../Entity.js"
|
|
27
28
|
import type { EntityAddress } from "../EntityAddress.js"
|
|
28
29
|
import type { EntityId } from "../EntityId.js"
|
|
29
|
-
import * as Envelope from "../Envelope.js"
|
|
30
|
+
import type * as Envelope from "../Envelope.js"
|
|
30
31
|
import * as Message from "../Message.js"
|
|
31
32
|
import * as MessageStorage from "../MessageStorage.js"
|
|
32
33
|
import * as Reply from "../Reply.js"
|
|
@@ -65,6 +66,7 @@ export interface EntityManager {
|
|
|
65
66
|
/** @internal */
|
|
66
67
|
export type EntityState = {
|
|
67
68
|
readonly address: EntityAddress
|
|
69
|
+
readonly scope: Scope.Scope
|
|
68
70
|
readonly activeRequests: Map<bigint, {
|
|
69
71
|
readonly rpc: Rpc.AnyWithProps
|
|
70
72
|
readonly message: Message.IncomingRequestLocal<any>
|
|
@@ -74,6 +76,8 @@ export type EntityState = {
|
|
|
74
76
|
}>
|
|
75
77
|
lastActiveCheck: number
|
|
76
78
|
write: RpcServer.RpcServer<any>["write"]
|
|
79
|
+
readonly keepAliveLatch: Effect.Latch
|
|
80
|
+
keepAliveEnabled: boolean
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
/** @internal */
|
|
@@ -107,6 +111,10 @@ export const make = Effect.fnUntraced(function*<
|
|
|
107
111
|
const retryDriver = yield* Schedule.driver(
|
|
108
112
|
options.defectRetryPolicy ? Schedule.andThen(options.defectRetryPolicy, defaultRetryPolicy) : defaultRetryPolicy
|
|
109
113
|
)
|
|
114
|
+
const entityRpcs = new Map(entity.protocol.requests)
|
|
115
|
+
|
|
116
|
+
// add internal rpcs
|
|
117
|
+
entityRpcs.set(KeepAliveRpc._tag, KeepAliveRpc as any)
|
|
110
118
|
|
|
111
119
|
const activeServers = new Map<EntityId, EntityState>()
|
|
112
120
|
const serverCloseLatches = new Map<EntityAddress, Effect.Latch>()
|
|
@@ -122,7 +130,8 @@ export const make = Effect.fnUntraced(function*<
|
|
|
122
130
|
}
|
|
123
131
|
|
|
124
132
|
const scope = yield* Effect.scope
|
|
125
|
-
const endLatch =
|
|
133
|
+
const endLatch = Effect.unsafeMakeLatch()
|
|
134
|
+
const keepAliveLatch = Effect.unsafeMakeLatch()
|
|
126
135
|
|
|
127
136
|
// on shutdown, reset the storage for the entity
|
|
128
137
|
yield* Scope.addFinalizerExit(
|
|
@@ -149,6 +158,7 @@ export const make = Effect.fnUntraced(function*<
|
|
|
149
158
|
Effect.provide(context.pipe(
|
|
150
159
|
Context.add(CurrentAddress, address),
|
|
151
160
|
Context.add(CurrentRunnerAddress, options.runnerAddress),
|
|
161
|
+
Context.add(KeepAliveLatch, keepAliveLatch),
|
|
152
162
|
Context.add(Scope.Scope, scope)
|
|
153
163
|
)),
|
|
154
164
|
Effect.locally(FiberRef.currentLogAnnotations, HashMap.empty())
|
|
@@ -306,6 +316,7 @@ export const make = Effect.fnUntraced(function*<
|
|
|
306
316
|
}
|
|
307
317
|
|
|
308
318
|
const state: EntityState = {
|
|
319
|
+
scope,
|
|
309
320
|
address,
|
|
310
321
|
write(clientId, message) {
|
|
311
322
|
if (writeRef.state.current._tag !== "Acquired") {
|
|
@@ -314,7 +325,9 @@ export const make = Effect.fnUntraced(function*<
|
|
|
314
325
|
return writeRef.state.current.value(clientId, message)
|
|
315
326
|
},
|
|
316
327
|
activeRequests,
|
|
317
|
-
lastActiveCheck: clock.unsafeCurrentTimeMillis()
|
|
328
|
+
lastActiveCheck: clock.unsafeCurrentTimeMillis(),
|
|
329
|
+
keepAliveLatch,
|
|
330
|
+
keepAliveEnabled: false
|
|
318
331
|
}
|
|
319
332
|
|
|
320
333
|
// During shutdown, signal that no more messages will be processed
|
|
@@ -380,13 +393,43 @@ export const make = Effect.fnUntraced(function*<
|
|
|
380
393
|
)
|
|
381
394
|
}
|
|
382
395
|
|
|
383
|
-
const rpc =
|
|
396
|
+
const rpc = entityRpcs.get(message.envelope.tag)! as any as Rpc.AnyWithProps
|
|
384
397
|
if (!storageEnabled && Context.get(rpc.annotations, Persisted)) {
|
|
385
398
|
return Effect.dieMessage(
|
|
386
399
|
"EntityManager.sendLocal: Cannot process a persisted message without MessageStorage"
|
|
387
400
|
)
|
|
388
401
|
}
|
|
389
402
|
|
|
403
|
+
// Cluster internal RPCs
|
|
404
|
+
|
|
405
|
+
// keep-alive RPC
|
|
406
|
+
if (rpc._tag === KeepAliveRpc._tag) {
|
|
407
|
+
const msg = message as unknown as Message.IncomingRequestLocal<typeof KeepAliveRpc>
|
|
408
|
+
const reply = Effect.suspend(() =>
|
|
409
|
+
Effect.orDie(retryRespond(
|
|
410
|
+
4,
|
|
411
|
+
msg.respond(
|
|
412
|
+
new Reply.WithExit<typeof KeepAliveRpc>({
|
|
413
|
+
requestId: message.envelope.requestId,
|
|
414
|
+
id: snowflakeGen.unsafeNext(),
|
|
415
|
+
exit: Exit.void
|
|
416
|
+
})
|
|
417
|
+
)
|
|
418
|
+
))
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
if (server.keepAliveEnabled) return reply
|
|
422
|
+
server.keepAliveEnabled = true
|
|
423
|
+
server.keepAliveLatch.unsafeClose()
|
|
424
|
+
return server.keepAliveLatch.whenOpen(Effect.suspend(() => {
|
|
425
|
+
server.keepAliveEnabled = false
|
|
426
|
+
return reply
|
|
427
|
+
})).pipe(
|
|
428
|
+
Effect.forkIn(server.scope),
|
|
429
|
+
Effect.asVoid
|
|
430
|
+
)
|
|
431
|
+
}
|
|
432
|
+
|
|
390
433
|
if (mailboxCapacity !== "unbounded" && server.activeRequests.size >= mailboxCapacity) {
|
|
391
434
|
return Effect.fail(new MailboxFull({ address: message.envelope.address }))
|
|
392
435
|
}
|
|
@@ -437,7 +480,7 @@ export const make = Effect.fnUntraced(function*<
|
|
|
437
480
|
)
|
|
438
481
|
}
|
|
439
482
|
|
|
440
|
-
const decodeMessage =
|
|
483
|
+
const decodeMessage = makeMessageDecode(entity, entityRpcs)
|
|
441
484
|
|
|
442
485
|
const runFork = Runtime.runFork(
|
|
443
486
|
yield* Effect.runtime<never>().pipe(
|
|
@@ -533,49 +576,65 @@ const defaultRetryPolicy = Schedule.exponential(500, 1.5).pipe(
|
|
|
533
576
|
Schedule.union(Schedule.spaced("10 seconds"))
|
|
534
577
|
)
|
|
535
578
|
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
payload: (rpc as any as Rpc.AnyWithProps).payloadSchema
|
|
558
|
-
}),
|
|
559
|
-
Envelope.RequestFromSelf,
|
|
560
|
-
{
|
|
561
|
-
decode: (encoded) => Envelope.makeRequest(encoded),
|
|
562
|
-
encode: identity
|
|
563
|
-
}
|
|
564
|
-
),
|
|
565
|
-
lastSentReply: Schema.OptionFromSelf(Reply.Reply(rpc))
|
|
566
|
-
})
|
|
567
|
-
)
|
|
579
|
+
const makeMessageDecode = <Type extends string, Rpcs extends Rpc.Any>(
|
|
580
|
+
entity: Entity<Type, Rpcs>,
|
|
581
|
+
entityRpcs: Map<string, Rpcs>
|
|
582
|
+
) => {
|
|
583
|
+
const decodeRequest = (
|
|
584
|
+
message: Message.IncomingRequest<Rpcs>,
|
|
585
|
+
rpc: Rpc.AnyWithProps
|
|
586
|
+
) => {
|
|
587
|
+
const payload = Schema.decode(rpc.payloadSchema)(message.envelope.payload)
|
|
588
|
+
const lastSentReply = Option.isSome(message.lastSentReply)
|
|
589
|
+
? Effect.asSome(Schema.decode(Reply.Reply(rpc as any))(message.lastSentReply.value))
|
|
590
|
+
: Effect.succeedNone
|
|
591
|
+
return Effect.flatMap(payload, (payload) =>
|
|
592
|
+
Effect.map(lastSentReply, (lastSentReply) => ({
|
|
593
|
+
_tag: "IncomingRequest" as const,
|
|
594
|
+
envelope: {
|
|
595
|
+
...message.envelope,
|
|
596
|
+
payload
|
|
597
|
+
} as Envelope.Request.Any,
|
|
598
|
+
lastSentReply
|
|
599
|
+
})))
|
|
568
600
|
}
|
|
569
601
|
|
|
570
|
-
return
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
envelope:
|
|
574
|
-
|
|
575
|
-
|
|
602
|
+
return (message: Message.Incoming<Rpcs>): Effect.Effect<
|
|
603
|
+
{
|
|
604
|
+
readonly _tag: "IncomingRequest"
|
|
605
|
+
readonly envelope: Envelope.Request.Any
|
|
606
|
+
readonly lastSentReply: Option.Option<Reply.Reply<Rpcs>>
|
|
607
|
+
} | {
|
|
608
|
+
readonly _tag: "IncomingEnvelope"
|
|
609
|
+
readonly envelope: Envelope.AckChunk | Envelope.Interrupt
|
|
610
|
+
},
|
|
611
|
+
ParseResult.ParseError,
|
|
612
|
+
Rpc.Context<Rpcs>
|
|
613
|
+
> => {
|
|
614
|
+
if (message._tag === "IncomingEnvelope") {
|
|
615
|
+
return Effect.succeed(message)
|
|
616
|
+
}
|
|
617
|
+
const rpc = entityRpcs.get(message.envelope.tag) as any as Rpc.AnyWithProps
|
|
618
|
+
if (!rpc) {
|
|
619
|
+
return Effect.fail(
|
|
620
|
+
new ParseResult.ParseError({
|
|
621
|
+
issue: new ParseResult.Unexpected(
|
|
622
|
+
message,
|
|
623
|
+
`Unknown tag ${message.envelope.tag} for entity type ${entity.type}`
|
|
624
|
+
)
|
|
625
|
+
})
|
|
576
626
|
)
|
|
577
|
-
}
|
|
578
|
-
|
|
627
|
+
}
|
|
628
|
+
return decodeRequest(message, rpc) as Effect.Effect<
|
|
629
|
+
{
|
|
630
|
+
readonly _tag: "IncomingRequest"
|
|
631
|
+
readonly envelope: Envelope.Request.Any
|
|
632
|
+
readonly lastSentReply: Option.Option<Reply.Reply<Rpcs>>
|
|
633
|
+
},
|
|
634
|
+
ParseResult.ParseError,
|
|
635
|
+
Rpc.Context<Rpcs>
|
|
636
|
+
>
|
|
637
|
+
}
|
|
579
638
|
}
|
|
580
639
|
|
|
581
640
|
const retryRespond = <A, E, R>(times: number, effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
|