effect 3.2.9 → 3.3.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.
Files changed (142) hide show
  1. package/Redacted/package.json +6 -0
  2. package/dist/cjs/Config.js +9 -1
  3. package/dist/cjs/Config.js.map +1 -1
  4. package/dist/cjs/Either.js +2 -1
  5. package/dist/cjs/Either.js.map +1 -1
  6. package/dist/cjs/Iterable.js +15 -2
  7. package/dist/cjs/Iterable.js.map +1 -1
  8. package/dist/cjs/Layer.js +11 -1
  9. package/dist/cjs/Layer.js.map +1 -1
  10. package/dist/cjs/Option.js +7 -1
  11. package/dist/cjs/Option.js.map +1 -1
  12. package/dist/cjs/Pool.js +26 -0
  13. package/dist/cjs/Pool.js.map +1 -1
  14. package/dist/cjs/Predicate.js +51 -1
  15. package/dist/cjs/Predicate.js.map +1 -1
  16. package/dist/cjs/Redacted.js +65 -0
  17. package/dist/cjs/Redacted.js.map +1 -0
  18. package/dist/cjs/STM.js.map +1 -1
  19. package/dist/cjs/Secret.js +7 -0
  20. package/dist/cjs/Secret.js.map +1 -1
  21. package/dist/cjs/Stream.js +32 -1
  22. package/dist/cjs/Stream.js.map +1 -1
  23. package/dist/cjs/Tuple.js +15 -1
  24. package/dist/cjs/Tuple.js.map +1 -1
  25. package/dist/cjs/Utils.js.map +1 -1
  26. package/dist/cjs/index.js +4 -2
  27. package/dist/cjs/index.js.map +1 -1
  28. package/dist/cjs/internal/config.js +8 -1
  29. package/dist/cjs/internal/config.js.map +1 -1
  30. package/dist/cjs/internal/layer.js +14 -2
  31. package/dist/cjs/internal/layer.js.map +1 -1
  32. package/dist/cjs/internal/pool.js +206 -235
  33. package/dist/cjs/internal/pool.js.map +1 -1
  34. package/dist/cjs/internal/redacted.js +87 -0
  35. package/dist/cjs/internal/redacted.js.map +1 -0
  36. package/dist/cjs/internal/secret.js +40 -23
  37. package/dist/cjs/internal/secret.js.map +1 -1
  38. package/dist/cjs/internal/stm/stm.js +2 -1
  39. package/dist/cjs/internal/stm/stm.js.map +1 -1
  40. package/dist/cjs/internal/stream.js +15 -7
  41. package/dist/cjs/internal/stream.js.map +1 -1
  42. package/dist/cjs/internal/version.js +1 -1
  43. package/dist/dts/Config.d.ts +9 -0
  44. package/dist/dts/Config.d.ts.map +1 -1
  45. package/dist/dts/Either.d.ts.map +1 -1
  46. package/dist/dts/Iterable.d.ts +7 -0
  47. package/dist/dts/Iterable.d.ts.map +1 -1
  48. package/dist/dts/Layer.d.ts +20 -0
  49. package/dist/dts/Layer.d.ts.map +1 -1
  50. package/dist/dts/Option.d.ts.map +1 -1
  51. package/dist/dts/Pool.d.ts +31 -0
  52. package/dist/dts/Pool.d.ts.map +1 -1
  53. package/dist/dts/Predicate.d.ts +57 -0
  54. package/dist/dts/Predicate.d.ts.map +1 -1
  55. package/dist/dts/Redacted.d.ts +68 -0
  56. package/dist/dts/Redacted.d.ts.map +1 -0
  57. package/dist/dts/STM.d.ts +4 -1
  58. package/dist/dts/STM.d.ts.map +1 -1
  59. package/dist/dts/Secret.d.ts +14 -1
  60. package/dist/dts/Secret.d.ts.map +1 -1
  61. package/dist/dts/Stream.d.ts +61 -4
  62. package/dist/dts/Stream.d.ts.map +1 -1
  63. package/dist/dts/Tuple.d.ts +51 -0
  64. package/dist/dts/Tuple.d.ts.map +1 -1
  65. package/dist/dts/Types.d.ts +46 -0
  66. package/dist/dts/Types.d.ts.map +1 -1
  67. package/dist/dts/Utils.d.ts +6 -5
  68. package/dist/dts/Utils.d.ts.map +1 -1
  69. package/dist/dts/index.d.ts +5 -0
  70. package/dist/dts/index.d.ts.map +1 -1
  71. package/dist/dts/internal/layer.d.ts +15 -1
  72. package/dist/dts/internal/layer.d.ts.map +1 -1
  73. package/dist/dts/internal/redacted.d.ts +2 -0
  74. package/dist/dts/internal/redacted.d.ts.map +1 -0
  75. package/dist/dts/internal/stm/stm.d.ts.map +1 -1
  76. package/dist/dts/internal/stream.d.ts +1 -0
  77. package/dist/dts/internal/stream.d.ts.map +1 -1
  78. package/dist/esm/Config.js +8 -0
  79. package/dist/esm/Config.js.map +1 -1
  80. package/dist/esm/Either.js +2 -1
  81. package/dist/esm/Either.js.map +1 -1
  82. package/dist/esm/Iterable.js +12 -0
  83. package/dist/esm/Iterable.js.map +1 -1
  84. package/dist/esm/Layer.js +10 -0
  85. package/dist/esm/Layer.js.map +1 -1
  86. package/dist/esm/Option.js +7 -1
  87. package/dist/esm/Option.js.map +1 -1
  88. package/dist/esm/Pool.js +26 -0
  89. package/dist/esm/Pool.js.map +1 -1
  90. package/dist/esm/Predicate.js +50 -0
  91. package/dist/esm/Predicate.js.map +1 -1
  92. package/dist/esm/Redacted.js +33 -0
  93. package/dist/esm/Redacted.js.map +1 -0
  94. package/dist/esm/STM.js.map +1 -1
  95. package/dist/esm/Secret.js +7 -0
  96. package/dist/esm/Secret.js.map +1 -1
  97. package/dist/esm/Stream.js +31 -0
  98. package/dist/esm/Stream.js.map +1 -1
  99. package/dist/esm/Tuple.js +51 -0
  100. package/dist/esm/Tuple.js.map +1 -1
  101. package/dist/esm/Utils.js.map +1 -1
  102. package/dist/esm/index.js +5 -0
  103. package/dist/esm/index.js.map +1 -1
  104. package/dist/esm/internal/config.js +6 -0
  105. package/dist/esm/internal/config.js.map +1 -1
  106. package/dist/esm/internal/layer.js +12 -0
  107. package/dist/esm/internal/layer.js.map +1 -1
  108. package/dist/esm/internal/pool.js +205 -235
  109. package/dist/esm/internal/pool.js.map +1 -1
  110. package/dist/esm/internal/redacted.js +52 -0
  111. package/dist/esm/internal/redacted.js.map +1 -0
  112. package/dist/esm/internal/secret.js +39 -22
  113. package/dist/esm/internal/secret.js.map +1 -1
  114. package/dist/esm/internal/stm/stm.js +2 -1
  115. package/dist/esm/internal/stm/stm.js.map +1 -1
  116. package/dist/esm/internal/stream.js +13 -4
  117. package/dist/esm/internal/stream.js.map +1 -1
  118. package/dist/esm/internal/version.js +1 -1
  119. package/package.json +9 -1
  120. package/src/Config.ts +10 -0
  121. package/src/Either.ts +4 -1
  122. package/src/Iterable.ts +13 -0
  123. package/src/Layer.ts +22 -0
  124. package/src/Option.ts +7 -1
  125. package/src/Pool.ts +39 -6
  126. package/src/Predicate.ts +59 -0
  127. package/src/Redacted.ts +79 -0
  128. package/src/STM.ts +7 -2
  129. package/src/Secret.ts +14 -1
  130. package/src/Stream.ts +67 -7
  131. package/src/Tuple.ts +53 -0
  132. package/src/Types.ts +48 -0
  133. package/src/Utils.ts +9 -6
  134. package/src/index.ts +6 -0
  135. package/src/internal/config.ts +11 -0
  136. package/src/internal/layer.ts +63 -0
  137. package/src/internal/pool.ts +320 -447
  138. package/src/internal/redacted.ts +69 -0
  139. package/src/internal/secret.ts +39 -28
  140. package/src/internal/stm/stm.ts +4 -1
  141. package/src/internal/stream.ts +111 -50
  142. package/src/internal/version.ts +1 -1
