@effect/cluster 0.52.5 → 0.52.7
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/Sharding.js +34 -29
- package/dist/cjs/Sharding.js.map +1 -1
- package/dist/cjs/SqlRunnerStorage.js +29 -30
- package/dist/cjs/SqlRunnerStorage.js.map +1 -1
- package/dist/cjs/internal/entityManager.js +18 -21
- package/dist/cjs/internal/entityManager.js.map +1 -1
- package/dist/cjs/internal/fiber.js +40 -0
- package/dist/cjs/internal/fiber.js.map +1 -0
- package/dist/dts/Sharding.d.ts.map +1 -1
- package/dist/dts/SqlRunnerStorage.d.ts +2 -1
- package/dist/dts/SqlRunnerStorage.d.ts.map +1 -1
- package/dist/dts/internal/fiber.d.ts +2 -0
- package/dist/dts/internal/fiber.d.ts.map +1 -0
- package/dist/esm/Sharding.js +34 -29
- package/dist/esm/Sharding.js.map +1 -1
- package/dist/esm/SqlRunnerStorage.js +29 -30
- package/dist/esm/SqlRunnerStorage.js.map +1 -1
- package/dist/esm/internal/entityManager.js +18 -21
- package/dist/esm/internal/entityManager.js.map +1 -1
- package/dist/esm/internal/fiber.js +32 -0
- package/dist/esm/internal/fiber.js.map +1 -0
- package/package.json +3 -3
- package/src/Sharding.ts +44 -36
- package/src/SqlRunnerStorage.ts +44 -43
- package/src/internal/entityManager.ts +25 -26
- package/src/internal/fiber.ts +35 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as Cause from "effect/Cause";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
/** @internal */
|
|
4
|
+
export const joinAllDiscard = fibers => Effect.async(resume => {
|
|
5
|
+
let cause = undefined;
|
|
6
|
+
let i = 0;
|
|
7
|
+
function loop() {
|
|
8
|
+
while (i < fibers.length) {
|
|
9
|
+
const fiber = fibers[i];
|
|
10
|
+
const exit = fiber.unsafePoll();
|
|
11
|
+
if (exit) {
|
|
12
|
+
i++;
|
|
13
|
+
if (exit._tag === "Success") continue;
|
|
14
|
+
cause = cause ? Cause.parallel(cause, exit.cause) : exit.cause;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
fiber.addObserver(onExit);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
resume(cause ? Effect.failCause(cause) : Effect.void);
|
|
21
|
+
}
|
|
22
|
+
function onExit(exit) {
|
|
23
|
+
i++;
|
|
24
|
+
if (exit._tag === "Failure") {
|
|
25
|
+
cause = cause ? Cause.parallel(cause, exit.cause) : exit.cause;
|
|
26
|
+
}
|
|
27
|
+
loop();
|
|
28
|
+
}
|
|
29
|
+
loop();
|
|
30
|
+
return Effect.sync(() => fibers[i].removeObserver(onExit));
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=fiber.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fiber.js","names":["Cause","Effect","joinAllDiscard","fibers","async","resume","cause","undefined","i","loop","length","fiber","exit","unsafePoll","_tag","parallel","addObserver","onExit","failCause","void","sync","removeObserver"],"sources":["../../../src/internal/fiber.ts"],"sourcesContent":[null],"mappings":"AAAA,OAAO,KAAKA,KAAK,MAAM,cAAc;AACrC,OAAO,KAAKC,MAAM,MAAM,eAAe;AAIvC;AACA,OAAO,MAAMC,cAAc,GAAUC,MAA+C,IAClFF,MAAM,CAACG,KAAK,CAAWC,MAAM,IAAI;EAC/B,IAAIC,KAAK,GAA+BC,SAAS;EACjD,IAAIC,CAAC,GAAG,CAAC;EACT,SAASC,IAAIA,CAAA;IACX,OAAOD,CAAC,GAAGL,MAAM,CAACO,MAAM,EAAE;MACxB,MAAMC,KAAK,GAAGR,MAAM,CAACK,CAAC,CAAC;MACvB,MAAMI,IAAI,GAAGD,KAAK,CAACE,UAAU,EAAE;MAC/B,IAAID,IAAI,EAAE;QACRJ,CAAC,EAAE;QACH,IAAII,IAAI,CAACE,IAAI,KAAK,SAAS,EAAE;QAC7BR,KAAK,GAAGA,KAAK,GAAGN,KAAK,CAACe,QAAQ,CAACT,KAAK,EAAEM,IAAI,CAACN,KAAK,CAAC,GAAGM,IAAI,CAACN,KAAK;QAC9D;MACF;MACAK,KAAK,CAACK,WAAW,CAACC,MAAM,CAAC;MACzB;IACF;IACAZ,MAAM,CAACC,KAAK,GAAGL,MAAM,CAACiB,SAAS,CAACZ,KAAK,CAAC,GAAGL,MAAM,CAACkB,IAAI,CAAC;EACvD;EACA,SAASF,MAAMA,CAACL,IAAqB;IACnCJ,CAAC,EAAE;IACH,IAAII,IAAI,CAACE,IAAI,KAAK,SAAS,EAAE;MAC3BR,KAAK,GAAGA,KAAK,GAAGN,KAAK,CAACe,QAAQ,CAACT,KAAK,EAAEM,IAAI,CAACN,KAAK,CAAC,GAAGM,IAAI,CAACN,KAAK;IAChE;IACAG,IAAI,EAAE;EACR;EACAA,IAAI,EAAE;EACN,OAAOR,MAAM,CAACmB,IAAI,CAAC,MAAMjB,MAAM,CAACK,CAAC,CAAC,CAACa,cAAc,CAACJ,MAAM,CAAC,CAAC;AAC5D,CAAC,CAAC","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect/cluster",
|
|
3
|
-
"version": "0.52.
|
|
3
|
+
"version": "0.52.7",
|
|
4
4
|
"description": "Unified interfaces for common cluster-specific services",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
"homepage": "https://effect.website",
|
|
13
13
|
"peerDependencies": {
|
|
14
14
|
"@effect/platform": "^0.93.0",
|
|
15
|
+
"@effect/rpc": "^0.72.1",
|
|
15
16
|
"@effect/sql": "^0.48.0",
|
|
16
17
|
"@effect/workflow": "^0.12.2",
|
|
17
|
-
"
|
|
18
|
-
"effect": "^3.19.2"
|
|
18
|
+
"effect": "^3.19.3"
|
|
19
19
|
},
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"provenance": true
|
package/src/Sharding.ts
CHANGED
|
@@ -8,12 +8,12 @@ import * as Arr from "effect/Array"
|
|
|
8
8
|
import * as Cause from "effect/Cause"
|
|
9
9
|
import * as Context from "effect/Context"
|
|
10
10
|
import type { DurationInput } from "effect/Duration"
|
|
11
|
+
import * as Duration from "effect/Duration"
|
|
11
12
|
import * as Effect from "effect/Effect"
|
|
12
13
|
import * as Either from "effect/Either"
|
|
13
14
|
import * as Equal from "effect/Equal"
|
|
14
15
|
import type * as Exit from "effect/Exit"
|
|
15
16
|
import * as Fiber from "effect/Fiber"
|
|
16
|
-
import * as FiberHandle from "effect/FiberHandle"
|
|
17
17
|
import * as FiberMap from "effect/FiberMap"
|
|
18
18
|
import * as FiberRef from "effect/FiberRef"
|
|
19
19
|
import * as FiberSet from "effect/FiberSet"
|
|
@@ -42,6 +42,7 @@ import { make as makeEntityId } from "./EntityId.js"
|
|
|
42
42
|
import * as Envelope from "./Envelope.js"
|
|
43
43
|
import * as EntityManager from "./internal/entityManager.js"
|
|
44
44
|
import { EntityReaper } from "./internal/entityReaper.js"
|
|
45
|
+
import { joinAllDiscard } from "./internal/fiber.js"
|
|
45
46
|
import { hashString } from "./internal/hash.js"
|
|
46
47
|
import { internalInterruptors } from "./internal/interruptors.js"
|
|
47
48
|
import { ResourceMap } from "./internal/resourceMap.js"
|
|
@@ -260,36 +261,43 @@ const make = Effect.gen(function*() {
|
|
|
260
261
|
return Effect.ignore(runnerStorage.releaseAll(selfAddress))
|
|
261
262
|
})
|
|
262
263
|
|
|
263
|
-
const
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
(
|
|
269
|
-
|
|
270
|
-
entityManagers.values(),
|
|
271
|
-
(state) => state.status === "closed" ? Effect.void : state.manager.interruptShard(shardId),
|
|
272
|
-
{ concurrency: "unbounded", discard: true }
|
|
273
|
-
).pipe(
|
|
274
|
-
Effect.andThen(runnerStorage.release(selfAddress, shardId)),
|
|
275
|
-
Effect.annotateLogs({ runner: selfAddress }),
|
|
276
|
-
Effect.flatMap(() => {
|
|
277
|
-
MutableHashSet.remove(releasingShards, shardId)
|
|
278
|
-
return storage.unregisterShardReplyHandlers(shardId)
|
|
279
|
-
})
|
|
280
|
-
),
|
|
281
|
-
{ concurrency: "unbounded", discard: true }
|
|
282
|
-
),
|
|
283
|
-
() => {
|
|
284
|
-
if (MutableHashSet.size(releasingShards) === 0) {
|
|
285
|
-
return Effect.void
|
|
286
|
-
}
|
|
287
|
-
return loop()
|
|
264
|
+
const releaseShardsMap = yield* FiberMap.make<ShardId>()
|
|
265
|
+
const releaseShard = Effect.fnUntraced(
|
|
266
|
+
function*(shardId: ShardId) {
|
|
267
|
+
const fibers = Arr.empty<Fiber.RuntimeFiber<void>>()
|
|
268
|
+
for (const state of entityManagers.values()) {
|
|
269
|
+
if (state.status === "closed") continue
|
|
270
|
+
fibers.push(yield* Effect.fork(state.manager.interruptShard(shardId)))
|
|
288
271
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
272
|
+
yield* joinAllDiscard(fibers)
|
|
273
|
+
yield* runnerStorage.release(selfAddress, shardId)
|
|
274
|
+
MutableHashSet.remove(releasingShards, shardId)
|
|
275
|
+
yield* storage.unregisterShardReplyHandlers(shardId)
|
|
276
|
+
},
|
|
277
|
+
Effect.sandbox,
|
|
278
|
+
(effect, shardId) =>
|
|
279
|
+
effect.pipe(
|
|
280
|
+
Effect.tapError((cause) =>
|
|
281
|
+
Effect.logDebug(`Could not release shard, retrying`, cause).pipe(
|
|
282
|
+
Effect.annotateLogs({
|
|
283
|
+
package: "@effect/cluster",
|
|
284
|
+
module: "Sharding",
|
|
285
|
+
fiber: "releaseShard",
|
|
286
|
+
runner: selfAddress,
|
|
287
|
+
shardId
|
|
288
|
+
})
|
|
289
|
+
)
|
|
290
|
+
),
|
|
291
|
+
Effect.eventually,
|
|
292
|
+
FiberMap.run(releaseShardsMap, shardId, { onlyIfMissing: true })
|
|
293
|
+
)
|
|
292
294
|
)
|
|
295
|
+
const releaseShards = Effect.gen(function*() {
|
|
296
|
+
for (const shardId of releasingShards) {
|
|
297
|
+
if (FiberMap.unsafeHas(releaseShardsMap, shardId)) continue
|
|
298
|
+
yield* releaseShard(shardId)
|
|
299
|
+
}
|
|
300
|
+
})
|
|
293
301
|
|
|
294
302
|
yield* Effect.gen(function*() {
|
|
295
303
|
activeShardsLatch.unsafeOpen()
|
|
@@ -873,16 +881,17 @@ const make = Effect.gen(function*() {
|
|
|
873
881
|
const hashRings = new Map<string, HashRing.HashRing<RunnerAddress>>()
|
|
874
882
|
let nextRunners = MutableHashMap.empty<Runner, boolean>()
|
|
875
883
|
const healthyRunners = MutableHashSet.empty<Runner>()
|
|
884
|
+
const withTimeout = Effect.timeout(Duration.seconds(5))
|
|
876
885
|
|
|
877
886
|
while (true) {
|
|
878
887
|
// Ensure the current runner is registered
|
|
879
888
|
if (selfRunner && !isShutdown.current && !MutableHashMap.has(allRunners, selfRunner)) {
|
|
880
889
|
yield* Effect.logDebug("Registering runner", selfRunner)
|
|
881
|
-
const machineId = yield* runnerStorage.register(selfRunner, true)
|
|
890
|
+
const machineId = yield* withTimeout(runnerStorage.register(selfRunner, true))
|
|
882
891
|
yield* snowflakeGen.setMachineId(machineId)
|
|
883
892
|
}
|
|
884
893
|
|
|
885
|
-
const runners = yield* runnerStorage.getRunners
|
|
894
|
+
const runners = yield* withTimeout(runnerStorage.getRunners)
|
|
886
895
|
let changed = false
|
|
887
896
|
for (let i = 0; i < runners.length; i++) {
|
|
888
897
|
const [runner, healthy] = runners[i]
|
|
@@ -965,7 +974,7 @@ const make = Effect.gen(function*() {
|
|
|
965
974
|
yield* Effect.logWarning("No healthy runners available")
|
|
966
975
|
// to prevent a deadlock, we will mark the current node as healthy to
|
|
967
976
|
// start the health check singleton again
|
|
968
|
-
yield* runnerStorage.setRunnerHealth(selfRunner.address, true)
|
|
977
|
+
yield* withTimeout(runnerStorage.setRunnerHealth(selfRunner.address, true))
|
|
969
978
|
}
|
|
970
979
|
|
|
971
980
|
yield* Effect.sleep(config.refreshAssignmentsInterval)
|
|
@@ -1099,8 +1108,8 @@ const make = Effect.gen(function*() {
|
|
|
1099
1108
|
|
|
1100
1109
|
yield* Scope.addFinalizer(
|
|
1101
1110
|
yield* Effect.scope,
|
|
1102
|
-
Effect.
|
|
1103
|
-
internalInterruptors.add(
|
|
1111
|
+
Effect.fiberIdWith((fiberId) => {
|
|
1112
|
+
internalInterruptors.add(fiberId)
|
|
1104
1113
|
return Effect.void
|
|
1105
1114
|
})
|
|
1106
1115
|
)
|
|
@@ -1368,8 +1377,7 @@ const make = Effect.gen(function*() {
|
|
|
1368
1377
|
)
|
|
1369
1378
|
}
|
|
1370
1379
|
|
|
1371
|
-
|
|
1372
|
-
internalInterruptors.add(fiberId)
|
|
1380
|
+
internalInterruptors.add(yield* Effect.fiberId)
|
|
1373
1381
|
if (isShutdown.current) return
|
|
1374
1382
|
|
|
1375
1383
|
MutableRef.set(isShutdown, true)
|
package/src/SqlRunnerStorage.ts
CHANGED
|
@@ -8,8 +8,9 @@ import * as Arr from "effect/Array"
|
|
|
8
8
|
import * as Duration from "effect/Duration"
|
|
9
9
|
import * as Effect from "effect/Effect"
|
|
10
10
|
import * as Layer from "effect/Layer"
|
|
11
|
-
import * as
|
|
11
|
+
import * as Scope from "effect/Scope"
|
|
12
12
|
import { PersistenceError } from "./ClusterError.js"
|
|
13
|
+
import { ResourceRef } from "./internal/resourceRef.js"
|
|
13
14
|
import * as RunnerStorage from "./RunnerStorage.js"
|
|
14
15
|
import * as ShardId from "./ShardId.js"
|
|
15
16
|
import * as ShardingConfig from "./ShardingConfig.js"
|
|
@@ -27,22 +28,27 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
27
28
|
const sql = (yield* SqlClient.SqlClient).withoutTransforms()
|
|
28
29
|
const prefix = options?.prefix ?? "cluster"
|
|
29
30
|
const table = (name: string) => `${prefix}_${name}`
|
|
31
|
+
|
|
30
32
|
const acquireLockConn = sql.onDialectOrElse({
|
|
31
33
|
pg: () =>
|
|
32
|
-
Effect.
|
|
33
|
-
const conn = yield* Effect.orDie(sql.reserve)
|
|
34
|
-
|
|
34
|
+
Effect.fnUntraced(function*(scope: Scope.Scope) {
|
|
35
|
+
const conn = yield* Effect.orDie(sql.reserve).pipe(
|
|
36
|
+
Scope.extend(scope)
|
|
37
|
+
)
|
|
38
|
+
yield* Scope.addFinalizerExit(scope, () => Effect.orDie(conn.executeRaw("SELECT pg_advisory_unlock_all()", [])))
|
|
35
39
|
return conn
|
|
36
40
|
}),
|
|
37
41
|
mysql: () =>
|
|
38
|
-
Effect.
|
|
39
|
-
const conn = yield* Effect.orDie(sql.reserve)
|
|
40
|
-
|
|
42
|
+
Effect.fnUntraced(function*(scope: Scope.Scope) {
|
|
43
|
+
const conn = yield* Effect.orDie(sql.reserve).pipe(
|
|
44
|
+
Scope.extend(scope)
|
|
45
|
+
)
|
|
46
|
+
yield* Scope.addFinalizerExit(scope, () => Effect.orDie(conn.executeRaw("SELECT RELEASE_ALL_LOCKS()", [])))
|
|
41
47
|
return conn
|
|
42
48
|
}),
|
|
43
49
|
orElse: () => undefined
|
|
44
50
|
})
|
|
45
|
-
const
|
|
51
|
+
const lockConn = acquireLockConn && (yield* ResourceRef.from(yield* Effect.scope, acquireLockConn))
|
|
46
52
|
|
|
47
53
|
const runnersTable = table("runners")
|
|
48
54
|
const runnersTableSql = sql(runnersTable)
|
|
@@ -206,28 +212,28 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
206
212
|
})
|
|
207
213
|
|
|
208
214
|
const execWithLockConn = <A>(effect: Statement.Statement<A>): Effect.Effect<unknown, SqlError> => {
|
|
209
|
-
if (!
|
|
215
|
+
if (!lockConn) return effect
|
|
210
216
|
const [query, params] = effect.compile()
|
|
211
|
-
return
|
|
217
|
+
return lockConn.await.pipe(
|
|
212
218
|
Effect.flatMap((conn) => conn.executeRaw(query, params)),
|
|
213
|
-
Effect.onError(() =>
|
|
219
|
+
Effect.onError(() => lockConn.unsafeRebuild())
|
|
214
220
|
)
|
|
215
221
|
}
|
|
216
222
|
const execWithLockConnValues = <A>(
|
|
217
223
|
effect: Statement.Statement<A>
|
|
218
224
|
): Effect.Effect<ReadonlyArray<ReadonlyArray<any>>, SqlError> => {
|
|
219
|
-
if (!
|
|
225
|
+
if (!lockConn) return effect.values
|
|
220
226
|
const [query, params] = effect.compile()
|
|
221
|
-
return
|
|
227
|
+
return lockConn.await.pipe(
|
|
222
228
|
Effect.flatMap((conn) => conn.executeValues(query, params)),
|
|
223
|
-
Effect.onError(() =>
|
|
229
|
+
Effect.onError(() => lockConn.unsafeRebuild())
|
|
224
230
|
)
|
|
225
231
|
}
|
|
226
232
|
|
|
227
233
|
const acquireLock = sql.onDialectOrElse({
|
|
228
234
|
pg: () =>
|
|
229
235
|
Effect.fnUntraced(function*(_address: string, shardIds: ReadonlyArray<string>) {
|
|
230
|
-
const conn = yield*
|
|
236
|
+
const conn = yield* lockConn!.await
|
|
231
237
|
const acquiredShardIds: Array<string> = []
|
|
232
238
|
const toAcquire = new Map(shardIds.map((shardId) => [lockNumbers.get(shardId)!, shardId]))
|
|
233
239
|
const takenLocks = yield* conn.executeValues(
|
|
@@ -250,11 +256,11 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
250
256
|
}
|
|
251
257
|
}
|
|
252
258
|
return acquiredShardIds
|
|
253
|
-
}, Effect.onError(() =>
|
|
259
|
+
}, Effect.onError(() => lockConn!.unsafeRebuild())),
|
|
254
260
|
|
|
255
261
|
mysql: () =>
|
|
256
262
|
Effect.fnUntraced(function*(_address: string, shardIds: ReadonlyArray<string>) {
|
|
257
|
-
const conn = yield*
|
|
263
|
+
const conn = yield* lockConn!.await
|
|
258
264
|
const takenLocks = (yield* conn.executeUnprepared(`SELECT ${allMySqlTakenLocks}`, [], undefined))[0] as Record<
|
|
259
265
|
string,
|
|
260
266
|
1 | null
|
|
@@ -281,7 +287,7 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
281
287
|
}
|
|
282
288
|
}
|
|
283
289
|
return acquiredShardIds
|
|
284
|
-
}, Effect.onError(() =>
|
|
290
|
+
}, Effect.onError(() => lockConn!.unsafeRebuild())),
|
|
285
291
|
|
|
286
292
|
mssql: () => (address: string, shardIds: ReadonlyArray<string>) => {
|
|
287
293
|
const values = shardIds.map((shardId) => sql`(${stringLiteral(shardId)}, ${stringLiteral(address)}, ${sqlNow})`)
|
|
@@ -446,48 +452,43 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
446
452
|
Effect.fnUntraced(
|
|
447
453
|
function*(_address, shardId) {
|
|
448
454
|
const lockNum = lockNumbers.get(shardId)!
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
yield* release
|
|
457
|
-
const takenLocks = yield* check
|
|
455
|
+
for (let i = 0; i < 5; i++) {
|
|
456
|
+
const conn = yield* lockConn!.await
|
|
457
|
+
yield* conn.executeRaw(`SELECT pg_advisory_unlock(${lockNum})`, [])
|
|
458
|
+
const takenLocks = yield* conn.executeValues(
|
|
459
|
+
`SELECT 1 FROM pg_locks WHERE locktype = 'advisory' AND granted = true AND pid = pg_backend_pid() AND objid = ${lockNum}`,
|
|
460
|
+
[]
|
|
461
|
+
)
|
|
458
462
|
if (takenLocks.length === 0) return
|
|
459
463
|
}
|
|
464
|
+
const conn = yield* lockConn!.await
|
|
465
|
+
yield* conn.executeRaw(`SELECT pg_advisory_unlock_all()`, [])
|
|
460
466
|
},
|
|
461
|
-
Effect.onError(() =>
|
|
467
|
+
Effect.onError(() => lockConn!.unsafeRebuild()),
|
|
462
468
|
Effect.asVoid,
|
|
463
|
-
PersistenceError.refail
|
|
464
|
-
withTracerDisabled
|
|
469
|
+
PersistenceError.refail
|
|
465
470
|
),
|
|
466
471
|
mysql: () =>
|
|
467
472
|
Effect.fnUntraced(
|
|
468
473
|
function*(_address, shardId) {
|
|
469
|
-
const conn = yield* Resource.get(lockConnRef!)
|
|
470
474
|
const lockName = lockNames.get(shardId)!
|
|
471
|
-
const release = conn.executeRaw(`SELECT RELEASE_LOCK('${lockName}')`, [])
|
|
472
|
-
const check = conn.executeValues(
|
|
473
|
-
`SELECT IS_USED_LOCK('${lockName}') = CONNECTION_ID() AS is_taken`,
|
|
474
|
-
[]
|
|
475
|
-
)
|
|
476
475
|
while (true) {
|
|
477
|
-
yield*
|
|
478
|
-
|
|
476
|
+
const conn = yield* lockConn!.await
|
|
477
|
+
yield* conn.executeRaw(`SELECT RELEASE_LOCK('${lockName}')`, [])
|
|
478
|
+
const takenLocks = yield* conn.executeValues(
|
|
479
|
+
`SELECT IS_USED_LOCK('${lockName}') = CONNECTION_ID() AS is_taken`,
|
|
480
|
+
[]
|
|
481
|
+
)
|
|
479
482
|
if (takenLocks.length === 0 || takenLocks[0][0] !== 1) return
|
|
480
483
|
}
|
|
481
484
|
},
|
|
482
|
-
Effect.onError(() =>
|
|
485
|
+
Effect.onError(() => lockConn!.unsafeRebuild()),
|
|
483
486
|
Effect.asVoid,
|
|
484
|
-
PersistenceError.refail
|
|
485
|
-
withTracerDisabled
|
|
487
|
+
PersistenceError.refail
|
|
486
488
|
),
|
|
487
489
|
orElse: () => (address, shardId) =>
|
|
488
490
|
sql`DELETE FROM ${locksTableSql} WHERE address = ${address} AND shard_id = ${shardId}`.pipe(
|
|
489
|
-
PersistenceError.refail
|
|
490
|
-
withTracerDisabled
|
|
491
|
+
PersistenceError.refail
|
|
491
492
|
)
|
|
492
493
|
}),
|
|
493
494
|
|
|
@@ -9,11 +9,13 @@ import type { DurationInput } from "effect/Duration"
|
|
|
9
9
|
import * as Effect from "effect/Effect"
|
|
10
10
|
import * as Equal from "effect/Equal"
|
|
11
11
|
import * as Exit from "effect/Exit"
|
|
12
|
+
import type * as Fiber from "effect/Fiber"
|
|
12
13
|
import * as FiberRef from "effect/FiberRef"
|
|
13
14
|
import { identity } from "effect/Function"
|
|
14
15
|
import * as HashMap from "effect/HashMap"
|
|
15
16
|
import * as Metric from "effect/Metric"
|
|
16
17
|
import * as Option from "effect/Option"
|
|
18
|
+
import * as Runtime from "effect/Runtime"
|
|
17
19
|
import * as Schedule from "effect/Schedule"
|
|
18
20
|
import * as Schema from "effect/Schema"
|
|
19
21
|
import * as Scope from "effect/Scope"
|
|
@@ -34,6 +36,7 @@ import type { Sharding } from "../Sharding.js"
|
|
|
34
36
|
import { ShardingConfig } from "../ShardingConfig.js"
|
|
35
37
|
import * as Snowflake from "../Snowflake.js"
|
|
36
38
|
import { EntityReaper } from "./entityReaper.js"
|
|
39
|
+
import { joinAllDiscard } from "./fiber.js"
|
|
37
40
|
import { internalInterruptors } from "./interruptors.js"
|
|
38
41
|
import { ResourceMap } from "./resourceMap.js"
|
|
39
42
|
import { ResourceRef } from "./resourceRef.js"
|
|
@@ -434,35 +437,31 @@ export const make = Effect.fnUntraced(function*<
|
|
|
434
437
|
)
|
|
435
438
|
}
|
|
436
439
|
|
|
437
|
-
const interruptShard = (shardId: ShardId) =>
|
|
438
|
-
Effect.suspend(function loop(): Effect.Effect<void> {
|
|
439
|
-
const toAwait = Arr.empty<Effect.Effect<void>>()
|
|
440
|
-
activeServers.forEach((state) => {
|
|
441
|
-
if (shardId[Equal.symbol](state.address.shardId)) {
|
|
442
|
-
toAwait.push(entities.removeIgnore(state.address))
|
|
443
|
-
}
|
|
444
|
-
})
|
|
445
|
-
serverCloseLatches.forEach((latch, address) => {
|
|
446
|
-
if (shardId[Equal.symbol](address.shardId)) {
|
|
447
|
-
toAwait.push(latch.await)
|
|
448
|
-
}
|
|
449
|
-
})
|
|
450
|
-
if (toAwait.length === 0) {
|
|
451
|
-
return Effect.void
|
|
452
|
-
}
|
|
453
|
-
return Effect.flatMap(
|
|
454
|
-
Effect.all(toAwait, {
|
|
455
|
-
concurrency: "unbounded",
|
|
456
|
-
discard: true
|
|
457
|
-
}),
|
|
458
|
-
loop
|
|
459
|
-
)
|
|
460
|
-
})
|
|
461
|
-
|
|
462
440
|
const decodeMessage = Schema.decode(makeMessageSchema(entity))
|
|
463
441
|
|
|
442
|
+
const runFork = Runtime.runFork(
|
|
443
|
+
yield* Effect.runtime<never>().pipe(
|
|
444
|
+
Effect.interruptible
|
|
445
|
+
)
|
|
446
|
+
)
|
|
447
|
+
|
|
464
448
|
return identity<EntityManager>({
|
|
465
|
-
interruptShard
|
|
449
|
+
interruptShard: (shardId: ShardId) =>
|
|
450
|
+
Effect.suspend(function loop(): Effect.Effect<void> {
|
|
451
|
+
const fibers = Arr.empty<Fiber.RuntimeFiber<void>>()
|
|
452
|
+
activeServers.forEach((state) => {
|
|
453
|
+
if (shardId[Equal.symbol](state.address.shardId)) {
|
|
454
|
+
fibers.push(runFork(entities.removeIgnore(state.address)))
|
|
455
|
+
}
|
|
456
|
+
})
|
|
457
|
+
serverCloseLatches.forEach((latch, address) => {
|
|
458
|
+
if (shardId[Equal.symbol](address.shardId)) {
|
|
459
|
+
fibers.push(runFork(latch.await))
|
|
460
|
+
}
|
|
461
|
+
})
|
|
462
|
+
if (fibers.length === 0) return Effect.void
|
|
463
|
+
return Effect.flatMap(joinAllDiscard(fibers), loop)
|
|
464
|
+
}),
|
|
466
465
|
isProcessingFor(message, options) {
|
|
467
466
|
if (options?.excludeReplies !== true && processedRequestIds.has(message.envelope.requestId)) {
|
|
468
467
|
return true
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as Cause from "effect/Cause"
|
|
2
|
+
import * as Effect from "effect/Effect"
|
|
3
|
+
import type * as Exit from "effect/Exit"
|
|
4
|
+
import type * as Fiber from "effect/Fiber"
|
|
5
|
+
|
|
6
|
+
/** @internal */
|
|
7
|
+
export const joinAllDiscard = <A, E>(fibers: ReadonlyArray<Fiber.RuntimeFiber<A, E>>) =>
|
|
8
|
+
Effect.async<void, E>((resume) => {
|
|
9
|
+
let cause: Cause.Cause<E> | undefined = undefined
|
|
10
|
+
let i = 0
|
|
11
|
+
function loop() {
|
|
12
|
+
while (i < fibers.length) {
|
|
13
|
+
const fiber = fibers[i]
|
|
14
|
+
const exit = fiber.unsafePoll()
|
|
15
|
+
if (exit) {
|
|
16
|
+
i++
|
|
17
|
+
if (exit._tag === "Success") continue
|
|
18
|
+
cause = cause ? Cause.parallel(cause, exit.cause) : exit.cause
|
|
19
|
+
continue
|
|
20
|
+
}
|
|
21
|
+
fiber.addObserver(onExit)
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
resume(cause ? Effect.failCause(cause) : Effect.void)
|
|
25
|
+
}
|
|
26
|
+
function onExit(exit: Exit.Exit<A, E>) {
|
|
27
|
+
i++
|
|
28
|
+
if (exit._tag === "Failure") {
|
|
29
|
+
cause = cause ? Cause.parallel(cause, exit.cause) : exit.cause
|
|
30
|
+
}
|
|
31
|
+
loop()
|
|
32
|
+
}
|
|
33
|
+
loop()
|
|
34
|
+
return Effect.sync(() => fibers[i].removeObserver(onExit))
|
|
35
|
+
})
|