Skip to content

Commit 3790411

Browse files
committed
parallel container startup with deferred values
This commit tries to be as unobtrusive as possible, attaching new behavior to existing types where possible rather than building out new infrastructure. constructorNode returns a deferred value when called. On the first call, it asks paramList to start building an arg slice, which may also be deferred. Once the arg slice is resolved, constructorNode schedules its constructor function to be called. Once it's called, it resolves its own deferral. Multiple paramSingles can observe the same constructorNode before it's ready. If there's an error, they may all see the same error, which is a change in behavior. There are two schedulers: synchronous and parallel. The synchronous scheduler returns things in the same order as before. The parallel may not (and the tests that rely on shuffle order will fail). The scheduler needs to be flushed after deferred values are created. The synchronous scheduler does nothing on when flushing, but the parallel scheduler runs a pool of goroutines to resolve constructors. Calls to dig functions always happen on the same goroutine as Scope.Invoke(). Calls to constructor functions can happen on pooled goroutines. The choice of scheduler is up to the Scope. Whether constructor functions are safe to call in parallel seems most logically to be a property of the scope, and the scope is passed down the constructor/param call chain.
1 parent c0dbff4 commit 3790411

File tree

5 files changed

+185
-142
lines changed

5 files changed

+185
-142
lines changed

constructor.go

+27-29
Original file line numberDiff line numberDiff line change
@@ -153,38 +153,36 @@ func (n *constructorNode) Call(c containerStore) *deferred {
153153
}
154154

