-
Notifications
You must be signed in to change notification settings - Fork 0
Concurrency
Container and Scope are both safe for concurrent use. Registration and
resolution may run on different goroutines at the same time, and a single scope
may be resolved from several goroutines at once.
Registration is safe at any time. There is no "wiring phase" the container enforces. Registering while another goroutine resolves is defined behaviour — the resolve either sees the new registration or does not, but the container's internal maps are never read and written concurrently.
Singletons are built exactly once. Concurrent first resolves of the same singleton produce one instance; the losers wait and receive it.
Scoped instances are built at most once per scope. If two goroutines race, the factory may run twice, but only one value is kept and stored. The redundant one is closed immediately, unless the factory returned the identical value.
Factories always run with the container's lock released. This is what lets a factory resolve its own dependencies without deadlocking. It is also why a scoped factory can race with itself as described above — the trade is deliberate: a factory that recurses is common, and a factory that runs twice under contention is cheap.
A scope is not a synchronisation primitive. It protects its own bookkeeping,
not the objects it hands out. Two goroutines resolving the same scoped
*UnitOfWork get the same value, with no locking around it — if that value
is not itself safe for concurrent use, the fan-out is a data race in your code,
not in the container.
// Two goroutines, one shared *Tx. The container is fine; the transaction is not.
var tx *Tx
_ = ctx.Resolver().Resolve(&tx)
go func() { tx.Insert(a) }()
go func() { tx.Insert(b) }()For per-goroutine values, use Transient — or give each goroutine its own
scope.
Close racing a resolve is safe but not ordered. A resolve concurrent with
Close either completes before it, or returns ErrScopeClosed and closes the
value it built. It never hands back an orphan. But you cannot rely on which
happens: shut down the work before closing the scope.
Ordering between registrations is not synchronised. If goroutine A
registers *Database and goroutine B resolves it, B may see
ErrNotRegistered. Register everything before starting the work that resolves
it — in a rex application, that is what the
declare-then-build lifecycle
gives you for free.
dix — Dependency Injection eXperience · MIT · © 2026 Kryovyx · pre-1.0, interfaces may change
Concepts
Reference
Practice
Ecosystem