github.com/blend/go-sdk@v1.20220411.3/logger/worker.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package logger 9 10 import ( 11 "context" 12 13 "github.com/blend/go-sdk/async" 14 "github.com/blend/go-sdk/ex" 15 ) 16 17 // NewWorker returns a new worker. 18 func NewWorker(listener Listener) *Worker { 19 return &Worker{ 20 Latch: async.NewLatch(), 21 Listener: listener, 22 Work: make(chan EventWithContext, DefaultWorkerQueueDepth), 23 } 24 } 25 26 // Worker is an agent that processes a listener. 27 type Worker struct { 28 *async.Latch 29 Errors chan error 30 Listener Listener 31 Work chan EventWithContext 32 } 33 34 // Start starts the worker. 35 func (w *Worker) Start() error { 36 if !w.CanStart() { 37 return ex.New(async.ErrCannotStart) 38 } 39 w.Starting() 40 w.DispatchWork() 41 return nil 42 } 43 44 // Stop stops the worker. 45 func (w *Worker) Stop() error { 46 return w.StopContext(context.Background()) 47 } 48 49 // StopContext stops the worker and processe what work is left in the queue. 50 // The worker will be stopped at the end, and it will be required to Start 51 // the worker again to 52 func (w *Worker) StopContext(ctx context.Context) error { 53 // if the worker is currently processing work, wait for it to finish. 54 notifyStopped := w.NotifyStopped() 55 56 // signal that the DispatchWork loop should stop. 57 w.Stopping() 58 59 // wait for the DispatchWork loop to stop 60 // but also check if we've timed out. 61 select { 62 case <-notifyStopped: 63 break 64 case <-ctx.Done(): 65 return context.Canceled 66 } 67 68 // process what's left of the work queue. 69 var work EventWithContext 70 var err error 71 72 workLeft := len(w.Work) 73 drained := make(chan struct{}) 74 go func() { 75 // notify once the last of the work 76 // in the queue is complete. 77 defer close(drained) 78 79 // go through what's left of the work 80 // be mindful of the outer context timeout. 81 for index := 0; index < workLeft; index++ { 82 select { 83 case <-ctx.Done(): 84 w.Errors <- context.Canceled 85 return 86 case work = <-w.Work: 87 if err = w.Process(work); err != nil && w.Errors != nil { 88 w.Errors <- err 89 } 90 } 91 } 92 }() 93 94 select { 95 case <-ctx.Done(): 96 return context.Canceled 97 case <-drained: 98 return nil 99 } 100 } 101 102 // AbortContext stops the worker but does not process the remaining work. 103 func (w *Worker) AbortContext(ctx context.Context) error { 104 notifyStopped := w.NotifyStopped() 105 w.Stopping() 106 select { 107 case <-notifyStopped: 108 return nil 109 case <-ctx.Done(): 110 return context.Canceled 111 } 112 } 113 114 // DispatchWork is the dispatch loop where 115 // work is processed. 116 func (w *Worker) DispatchWork() { 117 w.Started() 118 var e EventWithContext 119 var err error 120 121 notifyStopping := w.NotifyStopping() 122 for { 123 select { 124 case <-notifyStopping: 125 w.Stopped() 126 return 127 case e = <-w.Work: 128 if err = w.Process(e); err != nil && w.Errors != nil { 129 w.Errors <- err 130 } 131 } 132 } 133 } 134 135 // Process calls the listener for an event. 136 func (w *Worker) Process(ec EventWithContext) (err error) { 137 defer func() { 138 if r := recover(); r != nil { 139 err = ex.New(r) 140 return 141 } 142 }() 143 w.Listener(ec.Context, ec.Event) 144 return 145 }