@effect/cluster 0.52.3 → 0.52.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect/cluster",
3
- "version": "0.52.3",
3
+ "version": "0.52.5",
4
4
  "description": "Unified interfaces for common cluster-specific services",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -15,7 +15,7 @@
15
15
  "@effect/sql": "^0.48.0",
16
16
  "@effect/workflow": "^0.12.2",
17
17
  "@effect/rpc": "^0.72.1",
18
- "effect": "^3.19.1"
18
+ "effect": "^3.19.2"
19
19
  },
20
20
  "publishConfig": {
21
21
  "provenance": true
package/src/Sharding.ts CHANGED
@@ -13,6 +13,7 @@ import * as Either from "effect/Either"
13
13
  import * as Equal from "effect/Equal"
14
14
  import type * as Exit from "effect/Exit"
15
15
  import * as Fiber from "effect/Fiber"
16
+ import * as FiberHandle from "effect/FiberHandle"
16
17
  import * as FiberMap from "effect/FiberMap"
17
18
  import * as FiberRef from "effect/FiberRef"
18
19
  import * as FiberSet from "effect/FiberSet"
@@ -236,6 +237,14 @@ const make = Effect.gen(function*() {
236
237
  return MutableHashSet.has(acquiredShards, address.shardId)
237
238
  }
238
239
 
240
+ yield* Scope.addFinalizer(
241
+ shardingScope,
242
+ Effect.logDebug("Shutdown complete").pipe(Effect.annotateLogs({
243
+ package: "@effect/cluster",
244
+ module: "Sharding"
245
+ }))
246
+ )
247
+
239
248
  // --- Shard acquisition ---
240
249
  //
241
250
  // Responsible for acquiring and releasing shards from RunnerStorage.
@@ -251,6 +260,37 @@ const make = Effect.gen(function*() {
251
260
  return Effect.ignore(runnerStorage.releaseAll(selfAddress))
252
261
  })
253
262
 
263
+ const releaseShardsHandle = yield* FiberHandle.make()
264
+ const releaseShards = Effect.suspend(function loop(): Effect.Effect<void, PersistenceError> {
265
+ return Effect.flatMap(
266
+ Effect.forEach(
267
+ releasingShards,
268
+ (shardId) =>
269
+ Effect.forEach(
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()
288
+ }
289
+ )
290
+ }).pipe(
291
+ FiberHandle.run(releaseShardsHandle, { onlyIfMissing: true })
292
+ )
293
+
254
294
  yield* Effect.gen(function*() {
255
295
  activeShardsLatch.unsafeOpen()
256
296
 
@@ -281,8 +321,19 @@ const make = Effect.gen(function*() {
281
321
  continue
282
322
  }
283
323
 
284
- const acquired = yield* runnerStorage.acquire(selfAddress, unacquiredShards)
285
- yield* Effect.ignore(storage.resetShards(acquired))
324
+ const oacquired = yield* runnerStorage.acquire(selfAddress, unacquiredShards).pipe(
325
+ Effect.timeoutOption(config.shardLockRefreshInterval)
326
+ )
327
+ if (Option.isNone(oacquired)) {
328
+ activeShardsLatch.unsafeOpen()
329
+ continue
330
+ }
331
+
332
+ const acquired = oacquired.value
333
+ yield* storage.resetShards(acquired).pipe(
334
+ Effect.ignore,
335
+ Effect.timeoutOption(config.shardLockRefreshInterval)
336
+ )
286
337
  for (const shardId of acquired) {
287
338
  if (MutableHashSet.has(releasingShards, shardId) || !MutableHashSet.has(selfShards, shardId)) {
288
339
  continue
@@ -308,7 +359,8 @@ const make = Effect.gen(function*() {
308
359
  fiber: "Shard acquisition loop",
309
360
  runner: selfAddress
310
361
  }),
311
- Effect.forkIn(shardingScope)
362
+ Effect.forkIn(shardingScope),
363
+ Effect.interruptible
312
364
  )
313
365
 
314
366
  // refresh the shard locks every `shardLockRefreshInterval`
@@ -347,37 +399,16 @@ const make = Effect.gen(function*() {
347
399
  ),
348
400
  Effect.repeat(Schedule.fixed(config.shardLockRefreshInterval)),
349
401
  Effect.forever,
350
- Effect.forkIn(shardingScope)
351
- )
352
-
353
- const releaseShardsLock = Effect.unsafeMakeSemaphore(1).withPermits(1)
354
- const releaseShards = releaseShardsLock(
355
- Effect.suspend(() =>
356
- Effect.forEach(
357
- releasingShards,
358
- (shardId) =>
359
- Effect.forEach(
360
- entityManagers.values(),
361
- (state) => state.manager.interruptShard(shardId),
362
- { concurrency: "unbounded", discard: true }
363
- ).pipe(
364
- Effect.andThen(runnerStorage.release(selfAddress, shardId)),
365
- Effect.annotateLogs({ runner: selfAddress }),
366
- Effect.flatMap(() => {
367
- MutableHashSet.remove(releasingShards, shardId)
368
- return storage.unregisterShardReplyHandlers(shardId)
369
- })
370
- ),
371
- { concurrency: "unbounded", discard: true }
372
- )
373
- )
402
+ Effect.forkIn(shardingScope),
403
+ Effect.interruptible
374
404
  )
375
405
 
376
406
  // open the shard latch every poll interval
377
407
  yield* activeShardsLatch.open.pipe(
378
408
  Effect.delay(config.entityMessagePollInterval),
379
409
  Effect.forever,
380
- Effect.forkIn(shardingScope)
410
+ Effect.forkIn(shardingScope),
411
+ Effect.interruptible
381
412
  )
382
413
  }
383
414
 
@@ -542,14 +573,16 @@ const make = Effect.gen(function*() {
542
573
  runner: selfAddress
543
574
  }),
544
575
  Effect.withUnhandledErrorLogLevel(Option.none()),
545
- Effect.forkIn(shardingScope)
576
+ Effect.forkIn(shardingScope),
577
+ Effect.interruptible
546
578
  )
547
579
 
548
580
  // open the storage latch every poll interval
549
581
  yield* storageReadLatch.open.pipe(
550
582
  Effect.delay(config.entityMessagePollInterval),
551
583
  Effect.forever,
552
- Effect.forkIn(shardingScope)
584
+ Effect.forkIn(shardingScope),
585
+ Effect.interruptible
553
586
  )
554
587
 
555
588
  // Resume unprocessed messages for entities that reached a full mailbox.
@@ -669,7 +702,8 @@ const make = Effect.gen(function*() {
669
702
  Effect.sync(() => MutableHashMap.remove(entityResumptionState, address))
670
703
  ),
671
704
  Effect.withUnhandledErrorLogLevel(Option.none()),
672
- Effect.forkIn(shardingScope)
705
+ Effect.forkIn(shardingScope),
706
+ Effect.interruptible
673
707
  )
674
708
  }
675
709
 
@@ -945,7 +979,8 @@ const make = Effect.gen(function*() {
945
979
  fiber: "RunnerStorage sync",
946
980
  runner: config.runnerAddress
947
981
  }),
948
- Effect.forkIn(shardingScope)
982
+ Effect.forkIn(shardingScope),
983
+ Effect.interruptible
949
984
  )
950
985
 
951
986
  // --- Clients ---
@@ -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 results = (yield* conn.executeUnprepared(`SELECT ${pgLocks(toAcquire)}`, [], undefined))[0] as Record<
246
- string,
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)
@@ -127,7 +127,7 @@ export const make = Effect.fnUntraced(function*<
127
127
  () => {
128
128
  serverCloseLatches.get(address)?.unsafeOpen()
129
129
  serverCloseLatches.delete(address)
130
- return Effect.ignore(options.storage.resetAddress(address))
130
+ return Effect.void
131
131
  }
132
132
  )
133
133