@@ -1,31 +1,23 @@
1
- import type * as Clock from "../Clock.js"
1
+ import type { Cause } from "effect/Cause"
2
2
  import * as Context from "../Context.js"
3
3
  import * as Duration from "../Duration.js"
4
- import type * as Effect from "../Effect.js"
5
- import * as Equal from "../Equal.js"
6
- import type * as Exit from "../Exit.js"
7
- import { dual, pipe } from "../Function.js"
8
- import * as Hash from "../Hash.js"
9
- import * as HashSet from "../HashSet.js"
4
+ import type { Effect, Semaphore } from "../Effect.js"
5
+ import type { Exit } from "../Exit.js"
6
+ import { dual, identity } from "../Function.js"
7
+ import * as Iterable from "../Iterable.js"
10
8
  import { pipeArguments } from "../Pipeable.js"
11
- import type * as Pool from "../Pool.js"
9
+ import type { Pool, PoolTypeId as PoolTypeId_ } from "../Pool.js"
12
10
  import { hasProperty } from "../Predicate.js"
13
- import type * as Queue from "../Queue.js"
14
- import type * as Ref from "../Ref.js"
15
- import type * as Scope from "../Scope.js"
16
- import * as effect from "./core-effect.js"
11
+ import type { Scope } from "../Scope.js"
12
+ import * as coreEffect from "./core-effect.js"
17
13
  import * as core from "./core.js"
14
+ import * as defaultServices from "./defaultServices.js"
15
+ import * as circular from "./effect/circular.js"
18
16
  import * as fiberRuntime from "./fiberRuntime.js"
19
- import * as queue from "./queue.js"
20
- import * as ref from "./ref.js"
17
+ import * as internalQueue from "./queue.js"
21
18
 
22
19
  /** @internal */
23
- const PoolSymbolKey = "effect/Pool"
24
-
25
- /** @internal */
26
- export const PoolTypeId: Pool.PoolTypeId = Symbol.for(
27
- PoolSymbolKey
28
- ) as Pool.PoolTypeId
20
+ export const PoolTypeId: PoolTypeId_ = Symbol.for("effect/Pool") as PoolTypeId_
29
21
 
