// 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(id string) { var tickChan <-chan time.Time { ticker := time.NewTicker(tc.heartbeatInterval) tickChan = ticker.C defer ticker.Stop() } defer func() { tc.Lock() tc.unregisterTxnLocked(id) tc.Unlock() }() var closer <-chan struct{} var trace *tracer.Trace { tc.Lock() txnMeta := tc.txns[id] // do not leak to outer scope closer = txnMeta.txnEnd trace = tc.tracer.NewTrace(tracer.Coord, &txnMeta.txn) defer trace.Finalize() 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 := tracer.ToCtx(context.Background(), trace) // Loop with ticker for periodic heartbeats. for { select { case <-tickChan: if !tc.heartbeat(id, trace, ctx) { return } case <-closer: // Transaction finished normally. return case <-tc.stopper.ShouldDrain(): return } } }
// heartbeat 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) heartbeat(id string) { var tickChan <-chan time.Time { ticker := time.NewTicker(tc.heartbeatInterval) tickChan = ticker.C defer ticker.Stop() } defer tc.unregisterTxn(id) var closer <-chan struct{} var trace *tracer.Trace { tc.Lock() txnMeta := tc.txns[id] // do not leak to outer scope closer = txnMeta.txnEnd trace = tc.tracer.NewTrace(&txnMeta.txn) 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 := tracer.ToCtx(context.Background(), trace) defer trace.Finalize() // Loop with ticker for periodic heartbeats. for { select { case <-tickChan: tc.Lock() proceed := true txnMeta := tc.txns[id] // Before we send a heartbeat, determine whether this transaction // should be considered abandoned. If so, exit heartbeat. if txnMeta.hasClientAbandonedCoord(tc.clock.PhysicalNow()) { // TODO(tschottdorf): should we be more proactive here? // The client might be continuing the transaction // through another coordinator, but in the most likely // case it's just gone and the open transaction record // could block concurrent operations. if log.V(1) { log.Infof("transaction %s abandoned; stopping heartbeat", txnMeta.txn) } proceed = false } // txnMeta.txn is possibly replaced concurrently, // so grab a copy before unlocking. txn := txnMeta.txn tc.Unlock() if !proceed { return } request := &proto.HeartbeatTxnRequest{ RequestHeader: proto.RequestHeader{ Key: txn.Key, User: security.RootUser, Txn: &txn, }, } request.Header().Timestamp = tc.clock.Now() reply := &proto.HeartbeatTxnResponse{} call := proto.Call{ Args: request, Reply: reply, } epochEnds := trace.Epoch("heartbeat") tc.wrapped.Send(ctx, call) epochEnds() // If the transaction is not in pending state, then we can stop // the heartbeat. It's either aborted or committed, and we resolve // write intents accordingly. if reply.GoError() != nil { log.Warningf("heartbeat to %s failed: %s", txn, reply.GoError()) } else if reply.Txn != nil && reply.Txn.Status != proto.PENDING { // Signal cleanup. Doesn't do much but stop this goroutine, but // let's be future-proof. tc.cleanupTxn(trace, *reply.Txn) return } case <-closer: // Transaction finished normally. return } } }