// New implements async.Source.New(). func (t *turnSource) New(f async.IOFunc) async.R { // Allocate a resolver for the caller to use to track the completion of the I/O computation. s := newTurnResolver(t.manager) r := async.R{async.NewResultT(s)} // Pre-allocate a turn from our manager that will execute on the manager later when the I/O // computation has completed. // THREADING: this turn MUST be allocated here on the manager's thread because manager operations // (e.g. NewID() are NOT multi-thread safe). var err error turn := NewTurn("IOResult"+t.manager.NewID().String(), func() { s.Resolve(nil, err) }) go func() { // Execute the function on an I/O thread (separate from the turn manager). err = f() // Once it is finished atomically marshall the result to the I/O source. t.lock.Lock() t.list = t.list.Append(turn) t.lock.Unlock() // Signal the source that there is a turn available. t.event.Signal() }() return r }
// NewTurnRunner creates a new runner that uses turns to schedule asynchronous // computations and completions. func NewTurnRunner(manager *Manager) async.Runner { return &turnRunner{ manager: manager, done: async.R{async.NewResultT(&turnResolver{ manager: manager, })}, } }
func (t *ManagerSuite) RunUntilSuccess() { m := NewManager(NewUniqueIDGenerator()) // Allocate a resolver to use as the "main" result for RunUntil. s := newTurnResolver(m) r := async.R{async.NewResultT(s)} m.Queue(NewTurn("main", func() { s.Complete(nil) })) // Verify that RunUntil completes when "main" fails. err := m.RunUntil(r) if err != nil { t.Errorf("Expected RunUntil to succeed. Got: %v, Want: nil", err) } }
func (t *ManagerSuite) RunUntilFailed() { m := NewManager(NewUniqueIDGenerator()) // Allocate a resolver to use as the "main" result for RunUntil. s := newTurnResolver(m) r := async.R{async.NewResultT(s)} expectedError := errors.New("Expected failure") m.Queue(NewTurn("main", func() { s.Fail(expectedError) })) // Verify that RunUntil completes when "main" fails. err := m.RunUntil(r) if err == nil { t.Errorf("Expected RunUntil to fail. Got: %v, Want: %v", err, expectedError) } }
// NewResult implements async.Runner.NewResultT(). func (t *turnRunner) NewResultT() (async.ResultT, async.ResolverT) { s := newTurnResolver(t.manager) r := async.NewResultT(s) return r, s }
// When implements Resolver.WhenT(). func (s *turnResolver) WhenT(in, out, outR reflect.Type, f interface{}) async.ResultT { // Validate function type - this is in lieu of static type checking from generics. fType := reflect.TypeOf(f) assert.True(fType.Kind() == reflect.Func, "f MUST be a WhenFunc") // Validate inputs - this is in lieu of static type checking from generics. assert.True(fType.NumIn() <= 2, "f MUST take val, err, both or neither") takeValue, takeError := true, true if numIn := fType.NumIn(); numIn == 2 { assert.True(in.AssignableTo(fType.In(0)), "in MUST be assignable to value") assert.True(fType.In(1) == reflectTypeError, "f MUST take err") } else if numIn == 1 { takeError = (fType.In(0) == reflectTypeError) takeValue = !takeError if takeValue { assert.True(in.AssignableTo(fType.In(0)), "in MUST be assignable to value") } else { assert.True(fType.In(0) == reflectTypeError, "f MUST take err") } } else if numIn == 0 { takeValue, takeError = false, false } // Validate outputs - this is in lieu of static type checking from generics. assert.True(fType.NumOut() <= 2, "f MUST return val, (val, err), or async") returnsResult := false if numOut := fType.NumOut(); numOut == 2 { assert.True(fType.Out(0).AssignableTo(out), "value MUST be assignable to out") assert.True(fType.Out(1) == reflectTypeError, "err MUST be error") } else if numOut == 1 { if fType.Out(0).Implements(reflectTypeAwaitableT) { returnsResult = true assert.True(fType.Out(0) == outR, "result MUST be ResultT") } else if fType.Out(0) == reflectTypeError { assert.True(out == reflectTypeInterface, "func() error ONLY allowed on void results") } else { assert.True(fType.Out(0).AssignableTo(out), "value MUST be assignable to out") } } else { assert.True(out == reflectTypeInterface, "func() ONLY allowed on void results") } // If this result is already forwarded then just forward the When as well. if s.next != nil { return s.getShortest().WhenT(in, out, outR, f) } // Create a new turn that will run once the result is resolved. outer := newTurnResolver(s.manager) turn := NewTurn("When"+s.manager.NewID().String(), func() { // Find the resolved value. final := s.getShortest() assert.True(final.isResolved(), "When's shouldn't run if the target is not resolved.") // Distinguish the error from the value. value := final.outcome err, isError := final.outcome.(error) if isError { value = nil } // If f doesn't take an error but the previous computation failed, then just flow it through to // the output by failing immediately. This allows for null-style error propagation which // simplifies handlers since that is a very common case. f is NEVER called in this case under // the assumption that its first line would be: if err != nil { return err }. if !takeError { if err != nil { outer.Fail(err) return } } // Convert the arguments into an array of Values args := make([]reflect.Value, 0, 2) if takeValue { if value == nil { args = append(args, reflect.New(in).Elem()) } else { args = append(args, reflect.ValueOf(value)) } } if takeError { if err == nil { args = append(args, reflect.New(reflectTypeError).Elem()) } else { args = append(args, reflect.ValueOf(err)) } } // Dispatch the result to the WhenFunc. retvals := reflect.ValueOf(f).Call(args) // Resolve the outer result. if returnsResult { outer.Forward(retvals[0].Interface().(async.AwaitableT).Base()) return } if numRets := len(retvals); numRets == 2 { if err, _ = retvals[1].Interface().(error); err != nil { outer.resolve(err) } else { outer.resolve(retvals[0].Interface()) } } else if numRets == 1 { outer.resolve(retvals[0].Interface()) } else { outer.resolve(nil) } }) s.queue(turn) return async.NewResultT(outer) }