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 }