diff --git a/.changeset/few-mayflies-speak.md b/.changeset/few-mayflies-speak.md
new file mode 100644
index 0000000000..1db7728fde
--- /dev/null
+++ b/.changeset/few-mayflies-speak.md
@@ -0,0 +1,21 @@
+---
+"effect": minor
+---
+
+add Semaphore.withPermitsIfAvailable
+
+You can now use `Semaphore.withPermitsIfAvailable` to run an Effect only if the
+Semaphore has enough permits available. This is useful when you want to run an
+Effect only if you can acquire a permit without blocking.
+
+It will return an `Option.Some` with the result of the Effect if the permits were
+available, or `None` if they were not.
+
+```ts
+import { Effect } from "effect"
+
+Effect.gen(function* () {
+ const semaphore = yield* Effect.makeSemaphore(1)
+ semaphore.withPermitsIfAvailable(1)(Effect.void)
+})
+```
diff --git a/packages/effect/src/Effect.ts b/packages/effect/src/Effect.ts
index e667425550..f58f064728 100644
--- a/packages/effect/src/Effect.ts
+++ b/packages/effect/src/Effect.ts
@@ -5374,6 +5374,8 @@ export interface Permit {
export interface Semaphore {
/** when the given amount of permits are available, run the effect and release the permits when finished */
withPermits(permits: number): (self: Effect) => Effect
+ /** only if the given permits are available, run the effect and release the permits when finished */
+ withPermitsIfAvailable(permits: number): (self: Effect) => Effect, E, R>
/** take the given amount of permits, suspending if they are not yet available */
take(permits: number): Effect
/** release the given amount of permits, and return the resulting available permits */
diff --git a/packages/effect/src/internal/effect/circular.ts b/packages/effect/src/internal/effect/circular.ts
index cff08b7670..a1c790964f 100644
--- a/packages/effect/src/internal/effect/circular.ts
+++ b/packages/effect/src/internal/effect/circular.ts
@@ -93,6 +93,17 @@ class Semaphore {
(permits) => fiberRuntime.ensuring(restore(self), this.release(permits))
)
)
+
+ readonly withPermitsIfAvailable = (n: number) => (self: Effect.Effect) =>
+ core.uninterruptibleMask((restore) =>
+ core.suspend(() => {
+ if (this.free < n) {
+ return effect.succeedNone
+ }
+ this.taken += n
+ return fiberRuntime.ensuring(restore(effect.asSome(self)), this.release(n))
+ })
+ )
}
/** @internal */