1/**
2 * Reusable synchronization primitives for coordinating fibers. A `Latch` is
3 * either open or closed: when it is closed, `await` and `whenOpen` suspend
4 * until the latch opens or the current waiters are released. The module
5 * includes effectful and synchronous constructors plus helpers to open, release,
6 * close, wait, and gate effects behind the latch.
7 *
8 * @since 4.0.0
9 */
10import type * as Effect from "./Effect.ts"
11import * as internal from "./internal/effect.ts"
12
13/**
14 * A reusable coordination primitive that lets fibers wait until they are
15 * released by the latch.
16 *
17 * **When to use**
18 *
19 * Use to coordinate fibers that must wait for an explicit open or release
20 * signal before continuing.
21 *
22 * **Details**
23 *
24 * A closed latch causes `await` and `whenOpen` to suspend. `open` opens the
25 * latch and releases current and future waiters, `release` releases only
26 * current waiters without opening it, and `close` makes future waiters suspend
27 * again.
28 *
29 * **Example** (Coordinating fibers with a latch)
30 *
31 * ```ts import.meta.vitest
32 * import { Effect, Fiber, Latch } from "effect"
33 *
34 * // Create and use a latch for coordination between fibers
35 * const program = Effect.gen(function*() {
36 * const latch = yield* Latch.make()
37 * const waiter = yield* Effect.forkChild(latch.await.pipe(Effect.as("opened")))
38 * yield* latch.open
39 * return yield* Fiber.join(waiter)
40 * })
41 *
42 * await Effect.runPromise(program) // => "opened"
43 * ```
44 *
45 * @see {@link make} for creating a latch inside Effect code
46 * @see {@link open} for releasing current and future waiters
47 * @see {@link release} for releasing only the current waiters
48 *
49 * @category models
50 * @since 4.0.0
51 */
52export interface Latch {
53 /**
54 * Opens the latch, releasing all fibers waiting on it.
55 *
56 * **When to use**
57 *
58 * Use to let current and future waiters continue.
59 */
60 readonly open: Effect.Effect<boolean>
61
62 /**
63 * Opens the latch synchronously, releasing all fibers waiting on it.
64 *
65 * **When to use**
66 *
67 * Use when synchronous code must open the latch immediately.
68 */
69 openUnsafe(this: Latch): boolean
70
71 /**
72 * Releases all fibers currently waiting on the latch without opening it.
73 *
74 * **When to use**
75 *
76 * Use to let current waiters continue while future waiters still suspend.
77 */
78 readonly release: Effect.Effect<boolean>
79
80 /**
81 * Waits for the latch to be opened or released.
82 *
83 * **When to use**
84 *
85 * Use to suspend until the latch allows the current fiber to continue.
86 */
87 readonly await: Effect.Effect<void>
88
89 /**
90 * Closes the latch so future waiters suspend again.
91 *
92 * **When to use**
93 *
94 * Use to re-enable waiting after a latch has been opened.
95 */
96 readonly close: Effect.Effect<boolean>
97
98 /**
99 * Closes the latch synchronously so future waiters suspend again.
100 *
101 * **When to use**
102 *
103 * Use when synchronous code must close the latch immediately.
104 */
105 closeUnsafe(this: Latch): boolean
106
107 /**
108 * Runs the given effect only after the latch allows waiting fibers to
109 * continue.
110 *
111 * **When to use**
112 *
113 * Use to gate an effect behind the latch signal.
114 */
115 whenOpen<A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
116
117 /**
118 * Checks whether the latch is currently open or closed.
119 *
120 * **When to use**
121 *
122 * Use to check the state of the latch without suspending or changing its state.
123 */
124 isOpen(this: Latch): boolean
125}
126
127/**
128 * Creates a `Latch` synchronously, outside of `Effect`.
129 *
130 * **When to use**
131 *
132 * Use when you need to allocate a `Latch` synchronously outside an Effect
133 * workflow.
134 *
135 * **Details**
136 *
137 * The latch starts closed by default; pass `true` to create it open.
138 *
139 * **Example** (Creating a latch unsafely)
140 *
141 * ```ts import.meta.vitest
142 * import { Effect, Fiber, Latch } from "effect"
143 *
144 * const latch = Latch.makeUnsafe(false)
145 * const waiter = latch.await.pipe(Effect.as("opened"))
146 *
147 * const program = Effect.gen(function*() {
148 * const fiber = yield* Effect.forkChild(waiter)
149 * yield* latch.open
150 * return yield* Fiber.join(fiber)
151 * })
152 *
153 * await Effect.runPromise(program) // => "opened"
154 * ```
155 *
156 * @see {@link make} for creating a latch inside Effect code
157 *
158 * @category constructors
159 * @since 4.0.0
160 */
161export const makeUnsafe: (open?: boolean | undefined) => Latch = internal.makeLatchUnsafe
162
163/**
164 * Creates a `Latch` inside `Effect`.
165 *
166 * **When to use**
167 *
168 * Use to create a latch for coordinating fibers inside Effect code.
169 *
170 * **Details**
171 *
172 * The latch starts closed by default; pass `true` to create it open.
173 *
174 * **Example** (Creating a latch)
175 *
176 * ```ts import.meta.vitest
177 * import { Effect, Fiber, Latch } from "effect"
178 *
179 * const program = Effect.gen(function*() {
180 * const latch = yield* Latch.make(false)
181 * const waiter = latch.await.pipe(Effect.as("opened"))
182 *
183 * const fiber = yield* Effect.forkChild(waiter)
184 * yield* latch.open
185 * return yield* Fiber.join(fiber)
186 * })
187 *
188 * await Effect.runPromise(program) // => "opened"
189 * ```
190 *
191 * @see {@link makeUnsafe} for synchronous allocation outside Effect code
192 *
193 * @category constructors
194 * @since 4.0.0
195 */
196export const make: (open?: boolean | undefined) => Effect.Effect<Latch> = internal.makeLatch
197
198/**
199 * Opens the latch and releases fibers waiting on it.
200 *
201 * **When to use**
202 *
203 * Use to open a latch and release all fibers that are waiting on it.
204 *
205 * **Details**
206 *
207 * The returned effect succeeds with `true` when this call changed the latch
208 * from closed to open, or `false` if it was already open.
209 *
210 * @see {@link openUnsafe} for a synchronous variant
211 * @see {@link release} to release waiting fibers without opening the latch
212 *
213 * @category combinators
214 * @since 4.0.0
215 */
216export const open = (self: Latch): Effect.Effect<boolean> => self.open
217
218/**
219 * Opens the latch synchronously and releases fibers waiting on it.
220 *
221 * **When to use**
222 *
223 * Use when you need synchronous code to open a latch immediately and release
224 * the fibers waiting on it.
225 *
226 * **Details**
227 *
228 * Returns `true` when this call changed the latch from closed to open, or
229 * `false` if it was already open. This unsafe variant performs the state
230 * change immediately instead of returning an `Effect`.
231 *
232 * @see {@link open} for the effectful variant
233 * @see {@link release} to release waiting fibers without opening the latch
234 * @see {@link closeUnsafe} for the synchronous inverse operation
235 *
236 * @category unsafe
237 * @since 4.0.0
238 */
239export const openUnsafe = (self: Latch): boolean => self.openUnsafe()
240
241/**
242 * Releases the fibers currently waiting on a closed latch without opening it.
243 *
244 * **When to use**
245 *
246 * Use to let the fibers currently waiting on a latch proceed while keeping the
247 * latch closed for future waiters.
248 *
249 * **Details**
250 *
251 * The returned effect succeeds with `true` when release was requested while
252 * the latch was closed, or `false` if the latch was already open. Future
253 * waiters still suspend until the latch is opened or released again.
254 *
255 * @see {@link open} for opening the latch for current and future waiters
256 *
257 * @category combinators
258 * @since 4.0.0
259 */
260export const release = (self: Latch): Effect.Effect<boolean> => self.release
261
262const _await = (self: Latch): Effect.Effect<void> => self.await
263
264export {
265 /**
266 * Waits for the latch to be opened.
267 *
268 * **When to use**
269 *
270 * Use to suspend the current fiber until the latch is opened or the current
271 * set of waiters is released.
272 *
273 * **Details**
274 *
275 * Awaiting an already open latch completes immediately. Awaiting a closed
276 * latch suspends until `open` or `release` resumes the waiters.
277 *
278 * **Gotchas**
279 *
280 * `release` can resume current waiters without opening the latch, so later
281 * waiters may still suspend.
282 *
283 * @see {@link open} for opening the latch for current and future waiters
284 * @see {@link release} for resuming current waiters without opening the latch
285 * @see {@link whenOpen} for waiting before running another effect
286 *
287 * @category getters
288 * @since 4.0.0
289 */
290 _await as await
291}
292
293/**
294 * Closes the latch so future `await` and `whenOpen` calls suspend.
295 *
296 * **When to use**
297 *
298 * Use to re-enable waiting on a latch after it was opened, so later `await`
299 * and `whenOpen` calls suspend again.
300 *
301 * **Details**
302 *
303 * The returned effect succeeds with `true` when this call changed the latch
304 * from open to closed, or `false` if it was already closed.
305 *
306 * @see {@link closeUnsafe} for a synchronous variant
307 * @see {@link open} for opening the latch for current and future waiters
308 *
309 * @category combinators
310 * @since 4.0.0
311 */
312export const close = (self: Latch): Effect.Effect<boolean> => self.close
313
314/**
315 * Closes the latch synchronously so future `await` and `whenOpen` calls
316 * suspend.
317 *
318 * **When to use**
319 *
320 * Use to close a latch synchronously when the state change must happen outside
321 * an `Effect`.
322 *
323 * **Details**
324 *
325 * Returns `true` when this call changed the latch from open to closed, or
326 * `false` if it was already closed. This unsafe variant performs the state
327 * change immediately instead of returning an `Effect`.
328 *
329 * @see {@link close} for the effectful variant
330 * @see {@link openUnsafe} to synchronously open the latch and release waiting
331 * fibers
332 *
333 * @category unsafe
334 * @since 4.0.0
335 */
336export const closeUnsafe = (self: Latch): boolean => self.closeUnsafe()
337
338/**
339 * Waits on the latch, then runs the provided effect.
340 *
341 * **When to use**
342 *
343 * Use to gate another effect so it starts only after the latch is opened or
344 * the current waiters are released.
345 *
346 * **Details**
347 *
348 * If the latch is open, the effect runs immediately. If it is closed, the
349 * returned effect suspends until the latch is opened or the current waiters are
350 * released. The provided effect's success, failure, and requirements are
351 * preserved.
352 *
353 * @see `await` for waiting without running another effect
354 * @see {@link open} for opening the latch for current and future waiters
355 * @see {@link release} for resuming current waiters without opening the latch
356 *
357 * @category combinators
358 * @since 4.0.0
359 */
360export const whenOpen: {
361 (self: Latch): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
362 <A, E, R>(self: Latch, effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
363} = ((...args: Array<any>) => {
364 if (args.length === 1) {
365 const [self] = args
366 return (effect: Effect.Effect<any, any, any>) => self.whenOpen(effect)
367 }
368 const [self, effect] = args
369 return self.whenOpen(effect)
370}) as any
371
372/**
373 * Checks whether the latch is currently open or closed.
374 *
375 * **When to use**
376 *
377 * Use to check the state of the latch without suspending or changing its state.
378 *
379 * @category predicates
380 * @since 4.0.0
381 */
382export const isOpen = (self: Latch): boolean => self.isOpen()