# Transactions (STM)

Concurrent code that shares mutable state is hard to get right. The moment two
fibers read-modify-write the same data, you risk lost updates, partial writes,
and inconsistent reads. Effect's answer is **Software Transactional Memory
(STM)**: you mark a block of state changes as a transaction with `Effect.tx`,
and Effect guarantees that the whole block commits **atomically** — all of it,
or none of it — without you ever writing a lock by hand.

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

// Move money between two accounts. The read, the check, and both writes
// must all happen as a single atomic step — no other fiber can observe a
// state where the money has left one account but not arrived in the other.
const transfer = (
  from: TxRef.TxRef<number>,
  to: TxRef.TxRef<number>,
  amount: number
) =>
  // Effect.tx is the transaction boundary: everything inside commits together
  Effect.tx(
    Effect.gen(function*() {
      const balance = yield* TxRef.get(from)
      if (balance < amount) {
        // Failing aborts the transaction — none of the writes below are kept
        return yield* Effect.fail("insufficient funds" as const)
      }
      yield* TxRef.update(from, (n) => n - amount)
      yield* TxRef.update(to, (n) => n + amount)
    })
  )
```

## What STM solves

Without a transaction, a read-modify-write across several values is a sequence
of independent steps, and another fiber can interleave between any two of them.
The transfer above, written with a plain `Ref`, can lose money: fiber A reads
`from`, fiber B reads `from`, both subtract, and one write clobbers the other —
a classic *lost update*. Worse, a third fiber reading both accounts mid-transfer
can see money that has left `from` but not yet arrived in `to`.

STM removes the interleaving entirely. Inside `Effect.tx`, the body either
observes a single consistent snapshot and commits all its writes at once, or it
sees a conflict and runs again — there is never a partially-applied result.

## How it works

Effect's STM is **optimistic**. A transaction body runs against a private
journal of reads and writes rather than touching the shared values directly. At
commit time Effect checks whether any value the transaction read was changed by
another fiber in the meantime:

- If nothing it read has changed, the journal is committed in one atomic step.
- If there was a conflict, the journal is thrown away and the body **re-runs**
  from scratch against fresh values.

There are no locks to acquire and no deadlocks to design around: contention is
resolved by re-running the loser, not by blocking the winner.
**Transaction bodies can run more than once:** Because a conflict or `Effect.txRetry` causes the body to re-run, keep
externally visible side effects (logging, network calls) **out** of the
transaction body, or make them idempotent. The transaction body should only
touch transactional values.

## The transaction boundary

`Effect.tx` defines the boundary. The **outermost** `Effect.tx` call is what
creates the journal and decides when to commit or roll back; everything that
runs inside it — including nested `Effect.tx` calls — joins that **same**
journal instead of starting its own. Nested transactions do not commit
independently, so you can wrap reusable helpers in `Effect.tx` and still compose
them into a larger atomic unit safely.

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

const program = Effect.gen(function*() {
  const ref1 = yield* TxRef.make(0)
  const ref2 = yield* TxRef.make(0)

  // The outer tx is the boundary; the inner tx joins the same journal,
  // so both writes commit together (or not at all).
  yield* Effect.tx(
    Effect.gen(function*() {
      yield* TxRef.set(ref1, 10)
      yield* Effect.tx(TxRef.set(ref2, 20)) // composes, does not commit alone
    })
  )

  console.log(yield* TxRef.get(ref1)) // => 10
  console.log(yield* TxRef.get(ref2)) // => 20
})
```

A single bare `Tx*` operation that is **not** wrapped in `Effect.tx` still runs
atomically — it executes in its own implicit one-operation transaction. You only
need an explicit `Effect.tx` when you want **several** operations to commit as a
unit.

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

const program = Effect.gen(function*() {
  const ref = yield* TxRef.make(0)
  // No Effect.tx needed: this single update is its own transaction.
  yield* TxRef.update(ref, (n) => n + 1)
})
```

## Blocking with Effect.txRetry

A transaction can also wait deliberately. `Effect.txRetry` aborts the current
transaction and suspends it until one of the transactional values it read
changes — then the body re-runs. This turns "there isn't enough data yet" into a
clean, declarative blocking primitive instead of a polling loop, and it parks
the fiber cheaply (it is only rescheduled when a relevant value actually moves).

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

const program = Effect.gen(function*() {
  const ref = yield* TxRef.make(0)

  // A background fiber slowly raises the value.
  yield* Effect.forkChild(
    Effect.forever(
      Effect.tx(TxRef.update(ref, (n) => n + 1)).pipe(Effect.delay("100 millis"))
    )
  )

  // Block until the value reaches 10. Each time `ref` changes, the body
  // re-runs; while it is below 10 we suspend instead of busy-waiting.
  yield* Effect.tx(
    Effect.gen(function*() {
      const value = yield* TxRef.get(ref)
      if (value < 10) {
        return yield* Effect.txRetry
      }
    })
  )
})
```

