Esempio n. 1
0
// 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
}
Esempio n. 2
0
// 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,
		})},
	}
}
Esempio n. 3
0
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)
	}
}
Esempio n. 4
0
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)
	}
}
Esempio n. 5
0
// NewResult implements async.Runner.NewResultT().
func (t *turnRunner) NewResultT() (async.ResultT, async.ResolverT) {
	s := newTurnResolver(t.manager)
	r := async.NewResultT(s)
	return r, s
}
Esempio n. 6
0
// 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)
}