// heartbeatLoop periodically sends a HeartbeatTxn RPC to an extant // transaction, stopping in the event the transaction is aborted or // committed after attempting to resolve the intents. When the // heartbeat stops, the transaction is unregistered from the // coordinator, func (tc *TxnCoordSender) heartbeatLoop(txnID uuid.UUID) { var tickChan <-chan time.Time { ticker := time.NewTicker(tc.heartbeatInterval) tickChan = ticker.C defer ticker.Stop() } defer func() { tc.Lock() duration, restarts, status := tc.unregisterTxnLocked(txnID) tc.Unlock() tc.updateStats(duration, int64(restarts), status) }() var closer <-chan struct{} var sp opentracing.Span { tc.Lock() txnMeta := tc.txns[txnID] // do not leak to outer scope closer = txnMeta.txnEnd // TODO(tschottdorf): this should join to the trace of the request // which starts this goroutine. sp = tc.tracer.StartSpan(opHeartbeatLoop) defer sp.Finish() tc.Unlock() } if closer == nil { // Avoid race in which a Txn is cleaned up before the heartbeat // goroutine gets a chance to start. return } ctx := opentracing.ContextWithSpan(context.Background(), sp) // Loop with ticker for periodic heartbeats. for { select { case <-tickChan: if !tc.heartbeat(txnID, sp, ctx) { return } case <-closer: // Transaction finished normally. return case <-tc.stopper.ShouldDrain(): return } } }
// FinishSpan closes the given span (if not nil). It is a convenience wrapper // for span.Finish() which tolerates nil spans. func FinishSpan(span opentracing.Span) { if span != nil { span.Finish() } }