decred.org/dcrdex@v1.0.5/dex/runner.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 dex 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "sync" 11 "sync/atomic" 12 ) 13 14 // contextManager is used to manage a context and its cancellation function. 15 type contextManager struct { 16 mtx sync.RWMutex 17 cancel context.CancelFunc 18 ctx context.Context 19 } 20 21 // init uses the passed context to create and save a child context and 22 // its cancellation function. 23 func (cm *contextManager) init(ctx context.Context) { 24 cm.mtx.Lock() 25 defer cm.mtx.Unlock() 26 cm.ctx, cm.cancel = context.WithCancel(ctx) 27 } 28 29 // On will be true until the context is canceled. 30 func (cm *contextManager) On() bool { 31 cm.mtx.RLock() 32 defer cm.mtx.RUnlock() 33 return cm.ctx != nil && cm.ctx.Err() == nil 34 } 35 36 // Runner is satisfied by DEX subsystems, which must start any of their 37 // goroutines via the Run method. 38 type Runner interface { 39 Run(ctx context.Context) 40 // Ready() <-chan struct{} 41 } 42 43 // StartStopWaiter wraps a Runner, providing the non-blocking Start and Stop 44 // methods, and the blocking WaitForShutdown method. 45 type StartStopWaiter struct { 46 contextManager 47 wg sync.WaitGroup 48 runner Runner 49 } 50 51 // NewStartStopWaiter creates a StartStopWaiter from a Runner. 52 func NewStartStopWaiter(runner Runner) *StartStopWaiter { 53 return &StartStopWaiter{ 54 runner: runner, 55 } 56 } 57 58 // Start launches the Runner in a goroutine. Start will return immediately. Use 59 // Stop to signal the Runner to stop, followed by WaitForShutdown to allow 60 // shutdown to complete. 61 func (ssw *StartStopWaiter) Start(ctx context.Context) { 62 ssw.init(ctx) 63 ssw.wg.Add(1) 64 go func() { 65 ssw.runner.Run(ssw.ctx) 66 ssw.cancel() // in case it stopped on its own 67 ssw.wg.Done() 68 }() 69 // TODO: do <-ssw.runner.Ready() 70 } 71 72 // WaitForShutdown blocks until the Runner has returned in response to Stop. 73 func (ssw *StartStopWaiter) WaitForShutdown() { 74 ssw.wg.Wait() 75 } 76 77 // Stop cancels the context. 78 func (ssw *StartStopWaiter) Stop() { 79 ssw.mtx.RLock() 80 ssw.cancel() 81 ssw.mtx.RUnlock() 82 } 83 84 // Connector is any type that implements the Connect method, which will return 85 // a connection error, and a WaitGroup that can be waited on at Disconnection. 86 type Connector interface { 87 Connect(ctx context.Context) (*sync.WaitGroup, error) 88 } 89 90 // ConnectionMaster manages a Connector. 91 type ConnectionMaster struct { 92 connector Connector 93 cancel context.CancelFunc 94 done atomic.Value // chan struct{} 95 } 96 97 // NewConnectionMaster creates a new ConnectionMaster. The Connect method should 98 // be used before Disconnect. The On, Done, and Wait methods may be used at any 99 // time. However, prior to Connect, Wait and Done immediately return and signal 100 // completion, respectively. 101 func NewConnectionMaster(c Connector) *ConnectionMaster { 102 return &ConnectionMaster{ 103 connector: c, 104 } 105 } 106 107 // Connect connects the Connector, and returns any initial connection error. Use 108 // Disconnect to shut down the Connector. Even if Connect returns a non-nil 109 // error, On may report true until Disconnect is called. You would use Connect 110 // if the wrapped Connector has a reconnect loop to continually attempt to 111 // establish a connection even if the initial attempt fails. Use ConnectOnce if 112 // the Connector should be given one chance to connect before being considered 113 // not to be "on". If the ConnectionMaster is discarded on error, it is not 114 // important which method is used. 115 func (c *ConnectionMaster) Connect(ctx context.Context) (err error) { 116 if c.On() { // probably a bug in the consumer 117 return errors.New("already running") 118 } 119 120 // Attempt to start the Connector. 121 ctx, cancel := context.WithCancel(ctx) 122 wg, err := c.connector.Connect(ctx) 123 if err != nil { 124 cancel() // no context leak 125 return fmt.Errorf("connect failure: %w", err) 126 } 127 // NOTE: A non-nil error currently does not indicate that the Connector is 128 // not running, only that the initial connection attempt has failed. As long 129 // as the WaitGroup is non-nil we need to wait on it. We return the error so 130 // that the caller may decide to stop it or wait (see ConnectOnce). 131 132 // It's running, enable Disconnect. 133 c.cancel = cancel // caller should synchronize Connect/Disconnect calls 134 135 // Done and On may be checked at any time. 136 done := make(chan struct{}) 137 c.done.Store(done) 138 139 go func() { // capture the local variables 140 wg.Wait() 141 cancel() // if the Connector just died on its own, don't leak the context 142 close(done) 143 }() 144 145 return err 146 } 147 148 // ConnectOnce is like Connect, but on error the internal status is updated so 149 // that the On method returns false. This method may be used if an error from 150 // the Connector is terminal. The caller may also use Connect if they cancel the 151 // parent context or call Disconnect. 152 func (c *ConnectionMaster) ConnectOnce(ctx context.Context) (err error) { 153 if err = c.Connect(ctx); err != nil { 154 // If still "On", disconnect. 155 // c.Disconnect() // no-op if not "On" 156 if c.cancel != nil { 157 c.cancel() 158 <-c.done.Load().(chan struct{}) // wait for Connector 159 } 160 } 161 return err 162 } 163 164 // Done returns a channel that is closed when the Connector's WaitGroup is done. 165 // If called before Connect, a closed channel is returned. 166 func (c *ConnectionMaster) Done() <-chan struct{} { 167 done, ok := c.done.Load().(chan struct{}) 168 if ok { 169 return done 170 } 171 done = make(chan struct{}) 172 close(done) 173 return done 174 } 175 176 // On indicates if the Connector is running. This returns false if never 177 // connected, or if the Connector has completed shut down. 178 func (c *ConnectionMaster) On() bool { 179 select { 180 case <-c.Done(): 181 return false 182 default: 183 return true 184 } 185 } 186 187 // Wait waits for the Connector to shut down. It returns immediately if 188 // Connect has not been called yet. 189 func (c *ConnectionMaster) Wait() { 190 <-c.Done() // let the anon goroutine from Connect return 191 } 192 193 // Disconnect closes the connection and waits for shutdown. This must not be 194 // used before or concurrently with Connect. 195 func (c *ConnectionMaster) Disconnect() { 196 if !c.On() { 197 return 198 } 199 c.cancel() 200 c.Wait() 201 }