# Coordination (TxSemaphore, TxReentrantLock, TxDeferred)

Sometimes what fibers contend over isn't a value but *access*: a limited number
of permits, exclusive use of a resource, or a signal that fires exactly once.
The `Tx*` coordination primitives — `TxSemaphore`, `TxReentrantLock`, and
`TxDeferred` — solve these, and because they are built on
[`TxRef`](https://effect.plants.sh/transactions/transactional-state/) their state changes commit
atomically alongside the rest of your transaction.

## TxSemaphore

A `TxSemaphore` holds a fixed number of permits. Acquiring blocks (via
`Effect.txRetry`) until enough permits are free; releasing returns them, capped
at the original capacity. The most ergonomic API is `withPermit`, which acquires
one permit, runs an effect, and releases the permit even on failure or
interruption.

```ts
import { Context, Effect, Layer, TxSemaphore } from "effect"

// A connection pool that lets at most 3 callers run a query concurrently.
class Db extends Context.Service<Db, {
  readonly query: (sql: string) => Effect.Effect<ReadonlyArray<unknown>>
}>()("app/Db") {
  static layer = Layer.effect(Db)(
    Effect.gen(function*() {
      // Fixed capacity of 3 permits == at most 3 concurrent queries.
      const semaphore = yield* TxSemaphore.make(3)

      const query = Effect.fn("Db.query")(function*(sql: string) {
        // withPermit brackets the work: acquire 1 permit, run, release.
        // A 4th concurrent caller waits here until a permit frees up.
        return yield* TxSemaphore.withPermit(
          semaphore,
          Effect.succeed([] as ReadonlyArray<unknown>)
        )
      })

      return Db.of({ query })
    })
  )
}
```

<Aside type="caution" title="Don't ask for more than capacity">
`acquireN(sem, n)` where `n` exceeds the semaphore's capacity waits forever — the
pool never grows to satisfy it. Acquiring or releasing a non-positive count is a
defect, as is creating a semaphore with a negative permit count.
</Aside>

### TxSemaphore

The transactional semaphore type. It carries a `permitsRef` (a `TxRef<number>`)
holding the current available permits and a fixed `capacity`.

```ts
import { TxSemaphore } from "effect"

declare const sem: TxSemaphore.TxSemaphore
// sem.capacity is the fixed total; sem.permitsRef is the live TxRef<number>
```

### make

Creates a semaphore with a fixed number of permits. Runs inside `Effect.tx`; a
negative permit count dies with a defect.

```ts
import { Effect, TxSemaphore } from "effect"

const program = Effect.gen(function*() {
  const sem = yield* TxSemaphore.make(3)
  return yield* TxSemaphore.available(sem) // => 3
})
```

### isTxSemaphore

Type guard narrowing an unknown value to `TxSemaphore`.

```ts
import { Effect, TxSemaphore } from "effect"

const program = Effect.gen(function*() {
  const sem = yield* TxSemaphore.make(1)
  TxSemaphore.isTxSemaphore(sem) // => true
  TxSemaphore.isTxSemaphore({}) // => false
})
```

### acquire

Acquires one permit, retrying the transaction until a permit is available.

```ts
import { Effect, TxSemaphore } from "effect"

const program = Effect.gen(function*() {
  const sem = yield* TxSemaphore.make(2)
  yield* TxSemaphore.acquire(sem)
  return yield* TxSemaphore.available(sem) // => 1
})
```

### acquireN

Acquires `n` permits, retrying until all `n` are available. A non-positive `n`
defects; an `n` greater than capacity waits forever.

```ts
import { Effect, TxSemaphore } from "effect"

const program = Effect.gen(function*() {
  const sem = yield* TxSemaphore.make(5)
  yield* TxSemaphore.acquireN(sem, 3)
  return yield* TxSemaphore.available(sem) // => 2
})
```

### release

Returns one permit. If the semaphore is already at capacity the count is left
unchanged.

```ts
import { Effect, TxSemaphore } from "effect"

const program = Effect.gen(function*() {
  const sem = yield* TxSemaphore.make(2)
  yield* TxSemaphore.acquire(sem) // available => 1
  yield* TxSemaphore.release(sem)
  return yield* TxSemaphore.available(sem) // => 2
})
```

### releaseN

Returns `n` permits, capped at capacity. A non-positive `n` defects.

```ts
import { Effect, TxSemaphore } from "effect"

const program = Effect.gen(function*() {
  const sem = yield* TxSemaphore.make(5)
  yield* TxSemaphore.acquireN(sem, 3) // available => 2
  yield* TxSemaphore.releaseN(sem, 2)
  return yield* TxSemaphore.available(sem) // => 4
})
```

### tryAcquire

Attempts to acquire one permit without waiting. Returns `true` if a permit was
taken, `false` otherwise.

```ts
import { Effect, TxSemaphore } from "effect"

const program = Effect.gen(function*() {
  const sem = yield* TxSemaphore.make(1)
  const first = yield* TxSemaphore.tryAcquire(sem) // => true
  const second = yield* TxSemaphore.tryAcquire(sem) // => false
})
```

### tryAcquireN

Attempts to acquire `n` permits without waiting. Returns `true` only if all `n`
were available. A non-positive `n` defects.

```ts
import { Effect, TxSemaphore } from "effect"

const program = Effect.gen(function*() {
  const sem = yield* TxSemaphore.make(3)
  const ok = yield* TxSemaphore.tryAcquireN(sem, 2) // => true
  const no = yield* TxSemaphore.tryAcquireN(sem, 2) // => false (only 1 left)
})
```

### withPermit

Runs an effect with one permit acquired beforehand and released afterwards —
even on failure or interruption.

```ts
import { Effect, TxSemaphore } from "effect"

const program = Effect.gen(function*() {
  const sem = yield* TxSemaphore.make(2)
  return yield* TxSemaphore.withPermit(sem, Effect.succeed("done")) // => "done"
})
```

### withPermits

Runs an effect while holding `n` permits, releasing all of them afterwards. A
non-positive `n` defects; an `n` over capacity waits forever.

```ts
import { Effect, TxSemaphore } from "effect"

const program = Effect.gen(function*() {
  const sem = yield* TxSemaphore.make(5)
  return yield* TxSemaphore.withPermits(sem, 3, Effect.succeed("batch")) // => "batch"
})
```

### withPermitScoped

Acquires one permit tied to the current [`Scope`](https://effect.plants.sh/resource-management/): the
permit is released when the scope closes rather than after a single effect.

```ts
import { Effect, TxSemaphore } from "effect"

const program = Effect.gen(function*() {
  const sem = yield* TxSemaphore.make(3)
  yield* Effect.scoped(
    Effect.gen(function*() {
      yield* TxSemaphore.withPermitScoped(sem) // held until the scope closes
      // ... work while holding the permit ...
    })
  )
  return yield* TxSemaphore.available(sem) // => 3
})
```

### available

Reads the current number of available permits.

```ts
import { Effect, TxSemaphore } from "effect"

const program = Effect.gen(function*() {
  const sem = yield* TxSemaphore.make(5)
  return yield* TxSemaphore.available(sem) // => 5
})
```

### capacity

Reads the fixed total permit count, which never changes.

```ts
import { Effect, TxSemaphore } from "effect"

const program = Effect.gen(function*() {
  const sem = yield* TxSemaphore.make(10)
  yield* TxSemaphore.acquire(sem)
  return yield* TxSemaphore.capacity(sem) // => 10
})
```

## TxReentrantLock

A `TxReentrantLock` is a read/write lock with per-fiber ownership tracking. Many
fibers may hold a **read** lock at once, but a **write** lock grants one fiber
exclusive access. It is *reentrant*: a fiber may re-acquire a lock it already
owns (as long as each acquisition is matched by a release), so nested critical
sections are safe.

```ts
import { Effect, Ref, TxReentrantLock } from "effect"

const program = Effect.gen(function*() {
  const lock = yield* TxReentrantLock.make()
  const state = yield* Ref.make(0)

  // withWriteLock grants exclusive access for the duration of the effect,
  // then releases — even on failure or interruption. Concurrent readers and
  // writers wait until the write lock is dropped.
  yield* TxReentrantLock.withWriteLock(lock, Ref.update(state, (n) => n + 1))

  // withReadLock allows multiple readers to proceed in parallel, but waits
  // while any fiber holds the write lock.
  return yield* TxReentrantLock.withReadLock(lock, Ref.get(state)) // => 1
})
```

Prefer the bracketing helpers `withReadLock`, `withWriteLock`, and `withLock`
over manual `acquireRead` / `releaseRead` (and their write counterparts) — they
guarantee the lock is released no matter how the effect ends. If you need lock
ownership tied to a [`Scope`](https://effect.plants.sh/resource-management/) rather than a single
effect, `readLock` and `writeLock` acquire a lock that is released when the scope
closes.

### TxReentrantLock

The reentrant read/write lock type. Internally it tracks per-fiber reader counts
and an optional writer in a single `TxRef`.

```ts
import { TxReentrantLock } from "effect"

declare const lock: TxReentrantLock.TxReentrantLock
```

### make

Creates a new unlocked reentrant lock. Runs inside `Effect.tx`.

```ts
import { Effect, TxReentrantLock } from "effect"

const program = Effect.gen(function*() {
  const lock = yield* TxReentrantLock.make()
  return yield* TxReentrantLock.locked(lock) // => false
})
```

### isTxReentrantLock

Type guard narrowing an unknown value to `TxReentrantLock`.

```ts
import { TxReentrantLock } from "effect"

declare const value: unknown
TxReentrantLock.isTxReentrantLock(value) // => boolean
```

### acquireRead

Acquires a read lock for the current fiber, waiting while another fiber holds the
write lock. Returns this fiber's new read-lock count.

```ts
import { Effect, TxReentrantLock } from "effect"

const program = Effect.gen(function*() {
  const lock = yield* TxReentrantLock.make()
  const count = yield* TxReentrantLock.acquireRead(lock) // => 1
  yield* TxReentrantLock.releaseRead(lock)
})
```

### acquireWrite

Acquires the write lock for the current fiber, waiting while any *other* fiber
holds a read or write lock. Reentrant for the owning fiber, and upgrades from a
read lock the fiber already holds. Returns this fiber's new write-lock count.

```ts
import { Effect, TxReentrantLock } from "effect"

const program = Effect.gen(function*() {
  const lock = yield* TxReentrantLock.make()
  const count = yield* TxReentrantLock.acquireWrite(lock) // => 1
  yield* TxReentrantLock.releaseWrite(lock)
})
```

### releaseRead

Releases one read lock held by the current fiber, returning the remaining count.
Releasing when this fiber holds none leaves the lock unchanged and returns `0`.

```ts
import { Effect, TxReentrantLock } from "effect"

const program = Effect.gen(function*() {
  const lock = yield* TxReentrantLock.make()
  yield* TxReentrantLock.acquireRead(lock)
  return yield* TxReentrantLock.releaseRead(lock) // => 0
})
```

### releaseWrite

Releases one write lock held by the current fiber, returning the remaining count.
Releasing when this fiber holds none leaves the lock unchanged and returns `0`.

```ts
import { Effect, TxReentrantLock } from "effect"

const program = Effect.gen(function*() {
  const lock = yield* TxReentrantLock.make()
  yield* TxReentrantLock.acquireWrite(lock)
  return yield* TxReentrantLock.releaseWrite(lock) // => 0
})
```

### withReadLock

Runs an effect while holding a read lock, releasing it afterwards even on failure
or interruption. Available data-first and data-last.

```ts
import { Effect, TxReentrantLock } from "effect"

const program = Effect.gen(function*() {
  const lock = yield* TxReentrantLock.make()
  return yield* TxReentrantLock.withReadLock(lock, Effect.succeed("read data")) // => "read data"
})
```

### withWriteLock

Runs an effect while holding the write lock (exclusive access), releasing it
afterwards even on failure or interruption.

```ts
import { Effect, TxReentrantLock } from "effect"

const program = Effect.gen(function*() {
  const lock = yield* TxReentrantLock.make()
  return yield* TxReentrantLock.withWriteLock(lock, Effect.succeed("wrote data")) // => "wrote data"
})
```

### withLock

A short alias for `withWriteLock` — runs an effect with exclusive access.

```ts
import { Effect, TxReentrantLock } from "effect"

const program = Effect.gen(function*() {
  const lock = yield* TxReentrantLock.make()
  return yield* TxReentrantLock.withLock(lock, Effect.succeed("exclusive")) // => "exclusive"
})
```

### readLock

Acquires a read lock tied to the current [`Scope`](https://effect.plants.sh/resource-management/),
released when the scope closes. Returns the fiber's read-lock count.

```ts
import { Effect, TxReentrantLock } from "effect"

const program = Effect.gen(function*() {
  const lock = yield* TxReentrantLock.make()
  yield* Effect.scoped(
    Effect.gen(function*() {
      yield* TxReentrantLock.readLock(lock) // held for the scope
    })
  )
})
```

### writeLock

Acquires the write lock tied to the current [`Scope`](https://effect.plants.sh/resource-management/),
released when the scope closes. Returns the fiber's write-lock count.

```ts
import { Effect, TxReentrantLock } from "effect"

const program = Effect.gen(function*() {
  const lock = yield* TxReentrantLock.make()
  yield* Effect.scoped(
    Effect.gen(function*() {
      yield* TxReentrantLock.writeLock(lock) // held for the scope
    })
  )
})
```

### readLocks

Returns the total number of read locks held across all fibers.

```ts
import { Effect, TxReentrantLock } from "effect"

const program = Effect.gen(function*() {
  const lock = yield* TxReentrantLock.make()
  yield* TxReentrantLock.acquireRead(lock)
  return yield* TxReentrantLock.readLocks(lock) // => 1
})
```

### writeLocks

Returns the number of write locks held — `0` or the reentrant count of the
owning fiber.

```ts
import { Effect, TxReentrantLock } from "effect"

const program = Effect.gen(function*() {
  const lock = yield* TxReentrantLock.make()
  return yield* TxReentrantLock.writeLocks(lock) // => 0
})
```

### locked

Returns `true` if any fiber holds a read or write lock.

```ts
import { Effect, TxReentrantLock } from "effect"

const program = Effect.gen(function*() {
  const lock = yield* TxReentrantLock.make()
  return yield* TxReentrantLock.locked(lock) // => false
})
```

### readLocked

Returns `true` if any fiber holds a read lock.

```ts
import { Effect, TxReentrantLock } from "effect"

const program = Effect.gen(function*() {
  const lock = yield* TxReentrantLock.make()
  return yield* TxReentrantLock.readLocked(lock) // => false
})
```

### writeLocked

Returns `true` if any fiber holds the write lock.

```ts
import { Effect, TxReentrantLock } from "effect"

const program = Effect.gen(function*() {
  const lock = yield* TxReentrantLock.make()
  return yield* TxReentrantLock.writeLocked(lock) // => false
})
```

## TxDeferred

A `TxDeferred<A, E>` is a transactional, write-once cell — the STM counterpart of
a regular deferred. Readers `await` it from inside a transaction: while it is
empty the transaction retries, and once another transaction completes it, every
waiter sees the same committed result. Completion is single-assignment, so only
the first `succeed` / `fail` / `done` wins; later attempts return `false`.

```ts
import { Effect, Fiber, TxDeferred } from "effect"

const program = Effect.gen(function*() {
  // A one-shot handoff: the worker computes a value, the waiter receives it.
  const result = yield* TxDeferred.make<number, string>()

  const waiter = yield* Effect.forkChild(
    // await parks the fiber (transaction retry) until the deferred is
    // completed, then resumes with the success value or typed failure.
    TxDeferred.await(result)
  )

  // Complete it exactly once. The waiter wakes with 42.
  const first = yield* TxDeferred.succeed(result, 42)
  const second = yield* TxDeferred.succeed(result, 99) // ignored

  return {
    value: yield* Fiber.join(waiter), // 42
    first, //  true
    second //  false
  }
})
```

### TxDeferred

The transactional write-once cell type, parameterized by its success type `A`
and error type `E` (default `never`). Its `ref` holds an
`Option<Result<A, E>>` — `None` while empty.

```ts
import { TxDeferred } from "effect"

declare const deferred: TxDeferred.TxDeferred<number, string>
```

### make

Creates a new empty `TxDeferred<A, E>`.

```ts
import { Effect, Option, TxDeferred } from "effect"

const program = Effect.gen(function*() {
  const deferred = yield* TxDeferred.make<string, Error>()
  const state = yield* TxDeferred.poll(deferred)
  return Option.isNone(state) // => true
})
```

### isTxDeferred

Type guard narrowing an unknown value to `TxDeferred`.

```ts
import { Effect, TxDeferred } from "effect"

const program = Effect.gen(function*() {
  const deferred = yield* TxDeferred.make<number>()
  TxDeferred.isTxDeferred(deferred) // => true
  TxDeferred.isTxDeferred("nope") // => false
})
```

### await

Reads the completed value, retrying the transaction while the cell is empty.
Resumes with the success value, or fails with the stored typed error.

```ts
import { Effect, TxDeferred } from "effect"

const program = Effect.gen(function*() {
  const deferred = yield* TxDeferred.make<number>()
  yield* TxDeferred.succeed(deferred, 42)
  return yield* TxDeferred.await(deferred) // => 42
})
```

### succeed

Completes the deferred with a success value. Returns `true` on the first
completion, `false` if already completed.

```ts
import { Effect, TxDeferred } from "effect"

const program = Effect.gen(function*() {
  const deferred = yield* TxDeferred.make<number>()
  const first = yield* TxDeferred.succeed(deferred, 42) // => true
  const second = yield* TxDeferred.succeed(deferred, 99) // => false
})
```

### fail

Completes the deferred with a typed failure. Returns `true` on the first
completion, `false` if already completed.

```ts
import { Effect, TxDeferred } from "effect"

const program = Effect.gen(function*() {
  const deferred = yield* TxDeferred.make<number, string>()
  const first = yield* TxDeferred.fail(deferred, "boom") // => true
  const second = yield* TxDeferred.fail(deferred, "boom2") // => false
})
```

### done

Completes the deferred with an already computed `Result`. Returns `true` on the
first completion, `false` if already completed. Available data-first and
data-last.

```ts
import { Effect, Result, TxDeferred } from "effect"

const program = Effect.gen(function*() {
  const deferred = yield* TxDeferred.make<number, string>()
  const first = yield* TxDeferred.done(deferred, Result.succeed(42)) // => true
  const second = yield* TxDeferred.done(deferred, Result.succeed(99)) // => false
})
```

### poll

Inspects the current state *without* retrying. Returns `Option<Result<A, E>>` —
`None` until the cell is completed, then `Some(result)`.

```ts
import { Effect, Option, TxDeferred } from "effect"

const program = Effect.gen(function*() {
  const deferred = yield* TxDeferred.make<number>()
  const before = yield* TxDeferred.poll(deferred)
  Option.isNone(before) // => true

  yield* TxDeferred.succeed(deferred, 42)
  return yield* TxDeferred.poll(deferred) // => Some(Success(42))
})
```
**Composing the primitives:** Because all three are backed by `TxRef`, you can combine them in a single
`Effect.tx` block — for example, acquire a permit *and* mark a `TxDeferred` done
*and* update a `TxHashMap` as one atomic step. That composability is the whole
point of STM: independent pieces of coordination commit together or not at all.

## Picking a primitive

| You need to coordinate… | Use |
| --- | --- |
| A limited number of concurrent users | `TxSemaphore` |
| Shared reads with exclusive writes | `TxReentrantLock` |
| A one-time signal or handoff | `TxDeferred` |

For coordinating *data* rather than access, see the
[transactional data structures](https://effect.plants.sh/transactions/transactional-data-structures/);
for the underlying atomic-state mechanics, see
[transactional state](https://effect.plants.sh/transactions/transactional-state/).