decred.org/dcrdex@v1.0.5/server/market/epump.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package market
     5  
     6  import (
     7  	"context"
     8  	"sync"
     9  
    10  	"decred.org/dcrdex/dex/order"
    11  	"decred.org/dcrdex/server/matcher"
    12  )
    13  
    14  type readyEpoch struct {
    15  	*EpochQueue
    16  	ready          chan struct{} // close this when the struct is ready
    17  	cSum           []byte
    18  	ordersRevealed []*matcher.OrderRevealed
    19  	misses         []order.Order
    20  }
    21  
    22  type epochPump struct {
    23  	ready chan *readyEpoch // consumer receives from this
    24  
    25  	mtx  sync.RWMutex
    26  	q    []*readyEpoch
    27  	halt bool
    28  
    29  	newQ chan struct{}
    30  }
    31  
    32  func newEpochPump() *epochPump {
    33  	return &epochPump{
    34  		ready: make(chan *readyEpoch, 1),
    35  		newQ:  make(chan struct{}, 1),
    36  	}
    37  }
    38  
    39  func (ep *epochPump) Run(ctx context.Context) {
    40  	// Context cancellation must cause a graceful shutdown.
    41  	go func() {
    42  		<-ctx.Done()
    43  
    44  		// next will close it after it drains the queue.
    45  		ep.mtx.Lock()
    46  		ep.halt = true
    47  		ep.mtx.Unlock()
    48  		ep.newQ <- struct{}{} // final popFront returns nil and next returns closed channel
    49  	}()
    50  
    51  	defer close(ep.ready)
    52  	for {
    53  		rq, ok := <-ep.next()
    54  		if !ok {
    55  			return
    56  		}
    57  		ep.ready <- rq // consumer should receive this
    58  	}
    59  }
    60  
    61  // Insert enqueues an EpochQueue and starts preimage collection immediately.
    62  // Access epoch queues in order and when they have completed preimage collection
    63  // by receiving from the epochPump.ready channel.
    64  func (ep *epochPump) Insert(epoch *EpochQueue) *readyEpoch {
    65  	rq := &readyEpoch{
    66  		EpochQueue: epoch,
    67  		ready:      make(chan struct{}),
    68  	}
    69  
    70  	ep.mtx.Lock()
    71  	defer ep.mtx.Unlock()
    72  	if ep.halt {
    73  		return nil
    74  	}
    75  
    76  	// push: append a new readyEpoch to the closed epoch queue.
    77  	ep.q = append(ep.q, rq)
    78  
    79  	// Signal to next in goroutine so slow or missing receiver doesn't block us.
    80  	go func() { ep.newQ <- struct{}{} }()
    81  
    82  	return rq
    83  }
    84  
    85  // popFront removes the next readyEpoch from the closed epoch queue, q. It is
    86  // not thread-safe. pop is only used in next to advance the head of the pump.
    87  func (ep *epochPump) popFront() *readyEpoch {
    88  	if len(ep.q) == 0 {
    89  		return nil
    90  	}
    91  	x := ep.q[0]
    92  	ep.q = ep.q[1:]
    93  	return x
    94  }
    95  
    96  // next provides a channel for receiving the next readyEpoch when it completes
    97  // preimage collection. next blocks until there is an epoch to send.
    98  func (ep *epochPump) next() <-chan *readyEpoch {
    99  	ready := make(chan *readyEpoch) // next sent on this channel when ready
   100  
   101  	// Wait for new epoch in queue or halt.
   102  	<-ep.newQ
   103  
   104  	ep.mtx.Lock()
   105  	head := ep.popFront()
   106  	ep.mtx.Unlock()
   107  
   108  	if head == nil { // pump halted
   109  		close(ready)
   110  		return ready
   111  	}
   112  
   113  	// Send next on the returned channel when it becomes ready. If the process
   114  	// dies before goroutine completion, the Market is down anyway.
   115  	go func() {
   116  		<-head.ready // block until preimage collection is complete (close this channel)
   117  		ready <- head
   118  	}()
   119  	return ready
   120  }