package di
// Registration: what a binding is, the methods that make one, and the typed
// handle that refines it. Nothing here builds anything; a binding's build
// func is called by the resolution in resolve.go.
import (
"context"
"fmt"
"reflect"
"runtime"
"slices"
"sync/atomic"
)
// binding is one registration: its key, lifetime, hooks and build func.
type binding struct {
key key
site string
module string // the Module this was registered from, or ""
group bool
scoped bool
eager bool
override bool // declared to replace an earlier registration of the key
isValue bool // registered with Value: lifetimes do not apply
wants []key // the parameter types of a Wire constructor; nil for a Provide closure
build func(*Scope) any
// inner is the registration a Wrap composes over, bound when Wrap is
// called, and innerAt the scope that registered it; both nil otherwise.
inner *binding
innerAt *state
onStart func(context.Context, any) error
onDrain func(context.Context, any) error
onStop func(context.Context, any) error
worker func(context.Context, any) error
guard // what stops this registration being replaced; see guard
single *instance // the singleton; scoped bindings keep one instance per state
}
// where names the registration for a message: its site, and the module it was
// registered from when there is one, as in "storage (wire.go:12)".
func (b *binding) where() string {
if b.module == "" {
return b.site
}
return b.module + " (" + b.site + ")"
}
// validate rejects lifetime and hook combinations that cannot be honoured. It
// runs at freeze, so the order the builder methods were called in does not
// matter.
func (b *binding) validate() {
bad := func(what, why string) {
panic(fmt.Sprintf("di: %s (provided at %s): %s %s", b.key, b.where(), what, why))
}
switch {
case b.eager && b.scoped:
// Rejected even if a later registration overrides it; whether an
// override inherits eagerness is decided in deriveEager.
bad("Eager", "does not apply to a Scoped binding: it is not built once")
case b.isValue && b.scoped:
bad("Scoped", "is meaningless for a Value binding: the instance already exists")
case b.group && b.override:
bad("Override", "does not apply to a group member: members accumulate rather than replace one another")
case b.inner != nil && b.group:
bad("Group", "does not apply to a wrapper: it serves the key it wraps")
case b.inner != nil && b.override:
bad("Override", "does not apply to a wrapper: it composes over the registration it wraps rather than replacing it")
}
}
// Binding is the typed handle returned by Provide, Value, Wire and Wrap. Its
// methods refine the registration; they must be called before the first
// resolution from this scope.
type Binding[T any] struct {
s *Scope
b *binding
}
// register makes a binding and queues it for the next freeze. init runs
// before the binding is queued: once it is in pending, freeze and teardown
// read it under the scope's mutex, and a field written afterwards races both.
func (s *Scope) register(k key, build func(*Scope) any, init func(*binding)) *binding {
b := &binding{key: k, site: callsite(2), module: s.module, build: build}
b.single = &instance{b: b}
if init != nil {
init(b)
}
s.st.mu.Lock()
s.st.pending = append(s.st.pending, b)
s.st.hasPending.Store(true)
stopped := s.st.stopped.Load()
s.st.mu.Unlock()
if stopped && b.inner != nil {
// teardown collected the scope's wrappers before this one was queued,
// and a stopped scope never serves the key, so drop the mark here.
b.inner.unwrap(b)
}
return b
}
// Provide registers a lazily built singleton. T is inferred from the
// constructor's return type; dependencies are pulled with s.Get[...]().
func (s *Scope) Provide[T any](ctor func(*Scope) T) Binding[T] {
return Binding[T]{s, s.register(key{t: reflect.TypeFor[T]()}, func(s *Scope) any { return ctor(s) }, nil)}
}
// Value registers an already-built instance.
func (s *Scope) Value[T any](v T) Binding[T] {
b := s.register(key{t: reflect.TypeFor[T]()}, func(*Scope) any { return v }, func(b *binding) { b.isValue = true })
return Binding[T]{s, b}
}
// callsite is the file:line skip frames above its caller. register passes 2,
// for itself and the registration method the user called, so every
// registration method must call register directly.
func callsite(skip int) string {
_, file, line, _ := runtime.Caller(skip + 1)
return fmt.Sprintf("%s:%d", file, line)
}
// Wire registers a lazily built singleton from a constructor of any arity,
// whose parameters are its dependencies:
//
// s.Wire[*Server](NewServer) // func NewServer(cfg Config, repo *Repo) *Server
//
// ctor must be a non-variadic function returning T, or T and an error, and is
// read with reflection once, here. Each parameter is resolved as a Provide
// closure would resolve it, so lifetimes, cycles, hooks and error paths are
// the same; what Wire adds is that the dependencies are known at
// registration. A non-nil error from ctor aborts the build as s.Must does.
//
// T is spelled out because it cannot be inferred from an untyped argument. A
// result merely assignable to T is accepted, so a concrete constructor may
// serve an interface key: s.Wire[Repository](NewPGRepo).
func (s *Scope) Wire[T any](ctor any) Binding[T] {
want := reflect.TypeFor[T]()
fv, ft, fails := function("Wire["+typeName(want)+"]", "constructor", ctor, want)
wants := params(ft, 0)
b := s.register(key{t: want}, func(s *Scope) any {
args := make([]reflect.Value, len(wants))
s.arguments(wants, args)
return call(fv, args, fails, want)
}, func(b *binding) { b.wants = wants })
return Binding[T]{s, b}
}
// Wrap registers a wrapper over the registration that serves T when Wrap is
// called: the latest one in this scope, or the one an ancestor provides. fn
// takes the value being wrapped first and its other dependencies after it,
// read with reflection as Wire reads a constructor, and returns T, or T and
// an error:
//
// s.Wrap[Store](func(next Store, c *Cache) Store { return &caching{next, c} })
//
// What is wrapped keeps its registration, hooks and lifetime: it is built
// first, as the wrapper's dependency, and stopped after it. The wrapper serves
// T from this scope down; in a child scope it wraps the parent's value for
// that child alone. Wrappers chain in registration order and take the
// lifetime of what they wrap; Scoped() on the wrapper makes it one per
// resolving scope over a shared inner value. An Override registered afterwards
// replaces the wrapper and everything it wrapped. Nothing to wrap is rejected
// here, and a group cannot be wrapped. A key this scope has already resolved
// is rejected at the next resolution, as an Override is.
func (s *Scope) Wrap[T any](fn any) Binding[T] {
want := reflect.TypeFor[T]()
name := "Wrap[" + typeName(want) + "]"
fv, ft, fails := function(name, "wrapper", fn, want)
if ft.NumIn() == 0 || !want.AssignableTo(ft.In(0)) {
panic(fmt.Sprintf("di: %s: wrapper %s must take the %s it wraps as its first parameter", name, ft, typeName(want)))
}
k := key{t: want}
// This scope is read pending batch included, without committing it:
// committing here would end the batch for every registration so far.
// Ancestors are looked up as a resolution would look them up.
inner, at := s.st.current(k)
if inner == nil && s.st.parent != nil {
inner, at = (&Scope{st: s.st.parent}).lookup(k)
}
if inner == nil {
panic(fmt.Sprintf("di: %s: nothing provides %s in scope %s or above; a group is read with All and cannot be wrapped", name, k, s.st.name))
}
wants := params(ft, 1)
b := s.register(k, func(s *Scope) any {
args := make([]reflect.Value, len(wants)+1)
// Resolved as a dependency, which records the edge, keeps build order
// and catches a wrapper that reaches back into itself.
args[0] = argument(s.resolve(inner, at), ft.In(0))
s.markServed(at, k)
s.arguments(wants, args[1:])
return call(fv, args, fails, want)
}, func(b *binding) {
b.inner, b.innerAt, b.wants, b.scoped = inner, at, wants, inner.scoped
inner.addWrapper(b)
})
return Binding[T]{s, b}
}
// guard is what stops a registration being replaced once that would leave two
// live values for its key, or a wrapper over a registration nothing else can
// reach: a value served, a resolution in flight, a wrapper in a live scope.
// The fourth guard, a scope that handed the key down from an ancestor, is
// state.served, since it belongs to that scope.
//
// resolve writes used and resolving from whichever scope is resolving, without
// the owner's mutex, which keeps a warm resolution off that mutex; so they are
// atomics. wraps is a small set, replaced whole by compare-and-swap.
type guard struct {
// used is set once this binding has served a value. A failed resolution
// built nothing and leaves the key re-registerable.
used atomic.Bool
// resolving counts the resolutions of this binding that have not served a
// value yet, the window used cannot cover: a constructor that registers
// over its own key and resolves the replacement would otherwise hand the
// nested call the new value and the outer call the old one.
resolving atomic.Int32
// wraps holds the Wraps bound to this binding whose scopes are still
// alive, in registration order, and whether the binding is retired. Wrap
// adds itself; the scope that registered a wrapper removes it as it stops.
// It is a set because sibling scopes wrap one parent registration
// independently, and one stopping must not release the others.
//
// retired is set on a wrapper in a chain an Override replaced. It never
// serves from its own scope again, but a live wrapper in a descendant may
// still compose over it, so it keeps its mark on what it wraps until
// nothing wraps it; see release.
//
// Both change rarely, so they are one immutable wrapSet, nil for a binding
// nobody has wrapped or retired.
wraps atomic.Pointer[wrapSet]
}
// against says why replacer may not replace or wrap the guarded registration,
// or returns "" when nothing stops it. The reasons read as the end of a
// sentence: "cannot be overridden at wire.go:9: it has already been resolved".
func (g *guard) against(replacer *binding) string {
switch {
case g.used.Load():
return "it has already been resolved"
case g.resolving.Load() > 0:
return "it is being resolved"
}
if replacer.inner == nil {
// An Override, not a Wrap: a wrapper over this registration would go
// on serving a value built from something nothing else can reach.
if w := g.wrapper(); w != nil {
return "it is wrapped at " + w.where()
}
}
return ""
}
// wrapSet is a guard's wrappers and retired flag. It is never written after
// it is stored.
type wrapSet struct {
wrappers []*binding
retired bool
}
// update replaces the guard's wrapSet with what f makes of the current one,
// retrying if another update landed first. f must not modify the slice it is
// given. An empty, unretired set is stored as nil.
func (g *guard) update(f func(cur wrapSet) wrapSet) {
for {
old := g.wraps.Load()
var cur wrapSet
if old != nil {
cur = *old
}
next := f(cur)
var p *wrapSet
if len(next.wrappers) > 0 || next.retired {
p = &next
}
if g.wraps.CompareAndSwap(old, p) {
return
}
}
}
// addWrapper records a Wrap bound to the guarded registration.
func (g *guard) addWrapper(w *binding) {
g.update(func(cur wrapSet) wrapSet {
cur.wrappers = append(slices.Clip(cur.wrappers), w)
return cur
})
}
// dropWrapper forgets a Wrap that can no longer serve. A wrapper is added
// before anything can drop it, so one not in the set was dropped already.
func (g *guard) dropWrapper(w *binding) {
if s := g.wraps.Load(); s == nil || !slices.Contains(s.wrappers, w) {
return
}
g.update(func(cur wrapSet) wrapSet {
cur.wrappers = slices.DeleteFunc(slices.Clone(cur.wrappers), func(x *binding) bool { return x == w })
return cur
})
}
// retire marks the guarded registration as a wrapper in a chain an Override
// replaced.
func (g *guard) retire() {
g.update(func(cur wrapSet) wrapSet {
cur.retired = true
return cur
})
}
// isRetired reports whether retire was called.
func (g *guard) isRetired() bool {
s := g.wraps.Load()
return s != nil && s.retired
}
// unwrap forgets w, a wrapper bound to b that can no longer serve, and lets b
// go too if b was retired and w was the last thing keeping it.
func (b *binding) unwrap(w *binding) {
b.dropWrapper(w)
b.release()
}
// release drops the mark a retired binding holds on what it wraps once nothing
// live wraps it, and carries on down the chain while each link is retired and
// unwrapped in turn. A link still wrapped keeps its mark: the wrapper over it
// composes over everything below.
func (b *binding) release() {
for r := b; r.inner != nil && r.isRetired() && r.wrapper() == nil; r = r.inner {
r.inner.dropWrapper(r)
}
}
// wrapper returns the first live Wrap bound to the guarded registration, or
// nil.
func (g *guard) wrapper() *binding {
if s := g.wraps.Load(); s != nil && len(s.wrappers) > 0 {
return s.wrappers[0]
}
return nil
}
var errorType = reflect.TypeFor[error]()
// function checks that fn is a non-variadic function returning want, or want
// and an error, and returns it with its type and whether it declares the
// error. name and role label the message: "di: Wire[*app.Server]: constructor
// must be a function".
func function(name, role string, fn any, want reflect.Type) (fv reflect.Value, ft reflect.Type, fails bool) {
fv = reflect.ValueOf(fn)
if !fv.IsValid() || fv.Kind() != reflect.Func {
panic(fmt.Sprintf("di: %s: %s must be a function, got %T", name, role, fn))
}
ft = fv.Type()
switch {
case ft.IsVariadic():
panic(fmt.Sprintf("di: %s: %s %s is variadic", name, role, ft))
case ft.NumOut() == 0 || ft.NumOut() > 2:
panic(fmt.Sprintf("di: %s: %s %s must return T or (T, error)", name, role, ft))
case !ft.Out(0).AssignableTo(want):
panic(fmt.Sprintf("di: %s: %s %s returns %s", name, role, ft, typeName(ft.Out(0))))
case ft.NumOut() == 2 && ft.Out(1) != errorType:
panic(fmt.Sprintf("di: %s: %s %s must return T or (T, error)", name, role, ft))
}
return fv, ft, ft.NumOut() == 2
}
// params lists the parameter types of ft from index from on, as keys.
func params(ft reflect.Type, from int) []key {
wants := make([]key, ft.NumIn()-from)
for i := range wants {
wants[i] = key{t: ft.In(i + from)}
}
return wants
}
// arguments resolves each of wants from s into the corresponding slot of
// args.
func (s *Scope) arguments(wants []key, args []reflect.Value) {
for i, k := range wants {
args[i] = argument(s.get(k), k.t)
}
}
// current is the registration serving k in this scope as of now, pending or
// committed, read without committing anything.
func (st *state) current(k key) (*binding, *state) {
st.mu.Lock()
defer st.mu.Unlock()
for _, b := range slices.Backward(st.pending) {
if b.key == k && !b.group {
return b, st
}
}
if b, ok := st.reg.Load().index[k]; ok {
return b, st
}
return nil, nil
}
// argument makes a stored value into an argument of type t. A nil interface
// is a legitimate service, and reflect.ValueOf(nil) is not a value of any
// type; see as.
func argument(v any, t reflect.Type) reflect.Value {
if v == nil {
return reflect.Zero(t)
}
return reflect.ValueOf(v)
}
// call runs a constructor through reflect and turns a declared, returned
// error into the abort s.Must would raise. The value is stored as the
// registered type, not the result type: registration accepted any assignable
// result, and a chan int stored for a <-chan int key would pass every check
// until Get asserted it. An interface key needs no conversion.
func call(fv reflect.Value, args []reflect.Value, fails bool, want reflect.Type) any {
out := fv.Call(args)
if fails && !out[1].IsNil() {
panic(abort{out[1].Interface().(error)})
}
v := out[0]
if v.Type() != want && want.Kind() != reflect.Interface {
v = v.Convert(want)
}
return v.Interface()
}
// edit applies a builder method to the binding, rejecting one made after the
// scope committed the registration.
func (b Binding[T]) edit(f func(*binding)) Binding[T] {
b.s.st.mu.Lock()
defer b.s.st.mu.Unlock()
if b.s.st.frozen && !slices.Contains(b.s.st.pending, b.b) {
panic(fmt.Sprintf("di: %s (provided at %s) modified after the scope was first resolved", b.b.key, b.b.where()))
}
f(b.b)
return b
}
// Group makes the binding a member of the multi-binding group for T instead
// of the binding for T: it neither shadows nor is shadowed by another
// registration of T, and the members are read back together with s.All[T]().
// A member keeps its own lifetime and hooks.
func (b Binding[T]) Group() Binding[T] {
return b.edit(func(b *binding) { b.group = true })
}
// Override declares that this registration replaces an earlier one of the same
// key in the same scope. Without it a second registration of a key is rejected
// at the next resolution, naming both sites. With it the later registration
// serves the key and inherits its eagerness, which is the test seam:
//
// s := di.Test(t, app.Production)
// s.Value(&DB{DSN: "sqlite://memory"}).Override()
//
// There must be something to override in this scope, or that is rejected too,
// since a fake for a renamed service would otherwise be a registration nobody
// resolves. A child scope shadows its parent without Override. A key that has
// already served a value cannot be overridden at all.
func (b Binding[T]) Override() Binding[T] {
return b.edit(func(b *binding) { b.override = true })
}
// Scoped makes the binding one-per-scope: each scope that resolves it gets
// its own instance, built in that scope (so it can see that scope's values)
// and stopped with it. Declare request-scoped services once in the root and
// resolve them through the request scope.
func (b Binding[T]) Scoped() Binding[T] {
return b.edit(func(b *binding) { b.scoped = true })
}
// Eager builds the service during Start rather than on first use.
//
// Eagerness belongs to the key, not the registration: it means the service
// exists by the time Start returns. Overriding an eager binding keeps the key
// eager and builds the replacement; a replacement with a per-scope lifetime
// is rejected.
func (b Binding[T]) Eager() Binding[T] { return b.edit(func(b *binding) { b.eager = true }) }
// OnStart runs once the service is built. Only a hook that returns normally
// starts it: one that panics fails the start step, like a panicking
// constructor, and the service is never served.
func (b Binding[T]) OnStart(f func(context.Context, T) error) Binding[T] {
return b.edit(func(b *binding) { b.onStart = func(ctx context.Context, v any) error { return f(ctx, as[T](v)) } })
}
// OnDrain runs before anything is stopped: Stop drains the whole tree, from
// the innermost scope outwards and in reverse build order, while every scope
// still resolves. It is where a service stops accepting new work and waits
// for the work it already has, such as an HTTP server finishing in-flight
// requests whose handlers still need their request scope. Anything those
// handlers build, including a request scope, is drained before the phase
// ends. Use OnStop for the release that follows.
func (b Binding[T]) OnDrain(f func(context.Context, T) error) Binding[T] {
return b.edit(func(b *binding) { b.onDrain = func(ctx context.Context, v any) error { return f(ctx, as[T](v)) } })
}
// OnStop releases the service, in reverse build order, once its drain step
// and its child scopes are done. It runs when OnStart succeeded, or when there
// is no OnStart to pair with, in which case it is a plain destructor; a
// service whose start step failed is not stopped.
func (b Binding[T]) OnStop(f func(context.Context, T) error) Binding[T] {
return b.edit(func(b *binding) { b.onStop = func(ctx context.Context, v any) error { return f(ctx, as[T](v)) } })
}
// Go registers a worker for T: a long-running function, such as a consumer
// loop, that runs in a goroutine of its own for as long as the service does,
// as errgroup's Go does for a group. The worker starts once the service has
// started; its context is cancelled when the service stops, and Stop waits
// for it to return, bounded by Stop's own context. A worker that outlasts
// that deadline is reported by Stop, and OnStop then waits for it rather
// than releasing the value underneath it.
//
// Returning a non-nil error calls Shutdown with it, stopping the application,
// even if the scope was already stopping. The exception is context.Canceled
// from a worker that was already cancelled. A worker that wants to stay quiet
// during shutdown should return nil.
func (b Binding[T]) Go(f func(context.Context, T) error) Binding[T] {
return b.edit(func(b *binding) { b.worker = func(ctx context.Context, v any) error { return f(ctx, as[T](v)) } })
}
// Package di is a dependency-injection container for Go 1.27+ built on
// generic methods.
//
// Services are registered on a [Scope] and resolved from it by type. A
// constructor is a plain function, and its parameters are its dependencies:
//
// app := di.New()
// app.Value(Config{DSN: "postgres://localhost/app"})
// app.Wire[*DB](NewDB). // func NewDB(Config) (*DB, error)
// OnStop(func(ctx context.Context, db *DB) error { return db.Close() })
// app.Wire[*Repo](NewRepo) // func NewRepo(*DB) *Repo
//
// repo, err := app.Resolve[*Repo]()
//
// Keys are Go types, so there is no naming scheme and no collisions between
// packages. [Scope.Wire] reads a constructor's signature once, at
// registration, so the graph is declared before anything is built:
// [Scope.Validate] checks every declared dependency without running a
// constructor, and a constructor that returns an error fails the enclosing
// [Scope.Resolve], [Scope.Start] or [Scope.Run] with the dependency path and
// the registration site.
//
// [Scope.Provide] takes a closure over the scope instead, for the rare
// constructor that needs the scope itself, such as middleware that opens a
// child scope per request. Inside it, [Scope.Get] and [Scope.Must] pull
// dependencies and abort on failure, with the same error. A closure's
// dependencies are known only once it runs, so Validate and [Scope.Modules]
// list it as unchecked, and [Scope.Explain] can show what it needed only
// after it has been built.
//
// # Lifetimes
//
// A binding is a singleton by default, cached in the scope that registered
// it. [Binding.Scoped] makes it one instance per resolving scope, built there
// so it can see that scope's values, which is how request-scoped services are
// declared once in the root. [Binding.Group] and [Scope.All] handle groups,
// and [Scope.Maybe] resolves optional dependencies. An interface is served by
// a constructor that returns the implementation, since Wire accepts any
// result assignable to the key: s.Wire[Reader](NewRepo).
//
// # Scopes
//
// [Scope.Child] creates a scope that resolves through its parent, reuses the
// parent's singletons and owns the lifecycle of what it builds. A child may
// shadow a key its parent provides; within one scope, a second registration
// of a key must be marked [Binding.Override], or the next resolution rejects
// it naming both sites. That marker is the test seam: wire the production
// graph into a fresh scope, then override what you want faked before anything
// is resolved ([Test] does the bookkeeping). For HTTP,
// [github.com/floatdrop/di/dihttp.Middleware] gives each request a child
// scope holding the *http.Request, reachable through [FromContext].
//
// # Modules
//
// A [Module] is a function that registers into a scope, and [Scope.Use]
// applies modules in order. Registrations are attributed to the module that
// made them, so a collision between two modules is reported as one: "*app.DB
// is provided at storage (wire.go:12) and again at caching (cache.go:8)".
//
// # Lifecycle
//
// [Binding.OnStart], [Binding.OnDrain] and [Binding.OnStop] are typed hooks.
// [Scope.Start] builds [Binding.Eager] bindings and runs start hooks in build
// order, rolling back on failure; services built later start as part of being
// built. [Scope.Stop] first drains, which lets work already in flight finish
// while the scope still resolves, then stops child scopes, then services in
// reverse build order, and afterwards the scope refuses to resolve anything.
// [Binding.Go] runs a worker, a long-lived function in a goroutine of its
// own, cancelled on stop. [Scope.Run] ties it together for a main function:
// start, wait for a signal or [Scope.Shutdown], stop with a deadline.
// [Scope.Observe] reports every step for logging and metrics.
//
// # Inspecting the graph
//
// A constructor's dependencies are recorded as it resolves them, so the graph
// is known for whatever has been built. [Scope.Explain] renders one service's
// dependency tree, with the registration site, lifetime and scope of each
// node, and what needed it. [Scope.Graph] renders everything built in a scope
// and its descendants as Graphviz DOT.
//
// # Concurrency
//
// A [Scope] is safe to use from many goroutines, including from goroutines a
// constructor starts for itself: the resolution path is immutable, so
// branches that run in parallel share nothing. A constructor may also keep
// the Scope it was handed and resolve through it later, once its own service
// is built; the finished part of that path is no longer a dependency, so such
// a resolution is not a cycle. A service is built once however many
// resolutions race for it, and a resolution of a running scope returns only a
// service whose start step has finished. A cycle is reported as [ErrCycle]
// even when the two halves are being built concurrently.
//
// Three re-entrancy limits apply. A goroutine started by a constructor must
// use [Scope.Resolve] rather than [Scope.Get], because Get reports failure by
// panicking and that panic cannot unwind to the enclosing call from another
// goroutine. An [Binding.OnStart] hook must not resolve a service that
// depends on the one being started: the hook already holds the value, and
// waiting for itself cannot make progress. And no hook may call [Scope.Stop]
// on its own scope or an ancestor, because Stop waits for the very step the
// hook is running; call [Scope.Shutdown], which never blocks.
package di
import (
"context"
"errors"
"reflect"
"runtime"
"slices"
"strings"
"time"
)
// key identifies a service: its Go type. Keys compare by reflect.Type
// identity, so same-named types in different packages never collide. There
// is no name alongside the type: a second binding of one type is declared as
// a distinct type instead, which makes a mistaken reference a compile error.
type key struct{ t reflect.Type }
func (k key) String() string { return typeName(k.t) }
// pkgPath is the import path of the named type k stands for, walking through
// pointers as typeName does, since a pointer type is unnamed. It is empty for
// exactly the types typeName writes with reflect's own short spelling.
func (k key) pkgPath() string {
t := k.t
for t.Kind() == reflect.Pointer {
t = t.Elem()
}
return t.PkgPath()
}
func typeName(t reflect.Type) string {
if t.Kind() == reflect.Pointer {
return "*" + typeName(t.Elem())
}
if t.PkgPath() != "" {
return t.PkgPath() + "." + t.Name()
}
return t.String()
}
var (
ErrNotProvided = errors.New("not provided")
ErrCycle = errors.New("dependency cycle")
ErrStopped = errors.New("scope stopped")
)
// EventKind classifies an Event.
type EventKind string
const (
EventBuild EventKind = "build" // a constructor ran
EventStart EventKind = "start" // an OnStart hook ran
EventDrain EventKind = "drain" // an OnDrain hook ran
EventStop EventKind = "stop" // a worker was cancelled and/or an OnStop hook ran
EventShutdown EventKind = "shutdown" // Shutdown was called
)
// Event describes one lifecycle step. Observers receive it after the step
// completes, with its duration and error if any.
type Event struct {
Kind EventKind
Service string // the service, e.g. "*github.com/acme/app.DB"; empty for shutdown
// Package is the import path of the type Service names, e.g.
// "github.com/acme/app", so an observer can shorten or group by it
// without parsing Service. It is empty for a shutdown, and for a key
// whose type is unnamed, since reflect already writes those short.
Package string
Scope string // name of the scope that owns the instance
Site string // file:line of the registration; empty for shutdown
Module string // the Module the service was registered from; empty when none
Duration time.Duration
Err error
}
// Scope is a container. A Scope value handed to a constructor is a view over
// the same state that carries the current resolution path.
type Scope struct {
st *state // what this handle is a view over
r *resolver
module string // the Module registering through this handle, or ""
}
// New creates a root scope: a container with no parent.
func New() *Scope { return &Scope{st: newState("root", nil)} }
func newState(name string, parent *state) *state {
st := &state{
name: name,
parent: parent,
scoped: map[*binding]*instance{},
shutdownCh: make(chan struct{}),
}
st.reg.Store(emptyRegistry)
// One graph per container, shared by every scope under the root.
if parent != nil {
st.graph = parent.graph
} else {
st.graph = &graph{under: map[*resolver]map[*waitEdge]struct{}{}}
}
return st
}
// Child creates a scope that resolves through s. Stopping s stops its
// children first.
//
// A child made inside a constructor carries that constructor's resolution
// path, so a cycle through it is reported rather than deadlocking. The path
// goes inert when the constructor returns (see resolver.done), so a child kept
// for later, such as a request scope, resolves as an independent branch.
func (s *Scope) Child(name string) *Scope {
st := newState(name, s.st)
s.st.mu.Lock()
s.st.children = append(s.st.children, st)
s.st.mu.Unlock()
return &Scope{st: st, r: s.r, module: s.module}
}
func (s *Scope) view(r *resolver) *Scope { return &Scope{st: s.st, r: r, module: s.module} }
// A Module is a unit of wiring: a function that registers into a scope.
// Modules compose by ordinary function composition, and [Scope.Use] applies
// them in order. Every registration a module makes is attributed to it, so a
// collision between two modules is reported as one.
type Module func(*Scope)
// Use applies each module to this scope. A registration made while a module
// runs, directly, from a child the module opens, or later from a constructor
// the module registered, carries that module's name, which is the name of the
// function: register modules as named functions rather than closures, or the
// name is the enclosing function's.
func (s *Scope) Use(mods ...Module) {
for _, m := range mods {
m(&Scope{st: s.st, r: s.r, module: moduleName(m)})
}
}
// moduleName is the function's name, package-qualified and without the import
// path: "app.Storage" for a function Storage in package app.
func moduleName(m Module) string {
if m == nil {
return ""
}
fn := runtime.FuncForPC(reflect.ValueOf(m).Pointer())
if fn == nil {
return ""
}
name := fn.Name()
if i := strings.LastIndex(name, "/"); i >= 0 {
name = name[i+1:]
}
return name
}
// Observe registers fn to receive lifecycle events from this scope and every
// scope under it. Use it for logging and metrics.
func (s *Scope) Observe(fn func(Event)) {
s.st.mu.Lock()
defer s.st.mu.Unlock()
var obs []func(Event)
if p := s.st.observers.Load(); p != nil {
obs = slices.Clone(*p)
}
obs = append(obs, fn)
s.st.observers.Store(&obs)
}
// emit delivers ev to the observers of st and its ancestors. It takes no
// lock: every build in every request scope reports through the root.
func (st *state) emit(ev Event) {
for ; st != nil; st = st.parent {
if p := st.observers.Load(); p != nil {
for _, fn := range *p {
fn(ev)
}
}
}
}
// report emits the event for one lifecycle step of b, owned by this scope,
// that began at t0 and ended with err.
func (st *state) report(kind EventKind, b *binding, t0 time.Time, err error) {
st.emit(Event{
Kind: kind, Service: b.key.String(), Package: b.key.pkgPath(),
Scope: st.name, Site: b.site, Module: b.module,
Duration: time.Since(t0), Err: err,
})
}
// TB is the subset of testing.TB that Test needs.
type TB interface {
Helper()
Cleanup(func())
Errorf(format string, args ...any)
}
// Test returns a scope for a test: the modules register the graph under test,
// and the scope is stopped when the test ends, failing it if a stop hook
// errors. Override what you need faked after wiring and before resolving,
// saying so:
//
// s := di.Test(t, app.Production)
// s.Value(&DB{DSN: "sqlite://memory"}).Override()
// repo := s.Get[*Repo]()
func Test(tb TB, wire ...Module) *Scope {
tb.Helper()
s := New()
s.Use(wire...)
tb.Cleanup(func() {
if err := s.Stop(context.Background()); err != nil {
tb.Errorf("di: stopping test scope: %v", err)
}
})
return s
}
type ctxKey struct{}
// WithScope attaches s to ctx so handlers and their callees can reach it
// with FromContext.
func WithScope(ctx context.Context, s *Scope) context.Context {
return context.WithValue(ctx, ctxKey{}, s)
}
// FromContext returns the scope attached with WithScope, if any.
func FromContext(ctx context.Context) (*Scope, bool) {
s, ok := ctx.Value(ctxKey{}).(*Scope)
return s, ok
}
// Package dihttp connects a di.Scope to net/http.
//
// A [Middleware] gives every request its own child scope holding the
// *http.Request, so services that depend on the request are declared once in
// the application scope as Scoped and built per request. Handlers reach the
// scope through [di.FromContext], or are made with [Handle], which resolves a
// handler type from that scope and calls one of its methods. [Module]
// registers the middleware as a service; [NewMiddleware] makes one directly.
package dihttp
import (
"context"
"net/http"
"github.com/floatdrop/di"
)
// Middleware gives every request its own child scope of the application
// scope: the *http.Request is registered in it, the scope is attached to the
// request context, and it is stopped and detached when the handler returns.
// Stop failures reach the application scope's observers as EventStop with Err
// set. It has the usual middleware shape, so it wraps a handler directly or
// goes into a router's Use.
type Middleware func(http.Handler) http.Handler
// Module registers a Middleware over the scope it is applied to, so that a
// constructor wired into that scope can take one as a parameter:
//
// app.Use(dihttp.Module, api.Module)
//
// func NewServer(cfg Config, mw dihttp.Middleware) *http.Server {
// return &http.Server{Addr: cfg.Addr, Handler: mw(mux)}
// }
//
// This is a Provide closure rather than a wired constructor because the
// middleware needs the scope itself, to open a child per request.
func Module(s *di.Scope) {
s.Provide(func(s *di.Scope) Middleware { return NewMiddleware(s) })
}
// NewMiddleware makes a Middleware whose request scopes are children of s.
func NewMiddleware(s *di.Scope) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
req := s.Child("request")
// The handler and the constructors must see one *http.Request:
// routers write path values and the matched pattern into the
// request they are given, so a copy registered here would miss
// them.
r = r.WithContext(di.WithScope(r.Context(), req))
req.Value(r)
defer func() { _ = req.Stop(context.WithoutCancel(r.Context())) }()
next.ServeHTTP(w, r)
})
}
}
// Handle serves each request with a method of H, resolved from the request's
// scope. A method expression names both, so no type argument is needed:
//
// mux.Handle("GET /users/{id}", dihttp.Handle((*Users).Show))
//
// H is a service like any other: declared Scoped when it needs the request,
// and once for the application when it does not. A wiring failure at request
// time panics with the error, which net/http recovers and logs with the
// request; Validate at startup is what keeps that from happening.
func Handle[H any](method func(H, http.ResponseWriter, *http.Request)) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
req, ok := di.FromContext(r.Context())
if !ok {
panic("dihttp: no request scope on the context; is the Middleware above this handler?")
}
method(req.Get[H](), w, r)
})
}
// Package dislog logs a di.Scope's lifecycle events through log/slog.
//
// [New] turns a *slog.Logger into the function [di.Scope.Observe] takes:
//
// app.Observe(dislog.New(slog.Default()))
//
// It imports nothing outside the standard library, so any slog handler will
// do. Events arrive on the goroutine that did the work, one per constructor
// and per hook, so a slow handler slows the application down; a *slog.Logger
// is safe to share, and events do arrive from several goroutines at once.
package dislog
import (
"context"
"log/slog"
"path"
"strings"
"github.com/floatdrop/di"
)
// New returns a function for [di.Scope.Observe] that logs each event
// through l. The message is the event's kind, "build", "start", "drain",
// "stop" or "shutdown", and the attributes are the service, its scope, the
// module it was registered from when there is one, and how long the step took.
//
// A service is named the way it is written in Go, "service=*mail.Mailer",
// with the import path alongside as "pkg=github.com/acme/app/internal/mail".
// The two come from [di.Event.Service] and [di.Event.Package], so nothing is
// parsed; a key whose type is unnamed reports no package and keeps its whole
// name.
//
// An event carrying an error is logged at [slog.LevelError] with an "err"
// attribute and the registration site; anything else at [slog.LevelInfo], or
// the level [Level] sets. Pass [Site] to log the site every time.
func New(l *slog.Logger, opts ...Option) func(di.Event) {
cfg := options{level: slog.LevelInfo}
for _, o := range opts {
o(&cfg)
}
return func(ev di.Event) {
level := cfg.level
if ev.Err != nil {
level = slog.LevelError
}
if !l.Enabled(context.Background(), level) {
return
}
attrs := make([]slog.Attr, 0, 7)
if ev.Service != "" { // a shutdown names no service
attrs = append(attrs, slog.String("service", short(ev.Service, ev.Package)))
if ev.Package != "" {
attrs = append(attrs, slog.String("pkg", ev.Package))
}
}
attrs = append(attrs, slog.String("scope", ev.Scope))
if ev.Module != "" {
attrs = append(attrs, slog.String("module", ev.Module))
}
if ev.Kind != di.EventShutdown {
attrs = append(attrs, slog.Duration("duration", ev.Duration))
}
if ev.Site != "" && (cfg.site || ev.Err != nil) {
attrs = append(attrs, slog.String("site", ev.Site))
}
if ev.Err != nil {
attrs = append(attrs, slog.Any("err", ev.Err))
}
l.LogAttrs(context.Background(), level, string(ev.Kind), attrs...)
}
}
// short is the service name with its import path taken off:
// "*github.com/acme/app.DB" and "github.com/acme/app" become "*app.DB".
//
// The event carries both, so nothing is guessed: there is no stdlib splitter
// for a qualified type name and no correct heuristic, since a dot or a slash
// can belong to a type argument. An empty pkg is a type with no path to take
// off, or a shutdown, and its name is returned as it came. A generic
// instantiation keeps its type arguments, because only the prefix is trimmed.
func short(service, pkg string) string {
if pkg == "" {
return service
}
stars := 0
for stars < len(service) && service[stars] == '*' {
stars++
}
name, ok := strings.CutPrefix(service[stars:], pkg+".")
if !ok {
return service // not the shape the pair promises; report it whole
}
return service[:stars] + path.Base(pkg) + "." + name
}
// Option configures the observer New returns.
type Option func(*options)
type options struct {
level slog.Level
site bool
}
// Level sets the level an event that succeeded is logged at; [slog.LevelDebug]
// is the usual choice once an application is wired. A failure is logged at
// [slog.LevelError] whatever this says.
func Level(lv slog.Level) Option { return func(o *options) { o.level = lv } }
// Site includes the registration site, the file:line the service was
// registered at, on every event rather than only on the ones that failed.
func Site() Option { return func(o *options) { o.site = true } }
package di
// Rendering the graph. Nothing here takes part in resolution or teardown: it
// reads the edges resolve.go records while constructors run, under the
// instance's owning mutex and never two of those at once, and the dependency
// lists Wire declares, which never change after registration.
import (
"fmt"
"reflect"
"slices"
"strings"
)
// Explain renders what T resolves to and what it was built from: the
// dependency tree, each node with its lifetime, scope, lifecycle state and
// registration site, followed by what needed it.
//
// A built service has a recorded tree. One that has not been built is
// reported as such; if it was registered with Wire, its declared dependencies
// are drawn under it with dashed edges, each continuing as a recorded tree
// where built and a declared one where not, and "declared by" lists the
// unbuilt services that declare it. A closure that has not run ends its
// branch, and so does a key nothing provides. A key served by a group is
// explained member by member, and a dependency reached twice is expanded once
// and named on later visits.
//
// Explain builds nothing. It commits pending registrations as a resolution
// from this scope would, so a configuration this scope would reject is
// reported here by the same panic.
func (s *Scope) Explain[T any]() string {
k := key{t: reflect.TypeFor[T]()}
b, owner := s.lookup(k)
members := s.groupMembers(k)
if b == nil && len(members) == 0 {
return fmt.Sprintf("%s: not provided\n", k)
}
var sb strings.Builder
seen := map[*instance]bool{}
if b != nil {
s.explainOne(&sb, b, owner, seen)
}
for _, m := range members {
if sb.Len() > 0 {
sb.WriteString("\n")
}
s.explainOne(&sb, m.b, m.owner, seen)
}
return sb.String()
}
// found is a binding and the scope that registered it.
type found struct {
b *binding
owner *state
}
// groupMembers lists the group registered for k across the scope chain, in
// the order All resolves them.
func (s *Scope) groupMembers(k key) []found {
var out []found
for st := s.st; st != nil; st = st.parent {
st.freeze()
for _, b := range st.reg.Load().groups[k] {
out = append(out, found{b: b, owner: st})
}
}
return out
}
// explainOne renders one binding's tree, and the instances that needed it.
func (s *Scope) explainOne(sb *strings.Builder, b *binding, owner *state, seen map[*instance]bool) {
holder := owner
if b.scoped {
holder = s.st
}
holder.mu.Lock()
in := holder.instanceAt(b)
holder.mu.Unlock()
// A Scoped binding this scope has never resolved has no instance; a
// singleton always has one, built or not.
phase, deps, fresh := "not built", []dep(nil), true
if in != nil {
phase, deps, fresh = dep{in: in, holder: holder}.inspect()
}
sb.WriteString(describe(b, holder, phase) + "\n")
var by []dep
if fresh {
s.declaredInto(sb, b, holder, "", seen, map[*binding]bool{b: true})
} else {
seen[in] = true
explainInto(sb, deps, "", seen)
by = dependentsOf(s.st.root(), in)
}
if len(by) > 0 {
names := make([]string, len(by))
for i, d := range by {
names[i] = d.in.b.key.String() + " in " + d.holder.name
}
sb.WriteString("needed by: " + strings.Join(names, ", ") + "\n")
}
if declared := s.declaredBy(b, by); len(declared) > 0 {
sb.WriteString("declared by: " + strings.Join(declared, ", ") + "\n")
}
}
// declaredInto draws the dependencies b declares under an unbuilt node, with
// dashed edges, looking each up from holder as the build would. A built one
// continues as its recorded tree; an unbuilt one as its own declaration, or
// ends the branch if it is a closure. drawn keeps a declared binding from
// being expanded twice, which a cycle needs.
func (s *Scope) declaredInto(sb *strings.Builder, b *binding, holder *state, prefix string, seen map[*instance]bool, drawn map[*binding]bool) {
edges := declared(b, holder)
for i, e := range edges {
branch, pad := "├╌╌ ", "│ "
if i == len(edges)-1 {
branch, pad = "└╌╌ ", " "
}
sb.WriteString(prefix + branch)
target, owner := e.b, e.owner
if target == nil {
sb.WriteString(e.k.String() + ": not provided\n")
continue
}
th := owner
if target.scoped {
th = holder
}
th.mu.Lock()
in := th.instanceAt(target)
th.mu.Unlock()
if in != nil {
if seen[in] {
sb.WriteString(target.key.String() + ": see above\n")
continue
}
if phase, next, fresh := (dep{in: in, holder: th}).inspect(); !fresh {
seen[in] = true
sb.WriteString(describe(target, th, phase) + "\n")
explainInto(sb, next, prefix+pad, seen)
continue
}
}
if drawn[target] {
sb.WriteString(target.key.String() + ": see above\n")
continue
}
drawn[target] = true
sb.WriteString(describe(target, th, "not built") + "\n")
s.declaredInto(sb, target, th, prefix+pad, seen, drawn)
}
}
// declaredBy lists the Wire bindings, in any scope of the container, that
// declare b's key and would resolve it to b from their own scope, leaving out
// the instances already named as needing it. It reads only committed
// registrations and commits nothing: a root Explain must not be the call that
// rejects a descendant's pending batch.
func (s *Scope) declaredBy(b *binding, except []dep) []string {
var out []string
for _, st := range walkScopes(s.st.root()) {
for _, d := range st.live() {
if d == b || slices.ContainsFunc(except, func(e dep) bool { return e.in.b == d }) {
continue
}
if d.inner != b && (!slices.Contains(d.wants, b.key) || peek(st, b.key) != b) {
continue
}
out = append(out, d.key.String()+" in "+st.name)
}
}
return out
}
// peek is lookup without the freeze: the binding k resolves to from st among
// the registrations already committed.
func peek(st *state, k key) *binding {
for ; st != nil; st = st.parent {
if b, ok := st.reg.Load().index[k]; ok {
return b
}
}
return nil
}
// explainInto writes one level of the tree and recurses.
func explainInto(sb *strings.Builder, deps []dep, prefix string, seen map[*instance]bool) {
for i, d := range deps {
branch, pad := "├── ", "│ "
if i == len(deps)-1 {
branch, pad = "└── ", " "
}
sb.WriteString(prefix + branch)
if seen[d.in] {
// The same instance by another route: named without its subtree,
// which says it is one value rather than two of a type.
sb.WriteString(d.in.b.key.String() + ": see above\n")
continue
}
seen[d.in] = true
phase, next, _ := d.inspect()
sb.WriteString(describe(d.in.b, d.holder, phase) + "\n")
explainInto(sb, next, prefix+pad, seen)
}
}
// Graph renders everything built in this scope and its descendants as
// Graphviz DOT: one box per instance, one cluster per scope that holds any,
// and an arrow from each instance to what its constructor resolved.
//
// It changes nothing, not even the pending registrations, so it is safe to
// call from a handler or a hook. Nodes are numbered in creation and build
// order, so the same run renders the same document. A stopped scope no longer
// holds its instances and contributes nothing. Use Explain for one service in
// full.
func (s *Scope) Graph() string {
scopes := walkScopes(s.st)
type node struct {
d dep
id int
phase string
}
ids := map[*instance]int{}
byScope := make([][]node, len(scopes))
var all []node
for i, st := range scopes {
st.mu.Lock()
built := slices.Clone(st.started)
st.mu.Unlock()
for _, in := range built {
d := dep{in: in, holder: st}
phase, _, _ := d.inspect()
n := node{d: d, id: len(all), phase: phase}
ids[in] = n.id
all = append(all, n)
byScope[i] = append(byScope[i], n)
}
}
var sb strings.Builder
sb.WriteString("digraph di {\n")
sb.WriteString(" rankdir=LR;\n")
sb.WriteString(" node [shape=box, fontname=\"monospace\"];\n")
for i, st := range scopes {
if len(byScope[i]) == 0 {
continue
}
fmt.Fprintf(&sb, " subgraph cluster%d {\n", i)
fmt.Fprintf(&sb, " label=%s;\n", dotLabel(scopePath(st, s.st)))
for _, n := range byScope[i] {
fmt.Fprintf(&sb, " n%d [label=%s];\n", n.id,
dotLabel(n.d.in.b.key.String(), lifetime(n.d.in.b)+", "+n.phase))
}
sb.WriteString(" }\n")
}
// Edges last and outside every cluster: one declared inside a cluster is
// drawn wrong when it crosses the boundary.
for _, n := range all {
_, deps, _ := n.d.inspect()
for _, d := range deps {
if to, ok := ids[d.in]; ok {
fmt.Fprintf(&sb, " n%d -> n%d;\n", n.id, to)
}
// An edge into a stopped scope, or one above the scope Graph was
// called on, is dropped rather than given a node.
}
}
sb.WriteString("}\n")
return sb.String()
}
// ---- rendering helpers -----------------------------------------------------
// inspect reads one instance's phase and edges together, the only critical
// section a rendering takes, and reports whether the instance is unbuilt, in
// which case what the binding declares stands in for the edges. Nothing is
// held across the recursion, so two scopes' mutexes are never held at once.
func (d dep) inspect() (phase string, deps []dep, fresh bool) {
d.holder.mu.Lock()
defer d.holder.mu.Unlock()
return phaseWord(d.in), slices.Clone(d.in.deps), d.in.ph == phaseNew
}
// phaseWord names where an instance is in its lifecycle. Called with the
// owning state's mutex held.
func phaseWord(in *instance) string {
switch in.ph {
case phaseNew:
return "not built"
case phaseBuilding:
return "building"
case phaseBuilt:
return "built"
case phaseStarting:
return "starting"
case phaseStarted:
return "started"
case phaseStopped:
return "stopped"
case phaseFailed:
if in.err != nil {
return "failed: " + in.err.Error()
}
return "failed"
}
return "unknown"
}
// lifetime names how a binding is kept, in the words the API uses.
func lifetime(b *binding) string {
life := "singleton"
switch {
case b.isValue:
life = "value"
case b.scoped:
life = "scoped"
}
if b.group {
life += " group member"
}
if b.inner != nil {
life += " wrapper"
}
return life
}
// describe is one line of a tree: the service, where it lives, its phase and
// its registration site.
func describe(b *binding, holder *state, phase string) string {
attrs := []string{lifetime(b) + " in " + holder.name}
if b.eager {
attrs = append(attrs, "eager")
}
attrs = append(attrs, phase)
return fmt.Sprintf("%s: %s (provided at %s)", b.key, strings.Join(attrs, ", "), b.site)
}
// dependentsOf finds the built instances whose constructors resolved target.
// It searches from the container root, since a dependent lives in the scope
// that holds it or below, never above what it depends on.
func dependentsOf(from *state, target *instance) []dep {
var out []dep
for _, st := range walkScopes(from) {
st.mu.Lock()
for _, in := range st.started {
if slices.ContainsFunc(in.deps, func(d dep) bool { return d.in == target }) {
out = append(out, dep{in: in, holder: st})
}
}
st.mu.Unlock()
}
return out
}
// walkScopes lists st and every scope under it, parents before children and
// in creation order, so a rendering is stable across runs.
func walkScopes(st *state) []*state {
st.mu.Lock()
children := slices.Clone(st.children)
st.mu.Unlock()
out := []*state{st}
for _, c := range children {
out = append(out, walkScopes(c)...)
}
return out
}
// root returns the topmost scope of this container.
func (st *state) root() *state {
for st.parent != nil {
st = st.parent
}
return st
}
// scopePath names st relative to from, so two scopes with the same name are
// told apart by where they hang.
func scopePath(st, from *state) string {
var parts []string
for ; st != nil; st = st.parent {
parts = append(parts, st.name)
if st == from {
break
}
}
slices.Reverse(parts)
return strings.Join(parts, "/")
}
// dotEscape is what a DOT quoted string needs escaped inside it.
var dotEscape = strings.NewReplacer(`\`, `\\`, `"`, `\"`)
// dotLabel quotes the parts as one DOT label, one per line. Scope names come
// from the caller, so they are escaped.
func dotLabel(parts ...string) string {
esc := make([]string, len(parts))
for i, p := range parts {
esc[i] = dotEscape.Replace(p)
}
return `"` + strings.Join(esc, `\n`) + `"`
}
// Modules renders the modules registered into this scope and its ancestors:
// what each provides, what it needs and which module serves it, what it
// wraps, and which of its constructors are closures whose needs are unknown
// until they run. A need only a resolving scope can provide is reported as
// owed, as Validate reports it. A dependency a module serves for itself is
// left out. Registrations made outside any module are grouped as "registered
// directly".
//
// Like Explain, it builds nothing and commits pending registrations as a
// resolution would, so a configuration this scope would reject is reported
// by the same panic.
func (s *Scope) Modules() string {
var chain []*state
for st := s.st; st != nil; st = st.parent {
st.freeze()
chain = append(chain, st)
}
type module struct {
name string
provides, needs, wraps, unchecked []string
seen map[string]bool
}
var order []*module
byName := map[string]*module{}
get := func(name string) *module {
if m := byName[name]; m != nil {
return m
}
m := &module{name: name, seen: map[string]bool{}}
byName[name] = m
order = append(order, m)
return m
}
// Deduped per section as well as per line: a key is listed under
// provides and again under unchecked.
add := func(m *module, list *[]string, line string) {
id := fmt.Sprintf("%p:%s", list, line)
if !m.seen[id] {
m.seen[id] = true
*list = append(*list, line)
}
}
// Ancestors first, so the report reads top-down like the scope tree.
for _, st := range slices.Backward(chain) {
for _, b := range st.live() {
m := get(moduleLabel(b))
if b.inner != nil {
add(m, &m.wraps, shortName(b.key.t)+" ← "+moduleLabel(b.inner))
} else {
add(m, &m.provides, shortName(b.key.t))
}
switch {
case b.isValue:
continue
case b.wants == nil:
add(m, &m.unchecked, shortName(b.key.t))
continue
}
holder := st
if b.scoped {
holder = s.st
}
for _, k := range b.wants {
dep, _ := (&Scope{st: holder}).lookup(k)
var from string
switch {
case dep == nil && b.scoped:
from = "owed to a resolving scope"
case dep == nil:
from = "not provided"
case moduleLabel(dep) == m.name:
continue // the module's own business
default:
from = moduleLabel(dep)
}
add(m, &m.needs, shortName(k.t)+" ← "+from)
}
}
}
var sb strings.Builder
for _, m := range order {
sb.WriteString(m.name + "\n")
section := func(label string, lines []string) {
for i, l := range lines {
if i == 0 {
fmt.Fprintf(&sb, " %-10s %s\n", label, l)
} else {
fmt.Fprintf(&sb, " %-10s %s\n", "", l)
}
}
}
if len(m.provides) > 0 {
section("provides", []string{strings.Join(m.provides, ", ")})
}
section("wraps", m.wraps)
section("needs", m.needs)
if len(m.unchecked) > 0 {
section("unchecked", []string{strings.Join(m.unchecked, ", ") + " (closures: needs known when they run)"})
}
}
return sb.String()
}
// moduleLabel names the module a binding was registered from, or says that
// there was none.
func moduleLabel(b *binding) string {
if b.module == "" {
return "registered directly"
}
return b.module
}
// shortName is a key with its package named as code names it, storage.Store
// rather than the import path, to match the module labels beside it. Explain
// keeps the full path, since an error must not confuse two packages of one
// name.
func shortName(t reflect.Type) string {
if t.Kind() == reflect.Pointer {
return "*" + shortName(t.Elem())
}
if t.PkgPath() != "" {
return t.PkgPath()[strings.LastIndex(t.PkgPath(), "/")+1:] + "." + t.Name()
}
return t.String()
}
package di
// The lifecycle: an instance's phase machine, the hooks that move it through
// its start, drain and stop steps, and Start and Stop, which drive that
// machine for a scope tree. Every phase is read and written under the owning
// state's mutex, and every user hook is called through callHook.
import (
"context"
"errors"
"fmt"
"slices"
"sync/atomic"
"time"
)
// phase is an instance's position in the build/start/stop sequence, read and
// written only under the owning state's mutex.
type phase int8
const (
phaseNew phase = iota // no value yet
phaseBuilding // a resolution has claimed the build step
phaseBuilt // constructor ran; the start step has not
phaseStarting // a goroutine has claimed the start step
phaseStarted // the start step succeeded
phaseFailed // the build or the start step failed
phaseStopped // the stop step ran, or was skipped for good
)
// drainPhase tracks OnDrain as phase tracks the other steps: a drain in
// progress is waited for, so it must be told from one that has finished.
type drainPhase int8
const (
drainNone drainPhase = iota // OnDrain has not been considered
draining // a Stop is running OnDrain now
drained // OnDrain ran, or was skipped for good
)
// dep is one recorded dependency edge: an instance a constructor resolved,
// and the scope holding it. The holder is carried because an instance does
// not know its scope, and the scope it was resolved from may be gone when the
// edge is read.
type dep struct {
in *instance
holder *state
}
// instance is one built value of a binding, owned by the state that stops it.
type instance struct {
b *binding
ph phase // guarded by the owning state's mutex
value any
err error // guarded by the owning state's mutex
// settled is set when the build step has finished and value and err are
// final.
settled bool // guarded by the owning state's mutex
dr drainPhase // guarded by the owning state's mutex
// ready summarises ph, err and settled for the warm path, which reads it
// without the mutex: set, the value is final and may be returned without
// waiting. Written only by refresh.
ready atomic.Bool
// deps are the services this instance's constructor resolved, in order,
// each once. Guarded by the owning state's mutex, since a constructor may
// resolve from several goroutines. Only Explain and Graph read them.
deps []dep
// Each step another goroutine may have to wait for has a channel closed
// when the step is done. The first goroutine that has to wait makes it
// (waitOn); the owner of the step closes it if it exists (wake). Both
// happen under the owning state's mutex, in the critical section that
// changes the phase, so a waiter never picks up the channel of a later
// step and an owner that finishes first leaves nil behind. Nil is the
// normal state: nothing is allocated unless someone waits. Never receive
// from one of these fields directly; go through waitOn.
settledCh chan struct{} // closed by settle: value and err are final
startingCh chan struct{} // closed when the start step is no longer in flight
drainedCh chan struct{} // closed when OnDrain has finished
// builder is the resolution running the build step, guarded by the
// container graph's mutex. It is the edge that makes a cycle between
// concurrent builds visible.
builder *resolver
// Worker bookkeeping, ordered by the phase machine rather than a mutex:
// cancel and runDone are written by start and read by stop, which runs
// only after startClaimed has moved the phase past phaseStarting under
// the owning mutex. runErr is written before runDone is closed and read
// only after a receive from it.
cancel context.CancelFunc
runDone chan struct{}
runErr error
}
// refresh recomputes ready: settled without an error, and built with no start
// step in flight, or started. That is the state in which await's locked loop
// returns the value at once. Called under the owning state's mutex after
// every change to ph, err or settled; a site that forgets it leaves ready
// stale, and only a stale false is harmless.
func (in *instance) refresh() {
in.ready.Store(in.settled && in.err == nil && (in.ph == phaseBuilt || in.ph == phaseStarted))
}
// wake closes a step's channel if a waiter made one.
func wake(ch chan struct{}) {
if ch != nil {
close(ch)
}
}
// waitOn returns a step's channel, making it on first use. Called under the
// owning state's mutex, in the critical section that read the phase.
func waitOn(ch *chan struct{}) chan struct{} {
if *ch == nil {
*ch = make(chan struct{})
}
return *ch
}
// once is a teardown phase that runs at most once per scope: the first caller
// runs it, and every later or concurrent caller waits for that run, bounded by
// its own context. Its fields are guarded by the state's mutex, so claiming
// the phase and recording what the claim decided are one critical section.
type once struct {
done chan struct{} // made by the claimer, closed once its run has finished
err error // that run's result
}
// claim reports whether this caller owns the run. The owner must call settle
// exactly once; everyone else calls wait. claimed, if non-nil, runs in the
// critical section that picks the winner.
func (o *once) claim(st *state, claimed func()) bool {
st.mu.Lock()
defer st.mu.Unlock()
if o.done != nil {
return false
}
o.done = make(chan struct{})
if claimed != nil {
claimed()
}
return true
}
// settle publishes the run's result and releases the waiters.
func (o *once) settle(st *state, err error) {
st.mu.Lock()
o.err = err
st.mu.Unlock()
close(o.done)
}
// wait blocks until the owning run has finished and reports its error, or
// reports false if ctx expires first. Only a caller whose claim returned
// false may wait: an unclaimed phase has no channel.
func (o *once) wait(st *state, ctx context.Context) (finished bool, err error) {
st.mu.Lock()
done := o.done
st.mu.Unlock()
select {
case <-done:
case <-ctx.Done():
return false, nil
}
st.mu.Lock()
defer st.mu.Unlock()
return true, o.err
}
// hookKey marks a context as belonging to a lifecycle hook.
type hookKey struct{}
// inHook tags the context a hook is called with, so a Stop made with that
// context can report the misuse instead of waiting for the step the caller is
// itself running. A hook that passes a context of its own is not seen.
func inHook(ctx context.Context, st *state) context.Context {
return context.WithValue(ctx, hookKey{}, st)
}
// hookOwner returns the scope whose hook ctx belongs to, or nil.
func hookOwner(ctx context.Context) *state {
st, _ := ctx.Value(hookKey{}).(*state)
return st
}
// callHook runs a lifecycle hook and reports a panic as its error. A panic
// that escaped a hook would leave stopOnce claimed and never settled: every
// later Stop would wait for ever, and nothing behind it would be released.
func callHook(hook func(context.Context, any) error, ctx context.Context, v any) (err error) {
defer func() {
if rec := recover(); rec != nil {
if a, ok := rec.(abort); ok {
err = a.err // a nested resolution failed; report that cause
} else {
err = fmt.Errorf("panic: %v", rec)
}
}
}()
return hook(ctx, v)
}
// start runs OnStart and launches the worker. The worker's context is
// detached from ctx so that Stop cancels it, in dependency order, rather than
// the application context.
func (in *instance) start(ctx context.Context, owner *state) error {
b := in.b
if b.onStart != nil {
t0 := time.Now()
err := callHook(b.onStart, inHook(ctx, owner), in.value)
owner.report(EventStart, b, t0, err)
if err != nil {
return err
}
}
if b.worker != nil {
rctx, cancel := context.WithCancel(context.WithoutCancel(ctx))
in.cancel, in.runDone = cancel, make(chan struct{})
hctx := inHook(rctx, owner)
go func() {
defer close(in.runDone)
// Through callHook, so a panicking worker is a failed worker
// rather than a crash with no OnStop and no event.
err := callHook(b.worker, hctx, in.value)
if err == nil {
return
}
if rctx.Err() != nil && onlyCancellation(err) {
return // we cancelled it and it reported just that
}
// The worker's own failure goes to Shutdown even if the scope
// was already stopping. It is wrapped once and kept, so Stop and
// Run report one failure rather than two.
in.runErr = fmt.Errorf("di: %s: %w", b.key, err)
(&Scope{st: owner}).Shutdown(in.runErr)
}()
}
return nil
}
// claim takes the start step for this goroutine, or reports that another one
// has it, the instance is past starting, or the scope has stopped.
func (in *instance) claim(owner *state) bool {
owner.mu.Lock()
defer owner.mu.Unlock()
if in.ph != phaseBuilt || owner.isStopped() {
return false
}
in.ph = phaseStarting
in.refresh()
return true
}
// startClaimed runs the start step of an instance in phaseStarting and settles
// the phase, releasing whoever waits for it. Only a hook that returned has
// started its service: a panic is a failed start, as for a constructor. The
// failure is recorded on the instance as well as returned, so a resolution
// that waited reports it too.
func (in *instance) startClaimed(ctx context.Context, owner *state) error {
err := in.start(ctx, owner)
owner.mu.Lock()
if err == nil {
in.ph = phaseStarted
} else {
in.ph = phaseFailed
if in.err == nil {
in.err = fmt.Errorf("di: starting %s (provided at %s): %w", in.b.key, in.b.where(), err)
}
}
in.refresh()
wake(in.startingCh)
owner.mu.Unlock()
return err
}
// paired reports whether the instance's OnStop has an OnStart to pair with:
// the binding declares one and the scope has been started. It walks the
// parent chain, so it is answered before the owning state's mutex is taken.
func (in *instance) paired(owner *state) bool {
return in.b.onStart != nil && owner.everStarted()
}
// owes reports whether the instance owes its drain and stop steps: it
// started, or it was built with no start step to pair with, so OnStop is a
// plain destructor. Called with the owning state's mutex held.
func (in *instance) owes(paired bool) bool {
return in.ph == phaseStarted || (in.ph == phaseBuilt && !paired)
}
// stopIfNeeded runs the stop step once, if it is owed, after waiting out any
// step another goroutine is still running for the instance, so the release
// never runs against a value a hook holds. That wait is what makes Stop
// synchronous, and it is safe because a hook may not Stop its own scope.
//
// If ctx expires first, the release is still owed and nothing else will reach
// the instance, since Stop took it off the scope's list. So the deadline ends
// the caller's wait, not the teardown, which finishes on its own goroutine
// with the spent deadline dropped.
func (in *instance) stopIfNeeded(ctx context.Context, owner *state) error {
paired := in.paired(owner)
for {
owner.mu.Lock()
step, what := in.outstanding()
if step == nil {
owed := in.owes(paired)
in.ph = phaseStopped
in.refresh()
owner.mu.Unlock()
if !owed {
return nil
}
return in.stop(ctx, owner)
}
owner.mu.Unlock()
select {
case <-step:
case <-ctx.Done():
go func() { _ = in.stopIfNeeded(context.WithoutCancel(ctx), owner) }()
return fmt.Errorf("di: stopping %s: %s did not return: %w", in.b.key, what, ctx.Err())
}
}
}
// outstanding names the step another goroutine is running for this instance,
// with the channel it will close, or nil. Called with the owning state's
// mutex held, so the phase and the channel are read together.
func (in *instance) outstanding() (chan struct{}, string) {
switch {
case in.ph == phaseStarting:
return waitOn(&in.startingCh), "OnStart"
case in.dr == draining:
return waitOn(&in.drainedCh), "OnDrain"
}
return nil, ""
}
// drainIfNeeded runs OnDrain once, if it is owed, and reports whether this
// call ran or waited for the hook, so a drain pass can tell that it did work.
// A drain another Stop has begun is waited for, or this Stop would go on to
// run OnStop under it. A start step in flight is waited for as well, since a
// service that is starting owes a drain as soon as it has started.
func (in *instance) drainIfNeeded(ctx context.Context, owner *state) (bool, error) {
b := in.b
if b.onDrain == nil {
return false, nil
}
paired := in.paired(owner)
for {
owner.mu.Lock()
if in.dr == drained {
owner.mu.Unlock()
return false, nil
}
if in.dr == draining {
done := waitOn(&in.drainedCh)
owner.mu.Unlock()
select {
case <-done:
return true, nil
case <-ctx.Done():
return true, fmt.Errorf("di: draining %s: another Stop did not finish OnDrain: %w", b.key, ctx.Err())
}
}
if in.ph == phaseStarting {
starting := waitOn(&in.startingCh)
owner.mu.Unlock()
select {
case <-starting:
continue
case <-ctx.Done():
return false, fmt.Errorf("di: draining %s: OnStart did not return: %w", b.key, ctx.Err())
}
}
if !owner.isStopped() && in.ph == phaseBuilt && paired {
// Built and waiting for its start step, so whether it will owe a
// drain is not decided yet: left alone rather than marked drained.
// A claim announces itself and sends the sweep round again; a seal
// nothing announced to means it never starts and owes nothing.
owner.mu.Unlock()
return false, nil
}
if owner.isStopped() || !in.owes(paired) {
// Not owed, or built into a scope whose own Stop is past draining
// while an ancestor's sweep is still running: winding down for
// work it can no longer take is not what the hook is for.
in.dr = drained
owner.mu.Unlock()
return false, nil
}
in.dr = draining
owner.mu.Unlock()
break
}
t0 := time.Now()
err := callHook(b.onDrain, inHook(ctx, owner), in.value)
owner.report(EventDrain, b, t0, err)
owner.mu.Lock()
in.dr = drained
wake(in.drainedCh)
owner.mu.Unlock()
if err != nil {
return true, fmt.Errorf("di: draining %s: %w", b.key, err)
}
return true, nil
}
// stop cancels the worker, waits for it within ctx, then runs OnStop. A
// worker that outlasts ctx still holds the value, so the missed deadline is
// reported and the release finishes when the worker returns.
func (in *instance) stop(ctx context.Context, owner *state) error {
b := in.b
if in.cancel == nil && b.onStop == nil {
return nil
}
t0 := time.Now()
var errs []error
if in.cancel != nil {
in.cancel()
select {
case <-in.runDone:
if in.runErr != nil {
errs = append(errs, in.runErr)
}
case <-ctx.Done():
err := fmt.Errorf("di: stopping %s: worker did not return: %w", b.key, ctx.Err())
if b.onStop == nil {
owner.report(EventStop, b, t0, err)
return err
}
go in.releaseAfterWorker(context.WithoutCancel(ctx), owner, err)
return err
}
}
if b.onStop != nil {
if err := callHook(b.onStop, inHook(ctx, owner), in.value); err != nil {
errs = append(errs, fmt.Errorf("di: stopping %s: %w", b.key, err))
}
}
err := errors.Join(errs...)
owner.report(EventStop, b, t0, err)
return err
}
// releaseAfterWorker finishes a stop step whose worker outlasted Stop's
// context. missed is what Stop returned; the instance's single EventStop is
// emitted here and carries it with the release's own result.
func (in *instance) releaseAfterWorker(ctx context.Context, owner *state, missed error) {
<-in.runDone
b := in.b
t0 := time.Now()
errs := []error{missed}
if in.runErr != nil {
errs = append(errs, in.runErr)
}
if err := callHook(b.onStop, inHook(ctx, owner), in.value); err != nil {
errs = append(errs, fmt.Errorf("di: stopping %s: %w", b.key, err))
}
owner.report(EventStop, b, t0, errors.Join(errs...))
}
// onlyCancellation reports whether err says nothing beyond context.Canceled.
// errors.Is would call errors.Join(ctx.Err(), failure) a cancellation and
// drop the failure with it.
func onlyCancellation(err error) bool {
if err == context.Canceled {
return true
}
switch u := err.(type) {
case interface{ Unwrap() []error }:
errs := u.Unwrap()
if len(errs) == 0 {
return false
}
for _, e := range errs {
if !onlyCancellation(e) {
return false
}
}
return true
case interface{ Unwrap() error }:
inner := u.Unwrap()
return inner != nil && onlyCancellation(inner)
}
return false
}
// Start builds every Eager binding in registration order, then runs the
// start step of everything built so far, in build order. If a constructor or
// a start step fails, the scope is stopped, rolling back the services that
// did start, child scopes included; a service built but never started is not
// stopped, so acquire resources in OnStart when the binding declares one.
//
// After Start returns, a service built later starts as part of being built.
// Start may be called once, and builds only this scope's own Eager bindings.
func (s *Scope) Start(ctx context.Context) error {
// The rollback detaches the caller's context: an already-cancelled ctx
// must not skip the teardown.
return s.start(ctx, func() (context.Context, func()) {
return context.WithoutCancel(ctx), func() {}
})
}
// start is Start with the rollback context supplied by the caller: Start
// detaches the caller's context, Run applies its StopTimeout and signals.
func (s *Scope) start(ctx context.Context, rollbackCtx func() (context.Context, func())) (err error) {
defer recoverAbort(&err)
s.st.freeze()
if !s.st.startCtx.CompareAndSwap(nil, &ctx) {
return errors.New("di: Start called twice")
}
eager := s.st.reg.Load().eager // a registry is never written to; a later freeze stores a new one
if err := s.buildEager(eager); err != nil {
return errors.Join(err, s.rollback(rollbackCtx))
}
s.st.running.Store(true)
// Anything built before the flag was set is still waiting here, and
// starting one service may build more.
for {
in, owner := s.st.claimNext()
if in == nil {
if s.st.isStopped() {
return fmt.Errorf("di: Start: %w", ErrStopped)
}
return nil
}
if !in.gateStart(owner) {
continue // the scope sealed and stopped first; claimNext now says so
}
if err := in.startClaimed(ctx, owner); err != nil {
err = fmt.Errorf("di: starting %s: %w", in.b.key, err)
return errors.Join(err, s.rollback(rollbackCtx))
}
}
}
func (s *Scope) rollback(mk func() (context.Context, func())) error {
ctx, cancel := mk()
defer cancel()
return s.Stop(ctx)
}
// buildEager builds the eager bindings, turning a constructor failure into an
// error rather than letting it unwind past Start's rollback.
func (s *Scope) buildEager(eager []*binding) (err error) {
defer recoverAbort(&err)
for _, b := range eager {
if b.group {
// A group member is not reachable by key.
s.enter().resolve(b, s.st)
continue
}
// By key, so whichever registration owns the key is what gets built.
s.enter().get(b.key)
}
return nil
}
// claimNext claims the start step of the next built-but-unstarted instance
// in this scope or a descendant, in build order, or nil for a stopped scope:
// a claim gateStart refused leaves its instance built, and the loop in start
// would otherwise find it again for ever.
func (st *state) claimNext() (*instance, *state) {
st.mu.Lock()
if st.isStopped() {
st.mu.Unlock()
return nil, nil
}
for _, in := range st.started {
if in.ph == phaseBuilt {
in.ph = phaseStarting
in.refresh()
st.mu.Unlock()
return in, st
}
}
children := slices.Clone(st.children)
st.mu.Unlock()
for _, c := range children {
if in, owner := c.claimNext(); in != nil {
return in, owner
}
}
return nil, nil
}
// Context returns the context passed to Start (or Run) on this scope or the
// nearest started ancestor, so constructors can dial with a deadline. Before
// Start it returns context.Background().
func (s *Scope) Context() context.Context {
if ctx, _ := s.st.runContext(); ctx != nil {
return ctx
}
return context.Background()
}
// Stop winds the scope down in three phases. First it drains: OnDrain hooks
// run from the innermost scope outwards, in reverse build order, while every
// scope still resolves, so work in flight can finish; a service or child scope
// that phase brings into being is drained too. Then the scope is marked
// stopped and its child scopes are stopped. Then OnStop hooks run in reverse
// build order. A service is stopped only if it started, or if it declares no
// OnStart, in which case OnStop is a plain destructor. Every failure is
// reported.
//
// Stop is synchronous: it waits out a start step, a drain hook or a worker
// another goroutine is still running, so when it returns the teardown has
// happened. Only an expired ctx cuts that short; the missed deadline is
// reported and the release finishes on a goroutine of its own, reaching
// observers. Afterwards the scope and its descendants refuse to resolve with
// ErrStopped, and a stopped child scope is detached from its parent.
//
// Stop is idempotent and concurrent calls are safe: the first tears the scope
// down and the others wait for it and report its result, bounded by their own
// context. Because Stop waits, a hook must not call Stop on its own scope or
// an ancestor; a hook that passes on its own context gets an error saying so.
// Call Shutdown, which never blocks.
func (s *Scope) Stop(ctx context.Context) error {
if h := hookOwner(ctx); h != nil && h.descendsFrom(s.st) {
return fmt.Errorf("di: a lifecycle hook of scope %s called Stop on scope %s, which it is inside: call Shutdown instead", h.name, s.st.name)
}
if !s.st.stopOnce.claim(s.st, func() {
if s.st.stopCtx == nil {
s.st.stopCtx = ctx // the first Stop owns it; a later call must not clobber it
}
}) {
finished, err := s.st.stopOnce.wait(s.st, ctx)
if !finished {
return fmt.Errorf("di: waiting for scope %s to stop: %w", s.st.name, ctx.Err())
}
return err
}
err := s.teardown(ctx)
s.st.stopOnce.settle(s.st, err)
return err
}
// teardown is the body of the first Stop.
func (s *Scope) teardown(ctx context.Context) error {
errs := []error{s.drain(ctx)}
s.st.mu.Lock()
children := slices.Clone(s.st.children)
started := s.st.started
s.st.started = nil // stopped was stored by drain's seal, before this snapshot
var wrappers []*binding
for _, b := range s.st.reg.Load().all {
if b.inner != nil {
wrappers = append(wrappers, b)
}
}
for _, b := range s.st.pending {
if b.inner != nil {
wrappers = append(wrappers, b)
}
}
s.st.mu.Unlock()
// The scope serves nothing from here on, so its wrappers stop guarding
// what they wrap. Outside the mutex: the binding may be an ancestor's.
for _, w := range wrappers {
w.inner.unwrap(w)
}
for _, c := range children {
errs = append(errs, (&Scope{st: c}).Stop(ctx))
}
errs = append(errs, stopAll(ctx, s.st, started))
if p := s.st.parent; p != nil {
p.mu.Lock()
p.children = slices.DeleteFunc(p.children, func(c *state) bool { return c == s.st })
p.mu.Unlock()
}
return errors.Join(errs...)
}
// drain runs the OnDrain hooks of this scope's subtree, innermost first and in
// reverse build order, and then seals the phase, which marks the scope
// stopped. Only the first drain of a scope runs its hooks; a Stop that reaches
// the scope by another route waits for it, or it would start releasing what
// those hooks are still using.
func (s *Scope) drain(ctx context.Context) error {
g0 := s.st.drainGen.Load()
var r *drainRun
newRun := func() *drainRun {
root := &drainScope{st: s.st, ours: true}
return &drainRun{root: root, seen: map[*state]*drainScope{s.st: root}}
}
claimed := s.st.drainOnce.claim(s.st, nil)
var err error
if claimed {
r = newRun()
err = r.sweepAll(ctx)
} else {
// The owner settles the phase with what this scope's own hooks
// reported, and every Stop of the scope reports that.
finished, werr := s.st.drainOnce.wait(s.st, ctx)
if !finished {
werr = fmt.Errorf("di: waiting for scope %s to drain: %w", s.st.name, ctx.Err())
}
err = werr
}
// Whoever ran the sweep, this Stop seals the scope against anything
// announced since the sweep began, and sweeps again if there was any.
for !s.st.seal(ctx, g0) {
g0 = s.st.drainGen.Load()
if r == nil {
r = newRun()
}
err = errors.Join(err, r.sweepAll(ctx))
}
if claimed {
s.st.drainOnce.settle(s.st, err) // this scope's phase is the last to end
}
return err
}
// seal ends the drain phase and marks the scope stopped, unless drain work was
// announced in the subtree since g0; then it reports false and the caller
// sweeps again. An expired ctx seals regardless.
//
// seal and announce are a Dekker pair: seal stores sealed then reads drainGen,
// announce adds to drainGen then reads sealed, so neither can miss the other.
// Either the sweep goes round again and finds the work, or the announcer
// waits for this decision, which is two atomic reads away, never a hook.
func (st *state) seal(ctx context.Context, g0 uint64) bool {
st.sealed.Store(true)
ok := st.drainGen.Load() == g0 || ctx.Err() != nil
if ok {
st.stopped.Store(true)
}
// sealCh exists only if an announcer had to wait. It checks sealed and
// makes the channel under the mutex, and this clears sealed and wakes it
// under the same mutex, so the wake cannot fall between the two.
st.mu.Lock()
st.sealed.Store(false)
wake(st.sealCh)
st.sealCh = nil
st.mu.Unlock()
return ok
}
// announce records drain work in st's subtree, an instance published or a
// start step claimed, and reports whether a teardown has sealed an enclosing
// scope and stopped it, in which case the work must be undone rather than
// drained. Called with no state mutex held, after the work is visible to a
// sweep.
func (st *state) announce() (stopped bool) {
for a := st; a != nil; a = a.parent {
a.drainGen.Add(1)
}
for a := st; a != nil; a = a.parent {
if !a.sealed.Load() {
continue
}
var ch chan struct{}
a.mu.Lock()
if a.sealed.Load() {
ch = waitOn(&a.sealCh) // still sealed under the mutex: the wake is ahead of us
}
a.mu.Unlock()
if ch != nil {
<-ch
}
}
return st.isStopped()
}
// gateStart announces a start step just claimed, and undoes the claim if an
// enclosing scope sealed and stopped first: the instance stays built, owes
// nothing, and never starts. A waiter on the start step is released.
func (in *instance) gateStart(owner *state) bool {
if !owner.announce() {
return true
}
owner.mu.Lock()
in.ph = phaseBuilt
in.refresh()
wake(in.startingCh)
in.startingCh = nil
owner.mu.Unlock()
return false
}
// drainRun is the bookkeeping of one drain phase: the scopes it has reached,
// whether it owns each one's phase, and whether that phase has ended.
type drainRun struct {
root *drainScope
seen map[*state]*drainScope
}
type drainScope struct {
st *state
ours bool // this run claimed the phase; otherwise another Stop owns it
settled bool // its phase has ended; for a descendant, when its own sweep does
}
// sweepAll sweeps the subtree until a pass finds no new work: the scope still
// resolves during this phase, so a hook may build a service or open a child
// scope that owes a drain too. ctx bounds the sweep as well as the hooks.
func (r *drainRun) sweepAll(ctx context.Context) error {
var errs []error
for {
progress := false
errs = append(errs, r.visit(ctx, r.root, &progress)...)
if !progress || ctx.Err() != nil {
return errors.Join(errs...)
}
}
}
// visit sweeps one scope this run owns and everything below it, innermost
// first and in reverse creation order. Every owned scope is swept on every
// pass, because a hook may build into a scope already visited.
//
// It returns the errors that belong to this scope's Stop. A descendant's are
// settled into that descendant's phase, so its own Stop reports them and they
// reach this caller through teardown, which joins what its children's Stop
// returns. Errors found in a descendant after its phase ended have nowhere
// else to go and bubble up here.
//
// A descendant's phase is claimed just before its subtree is swept and ended
// as soon as the sweep finishes, so while a hook runs the only unended phases
// this run holds are the scope being swept and its ancestors, which a hook may
// not Stop anyway. Claiming the whole subtree up front would deadlock a hook
// that stops a scope the walk has claimed but not yet reached. A scope another
// Stop already owns is waited for and then left alone, subtree included.
func (r *drainRun) visit(ctx context.Context, ds *drainScope, progress *bool) []error {
var errs []error
ds.st.mu.Lock()
children := slices.Clone(ds.st.children)
ds.st.mu.Unlock()
for _, c := range slices.Backward(children) {
cs := r.seen[c]
if cs == nil {
*progress = true
cs = &drainScope{st: c}
r.seen[c] = cs
if c.drainOnce.claim(c, nil) {
cs.ours = true
} else if finished, _ := c.drainOnce.wait(c, ctx); !finished {
errs = append(errs, fmt.Errorf("di: waiting for scope %s to drain: %w", c.name, ctx.Err()))
}
}
if cs.ours {
errs = append(errs, r.visit(ctx, cs, progress)...)
}
}
ds.st.mu.Lock()
started := slices.Clone(ds.st.started)
ds.st.mu.Unlock()
for _, in := range slices.Backward(started) {
ran, err := in.drainIfNeeded(ctx, ds.st)
*progress = *progress || ran
errs = append(errs, err)
}
if ds != r.root && !ds.settled {
ds.st.drainOnce.settle(ds.st, errors.Join(errs...))
ds.settled = true
return nil // reported by this scope's own Stop, not by its parent's
}
return errs
}
func stopAll(ctx context.Context, owner *state, started []*instance) error {
var errs []error
for _, in := range slices.Backward(started) {
errs = append(errs, in.stopIfNeeded(ctx, owner))
}
return errors.Join(errs...)
}
package di
// Resolution: the path a resolution walks, the two cycle detectors, the
// build step of an instance, and the entry points that resolve by type.
import (
"errors"
"fmt"
"reflect"
"slices"
"sync"
"sync/atomic"
"time"
)
// abort is the panic a wiring failure unwinds with. It reaches the nearest
// Resolve, Start or Run and becomes that call's error; a top-level Get panics
// with the plain error instead.
type abort struct{ err error }
// resolver is one node of a resolution path: what is being resolved and the
// node that needed it. The path is a linked list because a constructor may
// resolve from several goroutines at once; a node is never mutated after it
// is made, except done, so branches share nothing.
//
// A node is identified by binding and holder, not by key: a group member and
// a plain registration of the same type are different bindings, and one
// Scoped binding is a different node in each scope that holds an instance.
type resolver struct {
parent *resolver
b *binding // nil on the root node, which resolves nothing itself
holder *state
// done marks a node whose resolution has returned. The path stays whole
// for error messages, but a finished node is no longer a dependency: a
// constructor may keep its Scope and resolve through it later, and that
// resolution must not meet its own finished frame and be called a cycle.
// Written once by the resolution that owns the node, read from any branch.
done atomic.Bool
}
func (r *resolver) child(b *binding, holder *state) *resolver {
return &resolver{parent: r, b: b, holder: holder}
}
// onPath reports whether this exact binding is still being resolved further
// up the path, which is a dependency cycle within one branch.
//
// The walk stops at the first finished node rather than skipping it: what is
// above it may still be building, but not for this branch, so a resolution
// through a kept Scope has only to wait. The one shape this cannot tell apart
// without goroutine-local state deadlocks instead of being reported: a
// constructor that blocks on a resolution made through a finished
// descendant's Scope that leads back to itself.
func (r *resolver) onPath(b *binding, holder *state) bool {
for n := r; n != nil; n = n.parent {
if n.done.Load() {
return false
}
if n.b == b && n.holder == holder {
return true
}
}
return false
}
func (r *resolver) path() string {
if r == nil || r.b == nil {
return ""
}
return " (needed by " + r.pathStr() + ")"
}
func (r *resolver) pathStr() string {
var parts []string
for n := r; n != nil; n = n.parent {
if n.b != nil {
parts = append(parts, n.b.key.String())
}
}
slices.Reverse(parts)
return fmt.Sprint(parts)
}
// graph is one container's wait-for graph, with two kinds of edge: an
// instance points at the resolution building it (instance.builder), and a
// blocked resolution points at the instance it waits for (a waitEdge). Its
// mutex is the innermost lock: a state's mutex may be held while taking it,
// never the reverse. One graph per container is as far as a cycle can reach.
type graph struct {
mu sync.Mutex
// under indexes every wait by each node of the blocked resolution's path,
// up to and including the first finished one, as the path stood when it
// blocked. A node only ever becomes finished, so the set descends can
// match only shrinks and the index is a superset of it: wait narrows the
// search to under[builder] and descends still decides.
under map[*resolver]map[*waitEdge]struct{}
}
// waitEdge is one wait: the blocked resolution, the instance it waits for,
// and the nodes it was indexed under. Each wait has its own, so removing one
// never depends on another wait by the same resolution.
type waitEdge struct {
r *resolver
in *instance
path []*resolver
}
// descends reports whether n is anc or was created below it. A branch blocks
// at a leaf of its path, below the node that claimed the build it is holding
// up, so both directions of the graph are matched against whole paths. The
// walk stops at a finished node for the same reason onPath does.
func descends(n, anc *resolver) bool {
for ; n != nil; n = n.parent {
if n == anc {
return true
}
if n.done.Load() {
return false
}
}
return false
}
// wait records that r is about to wait for in and returns the edge to hand to
// unwait, or nil when waiting would close a wait-for cycle: reaching, through
// builds that are themselves blocked, a build this branch must finish. Called
// with the holder's mutex held; the check and the new edge are one critical
// section, so two branches closing a cycle at once cannot both decide to wait.
func (r *resolver) wait(g *graph, in *instance) *waitEdge {
g.mu.Lock()
defer g.mu.Unlock()
seen := map[*instance]bool{in: true}
for stack := []*instance{in}; len(stack) > 0; {
cur := stack[len(stack)-1]
stack = stack[:len(stack)-1]
builder := cur.builder
if builder == nil {
continue // nobody is building it: whoever holds it will settle it
}
if descends(r, builder) {
return nil // waiting on our own branch's work
}
for e := range g.under[builder] {
if !seen[e.in] && descends(e.r, builder) {
seen[e.in] = true
stack = append(stack, e.in)
}
}
}
e := &waitEdge{r: r, in: in}
for n := r; n != nil; n = n.parent {
e.path = append(e.path, n)
set := g.under[n]
if set == nil {
set = map[*waitEdge]struct{}{}
g.under[n] = set
}
set[e] = struct{}{}
if n.done.Load() {
break // descends matches a node before asking whether it finished
}
}
return e
}
// unwait removes the wait e recorded.
func (g *graph) unwait(e *waitEdge) {
g.mu.Lock()
defer g.mu.Unlock()
for _, n := range e.path {
set := g.under[n]
delete(set, e)
if len(set) == 0 {
delete(g.under, n)
}
}
}
// as unwraps a stored value. A nil interface is a legitimate service, and a
// nil any cannot be asserted back to the interface type it was stored as, so
// it becomes T's zero value. Every hand-back of a stored value goes through
// here.
func as[T any](v any) T {
if v == nil {
var zero T
return zero
}
return v.(T)
}
// lookup finds the binding registered for k in this scope or an ancestor,
// and the scope that owns it, or nil.
func (s *Scope) lookup(k key) (*binding, *state) {
for st := s.st; st != nil; st = st.parent {
st.freeze()
if b, ok := st.reg.Load().index[k]; ok {
return b, st
}
}
return nil, nil
}
// inFlight reports whether this Scope is a live view of a resolution: it
// carries a path whose last node has not returned. A Scope kept past that
// point makes top-level calls.
func (s *Scope) inFlight() bool { return s.r != nil && !s.r.done.Load() }
// enter returns a view carrying a resolver, starting a new resolution unless
// one is in flight.
func (s *Scope) enter() *Scope {
if s.inFlight() {
return s
}
return s.view(&resolver{})
}
// get resolves k. Outside a constructor the internal abort becomes a panic
// carrying the plain error; inside one it unwinds to the enclosing
// Resolve/Start call.
func (s *Scope) get(k key) any {
if !s.inFlight() {
defer unwrapAbort()
return s.enter().get(k)
}
b, owner := s.lookup(k)
if b == nil {
panic(abort{fmt.Errorf("di: %s: %w%s", k, ErrNotProvided, s.r.path())})
}
v := s.resolve(b, owner)
s.markServed(owner, k)
return v
}
// markServed records that k was served to this scope from owner, in every
// scope between the two: each handed out a value for k, and registering k in
// one of them afterwards would give the key two live values there.
func (s *Scope) markServed(owner *state, k key) {
for st := s.st; st != nil && st != owner; st = st.parent {
st.mu.Lock()
if st.served == nil {
st.served = make(map[key]bool, 4)
}
st.served[k] = true
st.mu.Unlock()
}
}
// resolve produces b's value for the resolving scope s, honouring the
// binding's lifetime and starting the instance when the scope is running.
func (s *Scope) resolve(b *binding, owner *state) any {
if s.st.isStopped() {
panic(abort{fmt.Errorf("di: %s: %w%s", b.key, ErrStopped, s.r.path())})
}
// The holder owns the instance: the registering scope for a singleton,
// the resolving scope for a Scoped binding.
holder := owner
if b.scoped {
holder = s.st
}
if s.r.onPath(b, holder) {
panic(abort{fmt.Errorf("di: %w: %s -> %s", ErrCycle, s.r.pathStr(), b.key)})
}
if !b.used.Load() {
// Hold the key against an override while this resolution runs, so a
// constructor cannot replace the registration it is built from. used
// is set before this is dropped, so the two guards leave no gap.
b.resolving.Add(1)
defer b.resolving.Add(-1)
}
sc := s.view(s.r.child(b, holder))
// The node stops being a dependency when this resolution returns, however
// it returns. See resolver.done.
defer sc.r.done.Store(true)
in := holder.instanceFor(b)
v, err := sc.await(in, holder)
if err != nil {
panic(abort{err})
}
b.used.Store(true)
// The edge belongs to the node that asked, and only a node with a binding
// has an instance to record it on: a top-level Get, or a Scope kept past
// its resolution, has none. The test is here rather than in dependOn so
// the warm path pays a pointer comparison instead of a call. A failed
// resolution records nothing.
if s.r.b != nil {
s.r.dependOn(in, holder)
}
return v
}
// dependOn records that the resolution at this node needed in, once per
// distinct dependency. The scan is over one constructor's own dependencies
// and runs only while it is building.
func (r *resolver) dependOn(in *instance, holder *state) {
r.holder.mu.Lock()
defer r.holder.mu.Unlock()
// The asking instance exists, since resolve made it before running the
// constructor; the nil check guards the recording only, because a mistake
// here must not break resolution.
asker := r.holder.instanceAt(r.b)
if asker == nil || slices.ContainsFunc(asker.deps, func(d dep) bool { return d.in == in }) {
return
}
asker.deps = append(asker.deps, dep{in: in, holder: holder})
}
// await returns the instance's value: this branch builds it if it gets there
// first, and otherwise waits for whoever did. It waits for the start step as
// well, so a running scope never hands out a service whose OnStart is in
// flight. A wait that would close a cycle is reported as ErrCycle.
func (s *Scope) await(in *instance, holder *state) (any, error) {
if in.ready.Load() && !s.st.isStopped() {
// The warm path, without the holder's mutex: the build is settled,
// no start step is owed or in flight, and the value is final. ready
// is written under that mutex at every change that could make the
// answer differ (see refresh), so a load that sees it set is ordered
// before any such change.
return in.value, nil
}
holder.mu.Lock()
for in.ph == phaseNew || !in.settled || in.ph == phaseStarting {
if in.ph == phaseNew {
in.claimBuild(holder, s.r)
holder.mu.Unlock()
s.materialise(in, holder)
holder.mu.Lock()
continue
}
// The phase says which step is outstanding and so which channel to
// block on. Both are read in this critical section, and the owner
// closes the channel under the same mutex.
var ready chan struct{}
if in.settled {
ready = waitOn(&in.startingCh) // settled, so OnStart is outstanding
} else {
ready = waitOn(&in.settledCh)
}
e := s.r.wait(holder.graph, in)
if e == nil {
holder.mu.Unlock()
return nil, fmt.Errorf("di: %w: %s -> %s", ErrCycle, s.r.parent.pathStr(), in.b.key)
}
holder.mu.Unlock()
<-ready
holder.graph.unwait(e)
holder.mu.Lock()
}
value, err := in.value, in.err
if err == nil && s.st.isStopped() {
// The scope stopped while this branch was building or waiting. The
// check is on the resolving scope, which covers the holder: a stopped
// scope must refuse whether or not the value is still alive above it.
value, err = nil, fmt.Errorf("di: %s: %w", in.b.key, ErrStopped)
}
holder.mu.Unlock()
return value, err
}
// claimBuild takes the build step for this resolution. Called with the
// holder's mutex held.
func (in *instance) claimBuild(holder *state, r *resolver) {
in.ph = phaseBuilding
in.refresh()
g := holder.graph
g.mu.Lock()
in.builder = r
g.mu.Unlock()
}
// settle publishes the outcome of the build step and wakes every resolution
// waiting for this instance.
func (in *instance) settle(holder *state) {
holder.mu.Lock()
in.settled = true
in.refresh()
g := holder.graph
g.mu.Lock()
in.builder = nil
g.mu.Unlock()
wake(in.settledCh)
holder.mu.Unlock()
}
// fail records a build failure, which is terminal for the instance.
func (in *instance) fail(holder *state, err error) {
holder.mu.Lock()
in.ph, in.err = phaseFailed, err
in.refresh()
holder.mu.Unlock()
}
// materialise builds an instance, once. A failure is recorded on the instance
// rather than unwound, so every later resolution reports it identically, and
// the instance is settled on the way out whatever happened.
func (s *Scope) materialise(in *instance, holder *state) {
defer in.settle(holder)
if err := s.construct(in, holder); err != nil {
in.fail(holder, err)
return
}
if !in.publish(holder) {
return
}
in.startIfRunning(holder)
}
// construct runs the constructor, turning a panic or an abort from a nested
// resolution into an error, and reports the attempt to observers either way.
func (s *Scope) construct(in *instance, holder *state) (err error) {
b := in.b
t0 := time.Now()
defer func() {
if rec := recover(); rec != nil {
if a, ok := rec.(abort); ok {
err = fmt.Errorf("di: building %s (provided at %s): %w", b.key, b.where(), a.err)
} else {
err = fmt.Errorf("di: building %s (provided at %s): panic: %v", b.key, b.where(), rec)
}
}
holder.report(EventBuild, b, t0, err)
}()
in.value = b.build(&Scope{st: holder, r: s.r, module: b.module})
return nil
}
// publish adds the instance to its owner's stop list. If the scope stopped
// while the constructor ran, Stop's snapshot did not include the instance,
// so it is undone here and reported as ErrStopped.
func (in *instance) publish(owner *state) bool {
owner.mu.Lock()
stopped := owner.isStopped()
in.ph = phaseBuilt
in.refresh()
if !stopped {
owner.started = append(owner.started, in)
}
owner.mu.Unlock()
if !stopped && !owner.announce() {
return true
}
err := errors.Join(fmt.Errorf("di: %s: %w", in.b.key, ErrStopped), in.stopIfNeeded(owner.stopContext(), owner))
owner.mu.Lock()
in.err = err
in.refresh()
owner.mu.Unlock()
return false
}
// startIfRunning runs the start step when the scope is already running.
// publish precedes the read of running, and Start sets running before it
// drains, so either this starts the instance or Start's drain finds it.
func (in *instance) startIfRunning(owner *state) {
if sctx, running := owner.runContext(); running && in.claim(owner) && in.gateStart(owner) {
_ = in.startClaimed(sctx, owner)
}
stopped := owner.isStopped()
owner.mu.Lock()
if in.err == nil && stopped {
// Stop waited for the start step and tore the instance down.
in.err = fmt.Errorf("di: %s: %w", in.b.key, ErrStopped)
in.refresh()
}
owner.mu.Unlock()
}
// Get resolves T. Inside a constructor, failure unwinds to the enclosing
// Resolve/Start call and becomes an error; at top level it panics. In a
// goroutine a constructor started, use Resolve instead: that panic has no
// enclosing call to unwind to.
func (s *Scope) Get[T any]() T { return as[T](s.get(key{t: reflect.TypeFor[T]()})) }
// Maybe resolves T if it is provided anywhere in the scope chain.
func (s *Scope) Maybe[T any]() (T, bool) {
if b, _ := s.lookup(key{t: reflect.TypeFor[T]()}); b == nil {
var zero T
return zero, false
}
return s.Get[T](), true
}
// All resolves the multi-binding group for T across the scope chain. Members
// have the same lifetimes and lifecycle as any other binding.
func (s *Scope) All[T any]() []T {
if !s.inFlight() {
defer unwrapAbort()
return s.enter().All[T]()
}
k := key{t: reflect.TypeFor[T]()}
var out []T
for st := s.st; st != nil; st = st.parent {
st.freeze()
for _, b := range st.reg.Load().groups[k] { // immutable: freeze appends to a copy
out = append(out, as[T](s.resolve(b, st)))
}
}
return out
}
// Must unwraps a (value, error) pair inside a constructor:
//
// db := s.Must(sql.Open("postgres", dsn))
//
// A non-nil error aborts the constructor and surfaces from the enclosing
// Resolve, Start or Run. Outside a constructor it panics with the error.
func (s *Scope) Must[T any](v T, err error) T {
if err == nil {
return v
}
if s.inFlight() {
panic(abort{err})
}
panic(err)
}
// Resolve resolves T, reporting a wiring failure as an error rather than a
// panic. It is the entry point for a goroutine a constructor started.
func (s *Scope) Resolve[T any]() (v T, err error) {
defer recoverAbort(&err)
return as[T](s.enter().get(key{t: reflect.TypeFor[T]()})), nil
}
// unwrapAbort turns an abort into a panic carrying the plain error, which is
// what a top-level Get or All reports.
func unwrapAbort() {
if rec := recover(); rec != nil {
if a, ok := rec.(abort); ok {
panic(a.err)
}
panic(rec)
}
}
// recoverAbort turns an abort into *err and re-panics anything else.
func recoverAbort(err *error) {
if rec := recover(); rec != nil {
if a, ok := rec.(abort); ok {
*err = a.err
return
}
panic(rec)
}
}
package di
// Run and Shutdown: the main-function loop over Start and Stop, and the
// signal handling around it.
import (
"context"
"errors"
"os"
"os/signal"
"syscall"
"time"
)
// Shutdown asks a running Run to stop and records the cause it should return.
// It never blocks, may be called from any goroutine, and the first call wins.
// It propagates to ancestor scopes, so a service in a child scope can stop the
// application.
func (s *Scope) Shutdown(cause error) {
first := false
for st := s.st; st != nil; st = st.parent {
st.shutdownOnce.Do(func() {
st.shutdownErr = cause
close(st.shutdownCh)
first = first || st == s.st
})
}
if first {
s.st.emit(Event{Kind: EventShutdown, Scope: s.st.name, Err: cause})
}
}
// RunOption configures Run.
type RunOption func(*runConfig)
type runConfig struct{ stopTimeout time.Duration }
// exitSignals are what make Run exit: an interrupt or a termination request.
var exitSignals = []os.Signal{os.Interrupt, syscall.SIGTERM}
// StopTimeout bounds how long Stop may take once Run decides to exit.
// The default is 15 seconds.
func StopTimeout(d time.Duration) RunOption { return func(c *runConfig) { c.stopTimeout = d } }
// stopContext builds the context Run stops with: detached from the caller's,
// bounded by StopTimeout, and cancelled by a second signal. A rollback from a
// failed Start gets the same context.
func (c runConfig) stopContext(ctx context.Context) (context.Context, func()) {
stopCtx, cancelStop := context.WithTimeout(context.WithoutCancel(ctx), c.stopTimeout)
forceCtx, cancelForce := signal.NotifyContext(stopCtx, exitSignals...)
return forceCtx, func() { cancelForce(); cancelStop() }
}
// Run starts the scope and blocks until ctx is cancelled, a termination
// signal arrives, or Shutdown is called. It then stops the scope within
// StopTimeout; a second signal during the stop cancels that context so a hung
// hook cannot keep the process alive. Run returns the Start error, the error
// passed to Shutdown, and any Stop errors, joined; a worker that died on its
// own is reported once.
func (s *Scope) Run(ctx context.Context, opts ...RunOption) error {
cfg := runConfig{stopTimeout: 15 * time.Second}
for _, o := range opts {
o(&cfg)
}
// Register before Start so a signal during a slow start is not lost.
sigCtx, cancelSig := signal.NotifyContext(ctx, exitSignals...)
defer cancelSig()
if err := s.start(ctx, func() (context.Context, func()) { return cfg.stopContext(ctx) }); err != nil {
// The rollback runs the hooks, so a worker can die and publish its
// failure here as it can during an ordinary shutdown.
return joinCause(err, s.publishedCause())
}
var cause error
select {
case <-sigCtx.Done():
case <-s.st.shutdownCh:
cause = s.st.shutdownErr
}
stopCtx, cancel := cfg.stopContext(ctx)
defer cancel()
stopErr := s.Stop(stopCtx)
if cause == nil {
// A worker that died during the stop published its failure after the
// select above had woken for a signal.
cause = s.publishedCause()
}
return joinCause(stopErr, cause)
}
// publishedCause reports the failure Shutdown recorded, without waiting for
// one.
func (s *Scope) publishedCause() error {
select {
case <-s.st.shutdownCh:
return s.st.shutdownErr
default:
return nil
}
}
// joinCause adds a published cause to what Run is already returning, unless
// it is in there already: a worker's error reaches Run both as the cause and
// through the Stop that cancelled it.
func joinCause(err, cause error) error {
if cause == nil {
return err
}
if errors.Is(err, cause) {
return err // one failure, reached by both routes
}
return errors.Join(err, cause)
}
package di
// A scope's state: its registry, the freeze that commits registrations into
// it, and the readers that walk the parent chain. No two state mutexes are
// ever ordered against each other; a walk takes and releases each in turn.
import (
"context"
"fmt"
"maps"
"slices"
"sync"
"sync/atomic"
)
// state is a scope's registry and lifecycle bookkeeping. A Scope is a handle
// over it.
type state struct {
name string
parent *state
graph *graph // the container's wait-for graph, shared with every other scope under the root
// reg is the committed registry, immutable once stored, so a lookup reads
// it without the mutex. hasPending says whether freeze has a batch to
// commit: register sets it and freeze clears it, both under mu, so a
// lookup that finds it clear skips the lock as well.
reg atomic.Pointer[registry]
hasPending atomic.Bool
frozen bool // guarded by mu
mu sync.Mutex
pending []*binding // registrations not yet indexed
started []*instance // build order; stopped in reverse
scoped map[*binding]*instance // per-scope instances of Scoped bindings
served map[key]bool // keys this scope resolved from an outer scope; lazily made
children []*state
// observers is replaced whole by Observe, under mu, and read without it
// by emit.
observers atomic.Pointer[[]func(Event)]
// startCtx is set once, by Start, and running once Start reaches its
// hook phase, after which a service built later starts itself. Atomic
// because every build reads them up the whole chain; what makes a late
// build start exactly once is their order against publish, not a mutex.
startCtx atomic.Pointer[context.Context]
running atomic.Bool
stopped atomic.Bool // set by the seal that ends Stop's drain; resolution then fails with ErrStopped
stopCtx context.Context // the context Stop was called with
stopOnce once // this scope's teardown; later Stop calls wait for it
// drainOnce is the scope-wide drain phase, once-with-wait like stopOnce.
drainOnce once
// drainGen counts what could create drain work in this scope's subtree:
// a build published into it, a start step claimed in it. sealed and
// sealCh are how a teardown ends the drain phase against those; see seal
// and announce.
drainGen atomic.Uint64
sealCh chan struct{} // guarded by mu; made by an announcer that must wait, closed when the seal is decided
sealed atomic.Bool
// The fields are ordered so the 4-byte atomics and the bool pack
// together: a state is allocated per request scope.
shutdownOnce sync.Once
shutdownCh chan struct{}
shutdownErr error
}
// registry is a scope's committed registrations. It is immutable once
// stored: freeze builds the next one from copies and swaps it in whole.
type registry struct {
index map[key]*binding
groups map[key][]*binding
all []*binding // every binding, in registration order
eager []*binding // derived by deriveEager: what Start builds
}
// emptyRegistry is what a scope starts with; no registry is ever written to.
var emptyRegistry = ®istry{index: map[key]*binding{}, groups: map[key][]*binding{}}
// freeze commits the pending registrations. The batch is validated against a
// copy of the registry and committed only if it passes, so a rejected
// registration leaves the scope as it was and is rejected identically on
// every later attempt.
func (st *state) freeze() {
if !st.hasPending.Load() {
return // the warm path: nothing queued, so nothing to lock for
}
st.mu.Lock()
defer st.mu.Unlock()
if len(st.pending) == 0 {
return
}
cur := st.reg.Load()
var replaced []*binding // chains an Override replaces, whose marks go on commit
index := maps.Clone(cur.index)
groups := maps.Clone(cur.groups)
all := slices.Clone(cur.all)
for _, b := range st.pending {
// A wrapper takes the lifetime of what it wraps, read here because the
// wrapped binding's own Scoped() may come later in the batch.
if b.inner != nil && b.inner.scoped {
b.scoped = true
}
b.validate()
if b.group {
groups[b.key] = append(slices.Clone(groups[b.key]), b)
} else {
prev, ok := index[b.key]
act := "overridden"
if b.inner != nil {
act = "wrapped"
}
switch {
case ok && !b.override && b.inner == nil:
// A later registration winning silently would let one module
// reroute another's wiring.
panic(fmt.Sprintf("di: %s is provided at %s and again at %s: a second registration of a key must be marked Override() to replace the first",
b.key, prev.where(), b.where()))
case !ok && b.override:
// Nearly always a fake for a service that was renamed. A child
// shadows its parent without Override.
panic(fmt.Sprintf("di: %s (provided at %s) is marked Override() but nothing in scope %s provides it; a child scope shadows its parent without Override",
b.key, b.where(), st.name))
}
if ok {
if why := prev.against(b); why != "" {
panic(fmt.Sprintf("di: %s (provided at %s) cannot be %s at %s: %s",
b.key, prev.where(), act, b.where(), why))
}
}
if st.served[b.key] {
// Shadowing a key this scope handed down from an outer scope
// would give it two live values here.
panic(fmt.Sprintf("di: %s cannot be registered at %s: this scope has already resolved it from an outer scope",
b.key, b.where()))
}
if ok && b.inner == nil {
replaced = append(replaced, prev)
}
index[b.key] = b
}
all = append(all, b)
}
eager := deriveEager(all, index)
st.reg.Store(®istry{index: index, groups: groups, all: all, eager: eager})
// The batch stands, so the chains its Overrides replaced never serve from
// this scope. Every link registered here is retired, down to the first
// that wraps an ancestor's registration, whose chain is intact; a retired
// link releases what it wraps only once nothing live wraps it. Not before
// the commit: a rejected batch keeps every mark it made.
for _, prev := range replaced {
for r := prev; r.inner != nil; r = r.inner {
r.retire() // only a wrapper's flag is ever read, so a plain registration is not marked
if r.innerAt != st {
break
}
}
prev.release()
}
st.pending, st.frozen = nil, true
st.hasPending.Store(false)
}
// deriveEager returns the ordered set of bindings Start builds, and is the
// one place that decides what Eager means: for every key with an Eager
// registration, the binding that serves that key, once, at the position of
// the first such registration. A group member is its own entry. A binding
// with a per-scope lifetime cannot honour eagerness and is rejected here,
// whether declared so directly or arriving through an override.
func deriveEager(all []*binding, index map[key]*binding) []*binding {
var eager []*binding
seen := make(map[*binding]bool, len(all))
for _, b := range all {
if !b.eager {
continue
}
w := b
if !b.group {
w = index[b.key] // whichever registration owns the key by now
}
if seen[w] {
continue
}
if w.scoped {
// b itself is caught by validate, so w is an override here.
panic(fmt.Sprintf("di: %s is Eager (provided at %s), but the Scoped registration at %s owns the key: eagerness cannot transfer to a per-scope lifetime",
b.key, b.where(), w.where()))
}
seen[w] = true
eager = append(eager, w)
}
return eager
}
// descendsFrom reports whether st is anc or a scope under it.
func (st *state) descendsFrom(anc *state) bool {
for ; st != nil; st = st.parent {
if st == anc {
return true
}
}
return false
}
// isStopped reports whether this scope or an ancestor has stopped.
func (st *state) isStopped() bool {
for ; st != nil; st = st.parent {
if st.stopped.Load() {
return true
}
}
return false
}
// runContext walks up to the nearest state Start was called on. running
// reports whether that Start has passed its hook phase; it is never true with
// a nil ctx, since start records the context before setting the flag.
func (st *state) runContext() (ctx context.Context, running bool) {
for ; st != nil; st = st.parent {
if p := st.startCtx.Load(); p != nil {
return *p, st.running.Load()
}
}
return nil, false
}
// everStarted reports whether Start was called on this scope or an ancestor.
func (st *state) everStarted() bool {
ctx, _ := st.runContext()
return ctx != nil
}
// stopContext returns the context Stop was called with, or a background one
// if the scope was stopped without recording it.
func (st *state) stopContext() context.Context {
for ; st != nil; st = st.parent {
st.mu.Lock()
ctx := st.stopCtx
st.mu.Unlock()
if ctx != nil {
return ctx
}
}
return context.Background()
}
// instanceFor picks the instance a resolution uses: a singleton has one for
// the whole binding, a Scoped binding one per scope that holds it.
func (st *state) instanceFor(b *binding) *instance {
if !b.scoped {
return b.single
}
st.mu.Lock()
defer st.mu.Unlock()
in := st.scoped[b]
if in == nil {
in = &instance{b: b}
st.scoped[b] = in
}
return in
}
// instanceAt is instanceFor without the making: the instance b already has
// in st, or nil. Called with st's mutex held, by callers that must not bring
// one into being.
func (st *state) instanceAt(b *binding) *instance {
if !b.scoped {
return b.single
}
return st.scoped[b]
}
package di
// Checking the declared graph. Only a Wire constructor declares its
// dependencies; a Provide closure reveals them as it runs and is reported as
// unchecked. Nothing here resolves or builds: it reads bindings and their
// declared lists after committing pending registrations, as a lookup would.
import (
"errors"
"fmt"
"reflect"
"slices"
)
// Validation is what Validate found.
type Validation struct {
// Errors are the failures the declared graph proves: a dependency nothing
// provides, a cycle among Wire constructors, or a singleton that would
// build a Scoped service in its own scope, where that service's
// dependencies are not provided. Each wraps ErrNotProvided or ErrCycle.
Errors []error
// Owed lists the dependencies of Scoped bindings that this scope does not
// provide. A Scoped service is built in the scope that resolves it, so
// these are left to that scope: call Validate from there, or say what it
// will hold with Provided stubs, and they are checked as errors instead.
Owed []string
// Unchecked lists the Provide constructors in the chain, whose
// dependencies are known only once they run.
Unchecked []string
}
// Err joins Errors, or is nil when the declared graph proves no failure.
func (v Validation) Err() error { return errors.Join(v.Errors...) }
// Validate checks the wiring visible from this scope without building
// anything. A singleton is checked against the scope that registered it,
// since that is where it is built. A Scoped binding is checked as if resolved
// from this scope, and what this scope does not provide for it is reported as
// Owed rather than as an error, because a descendant may:
//
// v := app.Validate() // *http.Request is owed to a request scope
//
// The stubs say what such a descendant will hold, so the check is made as
// that descendant would make it, and a dependency neither this scope nor the
// stubs provide is an error:
//
// err := app.Validate(di.Provided[*http.Request]()).Err()
//
// Like Explain, Validate commits pending registrations as a resolution would,
// so a configuration this scope would reject is reported by the same panic.
func (s *Scope) Validate(stubs ...Stub) Validation {
var chain []*state
for st := s.st; st != nil; st = st.parent {
st.freeze()
chain = append(chain, st)
}
v := &validator{seen: map[string]bool{}, done: map[visit]bool{}, stubs: map[key]bool{}, leaf: len(stubs) > 0}
for _, st := range stubs {
v.stubs[st.k] = true
}
// Ancestors first, so a report reads top-down like the scope tree.
for _, st := range slices.Backward(chain) {
for _, b := range st.live() {
switch {
case b.isValue:
case b.wants == nil:
v.out.Unchecked = append(v.out.Unchecked, fmt.Sprintf("%s (provided at %s)", b.key, b.where()))
case b.scoped:
v.walk(b, s.st, lenient, nil)
default:
v.walk(b, st, strict, nil)
}
}
}
return v.out
}
// live returns the bindings that can serve a key from this scope, in
// registration order: what index and groups hold, without the registrations
// an Override replaced. A wrapper's whole chain is live, since what it wraps
// is built underneath it.
func (st *state) live() []*binding {
reg := st.reg.Load()
serving := map[*binding]bool{}
chain := func(b *binding) {
for ; b != nil && !serving[b]; b = b.inner {
serving[b] = true
}
}
for _, b := range reg.index {
chain(b)
}
for _, bs := range reg.groups {
for _, b := range bs {
chain(b)
}
}
var out []*binding
for _, b := range reg.all {
if serving[b] {
out = append(out, b)
}
}
return out
}
type validator struct {
out Validation
seen map[string]bool // lines already reported, and cycles by their members
done map[visit]bool // nodes fully explored, so a diamond is walked once
stubs map[key]bool // what the resolving scope will hold, by the caller's word
leaf bool // stubs were given: the resolving scope is described, so nothing is owed
}
// Stub names a key the scope resolving a Scoped binding will provide, for
// Validate to take as given. Make one with Provided.
type Stub struct{ k key }
// Provided is a Stub for T: the resolving scope will hold a T, as a request
// scope holds an *http.Request.
func Provided[T any]() Stub { return Stub{k: key{t: reflect.TypeFor[T]()}} }
// visit is a node of the declared graph: a binding in the scope it would be
// built in. The same binding under another holder is another node, since a
// Scoped binding looks its dependencies up from where it is built.
type visit struct {
b *binding
holder *state
mode mode
}
// mode says what a missing dependency means on the current walk.
type mode uint8
const (
strict mode = iota // a singleton's own graph: missing is an error
lenient // a Scoped binding as this scope would resolve it: missing is owed to a descendant
cyclesOnly // a singleton reached from elsewhere: its own turn reports what it misses
)
// step is one node on a walk's path. The holder is part of the identity, as
// on a resolution path at run time: a Scoped binding reached again under
// another holder is another instance, not a cycle.
type step struct {
b *binding
holder *state
}
// walk follows b's declared dependencies from holder, the scope b would be
// built in. A Scoped dependency is built in the same holder and walked in the
// same mode. A singleton dependency is built in its own scope and reports its
// own misses on its own turn, so it is walked only for cycles. Cycles are
// reported once, by their members.
func (v *validator) walk(b *binding, holder *state, md mode, path []step) {
node := visit{b, holder, md}
if v.done[node] {
return
}
path = append(path, step{b, holder})
for _, e := range declared(b, holder) {
k, dep, owner := e.k, e.b, e.owner
next := step{dep, owner}
if dep != nil && dep.scoped {
next.holder = holder
}
switch {
case dep == nil && md == lenient && v.stubs[k]:
// A stub is honoured on the Scoped path only: a singleton builds
// in its own scope, where the resolving scope's values are not.
case dep == nil:
v.missing(k, b, holder, md, path)
case slices.Contains(path, next):
v.cycle(path[slices.Index(path, next):], k)
case dep.wants == nil:
// A Provide closure or a Value declares nothing to follow.
case dep.scoped:
v.walk(dep, holder, md, path)
default:
v.walk(dep, owner, cyclesOnly, path)
}
}
v.done[node] = true
}
func (v *validator) missing(k key, b *binding, holder *state, md mode, path []step) {
switch {
case md == cyclesOnly:
case md == lenient && v.leaf:
v.err(fmt.Errorf("di: %s: %w by this scope or the stubs (needed by %s; scoped, provided at %s)", k, ErrNotProvided, keysOf(path), b.site))
case md == lenient:
v.owed(fmt.Sprintf("%s: needed by %s (scoped, provided at %s)", k, b.key, b.site))
case len(path) > 1:
v.err(fmt.Errorf("di: %s: %w in scope %s (needed by %s; %s is Scoped, so the singleton %s would build it there)",
k, ErrNotProvided, holder.name, keysOf(path), b.key, path[0].b.key))
default:
v.err(fmt.Errorf("di: %s: %w (needed by %s, provided at %s)", k, ErrNotProvided, keysOf(path), b.where()))
}
}
// cycle reports the members once however many turns reach them.
func (v *validator) cycle(members []step, closing key) {
names := make([]string, len(members))
for i, m := range members {
names[i] = m.b.key.String()
}
slices.Sort(names)
id := "cycle " + fmt.Sprint(names)
if !v.seen[id] {
v.seen[id] = true
v.err(fmt.Errorf("di: %w: %s -> %s", ErrCycle, keysOf(members), closing))
}
}
func (v *validator) err(e error) {
if !v.seen[e.Error()] {
v.seen[e.Error()] = true
v.out.Errors = append(v.out.Errors, e)
}
}
func (v *validator) owed(line string) {
if !v.seen[line] {
v.seen[line] = true
v.out.Owed = append(v.out.Owed, line)
}
}
// edge is one declared dependency: the key, and the binding it resolves to
// from the holder with the scope that registered it, or nil.
type edge struct {
k key
b *binding
owner *state
}
// declared lists what b declares, in build order: the registration a wrapper
// composes over, bound rather than looked up, then the parameter types, each
// looked up from holder as the build would.
func declared(b *binding, holder *state) []edge {
out := make([]edge, 0, len(b.wants)+1)
if b.inner != nil {
out = append(out, edge{b.key, b.inner, b.innerAt})
}
for _, k := range b.wants {
dep, owner := (&Scope{st: holder}).lookup(k)
out = append(out, edge{k, dep, owner})
}
return out
}
// keysOf renders a path the way a resolution error does.
func keysOf(path []step) string {
keys := make([]string, len(path))
for i, s := range path {
keys[i] = s.b.key.String()
}
return fmt.Sprint(keys)
}