diff --git a/constructor.go b/constructor.go index 752854dd..50cafa07 100644 --- a/constructor.go +++ b/constructor.go @@ -44,12 +44,18 @@ type constructorNode struct { // id uniquely identifies the constructor that produces a node. id dot.CtorID + // Whether this node is already building its paramList and calling the constructor + calling bool + // Whether the constructor owned by this node was already called. called bool // Type information about constructor parameters. paramList paramList + // The result of calling the constructor + deferred deferred + // Type information about constructor results. resultList resultList @@ -121,42 +127,66 @@ func (n *constructorNode) String() string { return fmt.Sprintf("deps: %v, ctor: %v", n.paramList, n.ctype) } -// Call calls this constructor if it hasn't already been called and -// injects any values produced by it into the provided container. -func (n *constructorNode) Call(c containerStore) error { - if n.called { - return nil +// Call calls this constructor if it hasn't already been called and injects any values produced by it into the container +// passed to newConstructorNode. +// +// If constructorNode has a unresolved deferred already in the process of building, it will return that one. If it has +// already been successfully called, it will return an already-resolved deferred. Together these mean it will try the +// call again if it failed last time. +// +// On failure, the returned pointer is not guaranteed to stay in a failed state; another call will reset it back to its +// zero value; don't store the returned pointer. (It will still call each observer only once.) +func (n *constructorNode) Call(c containerStore) *deferred { + if n.calling || n.called { + return &n.deferred } + n.calling = true + n.deferred = deferred{} + if err := shallowCheckDependencies(c, n.paramList); err != nil { - return errMissingDependencies{ + n.deferred.resolve(errMissingDependencies{ Func: n.location, Reason: err, - } + }) } - args, err := n.paramList.BuildList(c) - if err != nil { - return errArgumentsFailed{ - Func: n.location, - Reason: err, + var args []reflect.Value + d := n.paramList.BuildList(c, &args) + + d.observe(func(err error) { + if err != nil { + n.calling = false + n.deferred.resolve(errArgumentsFailed{ + Func: n.location, + Reason: err, + }) + return } - } - - receiver := newStagingContainerWriter() - results := c.invoker()(reflect.ValueOf(n.ctor), args) - if err := n.resultList.ExtractList(receiver, results); err != nil { - return errConstructorFailed{Func: n.location, Reason: err} - } - - // Commit the result to the original container that this constructor - // was supplied to. The provided constructor is only used for a view of - // the rest of the graph to instantiate the dependencies of this - // container. - receiver.Commit(n.s) - n.called = true - return nil + var results []reflect.Value + + c.scheduler().schedule(func() { + results = c.invoker()(reflect.ValueOf(n.ctor), args) + }).observe(func(_ error) { + n.calling = false + receiver := newStagingContainerWriter() + if err := n.resultList.ExtractList(receiver, results); err != nil { + n.deferred.resolve(errConstructorFailed{Func: n.location, Reason: err}) + return + } + + // Commit the result to the original container that this constructor + // was supplied to. The provided container is only used for a view of + // the rest of the graph to instantiate the dependencies of this + // container. + receiver.Commit(n.s) + n.called = true + n.deferred.resolve(nil) + }) + }) + + return &n.deferred } // stagingContainerWriter is a containerWriter that records the changes that diff --git a/constructor_test.go b/constructor_test.go index 16fc2af7..992d06a4 100644 --- a/constructor_test.go +++ b/constructor_test.go @@ -59,7 +59,11 @@ func TestNodeAlreadyCalled(t *testing.T) { require.False(t, n.called, "node must not have been called") c := New() - require.NoError(t, n.Call(c.scope), "invoke failed") + d := n.Call(c.scope) + c.scope.sched.flush() + require.NoError(t, d.err, "invoke failed") require.True(t, n.called, "node must be called") - require.NoError(t, n.Call(c.scope), "calling again should be okay") + d = n.Call(c.scope) + c.scope.sched.flush() + require.NoError(t, d.err, "calling again should be okay") } diff --git a/container.go b/container.go index c4b020ca..83d51bee 100644 --- a/container.go +++ b/container.go @@ -119,6 +119,9 @@ type containerStore interface { // Returns invokerFn function to use when calling arguments. invoker() invokerFn + + // Returns the scheduler to use for this scope. + scheduler() scheduler } // New constructs a Container. @@ -208,6 +211,29 @@ func dryInvoker(fn reflect.Value, _ []reflect.Value) []reflect.Value { return results } +type maxConcurrencyOption int + +// MaxConcurrency run constructors in this container with a fixed pool of executor +// goroutines. max is the number of goroutines to start. +func MaxConcurrency(max int) Option { + return maxConcurrencyOption(max) +} + +func (m maxConcurrencyOption) applyOption(container *Container) { + container.scope.sched = ¶llelScheduler{concurrency: int(m)} +} + +type unboundedConcurrency struct{} + +// UnboundedConcurrency run constructors in this container as concurrently as possible. +// Go's resource limits like GOMAXPROCS will inherently limit how much can happen in +// parallel. +var UnboundedConcurrency Option = unboundedConcurrency{} + +func (u unboundedConcurrency) applyOption(container *Container) { + container.scope.sched = &unboundedScheduler{} +} + // String representation of the entire Container func (c *Container) String() string { return c.scope.String() diff --git a/deferred.go b/deferred.go new file mode 100644 index 00000000..001a6bd0 --- /dev/null +++ b/deferred.go @@ -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 +} diff --git a/dig_test.go b/dig_test.go index 2709466a..021ea95f 100644 --- a/dig_test.go +++ b/dig_test.go @@ -29,6 +29,7 @@ import ( "math/rand" "os" "reflect" + "sync/atomic" "testing" "time" @@ -3566,3 +3567,91 @@ func TestEndToEndSuccessWithAliases(t *testing.T) { }) } + +func TestConcurrency(t *testing.T) { + // Ensures providers will run at the same time + t.Run("TestMaxConcurrency", func(t *testing.T) { + t.Parallel() + + type ( + A int + B int + C int + ) + + var ( + timer = time.NewTimer(10 * time.Second) + max int32 = 3 + done = make(chan struct{}) + running int32 = 0 + waitForUs = func() error { + if atomic.AddInt32(&running, 1) == max { + close(done) + } + select { + case <-timer.C: + return errors.New("timeout expired") + case <-done: + return nil + } + } + c = digtest.New(t, dig.MaxConcurrency(int(max))) + ) + + c.RequireProvide(func() (A, error) { return 0, waitForUs() }) + c.RequireProvide(func() (B, error) { return 1, waitForUs() }) + c.RequireProvide(func() (C, error) { return 2, waitForUs() }) + + c.RequireInvoke(func(a A, b B, c C) { + require.Equal(t, a, A(0)) + require.Equal(t, b, B(1)) + require.Equal(t, c, C(2)) + require.Equal(t, running, int32(3)) + }) + }) + + t.Run("TestUnboundConcurrency", func(t *testing.T) { + t.Parallel() + + var ( + timer = time.NewTimer(10 * time.Second) + max int32 = 20 + done = make(chan struct{}) + running int32 = 0 + waitForUs = func() error { + if atomic.AddInt32(&running, 1) >= max { + close(done) + } + select { + case <-timer.C: + return errors.New("timeout expired") + case <-done: + return nil + } + } + c = digtest.New(t, dig.UnboundedConcurrency) + expected []int + ) + + for i := 0; i < int(max); i++ { + i := i + expected = append(expected, i) + type out struct { + dig.Out + + Value int `group:"a"` + } + c.RequireProvide(func() (out, error) { return out{Value: i}, waitForUs() }) + } + + type in struct { + dig.In + + Values []int `group:"a"` + } + + c.RequireInvoke(func(i in) { + require.ElementsMatch(t, expected, i.Values) + }) + }) +} diff --git a/funcinfo.go b/funcinfo.go new file mode 100644 index 00000000..ba3e1bf2 --- /dev/null +++ b/funcinfo.go @@ -0,0 +1,64 @@ +package dig + +import ( + "go.uber.org/dig/internal/digreflect" + "go.uber.org/dig/internal/dot" + "reflect" +) + +type funcInfo interface { + // ID is a unique numerical identifier for this provider. + ID() dot.CtorID + + CType() reflect.Type + + // ParamList returns information about the direct dependencies of this + // constructor. + ParamList() paramList + + // Location returns where this function was defined. + Location() *digreflect.Func +} + +type baseFuncInfo struct { + ctor interface{} + ctype reflect.Type + id dot.CtorID + paramList paramList + location *digreflect.Func +} + +func newFuncInfo(ctor interface{}, store containerStore) (baseFuncInfo, error) { + cval := reflect.ValueOf(ctor) + ctype := cval.Type() + cptr := cval.Pointer() + + params, err := newParamList(ctype, store) + if err != nil { + return baseFuncInfo{}, err + } + + return baseFuncInfo{ + ctor: ctor, + ctype: ctype, + id: dot.CtorID(cptr), + paramList: params, + location: digreflect.InspectFunc(ctor), + }, nil +} + +func (c *baseFuncInfo) ParamList() paramList { + return c.paramList +} + +func (c *baseFuncInfo) CType() reflect.Type { + return c.ctype +} + +func (c *baseFuncInfo) Location() *digreflect.Func { + return c.location +} + +func (c *baseFuncInfo) ID() dot.CtorID { + return c.id +} diff --git a/invoke.go b/invoke.go index acfc25af..d916f12c 100644 --- a/invoke.go +++ b/invoke.go @@ -82,7 +82,14 @@ func (s *Scope) Invoke(function interface{}, opts ...InvokeOption) error { s.isVerifiedAcyclic = true } - args, err := pl.BuildList(s) + var args []reflect.Value + + d := pl.BuildList(s, &args) + d.observe(func(err2 error) { + err = err2 + }) + s.sched.flush() + if err != nil { return errArgumentsFailed{ Func: digreflect.InspectFunc(function), diff --git a/param.go b/param.go index fef4089a..be206851 100644 --- a/param.go +++ b/param.go @@ -46,10 +46,13 @@ type param interface { fmt.Stringer // Builds this dependency and any of its dependencies from the provided - // Container. + // Container. It stores the result in the pointed-to reflect.Value, allocating + // it first if it points to an invalid reflect.Value. + // + // Build returns a deferred that resolves once the reflect.Value is filled in. // // This MAY panic if the param does not produce a single value. - Build(containerStore) (reflect.Value, error) + Build(containerStore, *reflect.Value) *deferred // DotParam returns a slice of dot.Param(s). DotParam() []*dot.Param @@ -137,23 +140,21 @@ func newParamList(ctype reflect.Type, c containerStore) (paramList, error) { return pl, nil } -func (pl paramList) Build(containerStore) (reflect.Value, error) { +func (pl paramList) Build(containerStore, *reflect.Value) *deferred { digerror.BugPanicf("paramList.Build() must never be called") panic("") // Unreachable, as BugPanicf above will panic. } -// BuildList returns an ordered list of values which may be passed directly -// to the underlying constructor. -func (pl paramList) BuildList(c containerStore) ([]reflect.Value, error) { - args := make([]reflect.Value, len(pl.Params)) +// BuildList builds an ordered list of values which may be passed directly +// to the underlying constructor and stores them in the pointed-to slice. +// It returns a deferred that resolves when the slice is filled out. +func (pl paramList) BuildList(c containerStore, targets *[]reflect.Value) *deferred { + children := make([]*deferred, len(pl.Params)) + *targets = make([]reflect.Value, len(pl.Params)) for i, p := range pl.Params { - var err error - args[i], err = p.Build(c) - if err != nil { - return nil, err - } + children[i] = p.Build(c, &(*targets)[i]) } - return args, nil + return whenAll(children...) } // paramSingle is an explicitly requested type, optionally with a name. @@ -207,9 +208,14 @@ func (ps paramSingle) getValue(c containerStore) (reflect.Value, bool) { return _noValue, false } -func (ps paramSingle) Build(c containerStore) (reflect.Value, error) { +func (ps paramSingle) Build(c containerStore, target *reflect.Value) *deferred { + if !target.IsValid() { + *target = reflect.New(ps.Type).Elem() + } + if v, ok := ps.getValue(c); ok { - return v, nil + target.Set(v) + return &alreadyResolved } // Starting at the given container and working our way up its parents, @@ -228,34 +234,52 @@ func (ps paramSingle) Build(c containerStore) (reflect.Value, error) { if len(providers) == 0 { if ps.Optional { - return reflect.Zero(ps.Type), nil + target.Set(reflect.Zero(ps.Type)) + return &alreadyResolved + } else { + return failedDeferred(newErrMissingTypes(c, key{name: ps.Name, t: ps.Type})) } - return _noValue, newErrMissingTypes(c, key{name: ps.Name, t: ps.Type}) } - for _, n := range providers { - err := n.Call(c) - if err == nil { - continue - } + var ( + doNext func(i int) + d = new(deferred) + ) - // If we're missing dependencies but the parameter itself is optional, - // we can just move on. - if _, ok := err.(errMissingDependencies); ok && ps.Optional { - return reflect.Zero(ps.Type), nil + doNext = func(i int) { + if i == len(providers) { + // If we get here, it's impossible for the value to be absent from the + // container. + v, _ := ps.getValue(c) + if v.IsValid() { + // Not valid during a dry run + target.Set(v) + } + d.resolve(nil) + return } - return _noValue, errParamSingleFailed{ - CtorID: n.ID(), - Key: key{t: ps.Type, name: ps.Name}, - Reason: err, - } + n := providers[i] + + n.Call(c).observe(func(err error) { + if err != nil { + // If we're missing dependencies but the parameter itself is optional, + // we can just move on. + if _, ok := err.(errMissingDependencies); !ok || !ps.Optional { + d.resolve(errParamSingleFailed{ + CtorID: n.ID(), + Key: key{t: ps.Type, name: ps.Name}, + Reason: err, + }) + return + } + } + doNext(i + 1) + }) } - // If we get here, it's impossible for the value to be absent from the - // container. - v, _ := ps.getValue(c) - return v, nil + doNext(0) + return d } // paramObject is a dig.In struct where each field is another param. @@ -342,16 +366,19 @@ func newParamObject(t reflect.Type, c containerStore) (paramObject, error) { return po, nil } -func (po paramObject) Build(c containerStore) (reflect.Value, error) { - dest := reflect.New(po.Type).Elem() - for _, f := range po.Fields { - v, err := f.Build(c) - if err != nil { - return dest, err - } - dest.Field(f.FieldIndex).Set(v) +func (po paramObject) Build(c containerStore, target *reflect.Value) *deferred { + if !target.IsValid() { + *target = reflect.New(po.Type).Elem() + } + + children := make([]*deferred, len(po.Fields)) + for i, f := range po.Fields { + f := f + field := target.Field(f.FieldIndex) + children[i] = f.Build(c, &field) } - return dest, nil + + return whenAll(children...) } // paramObjectField is a single field of a dig.In struct. @@ -417,12 +444,8 @@ func newParamObjectField(idx int, f reflect.StructField, c containerStore) (para return pof, nil } -func (pof paramObjectField) Build(c containerStore) (reflect.Value, error) { - v, err := pof.Param.Build(c) - if err != nil { - return v, err - } - return v, nil +func (pof paramObjectField) Build(c containerStore, target *reflect.Value) *deferred { + return pof.Param.Build(c, target) } // paramGroupedSlice is a param which produces a slice of values with the same @@ -485,28 +508,38 @@ func newParamGroupedSlice(f reflect.StructField, c containerStore) (paramGrouped return pg, nil } -func (pt paramGroupedSlice) Build(c containerStore) (reflect.Value, error) { +func (pt paramGroupedSlice) Build(c containerStore, target *reflect.Value) *deferred { var itemCount int stores := c.storesToRoot() + + var children []*deferred + for _, c := range stores { providers := c.getGroupProviders(pt.Group, pt.Type.Elem()) itemCount += len(providers) for _, n := range providers { - if err := n.Call(c); err != nil { - return _noValue, errParamGroupFailed{ + n := n + child := n.Call(c) + children = append(children, child.catch(func(err error) error { + return errParamGroupFailed{ CtorID: n.ID(), Key: key{group: pt.Group, t: pt.Type.Elem()}, Reason: err, } - } + })) } } - result := reflect.MakeSlice(pt.Type, 0, itemCount) - for _, c := range stores { - result = reflect.Append(result, c.getValueGroup(pt.Group, pt.Type.Elem())...) - } - return result, nil + return whenAll(children...).then(func() error { + if !target.IsValid() { + *target = reflect.MakeSlice(pt.Type, 0, itemCount) + } + + for _, c := range stores { + target.Set(reflect.Append(*target, c.getValueGroup(pt.Group, pt.Type.Elem())...)) + } + return nil + }) } // Checks if ignoring unexported files in an In struct is allowed. diff --git a/param_test.go b/param_test.go index 7a1f41ed..8f38c3bd 100644 --- a/param_test.go +++ b/param_test.go @@ -33,7 +33,8 @@ func TestParamListBuild(t *testing.T) { p, err := newParamList(reflect.TypeOf(func() io.Writer { return nil }), newScope()) require.NoError(t, err) assert.Panics(t, func() { - p.Build(newScope()) + var target reflect.Value + p.Build(newScope(), &target) }) } diff --git a/provide.go b/provide.go index 510476bc..5de862ce 100644 --- a/provide.go +++ b/provide.go @@ -355,7 +355,7 @@ type provider interface { // // The values produced by this provider should be submitted into the // containerStore. - Call(containerStore) error + Call(store containerStore) *deferred CType() reflect.Type } diff --git a/scheduler.go b/scheduler.go new file mode 100644 index 00000000..7010023e --- /dev/null +++ b/scheduler.go @@ -0,0 +1,153 @@ +package dig + +// A scheduler queues work during resolution of params. +// constructorNode uses it to call its constructor function. +// This may happen in parallel with other calls (parallelScheduler) or +// synchronously, right when enqueued. +// +// Work is enqueued when building a paramList, but the user of scheduler +// must call flush() for asynchronous calls to proceed after the top-level +// paramList.BuildList() is called. +type scheduler interface { + // schedule will call a the supplied func. The deferred will resolve + // after the func is called. The func may be called before schedule + // returns. The deferred will be resolved on the "main" goroutine, so + // it's safe to mutate containerStore during its resolution. It will + // always be resolved with a nil error. + schedule(func()) *deferred + + // flush processes enqueued work. This may in turn enqueue more work; + // flush will keep processing the work until it's empty. After flush is + // called, every deferred returned from schedule will have been resolved. + // Asynchronous deferred values returned from schedule are resolved on the + // same goroutine as the one calling this method. + // + // The scheduler is ready for re-use after flush is called. + flush() +} + +// synchronousScheduler is stateless and calls funcs as soon as they are schedule. It produces +// the exact same results as the code before deferred was introduced. +type synchronousScheduler struct{} + +// schedule calls func and returns an already-resolved deferred. +func (s synchronousScheduler) schedule(fn func()) *deferred { + fn() + return &alreadyResolved +} + +// flush does nothing. All returned deferred values are already resolved. +func (s synchronousScheduler) flush() { + +} + +// task is used by parallelScheduler to remember which function to +// call and which deferred to notify afterwards. +type task struct { + fn func() + d *deferred +} + +// parallelScheduler processes enqueued work using a fixed-size worker pool. +// The pool is started and stopped during the call to flush. +type parallelScheduler struct { + concurrency int + tasks []task +} + +// schedule enqueues a task and returns an unresolved deferred. It will be +// resolved during flush. +func (p *parallelScheduler) schedule(fn func()) *deferred { + d := &deferred{} + p.tasks = append(p.tasks, task{fn, d}) + return d +} + +// flush processes enqueued work. concurrency controls how many executor +// goroutines are started and thus the maximum number of calls that may +// proceed in parallel. The real level of concurrency may be lower for +// CPU-heavy workloads if Go doesn't assign these goroutines to OS threads. +func (p *parallelScheduler) flush() { + inFlight := 0 + taskChan := make(chan task) + resultChan := make(chan *deferred) + + for n := 0; n < p.concurrency; n++ { + go func() { + for t := range taskChan { + t.fn() + resultChan <- t.d + } + }() + } + + for inFlight > 0 || len(p.tasks) > 0 { + var t task + var outChan chan<- task + + if len(p.tasks) > 0 { + t = p.tasks[len(p.tasks)-1] + outChan = taskChan + } + + select { + case outChan <- t: + inFlight++ + p.tasks = p.tasks[0 : len(p.tasks)-1] + case d := <-resultChan: + inFlight-- + d.resolve(nil) + } + } + + close(taskChan) + close(resultChan) + + p.tasks = nil +} + +// unboundedScheduler starts a goroutine per task. Maximum concurrency is +// controlled by Go's allocation of OS threads to goroutines. +type unboundedScheduler struct { + tasks []task +} + +// schedule enqueues a task and returns an unresolved deferred. It will be +// resolved during flush. +func (p *unboundedScheduler) schedule(fn func()) *deferred { + d := &deferred{} + p.tasks = append(p.tasks, task{fn, d}) + return d +} + +// flush processes enqueued work with unlimited concurrency. The actual limit +// is up to Go's allocation of OS resources to goroutines. +func (p *unboundedScheduler) flush() { + inFlight := 0 + resultChan := make(chan *deferred) + + for inFlight > 0 || len(p.tasks) > 0 { + if len(p.tasks) > 0 { + t := p.tasks[len(p.tasks)-1] + p.tasks = p.tasks[0 : len(p.tasks)-1] + + go func() { + t.fn() + resultChan <- t.d + }() + + inFlight++ + continue + } + + select { + case d := <-resultChan: + inFlight-- + d.resolve(nil) + } + } + + close(resultChan) + + p.tasks = nil +} diff --git a/scope.go b/scope.go index 278064f0..619888b5 100644 --- a/scope.go +++ b/scope.go @@ -69,6 +69,8 @@ type Scope struct { // invokerFn calls a function with arguments provided to Provide or Invoke. invokerFn invokerFn + sched scheduler + // graph of this Scope. Note that this holds the dependency graph of all the // nodes that affect this Scope, not just the ones provided directly to this Scope. gh *graphHolder @@ -87,6 +89,7 @@ func newScope() *Scope { groups: make(map[key][]reflect.Value), invokerFn: defaultInvoker, rand: rand.New(rand.NewSource(time.Now().UnixNano())), + sched: synchronousScheduler{}, } s.gh = newGraphHolder(s) return s @@ -214,6 +217,10 @@ func (s *Scope) invoker() invokerFn { return s.invokerFn } +func (s *Scope) scheduler() scheduler { + return s.sched +} + // adds a new graphNode to this Scope and all of its descendent // scope. func (s *Scope) newGraphNode(wrapped interface{}, orders map[*Scope]int) {