Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/add-semaphore-take-if-available.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": minor
---

Add `Semaphore.takeIfAvailable` for non-blocking manual permit acquisition.
46 changes: 46 additions & 0 deletions packages/effect/src/Semaphore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ export interface Semaphore {
*/
take(this: Semaphore, permits: number): Effect.Effect<number>

/**
* Acquires the specified number of permits only if they are immediately
* available.
*
* **When to use**
*
* Use to manually acquire permits without waiting, paired with `release`.
*/
takeIfAvailable(this: Semaphore, permits: number): Effect.Effect<boolean>

/**
* Releases the specified number of permits and returns the resulting
* available permits.
Expand Down Expand Up @@ -226,6 +236,14 @@ class SemaphoreImpl implements Semaphore {
return take
}

takeIfAvailable(n: number): Effect.Effect<boolean> {
return internal.suspend(() => {
if (this.free < n) return internal.succeed(false)
this.taken += n
return internal.succeed(true)
})
}

updateTakenUnsafe(fiber: Fiber<any, any>, f: (n: number) => number): number {
this.taken = f(this.taken)
if (this.waiters.size > 0) {
Expand Down Expand Up @@ -458,6 +476,7 @@ export const withPermitsIfAvailable: {
*
* @see {@link withPermit} for automatically acquiring and releasing one permit around an effect
* @see {@link withPermits} for automatically acquiring and releasing multiple permits around an effect
* @see {@link takeIfAvailable} for manually acquiring permits without waiting
* @see {@link release} for returning manually acquired permits
*
* @category combinators
Expand All @@ -468,6 +487,33 @@ export const take: {
(self: Semaphore, permits: number): Effect.Effect<number>
} = dual(2, (self: Semaphore, permits: number) => self.take(permits))

/**
* Acquires the specified number of permits only if they are immediately
* available.
*
* **When to use**
*
* Use when you need fail-fast manual permit acquisition for a lower-level
* protocol with explicit acquisition and release control.
*
* **Details**
*
* If enough permits are available, they are acquired and the effect returns
* `true`. Otherwise, the effect returns `false` immediately without acquiring
* any permits.
*
* @see {@link take} for the variant that waits until permits are available
* @see {@link release} for returning manually acquired permits
* @see {@link withPermitsIfAvailable} for automatic acquisition and release around an effect
*
* @category combinators
* @since 4.0.0
*/
export const takeIfAvailable: {
(permits: number): (self: Semaphore) => Effect.Effect<boolean>
(self: Semaphore, permits: number): Effect.Effect<boolean>
} = dual(2, (self: Semaphore, permits: number) => self.takeIfAvailable(permits))

/**
* Releases the specified number of permits and returns the resulting available
* permits.
Expand Down
27 changes: 27 additions & 0 deletions packages/effect/test/Semaphore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,33 @@ describe("Semaphore", () => {
assert.isTrue(acquired)
}))

it.effect("takeIfAvailable acquires permits when they are available", () =>
Effect.gen(function*() {
const sem = yield* Semaphore.make(2)

const acquired = yield* Semaphore.takeIfAvailable(sem, 2)
assert.isTrue(acquired)

const unavailable = yield* sem.takeIfAvailable(1)
assert.isFalse(unavailable)

const released = yield* sem.release(2)
assert.strictEqual(released, 2)
}))

it.effect("takeIfAvailable returns immediately when permits are unavailable", () =>
Effect.gen(function*() {
const sem = yield* Semaphore.make(1)

yield* sem.take(1)

const acquired = yield* Semaphore.takeIfAvailable(1)(sem)
assert.isFalse(acquired)

yield* sem.release(1)
assert.isTrue(yield* sem.takeIfAvailable(1))
}))

it.effect("module-level combinators delegate to the instance api", () =>
Effect.gen(function*() {
const sem = yield* Semaphore.make(1)
Expand Down
Loading