Gamend.AfterCommit (gamend_core v1.0.1296)

Copy Markdown View Source

Side effects that wait for the enclosing transaction to commit.

A broadcast, a hook dispatch or a spawned task inside a transaction runs while the transaction holds the database, which on SQLite (a single connection, BEGIN IMMEDIATE) is every other request's database too. It is also observed before the write is visible, and survives a rollback of the write it announces. defer/1 queues the effect instead, and the queue runs once the outermost transaction/2 (or Gamend.Lock.serialize/3) has committed and released its lock. A rollback or an exception drops it.

Outside such a scope defer/1 runs the effect at once, so a helper can always defer and stay correct whether or not its caller holds a transaction. Inside a transaction opened with a bare Repo.transaction/2 there is no commit to wait for, and it also runs at once, as before.

Queued effects run in order, in the calling process, after the lock is released. The write is committed by then, so one that raises or exits is logged and does not stop the rest or fail the caller. An effect run at once behaves as a plain call.

Summary

Functions

Runs body, which opens a transaction, as the scope defer/1 queues into: the queue runs when body returns {:ok, _} and is dropped otherwise. For wrappers that take a lock around the transaction (Gamend.Lock.serialize/3), so the effects run after the lock is released as well.

Runs fun once the enclosing scope commits, or now when there is none.

Whether defer/1 would queue rather than run.

Repo.transact/2, with defer/1 inside it queued until it commits.

Repo.transaction/2 (a function or an Ecto.Multi), with defer/1 inside it queued until it commits. Every transaction in core goes through here.

Functions

collect(body)

@spec collect((-> result)) :: result when result: term()

Runs body, which opens a transaction, as the scope defer/1 queues into: the queue runs when body returns {:ok, _} and is dropped otherwise. For wrappers that take a lock around the transaction (Gamend.Lock.serialize/3), so the effects run after the lock is released as well.

Inside an outer scope it only runs body; the outer scope decides.

defer(fun)

@spec defer((-> any())) :: :ok

Runs fun once the enclosing scope commits, or now when there is none.

deferring?()

@spec deferring?() :: boolean()

Whether defer/1 would queue rather than run.

transact(fun, opts \\ [])

@spec transact((-> {:ok, term()} | {:error, term()}), keyword()) ::
  {:ok, term()} | {:error, term()}

Repo.transact/2, with defer/1 inside it queued until it commits.

transaction(fun_or_multi, opts \\ [])

@spec transaction((-> term()) | Ecto.Multi.t(), keyword()) ::
  {:ok, term()} | {:error, term()} | {:error, term(), term(), map()}

Repo.transaction/2 (a function or an Ecto.Multi), with defer/1 inside it queued until it commits. Every transaction in core goes through here.