github.com/haraldrudell/parl@v0.4.176/iters/base-iterator.go (about)

     1  /*
     2  © 2023–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package iters
     7  
     8  import (
     9  	"errors"
    10  	"sync"
    11  	"sync/atomic"
    12  
    13  	"github.com/haraldrudell/parl/internal/cyclebreaker"
    14  	"github.com/haraldrudell/parl/perrors"
    15  )
    16  
    17  // BaseIterator implements:
    18  //   - [Iterator] methods [Iterator.Cond] [Iterator.Next] [Iterator.Cancel]
    19  //   - consumer must:
    20  //   - — implement [Iterator.Init] since it returns the enclosing type
    21  //   - — provide IteratorAction[T]
    22  type BaseIterator[T any] struct {
    23  	iteratorAction IteratorAction[T]
    24  
    25  	// cancelState is updated by:
    26  	//	- Cancel() and
    27  	//	- enqueueForFn() inside i.publicsLock lock
    28  	//	- is state because Cancel is not complete until iteratorAction has returned
    29  	cancelState atomic.Uint32
    30  	// publicsLock serializes invocations of i.Next and i.Cancel
    31  	publicsLock sync.Mutex
    32  	// err contains any errors returned by fn other than parl.ErrEndCallbacks
    33  	//	- nil if fn is active
    34  	//	- non-nil pointer to nil error if fn completed without error
    35  	//	- — returned parl.ErrEndCallbacks
    36  	//	- — returned from FunctionIteratorCancel -1 invocation without error
    37  	//	- non-nil error if fn returned error
    38  	//	- updated by enqueueForFn() inside i.publicsLock lock
    39  	err         atomic.Pointer[error]
    40  	asyncCancel func()
    41  }
    42  
    43  // NewBaseIterator returns an implementation of Cond Next Cancel methods part of [iters.Iterator]
    44  //   - asyncCancel is used if function or converter iterators are blocking.
    45  //     asyncCancel indicates that a Cancel invocation has occurred
    46  func NewBaseIterator[T any](
    47  	iteratorAction IteratorAction[T],
    48  	asyncCancel ...func(),
    49  ) (iterator *BaseIterator[T]) {
    50  	if iteratorAction == nil {
    51  		panic(cyclebreaker.NilError("iteratorAction"))
    52  	}
    53  	var ac func()
    54  	if len(asyncCancel) > 0 {
    55  		ac = asyncCancel[0]
    56  	}
    57  	return &BaseIterator[T]{
    58  		iteratorAction: iteratorAction,
    59  		asyncCancel:    ac,
    60  	}
    61  }
    62  
    63  // Cond implements the condition statement of a Go “for” clause
    64  //   - condition is true if iterationVariable was assigned a value and the iteration should continue
    65  //   - the iterationVariable is updated by being provided as a pointer.
    66  //     iterationVariable cannot be nil
    67  //   - errp is an optional error pointer receiving any errors during iterator execution
    68  //
    69  // Usage:
    70  //
    71  //	for i, iterator := iters.NewSlicePointerIterator(someSlice).Init(); iterator.Cond(&i); {
    72  //	  // i is pointer to slice element
    73  func (i *BaseIterator[T]) Cond(iterationVariablep *T, errp ...*error) (condition bool) {
    74  	if iterationVariablep == nil {
    75  		cyclebreaker.NilError("iterationVariablep")
    76  	}
    77  
    78  	// outside lock check updating errp
    79  	if hasError, _ := i.getErr(replaceErrp, errp...); hasError {
    80  		return // iterator is canceled
    81  	}
    82  
    83  	// next value
    84  	var value T
    85  	if value, condition = i.Next(); condition {
    86  		*iterationVariablep = value
    87  	} else if len(errp) > 0 {
    88  		// collect any error for errp
    89  		i.getErr(replaceErrp, errp...)
    90  	}
    91  
    92  	return // condition and iterationVariablep updated, errp unchanged
    93  }
    94  
    95  // Next advances to next item and returns it
    96  //   - if hasValue true, value contains the next value
    97  //   - otherwise, no more items exist and value is the data type zero-value
    98  func (i *BaseIterator[T]) Next() (value T, hasValue bool) {
    99  
   100  	// fast outside-lock value-check
   101  	if i.err.Load() != nil {
   102  		return // no more values: zero-value and hasValue false
   103  	}
   104  	i.publicsLock.Lock()
   105  	defer i.publicsLock.Unlock()
   106  
   107  	// inside-lock check
   108  	if i.err.Load() != nil {
   109  		return // no more values: zero-value and hasValue false
   110  	}
   111  	value, hasValue = i.doAction(doNext)
   112  
   113  	return // hasValue true, valid value return
   114  }
   115  
   116  // Cancel stops an iteration
   117  //   - after Cancel invocation, Cond, Next and Same indicate no value available
   118  //   - Cancel returns the first error that occurred during iteration, if any
   119  //   - an iterator implementation may require Cancel invocation
   120  //     to release resources
   121  //   - Cancel is deferrable
   122  func (i *BaseIterator[T]) Cancel(errp ...*error) (err error) {
   123  
   124  	// fast outside-lock check
   125  	var hasErr bool
   126  	if hasErr, err = i.getErr(appendErrp, errp...); hasErr {
   127  		return // already canceled
   128  	}
   129  	// ensure cancel initiated prior to lock
   130  	if cancelStates(i.cancelState.Load()) == notCanceled {
   131  		i.cancelState.CompareAndSwap(uint32(notCanceled), uint32(cancelRequested))
   132  	}
   133  	// asyncCancel
   134  	if ac := i.asyncCancel; ac != nil {
   135  		ac()
   136  	}
   137  	// the lock provides wait mechanic
   138  	i.publicsLock.Lock()
   139  	defer i.publicsLock.Unlock()
   140  
   141  	// inside lock check
   142  	if hasErr, err = i.getErr(appendErrp, errp...); hasErr {
   143  		return // already canceled
   144  	}
   145  	// send cancel to iteratorAction
   146  	i.doAction(doCancel)
   147  	_, err = i.getErr(appendErrp, errp...)
   148  
   149  	return
   150  }
   151  
   152  const (
   153  	// [BaseIterator.doAction] invoked for next value
   154  	doNext anAction = false
   155  	// [BaseIterator.doAction] invoked for cancel
   156  	doCancel anAction = true
   157  )
   158  
   159  // Action for [BaseIterator.enqueueForFn]
   160  //   - doNext doCancel
   161  type anAction bool
   162  
   163  // doAction invokes [BaseIterator.iteratorAction]
   164  //   - invoked while holding [BaseIterator.publicsLock]
   165  //   - [BaseIterator.err] should have been checked inside lock
   166  //   - on return:
   167  //   - — if hasValue is true, value is valid
   168  //   - — [BaseIterator.err] may be updated
   169  //   - —
   170  //   - recovers from [BaseIterator.iteratorAction] panic
   171  func (i *BaseIterator[T]) doAction(action anAction) (value T, hasValue bool) {
   172  
   173  	// cancelState inside lock prior to invoking iteratorAction
   174  	var cancelState = cancelStates(i.cancelState.Load())
   175  	if cancelState == cancelRequested {
   176  		action = doCancel // after Cancel invoked, the only action is cancel
   177  	}
   178  	defer cyclebreaker.Recover(func() cyclebreaker.DA { return cyclebreaker.A() }, nil, i.setErr)
   179  
   180  	v, err := i.iteratorAction(bool(action))
   181  
   182  	// determine if value is valid
   183  	if hasValue = action == doNext && err == nil; hasValue {
   184  		value = v
   185  	}
   186  
   187  	// update state: cancelComplete endOfData errorReceived
   188  	var nextState cancelStates
   189  	if err != nil {
   190  		if !errors.Is(err, cyclebreaker.ErrEndCallbacks) {
   191  			// received an unknown error
   192  			nextState = errorReceived
   193  		} else {
   194  			// received end-of-data from iteratorAction
   195  			err = nil // ignore the error
   196  			if cancelState == notCanceled {
   197  				// spontaneous end of data
   198  				nextState = endOfData
   199  			} else {
   200  				// end-of-data return from cancel invocation
   201  				nextState = cancelComplete
   202  			}
   203  		}
   204  	} else if cancelState == cancelRequested {
   205  		// non-error return from cancel invocation
   206  		nextState = cancelComplete
   207  	}
   208  	if nextState != notCanceled {
   209  		i.setErr2(err, nextState)
   210  	}
   211  
   212  	return
   213  }
   214  
   215  const (
   216  	// [BaseIterator.getErr] uses [perrors.AppendError]
   217  	appendErrp getErrAppend = false
   218  	// [BaseIterator.getErr] replaces *errp
   219  	replaceErrp getErrAppend = true
   220  )
   221  
   222  // how [BaseIterator.getErr] updates *errp
   223  //   - appendErrp replaceErrp
   224  type getErrAppend bool
   225  
   226  // getErr reads [BaseIterator.err] and returns err, *errp
   227  func (i *BaseIterator[T]) getErr(replace getErrAppend, errp ...*error) (isPresent bool, err error) {
   228  	var ep = i.err.Load()
   229  	if isPresent = ep != nil; !isPresent {
   230  		return // err is not present yet
   231  	} else if err = *ep; err == nil {
   232  		return // result is no error
   233  	}
   234  	if len(errp) > 0 {
   235  		if errp0 := errp[0]; errp0 != nil {
   236  			if replace {
   237  				*errp0 = err // update errp with error
   238  			} else {
   239  				*errp0 = perrors.AppendError(*errp0, err)
   240  			}
   241  		}
   242  	}
   243  	return // error return
   244  }
   245  
   246  // setErr updates atomic error container
   247  func (i *BaseIterator[T]) setErr(err error) { i.setErr2(err, panicked) }
   248  
   249  // setErr2 updates atomic error container
   250  func (i *BaseIterator[T]) setErr2(err error, state cancelStates) {
   251  	i.cancelState.Store(uint32(state))
   252  	i.err.Store(&err)
   253  }