Skip to content

Commit 98de1ba

Browse files
committed
backport effect v4 cluster
1 parent 200d703 commit 98de1ba

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+2941
-4321
lines changed

.changeset/loud-cows-prove.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
"@effect/platform-node-shared": minor
3+
"@effect/platform-node": minor
4+
"@effect/platform-bun": minor
5+
"@effect/cluster": minor
6+
"@effect/rpc": patch
7+
"@effect/workflow": minor
8+
---
9+
10+
backport @effect/cluster from effect v4
11+
12+
@effect/cluster no longer requires a Shard Manager, and instead relies on the
13+
`RunnerStorage` service to track runner state.
14+
15+
To migrate, remove any Shard Manager deployments and use the updated layers in
16+
`@effect/platform-node` or `@effect/platform-bun`.
17+
18+
# Breaking Changes
19+
20+
- `ShardManager` module has been removed
21+
- `@effect/platform-node/NodeClusterSocketRunner` is now
22+
`@effect/cluster/NodeClusterSocket`
23+
- `@effect/platform-node/NodeClusterHttpRunner` is now
24+
`@effect/cluster/NodeClusterHttp`
25+
- `@effect/platform-bun/BunClusterSocketRunner` is now
26+
`@effect/cluster/BunClusterSocket`
27+
- `@effect/platform-bun/BunClusterHttpRunner` is now
28+
`@effect/cluster/BunClusterHttp`
29+
30+
# New Features
31+
32+
- `RunnerHealth.layerK8s` has been added, which uses the Kubernetes API to track
33+
runner health and liveness. To use it, you will need a service account with
34+
permissions to read pod information.

.changeset/petite-signs-thank.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@effect/sql-pg": patch
3+
---
4+
5+
disable pg onnotice by default

.changeset/plenty-bats-ask.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@effect/platform": patch
3+
---
4+
5+
expose Layer output in HttpLayerRouter.serve

.changeset/sad-bags-fall.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@effect/sql-pg": minor
3+
---
4+
5+
Use "pg" npm library for @effect/sql-pg backend

.changeset/warm-aliens-dig.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"effect": minor
3+
---
4+
5+
add experimental HashRing module

packages/cluster/src/ClusterMetrics.ts

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@
33
*/
44
import * as Metric from "effect/Metric"
55

6-
/**
7-
* @since 1.0.0
8-
* @category metrics
9-
*/
10-
export const shards = Metric.gauge("effect_cluster_shards")
11-
126
/**
137
* @since 1.0.0
148
* @category metrics
@@ -21,28 +15,30 @@ export const entities = Metric.gauge("effect_cluster_entities", {
2115
* @since 1.0.0
2216
* @category metrics
2317
*/
24-
export const singletons = Metric.gauge("effect_cluster_singletons")
25-
26-
/**
27-
* @since 1.0.0
28-
* @category metrics
29-
*/
30-
export const runners = Metric.gauge("effect_cluster_runners")
18+
export const singletons = Metric.gauge("effect_cluster_singletons", {
19+
bigint: true
20+
})
3121

3222
/**
3323
* @since 1.0.0
3424
* @category metrics
3525
*/
36-
export const assignedShards = Metric.gauge("effect_cluster_shards_assigned")
26+
export const runners = Metric.gauge("effect_cluster_runners", {
27+
bigint: true
28+
})
3729

3830
/**
3931
* @since 1.0.0
4032
* @category metrics
4133
*/
42-
export const unassignedShards = Metric.gauge("effect_cluster_shards_unassigned")
34+
export const runnersHealthy = Metric.gauge("effect_cluster_runners_healthy", {
35+
bigint: true
36+
})
4337

4438
/**
4539
* @since 1.0.0
4640
* @category metrics
4741
*/
48-
export const rebalances = Metric.counter("effect_cluster_rebalances")
42+
export const shards = Metric.gauge("effect_cluster_shards", {
43+
bigint: true
44+
})

