github.com/haraldrudell/parl@v0.4.176/future.go (about)

     1  /*
     2  © 2023–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package parl
     7  
     8  import (
     9  	"sync/atomic"
    10  
    11  	"github.com/haraldrudell/parl/perrors"
    12  )
    13  
    14  // Future is a container for an awaitable calculation result
    15  //   - Future allows a thread to await a value calculated in parallel
    16  //     by other threads
    17  //   - unlike for a promise, consumer manages any thread,
    18  //     therefore producing debuggable code and meaningful stack traces
    19  //   - a promise launches the thread why there is no trace of what code
    20  //     created the promise or why
    21  type Future[T any] struct {
    22  	// one-to-many wait mechanic based on channel
    23  	await Awaitable
    24  	// calculation outcome
    25  	result atomic.Pointer[TResult[T]]
    26  }
    27  
    28  // NewFuture returns an awaitable calculation
    29  //   - has an Awaitable and a thread-safe TResult container
    30  //
    31  // Usage:
    32  //
    33  //	 var calculation = NewFuture[someType]()
    34  //	 go calculateThread(calculation)
    35  //	 …
    36  //	 var result, isValid = calculation.Result()
    37  //
    38  //	func calculateThread(future *Future[someType]) {
    39  //	  var err error
    40  //	  var isPanic bool
    41  //	  var value someType
    42  //	  defer calculation.End(&value, &isPanic, &err)
    43  //	  defer parl.RecoverErr(func() parl.DA { return parl.A() }, &err, &isPanic)
    44  //
    45  //	   value = …
    46  func NewFuture[T any]() (calculation *Future[T]) { return &Future[T]{} }
    47  
    48  // IsCompleted returns whether the calculation is complete. Thread-safe
    49  func (f *Future[T]) IsCompleted() (isCompleted bool) { return f.await.IsClosed() }
    50  
    51  // Ch returns an awaitable channel. Thread-safe
    52  func (f *Future[T]) Ch() (ch AwaitableCh) { return f.await.Ch() }
    53  
    54  // Result retrieves the calculation’s result
    55  //   - May block. Thread-safe
    56  func (f *Future[T]) Result() (result T, hasValue bool) {
    57  
    58  	// blocks here
    59  	<-f.await.Ch()
    60  
    61  	if rp := f.result.Load(); rp != nil {
    62  		result = rp.Value
    63  		hasValue = rp.Err == nil
    64  	}
    65  
    66  	return
    67  }
    68  
    69  // TResult returns a pointer to the future’s result
    70  //   - nil if future has not resolved
    71  //   - thread-safe
    72  func (f *Future[T]) TResult() (tResult *TResult[T]) { return f.result.Load() }
    73  
    74  // End writes the result of the calculation, deferrable
    75  //   - value is considered valid if errp is nil or *errp is nil
    76  //   - End can make a goroutine channel-awaitable
    77  //   - End can only be invoked once or panic
    78  //   - any argument may be nil
    79  //   - thread-safe
    80  func (f *Future[T]) End(value *T, isPanic *bool, errp *error) {
    81  
    82  	// create result to swap-in for atomic
    83  	var result = NewTResult3(value, isPanic, errp)
    84  
    85  	// check for multiple invocations
    86  	if !f.result.CompareAndSwap(nil, result) {
    87  		panic(perrors.NewPF("End invoked multiple times"))
    88  	}
    89  
    90  	// trigger awaitable
    91  	f.await.Close()
    92  }