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  }