`Effect.txRetry` is the building block under every blocking `Tx*` operation:
`TxQueue.take` retries while the queue is empty, `TxSemaphore.acquire` retries
while no permit is free, and `TxDeferred.await` retries until the deferred is
set. Because they are all built on the same primitive, they compose: a single
`Effect.tx` can take from a queue *and* acquire a permit *and* update a `TxRef`,
and the whole thing blocks until all of its conditions can be satisfied at once.

## In this section
**Transactional state**
[`TxRef`](https://effect.plants.sh/transactions/transactional-state/) is the atom of STM — a
    transactional reference, plus the `Effect.tx` / `Effect.txRetry` mechanics
    that make groups of updates atomic.

**Transactional data structures**
[`TxHashMap`, `TxHashSet`, `TxChunk`, and the transactional
    queues/pubsub](https://effect.plants.sh/transactions/transactional-data-structures/) — collections
    (`TxQueue`, `TxPubSub`, `TxPriorityQueue`, `TxSubscriptionRef`) whose every
    operation composes inside a transaction.

**Coordination**
[`TxSemaphore`, `TxReentrantLock`, and
    `TxDeferred`](https://effect.plants.sh/transactions/coordination/) — permits, read/write locks, and
    one-shot signals that commit atomically with your state.

## When to reach for STM

Use the `Tx*` modules whenever **more than one fiber** touches a piece of state
and a single read-modify-write would race. The classic cases are transfers
between accounts, inventory and reservation systems, work queues, connection
pools, and any "wait until a condition holds" coordination.

For state owned by a single fiber, or where atomicity across multiple values is
not a concern, the simpler [`Ref` and `SynchronizedRef`](https://effect.plants.sh/state-management/)
primitives are a better fit — they have less overhead and no retry semantics.
STM earns its keep precisely when you need *several* values to move together, or
when you need a fiber to block until a condition becomes true.

## Effect-level STM reference

The `Tx*` modules supply the data; these three members of `Effect` supply the
machinery. (The per-module operations are documented on the pages linked above.)

### Effect.tx

Defines a transaction boundary. The body's changes to transactional values are
all-or-nothing: it commits on success, rolls back on failure, and re-runs on a
conflict. Nested `Effect.tx` calls reuse the outermost journal rather than
opening a new boundary.

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

const program = Effect.gen(function*() {
  const a = yield* TxRef.make(1)
  const b = yield* TxRef.make(1)
  // Both updates commit together.
  yield* Effect.tx(
    Effect.gen(function*() {
      yield* TxRef.update(a, (n) => n + 1)
      yield* TxRef.update(b, (n) => n * 10)
    })
  )
  console.log(yield* TxRef.get(a), yield* TxRef.get(b)) // => 2 10
})
```

A failure inside the body rolls back every write made in that transaction:

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

const program = Effect.gen(function*() {
  const ref = yield* TxRef.make(0)
  yield* Effect.tx(
    Effect.gen(function*() {
      yield* TxRef.set(ref, 99)
      return yield* Effect.fail("abort" as const) // rolls back the set above
    })
  ).pipe(Effect.catch(() => Effect.void))
  console.log(yield* TxRef.get(ref)) // => 0
})
```

### Effect.txRetry

Aborts the current transaction and suspends the fiber until one of the
transactional values read in the body changes, then re-runs the body. It is the
primitive behind every blocking `Tx*` operation. Its type is
`Effect<never, never, Effect.Transaction>`, so it can only be used inside an
`Effect.tx` boundary.

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

// Wait until `gate` is opened (set to true) before proceeding.
const waitForOpen = (gate: TxRef.TxRef<boolean>) =>
  Effect.tx(
    Effect.gen(function*() {
      const open = yield* TxRef.get(gate)
      if (!open) return yield* Effect.txRetry // park until `gate` changes
    })
  )
// => the fiber suspends, then resumes once another transaction sets `gate` true
```

### Effect.Transaction

The `Context.Service` that holds the active transaction's state — the journal of
pending reads/writes and the retry flag. You rarely access it directly; it is
the service requirement that `Effect.tx` provides and that `Effect.txRetry`
depends on. Reading it confirms whether code is currently running inside a
transaction.

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

const program = Effect.tx(
  Effect.gen(function*() {
    const state = yield* Effect.Transaction
    console.log(state.retry) // => false
  })
)
```