155155
var args []reflect.Value
156-
d := n.paramList.BuildList(c, false /* decorating */, &args)
157-
158-
d.observe(func(err error) {
159-
if err != nil {
160-
n.calling = false
161-
n.deferred.resolve(errArgumentsFailed{
162-
Func: n.location,
163-
Reason: err,
164-
})
165-
return
166-
}
167-
168-
var results []reflect.Value
156+
var results []reflect.Value
169157

170-
c.scheduler().schedule(func() {
158+
n.paramList.BuildList(c, false /* decorating */, &args).catch(func(err error) error {
159+
return errArgumentsFailed{
160+
Func: n.location,
161+
Reason: err,
162+
}
163+
}).then(func() *deferred {
164+
return c.scheduler().schedule(func() {
171165
results = c.invoker()(reflect.ValueOf(n.ctor), args)
172-
}).observe(func(_ error) {
173-
n.calling = false
174-
receiver := newStagingContainerWriter()
175-
if err := n.resultList.ExtractList(receiver, false /* decorating */, results); err != nil {
176-
n.deferred.resolve(errConstructorFailed{Func: n.location, Reason: err})
177-
return
178-
}
179-
180-
// Commit the result to the original container that this constructor
181-
// was supplied to. The provided container is only used for a view of
182-
// the rest of the graph to instantiate the dependencies of this
183-
// container.
184-
receiver.Commit(n.s)
185-
n.called = true
186-
n.deferred.resolve(nil)
187166
})
167+
}).then(func() *deferred {
168+
receiver := newStagingContainerWriter()
169+
if err := n.resultList.ExtractList(receiver, false /* decorating */, results); err != nil {
170+
return failedDeferred(errConstructorFailed{Func: n.location, Reason: err})
171+
}
172+
173+
// Commit the result to the original container that this constructor
174+
// was supplied to. The provided container is only used for a view of
175+
// the rest of the graph to instantiate the dependencies of this
176+
// container.
177+
receiver.Commit(n.s)
178+
n.calling = false
179+
n.called = true
180+
n.deferred.resolve(nil)
181+
return &alreadyResolved
182+
}).catch(func(err error) error {
183+
n.calling = false
184+
n.deferred.resolve(err)
185+
return nil
188186
})
189187

190188
return &n.deferred

decorate.go

+52-18
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import (
2929
)
3030

3131
type decorator interface {
32-
Call(c containerStore) error
32+
Call(c containerStore) *deferred
3333
ID() dot.CtorID
3434
}
3535

@@ -42,12 +42,18 @@ type decoratorNode struct {
4242
// Location where this function was defined.
4343
location *digreflect.Func
4444

45+
// Whether this node is already building its paramList and calling the constructor
46+
calling bool
47+
4548
// Whether the decorator owned by this node was already called.
4649
called bool
4750

4851
// Parameters of the decorator.
4952
params paramList
5053

54+
// The result of calling the constructor
55+
deferred deferred
56+
5157
// Results of the decorator.
5258
results resultList
5359

@@ -86,32 +92,60 @@ func newDecoratorNode(dcor interface{}, s *Scope) (*decoratorNode, error) {
8692
return n, nil
8793
}
8894

89-
func (n *decoratorNode) Call(s containerStore) error {
90-
if n.called {
91-
return nil
95+
// Call calls this decorator if it hasn't already been called and injects any values produced by it into the container
96+
// passed to newConstructorNode.
97+
//
98+
// If constructorNode has a unresolved deferred already in the process of building, it will return that one. If it has
99+
// already been successfully called, it will return an already-resolved deferred. Together these mean it will try the
100+
// call again if it failed last time.
101+
//
102+
// On failure, the returned pointer is not guaranteed to stay in a failed state; another call will reset it back to its
103+
// zero value; don't store the returned pointer. (It will still call each observer only once.)
104+
func (n *decoratorNode) Call(s containerStore) *deferred {
105+
if n.calling || n.called {
106+
return &n.deferred
92107
}
93108

109+
n.calling = true
110+
n.deferred = deferred{}
111+
94112
if err := shallowCheckDependencies(s, n.params); err != nil {
95-
return errMissingDependencies{
113+
n.deferred.resolve(errMissingDependencies{
96114
Func: n.location,
97115
Reason: err,
98-
}
116+
})
99117
}
100118

101-
args, err := n.params.BuildList(n.s, true /* decorating */)
102-
if err != nil {
103-
return errArgumentsFailed{
104-
Func: n.location,
105-
Reason: err,
119+
var args []reflect.Value
120+
d := n.params.BuildList(s, true /* decorating */, &args)
121+
122+
d.observe(func(err error) {
123+
if err != nil {
124+
n.calling = false
125+
n.deferred.resolve(errArgumentsFailed{
126+
Func: n.location,
127+
Reason: err,
128+
})
129+
return
106130
}
107-
}
108131

109-
results := reflect.ValueOf(n.dcor).Call(args)
110-
if err := n.results.ExtractList(n.s, true /* decorated */, results); err != nil {
111-
return err
112-
}
113-
n.called = true
114-
return nil
132+
var results []reflect.Value
133+
134+
s.scheduler().schedule(func() {
135+
results = s.invoker()(reflect.ValueOf(n.dcor), args)
136+
}).observe(func(_ error) {
137+
n.calling = false
138+
if err := n.results.ExtractList(n.s, true /* decorated */, results); err != nil {
139+
n.deferred.resolve(err)
140+
return
141+
}
142+
143+
n.called = true
144+
n.deferred.resolve(nil)
145+
})
146+
})
147+
148+
return &n.deferred
115149
}
116150

117151
func (n *decoratorNode) ID() dot.CtorID { return n.id }

deferred.go

+16-5
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,27 @@ func (d *deferred) resolve(err error) {
4343
d.observers = nil
4444
}
4545

46-
// then returns a new deferred that is either resolved with the same error as this deferred, or any error returned from
47-
// the supplied function. The supplied function is only called if this deferred is resolved without error.
48-
func (d *deferred) then(res func() error) *deferred {
46+
// then returns a new deferred that is either resolved with the same error as this deferred or the eventual result of
47+
// the deferred returned by res.
48+
func (d *deferred) then(res func() *deferred) *deferred {
49+
// Shortcut: if we're settled...
50+
if d.settled {
51+
if d.err == nil {
52+
// ...successfully, then return the other deferred
53+
return res()
54+
} else {
55+
// ...with an error, then return us
56+
return d
57+
}
58+
}
59+
4960
d2 := new(deferred)
5061
d.observe(func(err error) {
5162
if err != nil {
5263
d2.resolve(err)
53-
return
64+
} else {
65+
res().observe(d2.resolve)
5466
}
55-
d2.resolve(res())
5667
})
5768
return d2
5869
}

invoke.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func (s *Scope) Invoke(function interface{}, opts ...InvokeOption) error {
8484

8585
var args []reflect.Value
8686

87-
d := pl.BuildList(s, &args, false /* decorating */)
87+
d := pl.BuildList(s, false /* decorating */, &args)
8888
d.observe(func(err2 error) {
8989
err = err2
9090
})

0 commit comments

Comments
 (0)