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 }