github.com/decred/dcrlnd@v0.7.6/batch/scheduler.go (about) 1 package batch 2 3 import ( 4 "sync" 5 "time" 6 7 "github.com/decred/dcrlnd/kvdb" 8 ) 9 10 // TimeScheduler is a batching engine that executes requests within a fixed 11 // horizon. When the first request is received, a TimeScheduler waits a 12 // configurable duration for other concurrent requests to join the batch. Once 13 // this time has elapsed, the batch is closed and executed. Subsequent requests 14 // are then added to a new batch which undergoes the same process. 15 type TimeScheduler struct { 16 db kvdb.Backend 17 locker sync.Locker 18 duration time.Duration 19 20 mu sync.Mutex 21 b *batch 22 } 23 24 // NewTimeScheduler initializes a new TimeScheduler with a fixed duration at 25 // which to schedule batches. If the operation needs to modify a higher-level 26 // cache, the cache's lock should be provided to so that external consistency 27 // can be maintained, as successful db operations will cause a request's 28 // OnCommit method to be executed while holding this lock. 29 func NewTimeScheduler(db kvdb.Backend, locker sync.Locker, 30 duration time.Duration) *TimeScheduler { 31 32 return &TimeScheduler{ 33 db: db, 34 locker: locker, 35 duration: duration, 36 } 37 } 38 39 // Execute schedules the provided request for batch execution along with other 40 // concurrent requests. The request will be executed within a fixed horizon, 41 // parameterizeed by the duration of the scheduler. The error from the 42 // underlying operation is returned to the caller. 43 // 44 // NOTE: Part of the Scheduler interface. 45 func (s *TimeScheduler) Execute(r *Request) error { 46 req := request{ 47 Request: r, 48 errChan: make(chan error, 1), 49 } 50 51 // Add the request to the current batch. If the batch has been cleared 52 // or no batch exists, create a new one. 53 s.mu.Lock() 54 if s.b == nil { 55 s.b = &batch{ 56 db: s.db, 57 clear: s.clear, 58 locker: s.locker, 59 } 60 time.AfterFunc(s.duration, s.b.trigger) 61 } 62 s.b.reqs = append(s.b.reqs, &req) 63 64 // If this is a non-lazy request, we'll execute the batch immediately. 65 if !r.lazy { 66 go s.b.trigger() 67 } 68 69 s.mu.Unlock() 70 71 // Wait for the batch to process the request. If the batch didn't 72 // ask us to execute the request individually, simply return the error. 73 err := <-req.errChan 74 if err != errSolo { 75 return err 76 } 77 78 // Obtain exclusive access to the cache if this scheduler needs to 79 // modify the cache in OnCommit. 80 if s.locker != nil { 81 s.locker.Lock() 82 defer s.locker.Unlock() 83 } 84 85 // Otherwise, run the request on its own. 86 commitErr := kvdb.Update(s.db, req.Update, func() { 87 if req.Reset != nil { 88 req.Reset() 89 } 90 }) 91 92 // Finally, return the commit error directly or execute the OnCommit 93 // closure with the commit error if present. 94 if req.OnCommit != nil { 95 return req.OnCommit(commitErr) 96 } 97 98 return commitErr 99 } 100 101 // clear resets the scheduler's batch to nil so that no more requests can be 102 // added. 103 func (s *TimeScheduler) clear(b *batch) { 104 s.mu.Lock() 105 if s.b == b { 106 s.b = nil 107 } 108 s.mu.Unlock() 109 }