github.com/franono/tendermint@v0.32.2-0.20200527150959-749313264ce9/blockchain/v2/routine.go (about)

     1  package v2
     2  
     3  import (
     4  	"fmt"
     5  	"sync/atomic"
     6  
     7  	"github.com/Workiva/go-datastructures/queue"
     8  
     9  	"github.com/franono/tendermint/libs/log"
    10  )
    11  
    12  type handleFunc = func(event Event) (Event, error)
    13  
    14  // Routine is a structure that models a finite state machine as serialized
    15  // stream of events processed by a handle function. This Routine structure
    16  // handles the concurrency and messaging guarantees. Events are sent via
    17  // `send` are handled by the `handle` function to produce an iterator
    18  // `next()`. Calling `stop()` on a routine will conclude processing of all
    19  // sent events and produce `final()` event representing the terminal state.
    20  type Routine struct {
    21  	name    string
    22  	handle  handleFunc
    23  	queue   *queue.PriorityQueue
    24  	out     chan Event
    25  	fin     chan error
    26  	rdy     chan struct{}
    27  	running *uint32
    28  	logger  log.Logger
    29  	metrics *Metrics
    30  }
    31  
    32  func newRoutine(name string, handleFunc handleFunc, bufferSize int) *Routine {
    33  	return &Routine{
    34  		name:    name,
    35  		handle:  handleFunc,
    36  		queue:   queue.NewPriorityQueue(bufferSize, true),
    37  		out:     make(chan Event, bufferSize),
    38  		rdy:     make(chan struct{}, 1),
    39  		fin:     make(chan error, 1),
    40  		running: new(uint32),
    41  		logger:  log.NewNopLogger(),
    42  		metrics: NopMetrics(),
    43  	}
    44  }
    45  
    46  func (rt *Routine) setLogger(logger log.Logger) {
    47  	rt.logger = logger
    48  }
    49  
    50  // nolint:unused
    51  func (rt *Routine) setMetrics(metrics *Metrics) {
    52  	rt.metrics = metrics
    53  }
    54  
    55  func (rt *Routine) start() {
    56  	rt.logger.Info(fmt.Sprintf("%s: run\n", rt.name))
    57  	running := atomic.CompareAndSwapUint32(rt.running, uint32(0), uint32(1))
    58  	if !running {
    59  		panic(fmt.Sprintf("%s is already running", rt.name))
    60  	}
    61  	close(rt.rdy)
    62  	defer func() {
    63  		stopped := atomic.CompareAndSwapUint32(rt.running, uint32(1), uint32(0))
    64  		if !stopped {
    65  			panic(fmt.Sprintf("%s is failed to stop", rt.name))
    66  		}
    67  	}()
    68  
    69  	for {
    70  		events, err := rt.queue.Get(1)
    71  		if err == queue.ErrDisposed {
    72  			rt.terminate(nil)
    73  			return
    74  		} else if err != nil {
    75  			rt.terminate(err)
    76  			return
    77  		}
    78  		oEvent, err := rt.handle(events[0].(Event))
    79  		rt.metrics.EventsHandled.With("routine", rt.name).Add(1)
    80  		if err != nil {
    81  			rt.terminate(err)
    82  			return
    83  		}
    84  		rt.metrics.EventsOut.With("routine", rt.name).Add(1)
    85  		rt.logger.Debug(fmt.Sprintf("%s: produced %T %+v\n", rt.name, oEvent, oEvent))
    86  
    87  		rt.out <- oEvent
    88  	}
    89  }
    90  
    91  // XXX: look into returning OpError in the net package
    92  func (rt *Routine) send(event Event) bool {
    93  	rt.logger.Debug(fmt.Sprintf("%s: received %T %+v", rt.name, event, event))
    94  	if !rt.isRunning() {
    95  		return false
    96  	}
    97  	err := rt.queue.Put(event)
    98  	if err != nil {
    99  		rt.metrics.EventsShed.With("routine", rt.name).Add(1)
   100  		rt.logger.Info(fmt.Sprintf("%s: send failed, queue was full/stopped \n", rt.name))
   101  		return false
   102  	}
   103  
   104  	rt.metrics.EventsSent.With("routine", rt.name).Add(1)
   105  	return true
   106  }
   107  
   108  func (rt *Routine) isRunning() bool {
   109  	return atomic.LoadUint32(rt.running) == 1
   110  }
   111  
   112  func (rt *Routine) next() chan Event {
   113  	return rt.out
   114  }
   115  
   116  func (rt *Routine) ready() chan struct{} {
   117  	return rt.rdy
   118  }
   119  
   120  func (rt *Routine) stop() {
   121  	if !rt.isRunning() { // XXX: this should check rt.queue.Disposed()
   122  		return
   123  	}
   124  
   125  	rt.logger.Info(fmt.Sprintf("%s: stop\n", rt.name))
   126  	rt.queue.Dispose() // this should block until all queue items are free?
   127  }
   128  
   129  func (rt *Routine) final() chan error {
   130  	return rt.fin
   131  }
   132  
   133  // XXX: Maybe get rid of this
   134  func (rt *Routine) terminate(reason error) {
   135  	// We don't close the rt.out channel here, to avoid spinning on the closed channel
   136  	// in the event loop.
   137  	rt.fin <- reason
   138  }