packages/cluster/src/ClusterWorkflowEngine.ts

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,27 @@
22
* @since 1.0.0
33
*/
44
import * as Rpc from "@effect/rpc/Rpc"
5+
import * as RpcServer from "@effect/rpc/RpcServer"
56
import { DurableDeferred } from "@effect/workflow"
67
import * as Activity from "@effect/workflow/Activity"
78
import * as DurableClock from "@effect/workflow/DurableClock"
89
import * as Workflow from "@effect/workflow/Workflow"
910
import { WorkflowEngine, WorkflowInstance } from "@effect/workflow/WorkflowEngine"
1011
import * as Arr from "effect/Array"
12+
import * as Cause from "effect/Cause"
1113
import * as Context from "effect/Context"
1214
import * as DateTime from "effect/DateTime"
1315
import * as Duration from "effect/Duration"
1416
import * as Effect from "effect/Effect"
1517
import type * as Exit from "effect/Exit"
1618
import * as Fiber from "effect/Fiber"
19+
import * as FiberId from "effect/FiberId"
1720
import * as Layer from "effect/Layer"
1821
import * as Option from "effect/Option"
1922
import type * as ParseResult from "effect/ParseResult"
2023
import * as PrimaryKey from "effect/PrimaryKey"
2124
import * as RcMap from "effect/RcMap"
22-
import * as Record from "effect/Record"
25+
import type * as Record from "effect/Record"
2326
import * as Runtime from "effect/Runtime"
2427
import * as Schedule from "effect/Schedule"
2528
import * as Schema from "effect/Schema"
@@ -260,13 +263,12 @@ export const make = Effect.gen(function*() {
260263
return {
261264
run: (request: Entity.Request<any>) => {
262265
const instance = WorkflowInstance.initial(workflow, executionId)
263-
let payload = request.payload
266+
const payload = request.payload
264267
let parent: { workflowName: string; executionId: string } | undefined
265268
if (payload[payloadParentKey]) {
266269
parent = payload[payloadParentKey]
267-
payload = Record.remove(payload, payloadParentKey)
268270
}
269-
return execute(payload, executionId).pipe(
271+
return execute(workflow.payloadSchema.make(payload), executionId).pipe(
270272
Effect.ensuring(Effect.suspend(() => {
271273
if (!instance.suspended) {
272274
return parent ? ensureSuccess(sendResumeParent(parent)) : Effect.void
@@ -291,17 +293,17 @@ export const make = Effect.gen(function*() {
291293
) as any
292294
},
293295

294-
activity: Effect.fnUntraced(
295-
function*(request: Entity.Request<any>) {
296-
const activityId = `${executionId}/${request.payload.name}`
296+
activity(request: Entity.Request<any>) {
297+
const activityId = `${executionId}/${request.payload.name}`
298+
const instance = WorkflowInstance.initial(workflow, executionId)
299+
return Effect.gen(function*() {
297300
let entry = activities.get(activityId)
298301
while (!entry) {
299302
const latch = Effect.unsafeMakeLatch()
300303
activityLatches.set(activityId, latch)
301304
yield* latch.await
302305
entry = activities.get(activityId)
303306
}
304-
const instance = WorkflowInstance.initial(workflow, executionId)
305307
const contextMap = new Map(entry.runtime.context.unsafeMap)
306308
contextMap.set(Activity.CurrentAttempt.key, request.payload.attempt)
307309
contextMap.set(WorkflowInstance.key, instance)
@@ -311,23 +313,29 @@ export const make = Effect.gen(function*() {
311313
runtimeFlags: Runtime.defaultRuntimeFlags
312314
})
313315
return yield* entry.activity.executeEncoded.pipe(
314-
Effect.interruptible,
315-
Effect.onInterrupt(() => {
316-
instance.suspended = true
317-
return Effect.void
318-
}),
319-
Workflow.intoResult,
320-
Effect.provide(runtime),
321-
Effect.ensuring(Effect.sync(() => {
322-
activities.delete(activityId)
323-
}))
316+
Effect.provide(runtime)
324317
)
325-
},
326-
Rpc.wrap({
327-
fork: true,
328-
uninterruptible: true
329-
})
330-
),
318+
}).pipe(
319+
Workflow.intoResult,
320+
Effect.catchAllCause((cause) => {
321+
const interruptors = Cause.interruptors(cause)
322+
// we only want to store explicit interrupts
323+
const ids = Array.from(interruptors, (id) => Array.from(FiberId.ids(id))).flat()
324+
const suspend = ids.includes(RpcServer.fiberIdClientInterrupt.id) ||
325+
ids.includes(RpcServer.fiberIdTransientInterrupt.id)
326+
return suspend ? Effect.succeed(new Workflow.Suspended()) : Effect.failCause(cause)
327+
}),
328+
Effect.provideService(WorkflowInstance, instance),
329+
Effect.provideService(Activity.CurrentAttempt, request.payload.attempt),
330+
Effect.ensuring(Effect.sync(() => {
331+
activities.delete(activityId)
332+
})),
333+
Rpc.wrap({
334+
fork: true,
335+
uninterruptible: true
336+
})
337+
)
338+
},
331339

332340
deferred: Effect.fnUntraced(function*(request: Entity.Request<any>) {
333341
yield* ensureSuccess(resume(workflow, executionId))

packages/cluster/src/EntityAddress.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,13 @@ export class EntityAddress extends Schema.Class<EntityAddress>(SymbolKey)({
6363
export const EntityAddressFromSelf: Schema.Schema<EntityAddress> = Schema.typeSchema(
6464
EntityAddress
6565
)
66+
67+
/**
68+
* @since 4.0.0
69+
* @category constructors
70+
*/
71+
export const make = (options: {
72+
readonly shardId: ShardId
73+
readonly entityType: EntityType
74+
readonly entityId: EntityId
75+
}): EntityAddress => new EntityAddress(options, { disableValidation: true })

packages/cluster/src/EntityId.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,9 @@ export const EntityId = Schema.NonEmptyTrimmedString.pipe(Schema.brand("EntityId
1414
* @category models
1515
*/
1616
export type EntityId = typeof EntityId.Type
17+
18+
/**
19+
* @since 1.0.0
20+
* @category constructors
21+
*/
22+
export const make = (id: string): EntityId => id as EntityId

packages/cluster/src/HttpCommon.ts

Lines changed: 0 additions & 73 deletions
This file was deleted.

0 commit comments

Comments
 (0)