effect 3.19.3 → 3.19.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.
@@ -0,0 +1,200 @@
1
+ /**
2
+ * @since 3.19.4
3
+ * @experimental
4
+ */
5
+ import * as Effect from "./Effect.js"
6
+ import * as Iterable from "./Iterable.js"
7
+ import * as MutableHashMap from "./MutableHashMap.js"
8
+ import * as Option from "./Option.js"
9
+
10
+ /**
11
+ * @since 3.19.4
12
+ * @category Models
13
+ * @experimental
14
+ */
15
+ export const TypeId: TypeId = "~effect/PartitionedSemaphore"
16
+
17
+ /**
18
+ * @since 3.19.4
19
+ * @category Models
20
+ * @experimental
21
+ */
22
+ export type TypeId = "~effect/PartitionedSemaphore"
23
+
24
+ /**
25
+ * A `PartitionedSemaphore` is a concurrency primitive that can be used to
26
+ * control concurrent access to a resource across multiple partitions identified
27
+ * by keys.
28
+ *
29
+ * The total number of permits is shared across all partitions, with waiting
30
+ * permits equally distributed among partitions using a round-robin strategy.
31
+ *
32
+ * This is useful when you want to limit the total number of concurrent accesses
33
+ * to a resource, while still allowing for fair distribution of access across
34
+ * different partitions.
35
+ *
36
+ * @since 3.19.4
37
+ * @category Models
38
+ * @experimental
39
+ */
40
+ export interface PartitionedSemaphore<in K> {
41
+ readonly [TypeId]: TypeId
42
+
43
+ readonly withPermits: (
44
+ key: K,
45
+ permits: number
46
+ ) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
47
+ }
48
+
49
+ /**
50
+ * A `PartitionedSemaphore` is a concurrency primitive that can be used to
51
+ * control concurrent access to a resource across multiple partitions identified
52
+ * by keys.
53
+ *
54
+ * The total number of permits is shared across all partitions, with waiting
55
+ * permits equally distributed among partitions using a round-robin strategy.
56
+ *
57
+ * This is useful when you want to limit the total number of concurrent accesses
58
+ * to a resource, while still allowing for fair distribution of access across
59
+ * different partitions.
60
+ *
61
+ * @since 3.19.4
62
+ * @category Constructors
63
+ * @experimental
64
+ */
65
+ export const makeUnsafe = <K = unknown>(options: {
66
+ readonly permits: number
67
+ }): PartitionedSemaphore<K> => {
68
+ const maxPermits = Math.max(0, options.permits)
69
+
70
+ if (!Number.isFinite(maxPermits)) {
71
+ return {
72
+ [TypeId]: TypeId,
73
+ withPermits: () => (effect) => effect
74
+ }
75
+ }
76
+
77
+ let totalPermits = maxPermits
78
+ let waitingPermits = 0
79
+
80
+ type Waiter = {
81
+ permits: number
82
+ readonly resume: () => void
83
+ }
84
+ const partitions = MutableHashMap.empty<K, Set<Waiter>>()
85
+
86
+ const take = (key: K, permits: number) =>
87
+ Effect.async<void>((resume) => {
88
+ if (maxPermits < permits) {
89
+ return resume(Effect.never)
90
+ } else if (totalPermits >= permits) {
91
+ totalPermits -= permits
92
+ return resume(Effect.void)
93
+ }
94
+
95
+ const needed = permits - totalPermits
96
+ const taken = permits - needed
97
+ if (totalPermits > 0) {
98
+ totalPermits = 0
99
+ }
100
+ waitingPermits += needed
101
+
102
+ const waiters = Option.getOrElse(
103
+ MutableHashMap.get(partitions, key),
104
+ () => {
105
+ const set = new Set<Waiter>()
106
+ MutableHashMap.set(partitions, key, set)
107
+ return set
108
+ }
109
+ )
110
+
111
+ const entry: Waiter = {
112
+ permits: needed,
113
+ resume() {
114
+ cleanup()
115
+ resume(Effect.void)
116
+ }
117
+ }
118
+ function cleanup() {
119
+ waiters.delete(entry)
120
+ if (waiters.size === 0) {
121
+ MutableHashMap.remove(partitions, key)
122
+ }
123
+ }
124
+ waiters.add(entry)
125
+ return Effect.sync(() => {
126
+ cleanup()
127
+ waitingPermits -= entry.permits
128
+ if (taken > 0) {
129
+ releaseUnsafe(taken)
130
+ }
131
+ })
132
+ })
133
+
134
+ let iterator = partitions[Symbol.iterator]()
135
+ const releaseUnsafe = (permits: number) => {
136
+ while (permits > 0) {
137
+ if (waitingPermits === 0) {
138
+ totalPermits += permits
139
+ return
140
+ }
141
+
142
+ let state = iterator.next()
143
+ if (state.done) {
144
+ iterator = partitions[Symbol.iterator]()
145
+ state = iterator.next()
146
+ if (state.done) return
147
+ }
148
+
149
+ const entry = Iterable.unsafeHead(state.value[1])
150
+ entry.permits--
151
+ waitingPermits--
152
+ if (entry.permits === 0) entry.resume()
153
+ permits--
154
+ }
155
+ }
156
+
157
+ return {
158
+ [TypeId]: TypeId,
159
+ withPermits: (key, permits) => {
160
+ const takePermits = take(key, permits)
161
+ const release: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R> = Effect.matchCauseEffect({
162
+ onFailure(cause) {
163
+ releaseUnsafe(permits)
164
+ return Effect.failCause(cause)
165
+ },
166
+ onSuccess(value) {
167
+ releaseUnsafe(permits)
168
+ return Effect.succeed(value)
169
+ }
170
+ })
171
+ return (effect) =>
172
+ Effect.uninterruptibleMask((restore) =>
173
+ Effect.flatMap(
174
+ restore(takePermits),
175
+ () => release(restore(effect))
176
+ )
177
+ )
178
+ }
179
+ }
180
+ }
181
+
182
+ /**
183
+ * A `PartitionedSemaphore` is a concurrency primitive that can be used to
184
+ * control concurrent access to a resource across multiple partitions identified
185
+ * by keys.
186
+ *
187
+ * The total number of permits is shared across all partitions, with waiting
188
+ * permits equally distributed among partitions using a round-robin strategy.
189
+ *
190
+ * This is useful when you want to limit the total number of concurrent accesses
191
+ * to a resource, while still allowing for fair distribution of access across
192
+ * different partitions.
193
+ *
194
+ * @since 3.19.4
195
+ * @category Constructors
196
+ * @experimental
197
+ */
198
+ export const make = <K = unknown>(options: {
199
+ readonly permits: number
200
+ }): Effect.Effect<PartitionedSemaphore<K>> => Effect.sync(() => makeUnsafe<K>(options))
package/src/Types.ts CHANGED
@@ -261,7 +261,7 @@ export type Mutable<T> = {
261
261
  */
262
262
  export type DeepMutable<T> = T extends ReadonlyMap<infer K, infer V> ? Map<DeepMutable<K>, DeepMutable<V>>
263
263
  : T extends ReadonlySet<infer V> ? Set<DeepMutable<V>>
264
- : T extends string | number | boolean | bigint | symbol ? T
264
+ : T extends string | number | boolean | bigint | symbol | Function ? T
265
265
  : { -readonly [K in keyof T]: DeepMutable<T[K]> }
266
266
 
267
267
  /**
package/src/index.ts CHANGED
@@ -1090,6 +1090,12 @@ export * as Ordering from "./Ordering.js"
1090
1090
  */
1091
1091
  export * as ParseResult from "./ParseResult.js"
1092
1092
 
1093
+ /**
1094
+ * @since 3.19.4
1095
+ * @experimental
1096
+ */
1097
+ export * as PartitionedSemaphore from "./PartitionedSemaphore.js"
1098
+
1093
1099
  /**
1094
1100
  * @since 2.0.0
1095
1101
  */
@@ -194,10 +194,20 @@ const contOpSuccess = {
194
194
  cont: core.FromIterator,
195
195
  value: unknown
196
196
  ) => {
197
- const state = internalCall(() => cont.effect_instruction_i0.next(value))
198
- if (state.done) return core.exitSucceed(state.value)
199
- self.pushStack(cont)
200
- return yieldWrapGet(state.value)
197
+ while (true) {
198
+ const state = internalCall(() => cont.effect_instruction_i0.next(value))
199
+ if (state.done) {
200
+ return core.exitSucceed(state.value)
201
+ }
202
+ const primitive = yieldWrapGet(state.value)
203
+ if (!core.exitIsExit(primitive)) {
204
+ self.pushStack(cont)
205
+ return primitive
206
+ } else if (primitive._tag === "Failure") {
207
+ return primitive
208
+ }
209
+ value = primitive.value
210
+ }
201
211
  }
202
212
  }
203
213
 
@@ -1,4 +1,4 @@
1
- let moduleVersion = "3.19.3"
1
+ let moduleVersion = "3.19.5"
2
2
 
3
3
  export const getCurrentVersion = () => moduleVersion
4
4