@effect/cluster 0.52.2 → 0.52.4
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 +29 -13
- package/dist/cjs/Sharding.js.map +1 -1
- package/dist/cjs/ShardingConfig.js +2 -0
- package/dist/cjs/ShardingConfig.js.map +1 -1
- package/dist/cjs/SqlRunnerStorage.js +5 -4
- package/dist/cjs/SqlRunnerStorage.js.map +1 -1
- package/dist/dts/Sharding.d.ts.map +1 -1
- package/dist/dts/ShardingConfig.d.ts +12 -0
- package/dist/dts/ShardingConfig.d.ts.map +1 -1
- package/dist/dts/SqlRunnerStorage.d.ts.map +1 -1
- package/dist/esm/Sharding.js +29 -13
- package/dist/esm/Sharding.js.map +1 -1
- package/dist/esm/ShardingConfig.js +2 -0
- package/dist/esm/ShardingConfig.js.map +1 -1
- package/dist/esm/SqlRunnerStorage.js +5 -4
- package/dist/esm/SqlRunnerStorage.js.map +1 -1
- package/package.json +3 -3
- package/src/Sharding.ts +55 -35
- package/src/ShardingConfig.ts +11 -0
- package/src/SqlRunnerStorage.ts +7 -5
package/src/Sharding.ts
CHANGED
|
@@ -11,7 +11,9 @@ import type { DurationInput } from "effect/Duration"
|
|
|
11
11
|
import * as Effect from "effect/Effect"
|
|
12
12
|
import * as Either from "effect/Either"
|
|
13
13
|
import * as Equal from "effect/Equal"
|
|
14
|
+
import type * as Exit from "effect/Exit"
|
|
14
15
|
import * as Fiber from "effect/Fiber"
|
|
16
|
+
import * as FiberHandle from "effect/FiberHandle"
|
|
15
17
|
import * as FiberMap from "effect/FiberMap"
|
|
16
18
|
import * as FiberRef from "effect/FiberRef"
|
|
17
19
|
import * as FiberSet from "effect/FiberSet"
|
|
@@ -280,8 +282,19 @@ const make = Effect.gen(function*() {
|
|
|
280
282
|
continue
|
|
281
283
|
}
|
|
282
284
|
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
+
const oacquired = yield* runnerStorage.acquire(selfAddress, unacquiredShards).pipe(
|
|
286
|
+
Effect.timeoutOption(config.shardLockRefreshInterval)
|
|
287
|
+
)
|
|
288
|
+
if (Option.isNone(oacquired)) {
|
|
289
|
+
activeShardsLatch.unsafeOpen()
|
|
290
|
+
continue
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const acquired = oacquired.value
|
|
294
|
+
yield* storage.resetShards(acquired).pipe(
|
|
295
|
+
Effect.ignore,
|
|
296
|
+
Effect.timeoutOption(config.shardLockRefreshInterval)
|
|
297
|
+
)
|
|
285
298
|
for (const shardId of acquired) {
|
|
286
299
|
if (MutableHashSet.has(releasingShards, shardId) || !MutableHashSet.has(selfShards, shardId)) {
|
|
287
300
|
continue
|
|
@@ -349,27 +362,28 @@ const make = Effect.gen(function*() {
|
|
|
349
362
|
Effect.forkIn(shardingScope)
|
|
350
363
|
)
|
|
351
364
|
|
|
352
|
-
const
|
|
353
|
-
const releaseShards =
|
|
354
|
-
Effect.
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
(
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
{ concurrency: "unbounded", discard: true }
|
|
371
|
-
)
|
|
365
|
+
const releaseShardsHandle = yield* FiberHandle.make()
|
|
366
|
+
const releaseShards = Effect.suspend(() =>
|
|
367
|
+
Effect.forEach(
|
|
368
|
+
releasingShards,
|
|
369
|
+
(shardId) =>
|
|
370
|
+
Effect.forEach(
|
|
371
|
+
entityManagers.values(),
|
|
372
|
+
(state) => state.manager.interruptShard(shardId),
|
|
373
|
+
{ concurrency: "unbounded", discard: true }
|
|
374
|
+
).pipe(
|
|
375
|
+
Effect.andThen(runnerStorage.release(selfAddress, shardId)),
|
|
376
|
+
Effect.annotateLogs({ runner: selfAddress }),
|
|
377
|
+
Effect.flatMap(() => {
|
|
378
|
+
MutableHashSet.remove(releasingShards, shardId)
|
|
379
|
+
return storage.unregisterShardReplyHandlers(shardId)
|
|
380
|
+
})
|
|
381
|
+
),
|
|
382
|
+
{ concurrency: "unbounded", discard: true }
|
|
372
383
|
)
|
|
384
|
+
).pipe(
|
|
385
|
+
Effect.repeat({ until: () => MutableHashSet.size(releasingShards) === 0 }),
|
|
386
|
+
FiberHandle.run(releaseShardsHandle, { onlyIfMissing: true })
|
|
373
387
|
)
|
|
374
388
|
|
|
375
389
|
// open the shard latch every poll interval
|
|
@@ -1247,7 +1261,9 @@ const make = Effect.gen(function*() {
|
|
|
1247
1261
|
Effect.fiberIdWith((id) => {
|
|
1248
1262
|
state.status = "closing"
|
|
1249
1263
|
internalInterruptors.add(id)
|
|
1250
|
-
|
|
1264
|
+
// if preemptive shutdown is enabled, we start shutting down Sharding
|
|
1265
|
+
// too
|
|
1266
|
+
return config.preemptiveShutdown ? shutdown() : Effect.void
|
|
1251
1267
|
})
|
|
1252
1268
|
)
|
|
1253
1269
|
|
|
@@ -1320,23 +1336,27 @@ const make = Effect.gen(function*() {
|
|
|
1320
1336
|
|
|
1321
1337
|
// --- Finalization ---
|
|
1322
1338
|
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
yield* Effect.logDebug("Shutting down", exit._tag === "Success" ? {} : exit.cause).pipe(
|
|
1339
|
+
const shutdown = Effect.fnUntraced(function*(exit?: Exit.Exit<unknown, unknown>) {
|
|
1340
|
+
if (exit) {
|
|
1341
|
+
yield* Effect.logDebug("Shutting down", exit._tag === "Failure" ? exit.cause : {}).pipe(
|
|
1327
1342
|
Effect.annotateLogs({
|
|
1328
1343
|
package: "@effect/cluster",
|
|
1329
1344
|
module: "Sharding"
|
|
1330
1345
|
})
|
|
1331
1346
|
)
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
const fiberId = yield* Effect.fiberId
|
|
1350
|
+
internalInterruptors.add(fiberId)
|
|
1351
|
+
if (isShutdown.current) return
|
|
1352
|
+
|
|
1353
|
+
MutableRef.set(isShutdown, true)
|
|
1354
|
+
if (selfRunner) {
|
|
1355
|
+
yield* Effect.ignore(runnerStorage.unregister(selfRunner.address))
|
|
1356
|
+
}
|
|
1357
|
+
})
|
|
1358
|
+
|
|
1359
|
+
yield* Scope.addFinalizerExit(shardingScope, shutdown)
|
|
1340
1360
|
|
|
1341
1361
|
const activeEntityCount = Effect.gen(function*() {
|
|
1342
1362
|
let count = 0
|
package/src/ShardingConfig.ts
CHANGED
|
@@ -63,6 +63,12 @@ export class ShardingConfig extends Context.Tag("@effect/cluster/ShardingConfig"
|
|
|
63
63
|
* Shard lock expiration duration.
|
|
64
64
|
*/
|
|
65
65
|
readonly shardLockExpiration: DurationInput
|
|
66
|
+
/**
|
|
67
|
+
* Start shutting down as soon as an Entity has started shutting down.
|
|
68
|
+
*
|
|
69
|
+
* Defaults to `true`.
|
|
70
|
+
*/
|
|
71
|
+
readonly preemptiveShutdown: boolean
|
|
66
72
|
/**
|
|
67
73
|
* The default capacity of the mailbox for entities.
|
|
68
74
|
*/
|
|
@@ -118,6 +124,7 @@ export const defaults: ShardingConfig["Type"] = {
|
|
|
118
124
|
runnerShardWeight: 1,
|
|
119
125
|
shardsPerGroup: 300,
|
|
120
126
|
shardGroups: ["default"],
|
|
127
|
+
preemptiveShutdown: true,
|
|
121
128
|
shardLockRefreshInterval: Duration.seconds(10),
|
|
122
129
|
shardLockExpiration: Duration.seconds(35),
|
|
123
130
|
entityMailboxCapacity: 4096,
|
|
@@ -179,6 +186,10 @@ export const config: Config.Config<ShardingConfig["Type"]> = Config.all({
|
|
|
179
186
|
Config.withDefault(defaults.shardsPerGroup),
|
|
180
187
|
Config.withDescription("The number of shards to allocate per shard group.")
|
|
181
188
|
),
|
|
189
|
+
preemptiveShutdown: Config.boolean("preemptiveShutdown").pipe(
|
|
190
|
+
Config.withDefault(defaults.preemptiveShutdown),
|
|
191
|
+
Config.withDescription("Start shutting down as soon as an Entity has started shutting down.")
|
|
192
|
+
),
|
|
182
193
|
shardLockRefreshInterval: Config.duration("shardLockRefreshInterval").pipe(
|
|
183
194
|
Config.withDefault(defaults.shardLockRefreshInterval),
|
|
184
195
|
Config.withDescription("Shard lock refresh interval.")
|
package/src/SqlRunnerStorage.ts
CHANGED
|
@@ -242,10 +242,8 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
242
242
|
if (toAcquire.size === 0) {
|
|
243
243
|
return acquiredShardIds
|
|
244
244
|
}
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
boolean
|
|
248
|
-
>
|
|
245
|
+
const rows = yield* conn.executeUnprepared(`SELECT ${pgLocks(toAcquire)}`, [], undefined)
|
|
246
|
+
const results = rows[0] as Record<string, boolean>
|
|
249
247
|
for (const shardId in results) {
|
|
250
248
|
if (results[shardId]) {
|
|
251
249
|
acquiredShardIds.push(shardId)
|
|
@@ -449,7 +447,9 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
449
447
|
function*(_address, shardId) {
|
|
450
448
|
const lockNum = lockNumbers.get(shardId)!
|
|
451
449
|
const conn = yield* Resource.get(lockConnRef!)
|
|
452
|
-
const release = conn.executeRaw(`SELECT pg_advisory_unlock(${lockNum})`, [])
|
|
450
|
+
const release = conn.executeRaw(`SELECT pg_advisory_unlock(${lockNum})`, []).pipe(
|
|
451
|
+
Effect.timeoutOption(Duration.seconds(5))
|
|
452
|
+
)
|
|
453
453
|
const check = conn.executeValues(
|
|
454
454
|
`SELECT 1 FROM pg_locks WHERE locktype = 'advisory' AND granted = true AND pid = pg_backend_pid() AND objid = ${lockNum}`,
|
|
455
455
|
[]
|
|
@@ -460,6 +460,7 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
460
460
|
if (takenLocks.length === 0) return
|
|
461
461
|
}
|
|
462
462
|
},
|
|
463
|
+
Effect.timeout(config.shardLockExpiration),
|
|
463
464
|
Effect.onError(() => Resource.refresh(lockConnRef!)),
|
|
464
465
|
Effect.asVoid,
|
|
465
466
|
PersistenceError.refail,
|
|
@@ -481,6 +482,7 @@ export const make = Effect.fnUntraced(function*(options: {
|
|
|
481
482
|
if (takenLocks.length === 0 || takenLocks[0][0] !== 1) return
|
|
482
483
|
}
|
|
483
484
|
},
|
|
485
|
+
Effect.timeout(config.shardLockExpiration),
|
|
484
486
|
Effect.onError(() => Resource.refresh(lockConnRef!)),
|
|
485
487
|
Effect.asVoid,
|
|
486
488
|
PersistenceError.refail,
|