@effect/cluster 0.50.6 → 0.52.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/RunnerStorage/package.json +6 -0
- package/SqlRunnerStorage/package.json +6 -0
- package/dist/cjs/ClusterError.js +2 -24
- package/dist/cjs/ClusterError.js.map +1 -1
- package/dist/cjs/ClusterMetrics.js +13 -15
- package/dist/cjs/ClusterMetrics.js.map +1 -1
- package/dist/cjs/ClusterSchema.js +17 -2
- package/dist/cjs/ClusterSchema.js.map +1 -1
- package/dist/cjs/ClusterWorkflowEngine.js +50 -83
- package/dist/cjs/ClusterWorkflowEngine.js.map +1 -1
- package/dist/cjs/Entity.js +1 -13
- package/dist/cjs/Entity.js.map +1 -1
- package/dist/cjs/EntityAddress.js +9 -1
- package/dist/cjs/EntityAddress.js.map +1 -1
- package/dist/cjs/EntityId.js +7 -1
- package/dist/cjs/EntityId.js.map +1 -1
- package/dist/cjs/EntityProxy.js +1 -1
- package/dist/cjs/EntityProxy.js.map +1 -1
- package/dist/cjs/HttpRunner.js +69 -43
- package/dist/cjs/HttpRunner.js.map +1 -1
- package/dist/cjs/MessageStorage.js +64 -16
- package/dist/cjs/MessageStorage.js.map +1 -1
- package/dist/cjs/Runner.js +3 -3
- package/dist/cjs/Runner.js.map +1 -1
- package/dist/cjs/RunnerAddress.js +7 -0
- package/dist/cjs/RunnerAddress.js.map +1 -1
- package/dist/cjs/RunnerHealth.js +91 -32
- package/dist/cjs/RunnerHealth.js.map +1 -1
- package/dist/cjs/RunnerServer.js +38 -24
- package/dist/cjs/RunnerServer.js.map +1 -1
- package/dist/cjs/RunnerStorage.js +100 -0
- package/dist/cjs/RunnerStorage.js.map +1 -0
- package/dist/cjs/Runners.js +18 -22
- package/dist/cjs/Runners.js.map +1 -1
- package/dist/cjs/ShardId.js +17 -7
- package/dist/cjs/ShardId.js.map +1 -1
- package/dist/cjs/Sharding.js +444 -320
- package/dist/cjs/Sharding.js.map +1 -1
- package/dist/cjs/ShardingConfig.js +10 -14
- package/dist/cjs/ShardingConfig.js.map +1 -1
- package/dist/cjs/Snowflake.js +1 -1
- package/dist/cjs/SocketRunner.js +1 -1
- package/dist/cjs/SocketRunner.js.map +1 -1
- package/dist/cjs/SqlMessageStorage.js +22 -28
- package/dist/cjs/SqlMessageStorage.js.map +1 -1
- package/dist/cjs/SqlRunnerStorage.js +375 -0
- package/dist/cjs/SqlRunnerStorage.js.map +1 -0
- package/dist/cjs/index.js +5 -15
- package/dist/cjs/internal/entityManager.js +42 -23
- package/dist/cjs/internal/entityManager.js.map +1 -1
- package/dist/dts/ClusterError.d.ts +0 -22
- package/dist/dts/ClusterError.d.ts.map +1 -1
- package/dist/dts/ClusterMetrics.d.ts +4 -14
- package/dist/dts/ClusterMetrics.d.ts.map +1 -1
- package/dist/dts/ClusterSchema.d.ts +9 -1
- package/dist/dts/ClusterSchema.d.ts.map +1 -1
- package/dist/dts/ClusterWorkflowEngine.d.ts.map +1 -1
- package/dist/dts/Entity.d.ts +3 -14
- package/dist/dts/Entity.d.ts.map +1 -1
- package/dist/dts/EntityAddress.d.ts +11 -0
- package/dist/dts/EntityAddress.d.ts.map +1 -1
- package/dist/dts/EntityId.d.ts +5 -0
- package/dist/dts/EntityId.d.ts.map +1 -1
- package/dist/dts/EntityProxy.d.ts +5 -6
- package/dist/dts/EntityProxy.d.ts.map +1 -1
- package/dist/dts/HttpRunner.d.ts +48 -25
- package/dist/dts/HttpRunner.d.ts.map +1 -1
- package/dist/dts/MessageStorage.d.ts +13 -5
- package/dist/dts/MessageStorage.d.ts.map +1 -1
- package/dist/dts/Runner.d.ts +4 -4
- package/dist/dts/Runner.d.ts.map +1 -1
- package/dist/dts/RunnerAddress.d.ts +5 -0
- package/dist/dts/RunnerAddress.d.ts.map +1 -1
- package/dist/dts/RunnerHealth.d.ts +24 -16
- package/dist/dts/RunnerHealth.d.ts.map +1 -1
- package/dist/dts/RunnerServer.d.ts +5 -4
- package/dist/dts/RunnerServer.d.ts.map +1 -1
- package/dist/dts/{ShardStorage.d.ts → RunnerStorage.d.ts} +41 -54
- package/dist/dts/RunnerStorage.d.ts.map +1 -0
- package/dist/dts/Runners.d.ts +15 -11
- package/dist/dts/Runners.d.ts.map +1 -1
- package/dist/dts/ShardId.d.ts +1 -1
- package/dist/dts/ShardId.d.ts.map +1 -1
- package/dist/dts/Sharding.d.ts +20 -10
- package/dist/dts/Sharding.d.ts.map +1 -1
- package/dist/dts/ShardingConfig.d.ts +40 -14
- package/dist/dts/ShardingConfig.d.ts.map +1 -1
- package/dist/dts/SocketRunner.d.ts +4 -3
- package/dist/dts/SocketRunner.d.ts.map +1 -1
- package/dist/dts/SqlMessageStorage.d.ts +2 -3
- package/dist/dts/SqlMessageStorage.d.ts.map +1 -1
- package/dist/dts/SqlRunnerStorage.d.ts +40 -0
- package/dist/dts/SqlRunnerStorage.d.ts.map +1 -0
- package/dist/dts/index.d.ts +4 -24
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/esm/ClusterError.js +0 -21
- package/dist/esm/ClusterError.js.map +1 -1
- package/dist/esm/ClusterMetrics.js +12 -14
- package/dist/esm/ClusterMetrics.js.map +1 -1
- package/dist/esm/ClusterSchema.js +17 -2
- package/dist/esm/ClusterSchema.js.map +1 -1
- package/dist/esm/ClusterWorkflowEngine.js +50 -83
- package/dist/esm/ClusterWorkflowEngine.js.map +1 -1
- package/dist/esm/Entity.js +0 -12
- package/dist/esm/Entity.js.map +1 -1
- package/dist/esm/EntityAddress.js +7 -0
- package/dist/esm/EntityAddress.js.map +1 -1
- package/dist/esm/EntityId.js +5 -0
- package/dist/esm/EntityId.js.map +1 -1
- package/dist/esm/EntityProxy.js +2 -2
- package/dist/esm/EntityProxy.js.map +1 -1
- package/dist/esm/HttpRunner.js +62 -39
- package/dist/esm/HttpRunner.js.map +1 -1
- package/dist/esm/MessageStorage.js +65 -17
- package/dist/esm/MessageStorage.js.map +1 -1
- package/dist/esm/Runner.js +3 -3
- package/dist/esm/Runner.js.map +1 -1
- package/dist/esm/RunnerAddress.js +7 -0
- package/dist/esm/RunnerAddress.js.map +1 -1
- package/dist/esm/RunnerHealth.js +88 -30
- package/dist/esm/RunnerHealth.js.map +1 -1
- package/dist/esm/RunnerServer.js +38 -24
- package/dist/esm/RunnerServer.js.map +1 -1
- package/dist/esm/RunnerStorage.js +90 -0
- package/dist/esm/RunnerStorage.js.map +1 -0
- package/dist/esm/Runners.js +19 -23
- package/dist/esm/Runners.js.map +1 -1
- package/dist/esm/ShardId.js +16 -6
- package/dist/esm/ShardId.js.map +1 -1
- package/dist/esm/Sharding.js +447 -323
- package/dist/esm/Sharding.js.map +1 -1
- package/dist/esm/ShardingConfig.js +10 -14
- package/dist/esm/ShardingConfig.js.map +1 -1
- package/dist/esm/Snowflake.js +1 -1
- package/dist/esm/SocketRunner.js +1 -1
- package/dist/esm/SocketRunner.js.map +1 -1
- package/dist/esm/SqlMessageStorage.js +22 -28
- package/dist/esm/SqlMessageStorage.js.map +1 -1
- package/dist/esm/SqlRunnerStorage.js +366 -0
- package/dist/esm/SqlRunnerStorage.js.map +1 -0
- package/dist/esm/index.js +4 -24
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/entityManager.js +41 -22
- package/dist/esm/internal/entityManager.js.map +1 -1
- package/package.json +20 -60
- package/src/ClusterError.ts +0 -24
- package/src/ClusterMetrics.ts +12 -16
- package/src/ClusterSchema.ts +17 -2
- package/src/ClusterWorkflowEngine.ts +48 -80
- package/src/Entity.ts +3 -21
- package/src/EntityAddress.ts +10 -0
- package/src/EntityId.ts +6 -0
- package/src/EntityProxy.ts +10 -10
- package/src/HttpRunner.ts +132 -67
- package/src/MessageStorage.ts +89 -24
- package/src/Runner.ts +4 -4
- package/src/RunnerAddress.ts +8 -0
- package/src/RunnerHealth.ts +119 -56
- package/src/RunnerServer.ts +64 -47
- package/src/RunnerStorage.ts +218 -0
- package/src/Runners.ts +32 -45
- package/src/ShardId.ts +14 -3
- package/src/Sharding.ts +561 -417
- package/src/ShardingConfig.ts +39 -31
- package/src/Snowflake.ts +1 -1
- package/src/SocketRunner.ts +6 -4
- package/src/SqlMessageStorage.ts +28 -30
- package/src/SqlRunnerStorage.ts +537 -0
- package/src/index.ts +4 -29
- package/src/internal/entityManager.ts +45 -29
- package/HttpCommon/package.json +0 -6
- package/HttpShardManager/package.json +0 -6
- package/ShardManager/package.json +0 -6
- package/ShardStorage/package.json +0 -6
- package/SocketShardManager/package.json +0 -6
- package/SqlShardStorage/package.json +0 -6
- package/SynchronizedClock/package.json +0 -6
- package/dist/cjs/HttpCommon.js +0 -48
- package/dist/cjs/HttpCommon.js.map +0 -1
- package/dist/cjs/HttpShardManager.js +0 -139
- package/dist/cjs/HttpShardManager.js.map +0 -1
- package/dist/cjs/ShardManager.js +0 -549
- package/dist/cjs/ShardManager.js.map +0 -1
- package/dist/cjs/ShardStorage.js +0 -151
- package/dist/cjs/ShardStorage.js.map +0 -1
- package/dist/cjs/SocketShardManager.js +0 -32
- package/dist/cjs/SocketShardManager.js.map +0 -1
- package/dist/cjs/SqlShardStorage.js +0 -253
- package/dist/cjs/SqlShardStorage.js.map +0 -1
- package/dist/cjs/SynchronizedClock.js +0 -65
- package/dist/cjs/SynchronizedClock.js.map +0 -1
- package/dist/cjs/internal/shardManager.js +0 -353
- package/dist/cjs/internal/shardManager.js.map +0 -1
- package/dist/dts/HttpCommon.d.ts +0 -25
- package/dist/dts/HttpCommon.d.ts.map +0 -1
- package/dist/dts/HttpShardManager.d.ts +0 -119
- package/dist/dts/HttpShardManager.d.ts.map +0 -1
- package/dist/dts/ShardManager.d.ts +0 -459
- package/dist/dts/ShardManager.d.ts.map +0 -1
- package/dist/dts/ShardStorage.d.ts.map +0 -1
- package/dist/dts/SocketShardManager.d.ts +0 -17
- package/dist/dts/SocketShardManager.d.ts.map +0 -1
- package/dist/dts/SqlShardStorage.d.ts +0 -38
- package/dist/dts/SqlShardStorage.d.ts.map +0 -1
- package/dist/dts/SynchronizedClock.d.ts +0 -19
- package/dist/dts/SynchronizedClock.d.ts.map +0 -1
- package/dist/dts/internal/shardManager.d.ts +0 -2
- package/dist/dts/internal/shardManager.d.ts.map +0 -1
- package/dist/esm/HttpCommon.js +0 -38
- package/dist/esm/HttpCommon.js.map +0 -1
- package/dist/esm/HttpShardManager.js +0 -128
- package/dist/esm/HttpShardManager.js.map +0 -1
- package/dist/esm/ShardManager.js +0 -535
- package/dist/esm/ShardManager.js.map +0 -1
- package/dist/esm/ShardStorage.js +0 -141
- package/dist/esm/ShardStorage.js.map +0 -1
- package/dist/esm/SocketShardManager.js +0 -24
- package/dist/esm/SocketShardManager.js.map +0 -1
- package/dist/esm/SqlShardStorage.js +0 -244
- package/dist/esm/SqlShardStorage.js.map +0 -1
- package/dist/esm/SynchronizedClock.js +0 -57
- package/dist/esm/SynchronizedClock.js.map +0 -1
- package/dist/esm/internal/shardManager.js +0 -342
- package/dist/esm/internal/shardManager.js.map +0 -1
- package/src/HttpCommon.ts +0 -73
- package/src/HttpShardManager.ts +0 -273
- package/src/ShardManager.ts +0 -823
- package/src/ShardStorage.ts +0 -297
- package/src/SocketShardManager.ts +0 -48
- package/src/SqlShardStorage.ts +0 -329
- package/src/SynchronizedClock.ts +0 -82
- package/src/internal/shardManager.ts +0 -412
package/src/RunnerHealth.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @since 1.0.0
|
|
3
3
|
*/
|
|
4
|
+
import * as FileSystem from "@effect/platform/FileSystem"
|
|
5
|
+
import * as HttpClient from "@effect/platform/HttpClient"
|
|
6
|
+
import * as HttpClientRequest from "@effect/platform/HttpClientRequest"
|
|
7
|
+
import * as HttpClientResponse from "@effect/platform/HttpClientResponse"
|
|
4
8
|
import * as Context from "effect/Context"
|
|
5
9
|
import * as Effect from "effect/Effect"
|
|
10
|
+
import { identity } from "effect/Function"
|
|
6
11
|
import * as Layer from "effect/Layer"
|
|
7
|
-
import * as
|
|
12
|
+
import * as Schedule from "effect/Schedule"
|
|
13
|
+
import * as Schema from "effect/Schema"
|
|
8
14
|
import type * as Scope from "effect/Scope"
|
|
9
|
-
import * as MessageStorage from "./MessageStorage.js"
|
|
10
15
|
import type { RunnerAddress } from "./RunnerAddress.js"
|
|
11
16
|
import * as Runners from "./Runners.js"
|
|
12
|
-
import type { ShardingConfig } from "./ShardingConfig.js"
|
|
13
17
|
|
|
14
18
|
/**
|
|
15
19
|
* Represents the service used to check if a Runner is healthy.
|
|
@@ -24,46 +28,10 @@ import type { ShardingConfig } from "./ShardingConfig.js"
|
|
|
24
28
|
export class RunnerHealth extends Context.Tag("@effect/cluster/RunnerHealth")<
|
|
25
29
|
RunnerHealth,
|
|
26
30
|
{
|
|
27
|
-
/**
|
|
28
|
-
* Used to indicate that a Runner is connected to this host and is healthy,
|
|
29
|
-
* while the Scope is active.
|
|
30
|
-
*/
|
|
31
|
-
readonly onConnection: (address: RunnerAddress) => Effect.Effect<void, never, Scope.Scope>
|
|
32
31
|
readonly isAlive: (address: RunnerAddress) => Effect.Effect<boolean>
|
|
33
32
|
}
|
|
34
33
|
>() {}
|
|
35
34
|
|
|
36
|
-
/**
|
|
37
|
-
* @since 1.0.0
|
|
38
|
-
* @category Constructors
|
|
39
|
-
*/
|
|
40
|
-
export const make: (
|
|
41
|
-
options: { readonly isAlive: (address: RunnerAddress) => Effect.Effect<boolean> }
|
|
42
|
-
) => Effect.Effect<
|
|
43
|
-
RunnerHealth["Type"],
|
|
44
|
-
never,
|
|
45
|
-
Scope.Scope
|
|
46
|
-
> = Effect.fnUntraced(function*(options: {
|
|
47
|
-
readonly isAlive: (address: RunnerAddress) => Effect.Effect<boolean>
|
|
48
|
-
}) {
|
|
49
|
-
const connections = yield* RcMap.make({
|
|
50
|
-
lookup: (_address: RunnerAddress) => Effect.void
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
const onConnection = (address: RunnerAddress) => RcMap.get(connections, address)
|
|
54
|
-
const isAlive = Effect.fnUntraced(function*(address: RunnerAddress) {
|
|
55
|
-
if (yield* RcMap.has(connections, address)) {
|
|
56
|
-
return true
|
|
57
|
-
}
|
|
58
|
-
return yield* options.isAlive(address)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
return RunnerHealth.of({
|
|
62
|
-
onConnection,
|
|
63
|
-
isAlive
|
|
64
|
-
})
|
|
65
|
-
})
|
|
66
|
-
|
|
67
35
|
/**
|
|
68
36
|
* A layer which will **always** consider a Runner healthy.
|
|
69
37
|
*
|
|
@@ -72,12 +40,9 @@ export const make: (
|
|
|
72
40
|
* @since 1.0.0
|
|
73
41
|
* @category layers
|
|
74
42
|
*/
|
|
75
|
-
export const layerNoop = Layer.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
isAlive: () => Effect.succeed(true)
|
|
79
|
-
})
|
|
80
|
-
)
|
|
43
|
+
export const layerNoop = Layer.succeed(RunnerHealth, {
|
|
44
|
+
isAlive: () => Effect.succeed(true)
|
|
45
|
+
})
|
|
81
46
|
|
|
82
47
|
/**
|
|
83
48
|
* @since 1.0.0
|
|
@@ -89,16 +54,17 @@ export const makePing: Effect.Effect<
|
|
|
89
54
|
Runners.Runners | Scope.Scope
|
|
90
55
|
> = Effect.gen(function*() {
|
|
91
56
|
const runners = yield* Runners.Runners
|
|
57
|
+
const schedule = Schedule.spaced(500)
|
|
92
58
|
|
|
93
59
|
function isAlive(address: RunnerAddress): Effect.Effect<boolean> {
|
|
94
60
|
return runners.ping(address).pipe(
|
|
95
|
-
Effect.timeout(
|
|
96
|
-
Effect.retry({ times:
|
|
61
|
+
Effect.timeout(10_000),
|
|
62
|
+
Effect.retry({ times: 5, schedule }),
|
|
97
63
|
Effect.isSuccess
|
|
98
64
|
)
|
|
99
65
|
}
|
|
100
66
|
|
|
101
|
-
return
|
|
67
|
+
return RunnerHealth.of({ isAlive })
|
|
102
68
|
})
|
|
103
69
|
|
|
104
70
|
/**
|
|
@@ -107,23 +73,120 @@ export const makePing: Effect.Effect<
|
|
|
107
73
|
* @since 1.0.0
|
|
108
74
|
* @category layers
|
|
109
75
|
*/
|
|
110
|
-
export const
|
|
76
|
+
export const layerPing: Layer.Layer<
|
|
111
77
|
RunnerHealth,
|
|
112
78
|
never,
|
|
113
79
|
Runners.Runners
|
|
114
80
|
> = Layer.scoped(RunnerHealth, makePing)
|
|
115
81
|
|
|
116
82
|
/**
|
|
117
|
-
*
|
|
83
|
+
* @since 1.0.0
|
|
84
|
+
* @category Constructors
|
|
85
|
+
*/
|
|
86
|
+
export const makeK8s = Effect.fnUntraced(function*(options?: {
|
|
87
|
+
readonly namespace?: string | undefined
|
|
88
|
+
readonly labelSelector?: string | undefined
|
|
89
|
+
}) {
|
|
90
|
+
const fs = yield* FileSystem.FileSystem
|
|
91
|
+
const token = yield* fs.readFileString("/var/run/secrets/kubernetes.io/serviceaccount/token").pipe(
|
|
92
|
+
Effect.option
|
|
93
|
+
)
|
|
94
|
+
const client = (yield* HttpClient.HttpClient).pipe(
|
|
95
|
+
HttpClient.filterStatusOk
|
|
96
|
+
)
|
|
97
|
+
const baseRequest = HttpClientRequest.get("https://kubernetes.default.svc/api").pipe(
|
|
98
|
+
token._tag === "Some" ? HttpClientRequest.bearerToken(token.value.trim()) : identity
|
|
99
|
+
)
|
|
100
|
+
const getPods = baseRequest.pipe(
|
|
101
|
+
HttpClientRequest.appendUrl(options?.namespace ? `/v1/namespaces/${options.namespace}/pods` : "/v1/pods"),
|
|
102
|
+
HttpClientRequest.setUrlParam("fieldSelector", "status.phase=Running"),
|
|
103
|
+
options?.labelSelector ? HttpClientRequest.setUrlParam("labelSelector", options.labelSelector) : identity
|
|
104
|
+
)
|
|
105
|
+
const allPods = yield* client.execute(getPods).pipe(
|
|
106
|
+
Effect.flatMap(HttpClientResponse.schemaBodyJson(PodList)),
|
|
107
|
+
Effect.map((list) => {
|
|
108
|
+
const pods = new Map<string, Pod>()
|
|
109
|
+
for (let i = 0; i < list.items.length; i++) {
|
|
110
|
+
const pod = list.items[i]
|
|
111
|
+
pods.set(pod.status.podIP, pod)
|
|
112
|
+
}
|
|
113
|
+
return pods
|
|
114
|
+
}),
|
|
115
|
+
Effect.tapErrorCause((cause) => Effect.logWarning("Failed to fetch pods from Kubernetes API", cause)),
|
|
116
|
+
Effect.cachedWithTTL("10 seconds")
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return RunnerHealth.of({
|
|
120
|
+
isAlive: (address) =>
|
|
121
|
+
allPods.pipe(
|
|
122
|
+
Effect.map((pods) => pods.get(address.host)?.isReady ?? false),
|
|
123
|
+
Effect.catchAllCause(() => Effect.succeed(true))
|
|
124
|
+
)
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
class Pod extends Schema.Class<Pod>("effect/cluster/RunnerHealth/Pod")({
|
|
129
|
+
status: Schema.Struct({
|
|
130
|
+
phase: Schema.String,
|
|
131
|
+
conditions: Schema.Array(Schema.Struct({
|
|
132
|
+
type: Schema.String,
|
|
133
|
+
status: Schema.String,
|
|
134
|
+
lastTransitionTime: Schema.String
|
|
135
|
+
})),
|
|
136
|
+
podIP: Schema.String
|
|
137
|
+
})
|
|
138
|
+
}) {
|
|
139
|
+
get isReady(): boolean {
|
|
140
|
+
let initializedAt: string | undefined
|
|
141
|
+
let readyAt: string | undefined
|
|
142
|
+
for (let i = 0; i < this.status.conditions.length; i++) {
|
|
143
|
+
const condition = this.status.conditions[i]
|
|
144
|
+
switch (condition.type) {
|
|
145
|
+
case "Initialized": {
|
|
146
|
+
if (condition.status !== "True") {
|
|
147
|
+
return true
|
|
148
|
+
}
|
|
149
|
+
initializedAt = condition.lastTransitionTime
|
|
150
|
+
break
|
|
151
|
+
}
|
|
152
|
+
case "Ready": {
|
|
153
|
+
if (condition.status === "True") {
|
|
154
|
+
return true
|
|
155
|
+
}
|
|
156
|
+
readyAt = condition.lastTransitionTime
|
|
157
|
+
break
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// if the pod is still booting up, consider it ready as it would have
|
|
162
|
+
// already registered itself with RunnerStorage by now
|
|
163
|
+
return initializedAt === readyAt
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const PodList = Schema.Struct({
|
|
168
|
+
items: Schema.Array(Pod)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* A layer which will check the Kubernetes API to see if a Runner is healthy.
|
|
173
|
+
*
|
|
174
|
+
* The provided HttpClient will need to add the pod's CA certificate to its
|
|
175
|
+
* trusted root certificates in order to communicate with the Kubernetes API.
|
|
176
|
+
*
|
|
177
|
+
* The pod service account will also need to have permissions to list pods in
|
|
178
|
+
* order to use this layer.
|
|
118
179
|
*
|
|
119
180
|
* @since 1.0.0
|
|
120
181
|
* @category layers
|
|
121
182
|
*/
|
|
122
|
-
export const
|
|
183
|
+
export const layerK8s = (
|
|
184
|
+
options?: {
|
|
185
|
+
readonly namespace?: string | undefined
|
|
186
|
+
readonly labelSelector?: string | undefined
|
|
187
|
+
} | undefined
|
|
188
|
+
): Layer.Layer<
|
|
123
189
|
RunnerHealth,
|
|
124
190
|
never,
|
|
125
|
-
|
|
126
|
-
>
|
|
127
|
-
Layer.provide(Runners.layerRpc),
|
|
128
|
-
Layer.provide(MessageStorage.layerNoop)
|
|
129
|
-
)
|
|
191
|
+
HttpClient.HttpClient | FileSystem.FileSystem
|
|
192
|
+
> => Layer.effect(RunnerHealth, makeK8s(options))
|
package/src/RunnerServer.ts
CHANGED
|
@@ -3,20 +3,22 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import * as RpcServer from "@effect/rpc/RpcServer"
|
|
5
5
|
import * as Effect from "effect/Effect"
|
|
6
|
+
import type * as Exit from "effect/Exit"
|
|
7
|
+
import * as Fiber from "effect/Fiber"
|
|
6
8
|
import { constant } from "effect/Function"
|
|
7
9
|
import * as Layer from "effect/Layer"
|
|
8
10
|
import * as Mailbox from "effect/Mailbox"
|
|
9
11
|
import * as Option from "effect/Option"
|
|
10
|
-
import * as
|
|
12
|
+
import * as Runtime from "effect/Runtime"
|
|
13
|
+
import type * as ClusterError from "./ClusterError.js"
|
|
11
14
|
import * as Message from "./Message.js"
|
|
12
15
|
import * as MessageStorage from "./MessageStorage.js"
|
|
13
16
|
import * as Reply from "./Reply.js"
|
|
17
|
+
import * as RunnerHealth from "./RunnerHealth.js"
|
|
14
18
|
import * as Runners from "./Runners.js"
|
|
19
|
+
import type * as RunnerStorage from "./RunnerStorage.js"
|
|
15
20
|
import * as Sharding from "./Sharding.js"
|
|
16
21
|
import { ShardingConfig } from "./ShardingConfig.js"
|
|
17
|
-
import * as ShardManager from "./ShardManager.js"
|
|
18
|
-
import * as ShardStorage from "./ShardStorage.js"
|
|
19
|
-
import * as SynchronizedClock from "./SynchronizedClock.js"
|
|
20
22
|
|
|
21
23
|
const constVoid = constant(Effect.void)
|
|
22
24
|
|
|
@@ -41,41 +43,60 @@ export const layerHandlers = Runners.Rpcs.toLayer(Effect.gen(function*() {
|
|
|
41
43
|
: new Message.IncomingEnvelope({ envelope })
|
|
42
44
|
),
|
|
43
45
|
Effect: ({ persisted, request }) => {
|
|
44
|
-
let
|
|
45
|
-
|
|
46
|
+
let replyEncoded:
|
|
47
|
+
| Effect.Effect<
|
|
48
|
+
Reply.ReplyEncoded<any>,
|
|
49
|
+
ClusterError.EntityNotAssignedToRunner
|
|
50
|
+
>
|
|
51
|
+
| undefined = undefined
|
|
52
|
+
let resume = (reply: Effect.Effect<Reply.ReplyEncoded<any>, ClusterError.EntityNotAssignedToRunner>) => {
|
|
53
|
+
replyEncoded = reply
|
|
54
|
+
}
|
|
46
55
|
const message = new Message.IncomingRequest({
|
|
47
56
|
envelope: request,
|
|
48
57
|
lastSentReply: Option.none(),
|
|
49
58
|
respond(reply) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
resume(Effect.succeed(reply))
|
|
53
|
-
} else {
|
|
54
|
-
replyEncoded = reply
|
|
55
|
-
}
|
|
56
|
-
return Effect.void
|
|
57
|
-
})
|
|
59
|
+
resume(Effect.orDie(Reply.serialize(reply)))
|
|
60
|
+
return Effect.void
|
|
58
61
|
}
|
|
59
62
|
})
|
|
63
|
+
if (persisted) {
|
|
64
|
+
return Effect.async<
|
|
65
|
+
Reply.ReplyEncoded<any>,
|
|
66
|
+
ClusterError.EntityNotAssignedToRunner
|
|
67
|
+
>((resume_) => {
|
|
68
|
+
resume = resume_
|
|
69
|
+
const parent = Option.getOrThrow(Fiber.getCurrentFiber())
|
|
70
|
+
const runtime = Runtime.make({
|
|
71
|
+
context: parent.currentContext,
|
|
72
|
+
runtimeFlags: Runtime.defaultRuntimeFlags,
|
|
73
|
+
fiberRefs: parent.getFiberRefs()
|
|
74
|
+
})
|
|
75
|
+
const onExit = (
|
|
76
|
+
exit: Exit.Exit<
|
|
77
|
+
any,
|
|
78
|
+
ClusterError.EntityNotAssignedToRunner
|
|
79
|
+
>
|
|
80
|
+
) => {
|
|
81
|
+
if (exit._tag === "Failure") {
|
|
82
|
+
resume(exit as any)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const fiber = Runtime.runFork(runtime)(storage.registerReplyHandler(message))
|
|
86
|
+
fiber.addObserver(onExit)
|
|
87
|
+
Runtime.runFork(runtime)(Effect.catchTag(
|
|
88
|
+
sharding.notify(message, constWaitUntilRead),
|
|
89
|
+
"AlreadyProcessingMessage",
|
|
90
|
+
() => Effect.void
|
|
91
|
+
)).addObserver(onExit)
|
|
92
|
+
return Fiber.interrupt(fiber)
|
|
93
|
+
})
|
|
94
|
+
}
|
|
60
95
|
return Effect.zipRight(
|
|
61
|
-
|
|
62
|
-
Effect.zipRight(
|
|
63
|
-
storage.registerReplyHandler(
|
|
64
|
-
message,
|
|
65
|
-
Effect.sync(() =>
|
|
66
|
-
resume(Effect.fail(
|
|
67
|
-
new ClusterError.EntityNotAssignedToRunner({
|
|
68
|
-
address: request.address
|
|
69
|
-
})
|
|
70
|
-
))
|
|
71
|
-
)
|
|
72
|
-
),
|
|
73
|
-
sharding.notify(message)
|
|
74
|
-
) :
|
|
75
|
-
sharding.send(message),
|
|
96
|
+
sharding.send(message),
|
|
76
97
|
Effect.async<Reply.ReplyEncoded<any>, ClusterError.EntityNotAssignedToRunner>((resume_) => {
|
|
77
98
|
if (replyEncoded) {
|
|
78
|
-
resume_(
|
|
99
|
+
resume_(replyEncoded)
|
|
79
100
|
} else {
|
|
80
101
|
resume = resume_
|
|
81
102
|
}
|
|
@@ -99,17 +120,12 @@ export const layerHandlers = Runners.Rpcs.toLayer(Effect.gen(function*() {
|
|
|
99
120
|
return Effect.as(
|
|
100
121
|
persisted ?
|
|
101
122
|
Effect.zipRight(
|
|
102
|
-
storage.registerReplyHandler(
|
|
103
|
-
|
|
104
|
-
Effect.
|
|
105
|
-
|
|
106
|
-
new ClusterError.EntityNotAssignedToRunner({
|
|
107
|
-
address: request.address
|
|
108
|
-
})
|
|
109
|
-
)
|
|
110
|
-
)
|
|
123
|
+
storage.registerReplyHandler(message).pipe(
|
|
124
|
+
Effect.onError((cause) => mailbox.failCause(cause)),
|
|
125
|
+
Effect.forkScoped,
|
|
126
|
+
Effect.interruptible
|
|
111
127
|
),
|
|
112
|
-
sharding.notify(message)
|
|
128
|
+
sharding.notify(message, constWaitUntilRead)
|
|
113
129
|
) :
|
|
114
130
|
sharding.send(message),
|
|
115
131
|
mailbox
|
|
@@ -120,6 +136,8 @@ export const layerHandlers = Runners.Rpcs.toLayer(Effect.gen(function*() {
|
|
|
120
136
|
}
|
|
121
137
|
}))
|
|
122
138
|
|
|
139
|
+
const constWaitUntilRead = { waitUntilRead: true } as const
|
|
140
|
+
|
|
123
141
|
/**
|
|
124
142
|
* The `RunnerServer` recieves messages from other Runners and forwards them to the
|
|
125
143
|
* `Sharding` layer.
|
|
@@ -151,18 +169,17 @@ export const layerWithClients: Layer.Layer<
|
|
|
151
169
|
| ShardingConfig
|
|
152
170
|
| Runners.RpcClientProtocol
|
|
153
171
|
| MessageStorage.MessageStorage
|
|
154
|
-
|
|
|
172
|
+
| RunnerStorage.RunnerStorage
|
|
173
|
+
| RunnerHealth.RunnerHealth
|
|
155
174
|
> = layer.pipe(
|
|
156
175
|
Layer.provideMerge(Sharding.layer),
|
|
157
|
-
Layer.provideMerge(Runners.layerRpc)
|
|
158
|
-
Layer.provideMerge(SynchronizedClock.layer),
|
|
159
|
-
Layer.provide(ShardManager.layerClientRpc)
|
|
176
|
+
Layer.provideMerge(Runners.layerRpc)
|
|
160
177
|
)
|
|
161
178
|
|
|
162
179
|
/**
|
|
163
180
|
* A `Runners` layer that is client only.
|
|
164
181
|
*
|
|
165
|
-
* It will not register with
|
|
182
|
+
* It will not register with RunnerStorage and recieve shard assignments,
|
|
166
183
|
* so this layer can be used to embed a cluster client inside another effect
|
|
167
184
|
* application.
|
|
168
185
|
*
|
|
@@ -175,10 +192,10 @@ export const layerClientOnly: Layer.Layer<
|
|
|
175
192
|
| ShardingConfig
|
|
176
193
|
| Runners.RpcClientProtocol
|
|
177
194
|
| MessageStorage.MessageStorage
|
|
195
|
+
| RunnerStorage.RunnerStorage
|
|
178
196
|
> = Sharding.layer.pipe(
|
|
179
197
|
Layer.provideMerge(Runners.layerRpc),
|
|
180
|
-
Layer.provide(
|
|
181
|
-
Layer.provide(ShardStorage.layerNoop),
|
|
198
|
+
Layer.provide(RunnerHealth.layerNoop),
|
|
182
199
|
Layer.updateService(ShardingConfig, (config) => ({
|
|
183
200
|
...config,
|
|
184
201
|
runnerAddress: Option.none()
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
import { isNonEmptyArray, type NonEmptyArray } from "effect/Array"
|
|
5
|
+
import * as Context from "effect/Context"
|
|
6
|
+
import * as Effect from "effect/Effect"
|
|
7
|
+
import * as Layer from "effect/Layer"
|
|
8
|
+
import * as MutableHashMap from "effect/MutableHashMap"
|
|
9
|
+
import type { PersistenceError } from "./ClusterError.js"
|
|
10
|
+
import * as MachineId from "./MachineId.js"
|
|
11
|
+
import { Runner } from "./Runner.js"
|
|
12
|
+
import type { RunnerAddress } from "./RunnerAddress.js"
|
|
13
|
+
import { ShardId } from "./ShardId.js"
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Represents a generic interface to the persistent storage required by the
|
|
17
|
+
* cluster.
|
|
18
|
+
*
|
|
19
|
+
* @since 1.0.0
|
|
20
|
+
* @category models
|
|
21
|
+
*/
|
|
22
|
+
export class RunnerStorage extends Context.Tag("@effect/cluster/RunnerStorage")<RunnerStorage, {
|
|
23
|
+
/**
|
|
24
|
+
* Register a new runner with the cluster.
|
|
25
|
+
*/
|
|
26
|
+
readonly register: (runner: Runner, healthy: boolean) => Effect.Effect<MachineId.MachineId, PersistenceError>
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Unregister the runner with the given address.
|
|
30
|
+
*/
|
|
31
|
+
readonly unregister: (address: RunnerAddress) => Effect.Effect<void, PersistenceError>
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get all runners registered with the cluster.
|
|
35
|
+
*/
|
|
36
|
+
readonly getRunners: Effect.Effect<Array<readonly [runner: Runner, healthy: boolean]>, PersistenceError>
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Set the health status of the given runner.
|
|
40
|
+
*/
|
|
41
|
+
readonly setRunnerHealth: (address: RunnerAddress, healthy: boolean) => Effect.Effect<void, PersistenceError>
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Try to acquire the given shard ids for processing.
|
|
45
|
+
*
|
|
46
|
+
* It returns an array of shards it was able to acquire.
|
|
47
|
+
*/
|
|
48
|
+
readonly acquire: (
|
|
49
|
+
address: RunnerAddress,
|
|
50
|
+
shardIds: Iterable<ShardId>
|
|
51
|
+
) => Effect.Effect<Array<ShardId>, PersistenceError>
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Refresh the locks owned by the given runner.
|
|
55
|
+
*/
|
|
56
|
+
readonly refresh: (
|
|
57
|
+
address: RunnerAddress,
|
|
58
|
+
shardIds: Iterable<ShardId>
|
|
59
|
+
) => Effect.Effect<Array<ShardId>, PersistenceError>
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Release the given shard ids.
|
|
63
|
+
*/
|
|
64
|
+
readonly release: (
|
|
65
|
+
address: RunnerAddress,
|
|
66
|
+
shardId: ShardId
|
|
67
|
+
) => Effect.Effect<void, PersistenceError>
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Release all the shards assigned to the given runner.
|
|
71
|
+
*/
|
|
72
|
+
readonly releaseAll: (address: RunnerAddress) => Effect.Effect<void, PersistenceError>
|
|
73
|
+
}>() {}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @since 1.0.0
|
|
77
|
+
* @category Encoded
|
|
78
|
+
*/
|
|
79
|
+
export interface Encoded {
|
|
80
|
+
/**
|
|
81
|
+
* Get all runners registered with the cluster.
|
|
82
|
+
*/
|
|
83
|
+
readonly getRunners: Effect.Effect<Array<readonly [runner: string, healthy: boolean]>, PersistenceError>
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Register a new runner with the cluster.
|
|
87
|
+
*/
|
|
88
|
+
readonly register: (address: string, runner: string, healthy: boolean) => Effect.Effect<number, PersistenceError>
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Unregister the runner with the given address.
|
|
92
|
+
*/
|
|
93
|
+
readonly unregister: (address: string) => Effect.Effect<void, PersistenceError>
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Set the health status of the given runner.
|
|
97
|
+
*/
|
|
98
|
+
readonly setRunnerHealth: (address: string, healthy: boolean) => Effect.Effect<void, PersistenceError>
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Acquire the lock on the given shards, returning the shards that were
|
|
102
|
+
* successfully locked.
|
|
103
|
+
*/
|
|
104
|
+
readonly acquire: (
|
|
105
|
+
address: string,
|
|
106
|
+
shardIds: NonEmptyArray<string>
|
|
107
|
+
) => Effect.Effect<Array<string>, PersistenceError>
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Refresh the lock on the given shards, returning the shards that were
|
|
111
|
+
* successfully locked.
|
|
112
|
+
*/
|
|
113
|
+
readonly refresh: (
|
|
114
|
+
address: string,
|
|
115
|
+
shardIds: Array<string>
|
|
116
|
+
) => Effect.Effect<ReadonlyArray<string>, PersistenceError>
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Release the lock on the given shard.
|
|
120
|
+
*/
|
|
121
|
+
readonly release: (
|
|
122
|
+
address: string,
|
|
123
|
+
shardId: string
|
|
124
|
+
) => Effect.Effect<void, PersistenceError>
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Release the lock on all shards for the given runner.
|
|
128
|
+
*/
|
|
129
|
+
readonly releaseAll: (address: string) => Effect.Effect<void, PersistenceError>
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @since 1.0.0
|
|
134
|
+
* @category layers
|
|
135
|
+
*/
|
|
136
|
+
export const makeEncoded = (encoded: Encoded) =>
|
|
137
|
+
RunnerStorage.of({
|
|
138
|
+
getRunners: Effect.gen(function*() {
|
|
139
|
+
const runners = yield* encoded.getRunners
|
|
140
|
+
const results: Array<[Runner, boolean]> = []
|
|
141
|
+
for (let i = 0; i < runners.length; i++) {
|
|
142
|
+
const [runner, healthy] = runners[i]
|
|
143
|
+
try {
|
|
144
|
+
results.push([Runner.decodeSync(runner), healthy])
|
|
145
|
+
} catch {
|
|
146
|
+
//
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return results
|
|
150
|
+
}),
|
|
151
|
+
register: (runner, healthy) =>
|
|
152
|
+
Effect.map(
|
|
153
|
+
encoded.register(encodeRunnerAddress(runner.address), Runner.encodeSync(runner), healthy),
|
|
154
|
+
MachineId.make
|
|
155
|
+
),
|
|
156
|
+
unregister: (address) => encoded.unregister(encodeRunnerAddress(address)),
|
|
157
|
+
setRunnerHealth: (address, healthy) => encoded.setRunnerHealth(encodeRunnerAddress(address), healthy),
|
|
158
|
+
acquire: (address, shardIds) => {
|
|
159
|
+
const arr = Array.from(shardIds, (id) => id.toString())
|
|
160
|
+
if (!isNonEmptyArray(arr)) return Effect.succeed([])
|
|
161
|
+
return encoded.acquire(encodeRunnerAddress(address), arr).pipe(
|
|
162
|
+
Effect.map((shards) => shards.map(ShardId.fromString))
|
|
163
|
+
)
|
|
164
|
+
},
|
|
165
|
+
refresh: (address, shardIds) =>
|
|
166
|
+
encoded.refresh(encodeRunnerAddress(address), Array.from(shardIds, (id) => id.toString())).pipe(
|
|
167
|
+
Effect.map((shards) => shards.map(ShardId.fromString))
|
|
168
|
+
),
|
|
169
|
+
release(address, shardId) {
|
|
170
|
+
return encoded.release(encodeRunnerAddress(address), shardId.toString())
|
|
171
|
+
},
|
|
172
|
+
releaseAll(address) {
|
|
173
|
+
return encoded.releaseAll(encodeRunnerAddress(address))
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @since 1.0.0
|
|
179
|
+
* @category constructors
|
|
180
|
+
*/
|
|
181
|
+
export const makeMemory = Effect.gen(function*() {
|
|
182
|
+
const runners = MutableHashMap.empty<RunnerAddress, Runner>()
|
|
183
|
+
let acquired: Array<ShardId> = []
|
|
184
|
+
let id = 0
|
|
185
|
+
|
|
186
|
+
return RunnerStorage.of({
|
|
187
|
+
getRunners: Effect.sync(() => Array.from(MutableHashMap.values(runners), (runner) => [runner, true])),
|
|
188
|
+
register: (runner) =>
|
|
189
|
+
Effect.sync(() => {
|
|
190
|
+
MutableHashMap.set(runners, runner.address, runner)
|
|
191
|
+
return MachineId.make(id++)
|
|
192
|
+
}),
|
|
193
|
+
unregister: (address) =>
|
|
194
|
+
Effect.sync(() => {
|
|
195
|
+
MutableHashMap.remove(runners, address)
|
|
196
|
+
}),
|
|
197
|
+
setRunnerHealth: () => Effect.void,
|
|
198
|
+
acquire: (_address, shardIds) => {
|
|
199
|
+
acquired = Array.from(shardIds)
|
|
200
|
+
return Effect.succeed(Array.from(shardIds))
|
|
201
|
+
},
|
|
202
|
+
refresh: () => Effect.sync(() => acquired),
|
|
203
|
+
release: () => Effect.void,
|
|
204
|
+
releaseAll: () => Effect.void
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @since 1.0.0
|
|
210
|
+
* @category layers
|
|
211
|
+
*/
|
|
212
|
+
export const layerMemory: Layer.Layer<RunnerStorage> = Layer.effect(RunnerStorage)(makeMemory)
|
|
213
|
+
|
|
214
|
+
// -------------------------------------------------------------------------------------
|
|
215
|
+
// internal
|
|
216
|
+
// -------------------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
const encodeRunnerAddress = (runnerAddress: RunnerAddress) => `${runnerAddress.host}:${runnerAddress.port}`
|