30
22
  const poolVariance = {
31
23
  /* c8 ignore next */
@@ -34,467 +26,348 @@ const poolVariance = {
34
26
  _A: (_: any) => _
35
27
  }
36
28
 
37
- interface PoolState {
29
+ /** @internal */
30
+ export const isPool = (u: unknown): u is Pool<unknown, unknown> => hasProperty(u, PoolTypeId)
31
+
32
+ /** @internal */
33
+ export const makeWith = <A, E, R>(options: {
34
+ readonly acquire: Effect<A, E, R>
35
+ readonly min: number
36
+ readonly max: number
37
+ readonly concurrency?: number | undefined
38
+ readonly targetUtilization?: number | undefined
39
+ readonly strategy: Strategy<A, E>
40
+ }): Effect<Pool<A, E>, never, Scope | R> =>
41
+ core.uninterruptibleMask((restore) =>
42
+ core.flatMap(core.context<R | Scope>(), (context) => {
43
+ const scope = Context.get(context, fiberRuntime.scopeTag)
44
+ const acquire = core.mapInputContext(
45
+ options.acquire,
46
+ (input) => Context.merge(context, input)
47
+ ) as Effect<
48
+ A,
49
+ E,
50
+ Scope
51
+ >
52
+ const pool = new PoolImpl<A, E>(
53
+ acquire,
54
+ options.concurrency ?? 1,
55
+ options.min,
56
+ options.max,
57
+ options.strategy,
58
+ Math.min(Math.max(options.targetUtilization ?? 1, 0.1), 1)
59
+ )
60
+ const initialize = core.tap(fiberRuntime.forkDaemon(restore(pool.resize)), (fiber) =>
61
+ scope.addFinalizer(() => core.interruptFiber(fiber)))
62
+ const runStrategy = core.tap(fiberRuntime.forkDaemon(restore(options.strategy.run(pool))), (fiber) =>
63
+ scope.addFinalizer(() =>
64
+ core.interruptFiber(fiber)
65
+ ))
66
+ return core.succeed(pool).pipe(
67
+ core.zipLeft(scope.addFinalizer(() =>
68
+ pool.shutdown
69
+ )),
70
+ core.zipLeft(initialize),
71
+ core.zipLeft(runStrategy)
72
+ )
73
+ })
74
+ )
75
+
76
+ /** @internal */
77
+ export const make = <A, E, R>(options: {
78
+ readonly acquire: Effect<A, E, R>
38
79
  readonly size: number
39
- readonly free: number
40
- }
80
+ readonly concurrency?: number | undefined
81
+ readonly targetUtilization?: number | undefined
82
+ }): Effect<Pool<A, E>, never, R | Scope> =>
83
+ makeWith({ ...options, min: options.size, max: options.size, strategy: strategyNoop() })
41
84
 
42
- interface Attempted<A, E> {
43
- readonly result: Exit.Exit<A, E>
44
- readonly finalizer: Effect.Effect<unknown>
45
- }
85
+ /** @internal */
86
+ export const makeWithTTL = <A, E, R>(options: {
87
+ readonly acquire: Effect<A, E, R>
88
+ readonly min: number
89
+ readonly max: number
90
+ readonly concurrency?: number | undefined
91
+ readonly targetUtilization?: number | undefined
92
+ readonly timeToLive: Duration.DurationInput
93
+ readonly timeToLiveStrategy?: "creation" | "usage" | undefined
94
+ }): Effect<Pool<A, E>, never, R | Scope> =>
95
+ core.flatMap(
96
+ options.timeToLiveStrategy === "creation" ?
97
+ strategyCreationTTL<A, E>(options.timeToLive) :
98
+ strategyUsageTTL<A, E>(options.timeToLive),
99
+ (strategy) => makeWith({ ...options, strategy })
100
+ )
46
101
 
47
- /**
48
- * A `Strategy` describes the protocol for how a pool whose excess items are
49
- * not being used should shrink down to the minimum pool size.
50
- */
51
- interface Strategy<S, R, E, A> {
52
- /**
53
- * Describes how the initial state of the strategy should be allocated.
54
- */
55
- initial(): Effect.Effect<S, never, R>
56
- /**
57
- * Describes how the state of the strategy should be updated when an item is
58
- * added to the pool or returned to the pool.
59
- */
60
- track(state: S, attempted: Exit.Exit<A, E>): Effect.Effect<void>
61
- /**
62
- * Describes how excess items that are not being used should shrink down.
63
- */
64
- run(
65
- state: S,
66
- getExcess: Effect.Effect<number>,
67
- shrink: Effect.Effect<void>
68
- ): Effect.Effect<void>
69
- }
102
+ /** @internal */
103
+ export const get = <A, E>(self: Pool<A, E>): Effect<A, E, Scope> => self.get
70
104
 
71
- /**
72
- * A strategy that does nothing to shrink excess items. This is useful when
73
- * the minimum size of the pool is equal to its maximum size and so there is
74
- * nothing to do.
75
- */
76
- class NoneStrategy implements Strategy<unknown, never, never, never> {
77
- initial(): Effect.Effect<void> {
78
- return core.void
79
- }
80
- track(): Effect.Effect<void> {
81
- return core.void
82
- }
83
- run(): Effect.Effect<void> {
84
- return core.void
85
- }
105
+ /** @internal */
106
+ export const invalidate: {
107
+ <A>(item: A): <E>(self: Pool<A, E>) => Effect<void>
108
+ <A, E>(self: Pool<A, E>, item: A): Effect<void>
109
+ } = dual(2, <A, E>(self: Pool<A, E>, item: A): Effect<void> => self.invalidate(item))
110
+
111
+ interface PoolItem<A, E> {
112
+ readonly exit: Exit<A, E>
113
+ finalizer: Effect<void>
114
+ refCount: number
86
115
  }
87
116
 
88
- /**
89
- * A strategy that shrinks the pool down to its minimum size if items in the
90
- * pool have not been used for the specified duration.
91
- */
92
- class TimeToLiveStrategy implements Strategy<readonly [Clock.Clock, Ref.Ref<number>], never, never, never> {
93
- constructor(readonly timeToLive: Duration.Duration) {}
94
- initial(): Effect.Effect<readonly [Clock.Clock, Ref.Ref<number>]> {
95
- return core.flatMap(effect.clock, (clock) =>
96
- core.flatMap(clock.currentTimeMillis, (now) =>
97
- core.map(
98
- ref.make(now),
99
- (ref) => [clock, ref] as const
100
- )))
101
- }
102
- track(state: readonly [Clock.Clock, Ref.Ref<number>]): Effect.Effect<void> {
103
- return core.asVoid(core.flatMap(
104
- state[0].currentTimeMillis,
105
- (now) => ref.set(state[1], now)
106
- ))
107
- }
108
- run(
109
- state: readonly [Clock.Clock, Ref.Ref<number>],
110
- getExcess: Effect.Effect<number>,
111
- shrink: Effect.Effect<void>
112
- ): Effect.Effect<void> {
113
- return core.flatMap(getExcess, (excess) =>
114
- excess <= 0
115
- ? core.zipRight(
116
- state[0].sleep(this.timeToLive),
117
- this.run(state, getExcess, shrink)
118
- )
119
- : pipe(
120
- core.zipWith(
121
- ref.get(state[1]),
122
- state[0].currentTimeMillis,
123
- (start, end) => end - start
124
- ),
125
- core.flatMap((duration) => {
126
- if (duration >= Duration.toMillis(this.timeToLive)) {
127
- return core.zipRight(shrink, this.run(state, getExcess, shrink))
128
- } else {
129
- return core.zipRight(state[0].sleep(this.timeToLive), this.run(state, getExcess, shrink))
130
- }
131
- })
132
- ))
133
- }
117
+ interface Strategy<A, E> {
118
+ readonly run: (pool: PoolImpl<A, E>) => Effect<void>
119
+ readonly onAcquire: (item: PoolItem<A, E>) => Effect<void>
134
120
  }
135
121
 
136
- class PoolImpl<in out A, in out E> implements Pool.Pool<A, E> {
137
- readonly [PoolTypeId] = poolVariance
122
+ class PoolImpl<A, E> implements Pool<A, E> {
123
+ readonly [PoolTypeId]: Pool.Variance<A, E>[PoolTypeId_]
124
+
125
+ isShuttingDown = false
126
+ readonly semaphore: Semaphore
127
+ readonly items = new Set<PoolItem<A, E>>()
128
+ readonly available = new Set<PoolItem<A, E>>()
129
+ readonly invalidated = new Set<PoolItem<A, E>>()
130
+ waiters = 0
131
+
138
132
  constructor(
139
- readonly creator: Effect.Effect<A, E, Scope.Scope>,
140
- readonly min: number,
141
- readonly max: number,
142
- readonly isShuttingDown: Ref.Ref<boolean>,
143
- readonly state: Ref.Ref<PoolState>,
144
- readonly items: Queue.Queue<Attempted<A, E>>,
145
- readonly invalidated: Ref.Ref<HashSet.HashSet<A>>,
146
- readonly track: (exit: Exit.Exit<A, E>) => Effect.Effect<unknown>
147
- ) {}
133
+ readonly acquire: Effect<A, E, Scope>,
134
+ readonly concurrency: number,
135
+ readonly minSize: number,
136
+ readonly maxSize: number,
137
+ readonly strategy: Strategy<A, E>,
138
+ readonly targetUtilization: number
139
+ ) {
140
+ this[PoolTypeId] = poolVariance
141
+ this.semaphore = circular.unsafeMakeSemaphore(concurrency * maxSize)
142
+ }
148
143
 
149
- [Hash.symbol](): number {
150
- return pipe(
151
- Hash.hash(this.creator),
152
- Hash.combine(Hash.number(this.min)),
153
- Hash.combine(Hash.number(this.max)),
154
- Hash.combine(Hash.hash(this.isShuttingDown)),
155
- Hash.combine(Hash.hash(this.state)),
156
- Hash.combine(Hash.hash(this.items)),
157
- Hash.combine(Hash.hash(this.invalidated)),
158
- Hash.combine(Hash.hash(this.track)),
159
- Hash.cached(this)
160
- )
144
+ readonly allocate: Effect<PoolItem<A, E>> = core.acquireUseRelease(
145
+ fiberRuntime.scopeMake(),
146
+ (scope) =>
147
+ this.acquire.pipe(
148
+ fiberRuntime.scopeExtend(scope),
149
+ core.exit,
150
+ core.flatMap((exit) => {
151
+ const item: PoolItem<A, E> = {
152
+ exit,
153
+ finalizer: core.catchAllCause(scope.close(exit), reportUnhandledError),
154
+ refCount: 0
155
+ }
156
+ this.items.add(item)
157
+ this.available.add(item)
158
+ return core.as(
159
+ exit._tag === "Success"
160
+ ? this.strategy.onAcquire(item)
161
+ : core.zipRight(item.finalizer, this.strategy.onAcquire(item)),
162
+ item
163
+ )
164
+ })
165
+ ),
166
+ (scope, exit) => exit._tag === "Failure" ? scope.close(exit) : core.void
167
+ )
168
+
169
+ get currentUsage() {
170
+ let count = this.waiters
171
+ for (const item of this.items) {
172
+ count += item.refCount
173
+ }
174
+ return count
161
175
  }
162
176
 
163
- [Equal.symbol](that: unknown): boolean {
164
- return isPool(that) &&
165
- Equal.equals(this.creator, (that as PoolImpl<A, E>).creator) &&
166
- this.min === (that as PoolImpl<A, E>).min &&
167
- this.max === (that as PoolImpl<A, E>).max &&
168
- Equal.equals(this.isShuttingDown, (that as PoolImpl<A, E>).isShuttingDown) &&
169
- Equal.equals(this.state, (that as PoolImpl<A, E>).state) &&
170
- Equal.equals(this.items, (that as PoolImpl<A, E>).items) &&
171
- Equal.equals(this.invalidated, (that as PoolImpl<A, E>).invalidated) &&
172
- Equal.equals(this.track, (that as PoolImpl<A, E>).track)
177
+ get targetSize() {
178
+ if (this.isShuttingDown) return 0
179
+ const utilization = this.currentUsage / this.targetUtilization
180
+ const target = Math.ceil(utilization / this.concurrency)
181
+ return Math.min(Math.max(this.minSize, target), this.maxSize)
173
182
  }
174
183
 
175
- pipe() {
176
- return pipeArguments(this, arguments)
184
+ get activeSize() {
185
+ return this.items.size - this.invalidated.size
177
186
  }
178
187
 
179
- get get(): Effect.Effect<A, E, Scope.Scope> {
180
- const acquire = (
181
- restore: <AX, EX, RX>(effect: Effect.Effect<AX, EX, RX>) => Effect.Effect<AX, EX, RX>
182
- ): Effect.Effect<Attempted<A, E>> =>
183
- core.flatMap(ref.get(this.isShuttingDown), (down) =>
184
- down
185
- ? core.interrupt
186
- : core.flatten(ref.modify(this.state, (state) => {
187
- if (state.free > 0 || state.size >= this.max) {
188
- return [
189
- core.flatMap(
190
- queue.take(this.items),
191
- (attempted) =>
192
- core.exitMatch(attempted.result, {
193
- onFailure: () => core.succeed(attempted),
194
- onSuccess: (item) =>
195
- core.flatMap(
196
- ref.get(this.invalidated),
197
- (set) => {
198
- if (pipe(set, HashSet.has(item))) {
199
- return core.zipRight(finalizeInvalid(this, attempted), acquire(restore))
200
- }
201
- return core.succeed(attempted)
202
- }
203
- )
204
- })
205
- ),
206
- { ...state, free: state.free - 1 }
207
- ] as const
188
+ readonly resizeLoop: Effect<void> = core.suspend(() => {
189
+ if (this.activeSize >= this.targetSize) {
190
+ return core.void
191
+ }
192
+ return core.zipRight(this.allocate, this.resizeLoop)
193
+ })
194
+ readonly resizeSemaphore = circular.unsafeMakeSemaphore(1)
195
+ readonly resize = this.resizeSemaphore.withPermits(1)(this.resizeLoop)
196
+
197
+ readonly getPoolItem: Effect<PoolItem<A, E>, never, Scope> = core.uninterruptibleMask((restore) =>
198
+ restore(this.semaphore.take(1)).pipe(
199
+ core.zipRight(fiberRuntime.scopeTag),
200
+ core.flatMap((scope) =>
201
+ core.suspend(() => {
202
+ this.waiters++
203
+ if (this.isShuttingDown) {
204
+ return core.interrupt
205
+ } else if (this.targetSize > this.activeSize) {
206
+ return core.zipRight(
207
+ restore(this.resize),
208
+ core.sync(() => Iterable.unsafeHead(this.available))
209
+ )
210
+ }
211
+ return core.succeed(Iterable.unsafeHead(this.available))
212
+ }).pipe(
213
+ fiberRuntime.ensuring(core.sync(() => this.waiters--)),
214
+ core.tap((item) => {
215
+ if (item.exit._tag === "Failure") {
216
+ this.items.delete(item)
217
+ this.invalidated.delete(item)
218
+ this.available.delete(item)
219
+ return this.semaphore.release(1)
208
220
  }
209
- if (state.size >= 0) {
210
- return [
211
- core.zipRight(allocate(this, restore), acquire(restore)),
212
- { size: state.size + 1, free: state.free + 1 }
213
- ] as const
221
+ item.refCount++
222
+ this.available.delete(item)
223
+ if (item.refCount < this.concurrency) {
224
+ this.available.add(item)
214
225
  }
215
- return [core.interrupt, state] as const
216
- })))
217
-
218
- const release = (attempted: Attempted<A, E>): Effect.Effect<unknown> =>
219
- core.exitMatch(attempted.result, {
220
- onFailure: () =>
221
- core.zipRight(
222
- attempted.finalizer,
223
- core.flatten(ref.modify(this.state, (state) => {
224
- if (state.size <= this.min) {
225
- return [allocateUinterruptible(this), { ...state, free: state.free + 1 }] as const
226
- }
227
- return [core.void, { ...state, size: state.size - 1 }] as const
228
- }))
229
- ),
230
- onSuccess: (item) =>
231
- core.flatMap(ref.get(this.invalidated), (set) => {
232
- if (pipe(set, HashSet.has(item))) {
233
- return finalizeInvalid(this, attempted)
234
- }
235
- return pipe(
236
- ref.update(this.state, (state) => ({ ...state, free: state.free + 1 })),
237
- core.zipRight(queue.offer(this.items, attempted)),
238
- core.zipRight(this.track(attempted.result)),
239
- core.zipRight(core.whenEffect(getAndShutdown(this), ref.get(this.isShuttingDown)))
226
+ return scope.addFinalizer(() =>
227
+ core.zipRight(
228
+ core.suspend(() => {
229
+ item.refCount--
230
+ if (this.invalidated.has(item)) {
231
+ return this.invalidatePoolItem(item)
232
+ }
233
+ this.available.add(item)
234
+ return core.void
235
+ }),
236
+ this.semaphore.release(1)
237
+ )
240
238
  )
241
- })
242
- })
243
-
244
- return pipe(
245
- core.uninterruptibleMask((restore) =>
246
- core.tap(acquire(restore), (a) => fiberRuntime.addFinalizer((_exit) => release(a)))
247
- ),
248
- fiberRuntime.withEarlyRelease,
249
- fiberRuntime.disconnect,
250
- core.flatMap(([release, attempted]) =>
251
- pipe(
252
- effect.when(release, () => isFailure(attempted)),
253
- core.zipRight(toEffect(attempted))
254
- )
255
- )
256
- )
257
- }
258
-
259
- invalidate(item: A): Effect.Effect<void> {
260
- return ref.update(this.invalidated, HashSet.add(item))
261
- }
262
- }
263
-
264
- const allocate = <A, E>(
265
- self: PoolImpl<A, E>,
266
- restore: <AX, EX, RX>(effect: Effect.Effect<AX, EX, RX>) => Effect.Effect<AX, EX, RX>
267
- ): Effect.Effect<unknown> =>
268
- core.flatMap(fiberRuntime.scopeMake(), (scope) =>
269
- core.flatMap(
270
- core.exit(restore(fiberRuntime.scopeExtend(self.creator, scope))),
271
- (exit) =>
272
- core.flatMap(
273
- core.succeed<Attempted<A, E>>({
274
- result: exit as Exit.Exit<A, E>,
275
- finalizer: core.scopeClose(scope, core.exitSucceed(void 0))
276
239
  }),
277
- (attempted) =>
278
- pipe(
279
- queue.offer(self.items, attempted),
280
- core.zipRight(self.track(attempted.result)),
281
- core.zipRight(core.whenEffect(getAndShutdown(self), ref.get(self.isShuttingDown))),
282
- core.as(attempted)
283
- )
240
+ core.onInterrupt(() => this.semaphore.release(1))
284
241
  )
285
- ))
286
-
287
- const allocateUinterruptible = <A, E>(
288
- self: PoolImpl<A, E>
289
- ): Effect.Effect<unknown> => core.uninterruptibleMask((restore) => allocate(self, restore))
290
-
291
- /**
292
- * Returns the number of items in the pool in excess of the minimum size.
293
- */
294
- const excess = <A, E>(self: PoolImpl<A, E>): Effect.Effect<number> =>
295
- core.map(ref.get(self.state), (state) => state.size - Math.min(self.min, state.free))
296
-
297
- const finalizeInvalid = <A, E>(
298
- self: PoolImpl<A, E>,
299
- attempted: Attempted<A, E>
300
- ): Effect.Effect<unknown> =>
301
- pipe(
302
- forEach(attempted, (a) => ref.update(self.invalidated, HashSet.remove(a))),
303
- core.zipRight(attempted.finalizer),
304
- core.zipRight(
305
- core.flatten(ref.modify(self.state, (state) => {
306
- if (state.size <= self.min) {
307
- return [allocateUinterruptible(self), { ...state, free: state.free + 1 }] as const
308
- }
309
- return [core.void, { ...state, size: state.size - 1 }] as const
310
- }))
242
+ )
311
243
  )
312
244
  )
313
245
 
314
- /**
315
- * Gets items from the pool and shuts them down as long as there are items
316
- * free, signalling shutdown of the pool if the pool is empty.
317
- */
318
- const getAndShutdown = <A, E>(self: PoolImpl<A, E>): Effect.Effect<void> =>
319
- core.flatten(ref.modify(self.state, (state) => {
320
- if (state.free > 0) {
321
- return [
322
- core.matchCauseEffect(queue.take(self.items), {
323
- onFailure: () => core.void,
324
- onSuccess: (attempted) =>
325
- pipe(
326
- forEach(attempted, (a) => ref.update(self.invalidated, HashSet.remove(a))),
327
- core.zipRight(attempted.finalizer),
328
- core.zipRight(ref.update(self.state, (state) => ({ ...state, size: state.size - 1 }))),
329
- core.flatMap(() => getAndShutdown(self))
330
- )
331
- }),
332
- { ...state, free: state.free - 1 }
333
- ] as const
334
- }
335
- if (state.size > 0) {
336
- return [core.void, state] as const
337
- }
338
- return [queue.shutdown(self.items), { ...state, size: state.size - 1 }] as const
339
- }))
340
-
341
- /**
342
- * Begins pre-allocating pool entries based on minimum pool size.
343
- */
344
- const initialize = <A, E>(self: PoolImpl<A, E>): Effect.Effect<void> =>
345
- fiberRuntime.replicateEffect(
346
- core.uninterruptibleMask((restore) =>
347
- core.flatten(ref.modify(self.state, (state) => {
348
- if (state.size < self.min && state.size >= 0) {
349
- return [
350
- allocate(self, restore),
351
- { size: state.size + 1, free: state.free + 1 }
352
- ] as const
353
- }
354
- return [core.void, state] as const
355
- }))
356
- ),
357
- self.min,
358
- { discard: true }
246
+ readonly get: Effect<A, E, Scope> = core.flatMap(
247
+ core.suspend(() => this.isShuttingDown ? core.interrupt : this.getPoolItem),
248
+ (_) => _.exit
359
249
  )
360
250
 
361
- /**
362
- * Shrinks the pool down, but never to less than the minimum size.
363
- */
364
- const shrink = <A, E>(self: PoolImpl<A, E>): Effect.Effect<void> =>
365
- core.uninterruptible(
366
- core.flatten(ref.modify(self.state, (state) => {
367
- if (state.size > self.min && state.free > 0) {
368
- return [
369
- pipe(
370
- queue.take(self.items),
371
- core.flatMap((attempted) =>
372
- pipe(
373
- forEach(attempted, (a) => ref.update(self.invalidated, HashSet.remove(a))),
374
- core.zipRight(attempted.finalizer),
375
- core.zipRight(ref.update(self.state, (state) => ({ ...state, size: state.size - 1 })))
376
- )
377
- )
378
- ),
379
- { ...state, free: state.free - 1 }
380
- ] as const
251
+ invalidate(item: A): Effect<void> {
252
+ return core.suspend(() => {
253
+ if (this.isShuttingDown) return core.void
254
+ for (const poolItem of this.items) {
255
+ if (poolItem.exit._tag === "Success" && poolItem.exit.value === item) {
256
+ return core.uninterruptible(this.invalidatePoolItem(poolItem))
257
+ }
381
258
  }
382
- return [core.void, state] as const
383
- }))
384
- )
385
-
386
- const shutdown = <A, E>(self: PoolImpl<A, E>): Effect.Effect<void> =>
387
- core.flatten(ref.modify(self.isShuttingDown, (down) =>
388
- down
389
- ? [queue.awaitShutdown(self.items), true] as const
390
- : [core.zipRight(getAndShutdown(self), queue.awaitShutdown(self.items)), true]))
391
-
392
- const isFailure = <A, E>(self: Attempted<A, E>): boolean => core.exitIsFailure(self.result)
259
+ return core.void
260
+ })
261
+ }
393
262
 
394
- const forEach = <E, A, E2, R>(
395
- self: Attempted<A, E>,
396
- f: (a: A) => Effect.Effect<unknown, E2, R>
397
- ): Effect.Effect<unknown, E2, R> =>
398
- core.exitMatch(self.result, {
399
- onFailure: () => core.void,
400
- onSuccess: f
401
- })
263
+ invalidatePoolItem(poolItem: PoolItem<A, E>): Effect<void> {
264
+ return core.suspend(() => {
265
+ if (!this.items.has(poolItem)) {
266
+ return core.void
267
+ } else if (poolItem.refCount === 0) {
268
+ this.items.delete(poolItem)
269
+ this.available.delete(poolItem)
270
+ this.invalidated.delete(poolItem)
271
+ return core.zipRight(poolItem.finalizer, this.resize)
272
+ }
273
+ this.invalidated.add(poolItem)
274
+ this.available.delete(poolItem)
275
+ return core.void
276
+ })
277
+ }
402
278
 
403
- const toEffect = <A, E>(self: Attempted<A, E>): Effect.Effect<A, E> => self.result
279
+ get shutdown(): Effect<void> {
280
+ return core.suspend(() => {
281
+ if (this.isShuttingDown) return core.void
282
+ this.isShuttingDown = true
283
+ const size = this.items.size
284
+ const semaphore = circular.unsafeMakeSemaphore(size)
285
+ return core.forEachSequentialDiscard(this.items, (item) => {
286
+ if (item.refCount > 0) {
287
+ item.finalizer = core.zipLeft(item.finalizer, semaphore.release(1))
288
+ this.invalidated.add(item)
289
+ return semaphore.take(1)
290
+ }
291
+ this.items.delete(item)
292
+ this.available.delete(item)
293
+ this.invalidated.delete(item)
294
+ return item.finalizer
295
+ }).pipe(
296
+ core.zipRight(this.semaphore.releaseAll),
297
+ core.zipRight(semaphore.take(size))
298
+ )
299
+ })
300
+ }
404
301
 
405
- /**
406
- * A more powerful variant of `make` that allows specifying a `Strategy` that
407
- * describes how a pool whose excess items are not being used will be shrunk
408
- * down to the minimum size.
409
- */
410
- const makeWith = <A, E, R, S, R2>(
411
- options: {
412
- readonly acquire: Effect.Effect<A, E, R>
413
- readonly min: number
414
- readonly max: number
415
- readonly strategy: Strategy<S, R2, E, A>
302
+ pipe() {
303
+ return pipeArguments(this, arguments)
416
304
  }
417
- ): Effect.Effect<Pool.Pool<A, E>, never, R | R2 | Scope.Scope> =>
418
- core.uninterruptibleMask((restore) =>
419
- pipe(
420
- fiberRuntime.all([
421
- core.context<R>(),
422
- ref.make(false),
423
- ref.make<PoolState>({ size: 0, free: 0 }),
424
- queue.bounded<Attempted<A, E>>(options.max),
425
- ref.make(HashSet.empty<A>()),
426
- options.strategy.initial()
427
- ]),
428
- core.flatMap(([context, down, state, items, inv, initial]) => {
429
- const pool = new PoolImpl<A, E>(
430
- core.mapInputContext(options.acquire, (old) => Context.merge(old)(context)),
431
- options.min,
432
- options.max,
433
- down,
434
- state,
435
- items,
436
- inv,
437
- (exit) => options.strategy.track(initial, exit)
438
- )
439
- return pipe(
440
- fiberRuntime.forkDaemon(restore(initialize(pool))),
441
- core.flatMap((fiber) =>
442
- core.flatMap(
443
- fiberRuntime.forkDaemon(restore(options.strategy.run(initial, excess(pool), shrink(pool)))),
444
- (shrink) =>
445
- fiberRuntime.addFinalizer(() =>
446
- pipe(
447
- shutdown(pool),
448
- core.zipRight(core.interruptFiber(fiber)),
449
- core.zipRight(core.interruptFiber(shrink))
450
- )
451
- )
452
- )
453
- ),
454
- core.as<Pool.Pool<A, E>>(pool)
455
- )
305
+ }
306
+
307
+ const strategyNoop = <A, E>(): Strategy<A, E> => ({
308
+ run: (_) => core.void,
309
+ onAcquire: (_) => core.void
310
+ })
311
+
312
+ const strategyCreationTTL = <A, E>(ttl: Duration.DurationInput) =>
313
+ defaultServices.clockWith((clock) =>
314
+ core.map(internalQueue.unbounded<PoolItem<A, E>>(), (queue) => {
315
+ const ttlMillis = Duration.toMillis(ttl)
316
+ const creationTimes = new WeakMap<PoolItem<A, E>, number>()
317
+ return identity<Strategy<A, E>>({
318
+ run: (pool) => {
319
+ const process = (item: PoolItem<A, E>): Effect<void> =>
320
+ core.suspend(() => {
321
+ if (!pool.items.has(item) || pool.invalidated.has(item)) {
322
+ return core.void
323
+ }
324
+ const now = clock.unsafeCurrentTimeMillis()
325
+ const created = creationTimes.get(item)!
326
+ const remaining = ttlMillis - (now - created)
327
+ return remaining > 0
328
+ ? coreEffect.delay(process(item), remaining)
329
+ : pool.invalidatePoolItem(item)
330
+ })
331
+ return queue.take.pipe(
332
+ core.tap(process),
333
+ coreEffect.forever
334
+ )
335
+ },
336
+ onAcquire: (item) =>
337
+ core.suspend(() => {
338
+ creationTimes.set(item, clock.unsafeCurrentTimeMillis())
339
+ return queue.offer(item)
340
+ })
456
341
  })
457
- )
342
+ })
458
343
  )
459
344
 
460
- /** @internal */
461
- export const isPool = (u: unknown): u is Pool.Pool<unknown, unknown> => hasProperty(u, PoolTypeId)
462
-
463
- /** @internal */
464
- export const make = <A, E, R>(
465
- options: {
466
- readonly acquire: Effect.Effect<A, E, R>
467
- readonly size: number
468
- }
469
- ): Effect.Effect<Pool.Pool<A, E>, never, R | Scope.Scope> =>
470
- makeWith({
471
- acquire: options.acquire,
472
- min: options.size,
473
- max: options.size,
474
- strategy: new NoneStrategy()
345
+ const strategyUsageTTL = <A, E>(ttl: Duration.DurationInput) =>
346
+ core.map(internalQueue.unbounded<PoolItem<A, E>>(), (queue) => {
347
+ return identity<Strategy<A, E>>({
348
+ run: (pool) => {
349
+ const process: Effect<void> = core.suspend(() => {
350
+ const excess = pool.activeSize - pool.targetSize
351
+ if (excess <= 0) return core.void
352
+ return queue.take.pipe(
353
+ core.tap((item) => pool.invalidatePoolItem(item)),
354
+ core.zipRight(process)
355
+ )
356
+ })
357
+ return process.pipe(
358
+ coreEffect.delay(ttl),
359
+ coreEffect.forever
360
+ )
361
+ },
362
+ onAcquire: (item) => queue.offer(item)
363
+ })
475
364
  })
476
365
 
477
- /** @internal */
478
- export const makeWithTTL = <A, E, R>(
479
- options: {
480
- readonly acquire: Effect.Effect<A, E, R>
481
- readonly min: number
482
- readonly max: number
483
- readonly timeToLive: Duration.DurationInput
484
- }
485
- ): Effect.Effect<Pool.Pool<A, E>, never, R | Scope.Scope> =>
486
- makeWith({
487
- acquire: options.acquire,
488
- min: options.min,
489
- max: options.max,
490
- strategy: new TimeToLiveStrategy(Duration.decode(options.timeToLive))
366
+ const reportUnhandledError = <E>(cause: Cause<E>) =>
367
+ core.withFiberRuntime<void>((fiber) => {
368
+ const unhandledLogLevel = fiber.getFiberRef(core.currentUnhandledErrorLogLevel)
369
+ if (unhandledLogLevel._tag === "Some") {
370
+ fiber.log("Unhandled error in pool finalizer", cause, unhandledLogLevel)
371
+ }
372
+ return core.void
491
373
  })
492
-
493
- /** @internal */
494
- export const get = <A, E>(self: Pool.Pool<A, E>): Effect.Effect<A, E, Scope.Scope> => self.get
495
-
496
- /** @internal */
497
- export const invalidate = dual<
498
- <A>(value: A) => <E>(self: Pool.Pool<A, E>) => Effect.Effect<void, never, Scope.Scope>,
499
- <A, E>(self: Pool.Pool<A, E>, value: A) => Effect.Effect<void, never, Scope.Scope>
500
- >(2, (self, value) => self.invalidate(value))