-
Notifications
You must be signed in to change notification settings - Fork 206
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
Showing
12 changed files
with
609 additions
and
91 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package dig | ||
|
||
type observer func(error) | ||
|
||
// A deferred is an observable future result that may fail. Its zero value is unresolved and has no observers. It can | ||
// be resolved once, at which point every observer will be called. | ||
type deferred struct { | ||
observers []observer | ||
settled bool | ||
err error | ||
} | ||
|
||
// alreadyResolved is a deferred that has already been resolved with a nil error. | ||
var alreadyResolved = deferred{settled: true} | ||
|
||
// failedDeferred returns a deferred that is resolved with the given error. | ||
func failedDeferred(err error) *deferred { | ||
return &deferred{settled: true, err: err} | ||
} | ||
|
||
// observe registers an observer to receive a callback when this deferred is resolved. It will be called at most one | ||
// time. If this deferred is already resolved, the observer is called immediately, before observe returns. | ||
func (d *deferred) observe(obs observer) { | ||
if d.settled { | ||
obs(d.err) | ||
return | ||
} | ||
|
||
d.observers = append(d.observers, obs) | ||
} | ||
|
||
// resolve sets the status of this deferred and notifies all observers if it's not already resolved. | ||
func (d *deferred) resolve(err error) { | ||
if d.settled { | ||
return | ||
} | ||
|
||
d.settled = true | ||
d.err = err | ||
for _, obs := range d.observers { | ||
obs(err) | ||
} | ||
d.observers = nil | ||
} | ||
|
||
// then returns a new deferred that is either resolved with the same error as this deferred, or any error returned from | ||
// the supplied function. The supplied function is only called if this deferred is resolved without error. | ||
func (d *deferred) then(res func() error) *deferred { | ||
d2 := new(deferred) | ||
d.observe(func(err error) { | ||
if err != nil { | ||
d2.resolve(err) | ||
return | ||
} | ||
d2.resolve(res()) | ||
}) | ||
return d2 | ||
} | ||
|
||
// catch maps any error from this deferred using the supplied function. The supplied function is only called if this | ||
// deferred is resolved with an error. If the supplied function returns a nil error, the new deferred will resolve | ||
// successfully. | ||
func (d *deferred) catch(rej func(error) error) *deferred { | ||
d2 := new(deferred) | ||
d.observe(func(err error) { | ||
if err != nil { | ||
err = rej(err) | ||
} | ||
d2.resolve(err) | ||
}) | ||
return d2 | ||
} | ||
|
||
// whenAll returns a new deferred that resolves when all the supplied deferreds resolve. It resolves with the first | ||
// error reported by any deferred, or nil if they all succeed. | ||
func whenAll(others ...*deferred) *deferred { | ||
if len(others) == 0 { | ||
return &alreadyResolved | ||
} | ||
|
||
d := new(deferred) | ||
count := len(others) | ||
|
||
onResolved := func(err error) { | ||
if d.settled { | ||
return | ||
} | ||
|
||
if err != nil { | ||
d.resolve(err) | ||
} | ||
|
||
count-- | ||
if count == 0 { | ||
d.resolve(nil) | ||
} | ||
} | ||
|
||
for _, other := range others { | ||
other.observe(onResolved) | ||
} | ||
|
||
return d | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.