github.com/decred/dcrlnd@v0.7.6/batch/batch.go (about)

     1  package batch
     2  
     3  import (
     4  	"errors"
     5  	"sync"
     6  
     7  	"github.com/decred/dcrlnd/kvdb"
     8  )
     9  
    10  // errSolo is a sentinel error indicating that the requester should re-run the
    11  // operation in isolation.
    12  var errSolo = errors.New(
    13  	"batch function returned an error and should be re-run solo",
    14  )
    15  
    16  type request struct {
    17  	*Request
    18  	errChan chan error
    19  }
    20  
    21  type batch struct {
    22  	db     kvdb.Backend
    23  	start  sync.Once
    24  	reqs   []*request
    25  	clear  func(b *batch)
    26  	locker sync.Locker
    27  }
    28  
    29  // trigger is the entry point for the batch and ensures that run is started at
    30  // most once.
    31  func (b *batch) trigger() {
    32  	b.start.Do(b.run)
    33  }
    34  
    35  // run executes the current batch of requests. If any individual requests fail
    36  // alongside others they will be retried by the caller.
    37  func (b *batch) run() {
    38  	// Clear the batch from its scheduler, ensuring that no new requests are
    39  	// added to this batch.
    40  	b.clear(b)
    41  
    42  	// If a cache lock was provided, hold it until the this method returns.
    43  	// This is critical for ensuring external consistency of the operation,
    44  	// so that caches don't get out of sync with the on disk state.
    45  	if b.locker != nil {
    46  		b.locker.Lock()
    47  		defer b.locker.Unlock()
    48  	}
    49  
    50  	// Apply the batch until a subset succeeds or all of them fail. Requests
    51  	// that fail will be retried individually.
    52  	for len(b.reqs) > 0 {
    53  		var failIdx = -1
    54  		err := kvdb.Update(b.db, func(tx kvdb.RwTx) error {
    55  			for i, req := range b.reqs {
    56  				err := req.Update(tx)
    57  				if err != nil {
    58  					failIdx = i
    59  					return err
    60  				}
    61  			}
    62  			return nil
    63  		}, func() {
    64  			for _, req := range b.reqs {
    65  				if req.Reset != nil {
    66  					req.Reset()
    67  				}
    68  			}
    69  		})
    70  
    71  		// If a request's Update failed, extract it and re-run the
    72  		// batch. The removed request will be retried individually by
    73  		// the caller.
    74  		if failIdx >= 0 {
    75  			req := b.reqs[failIdx]
    76  
    77  			// It's safe to shorten b.reqs here because the
    78  			// scheduler's batch no longer points to us.
    79  			b.reqs[failIdx] = b.reqs[len(b.reqs)-1]
    80  			b.reqs = b.reqs[:len(b.reqs)-1]
    81  
    82  			// Tell the submitter re-run it solo, continue with the
    83  			// rest of the batch.
    84  			req.errChan <- errSolo
    85  			continue
    86  		}
    87  
    88  		// None of the remaining requests failed, process the errors
    89  		// using each request's OnCommit closure and return the error
    90  		// to the requester. If no OnCommit closure is provided, simply
    91  		// return the error directly.
    92  		for _, req := range b.reqs {
    93  			if req.OnCommit != nil {
    94  				req.errChan <- req.OnCommit(err)
    95  			} else {
    96  				req.errChan <- err
    97  			}
    98  		}
    99  
   100  		return
   101  	}
   102  }