// sendOne invokes the specified RPC on the supplied client when the // client is ready. On success, the reply is sent on the channel; // otherwise an error is sent. // // Do not call directly, but instead use sendOneFn. Tests mock out this method // via sendOneFn in order to test various error cases. func sendOne(client *rpc.Client, timeout time.Duration, method string, getArgs func(addr net.Addr) proto.Message, getReply func() proto.Message, context *rpc.Context, trace *tracer.Trace, done chan *netrpc.Call) { addr := client.RemoteAddr() args := getArgs(addr) if args == nil { done <- &netrpc.Call{Error: newRPCError( util.Errorf("nil arguments returned for client %s", addr))} return } if log.V(2) { log.Infof("%s: sending request to %s: %+v", method, addr, args) } trace.Event(fmt.Sprintf("sending to %s", addr)) if enableLocalCalls && context.LocalServer != nil && addr.String() == context.LocalAddr { if context.LocalServer.LocalCall(method, args, done) { return } } reply := getReply() // Don't bother firing off a goroutine in the common case where a client // is already healthy. select { case <-client.Healthy(): client.Go(method, args, reply, done) return default: } go func() { var timeoutChan <-chan time.Time if timeout != 0 { timeoutChan = time.After(timeout) } select { case <-client.Healthy(): client.Go(method, args, reply, done) case <-client.Closed: done <- &netrpc.Call{Error: newRPCError( util.Errorf("rpc to %s failed as client connection was closed", method))} case <-timeoutChan: done <- &netrpc.Call{Error: newRPCError( util.Errorf("rpc to %s: client not ready after %s", method, timeout))} } }() }
// cleanupTxn is called when a transaction ends. The transaction record is // updated and the heartbeat goroutine signaled to clean up the transaction // gracefully. func (tc *TxnCoordSender) cleanupTxn(trace *tracer.Trace, txn roachpb.Transaction) { trace.Event("coordinator stops") tc.Lock() defer tc.Unlock() txnMeta, ok := tc.txns[string(txn.ID)] // The heartbeat might've already removed the record. if !ok { return } // The supplied txn may be newer than the one in txnMeta, which is relevant // for stats. txnMeta.txn = txn // Trigger heartbeat shutdown. close(txnMeta.txnEnd) }
// cleanupTxn is called when a transaction ends. The transaction record is // updated and the heartbeat goroutine signaled to clean up the transaction // gracefully. func (tc *TxnCoordSender) cleanupTxn(trace *tracer.Trace, txn proto.Transaction, resolved []proto.Key) { tc.Lock() defer tc.Unlock() txnMeta, ok := tc.txns[string(txn.ID)] if !ok { return } // The supplied txn may be newed than the one in txnMeta, which is relevant // for stats. txnMeta.txn = txn // Trigger intent resolution and heartbeat shutdown. trace.Event("coordinator stops") txnMeta.txnEnd <- resolved // buffered, so does not block close(txnMeta.txnEnd) }
// cleanupTxn is called when a transaction ends. The transaction record is // updated and the heartbeat goroutine signaled to clean up the transaction // gracefully. func (tc *TxnCoordSender) cleanupTxn(trace *tracer.Trace, txn proto.Transaction) { tc.Lock() defer tc.Unlock() txnMeta, ok := tc.txns[string(txn.ID)] // Only clean up once per transaction. if !ok || txnMeta.txnEnd == nil { return } // The supplied txn may be newed than the one in txnMeta, which is relevant // for stats. txnMeta.txn = txn // Trigger heartbeat shutdown. trace.Event("coordinator stops") close(txnMeta.txnEnd) txnMeta.txnEnd = nil // for idempotency; checked above }
// close sends resolve intent commands for all key ranges this // transaction has covered, clears the keys cache and closes the // metadata heartbeat. Any keys listed in the resolved slice have // already been resolved and do not receive resolve intent commands. func (tm *txnMetadata) close(trace *tracer.Trace, txn *proto.Transaction, resolved []proto.Key, sender client.Sender, stopper *stop.Stopper) { close(tm.txnEnd) // stop heartbeat trace.Event("coordinator stops") if tm.keys.Len() > 0 { if log.V(2) { log.Infof("cleaning up %d intent(s) for transaction %s", tm.keys.Len(), txn) } } // TODO(tschottdorf): Should create a Batch here. for _, o := range tm.keys.GetOverlaps(proto.KeyMin, proto.KeyMax) { // If the op was range based, end key != start key: resolve a range. var call proto.Call key := o.Key.Start().(proto.Key) endKey := o.Key.End().(proto.Key) if !key.Next().Equal(endKey) { call.Args = &proto.InternalResolveIntentRangeRequest{ RequestHeader: proto.RequestHeader{ Timestamp: txn.Timestamp, Key: key, EndKey: endKey, User: security.RootUser, Txn: txn, }, } call.Reply = &proto.InternalResolveIntentRangeResponse{} } else { // Check if the key has already been resolved; skip if yes. found := false for _, k := range resolved { if key.Equal(k) { found = true } } if found { continue } call.Args = &proto.InternalResolveIntentRequest{ RequestHeader: proto.RequestHeader{ Timestamp: txn.Timestamp, Key: key, User: security.RootUser, Txn: txn, }, } call.Reply = &proto.InternalResolveIntentResponse{} } // We don't care about the reply channel; these are best // effort. We simply fire and forget, each in its own goroutine. ctx := tracer.ToCtx(context.Background(), trace.Fork()) stopper.RunAsyncTask(func() { if log.V(2) { log.Infof("cleaning up intent %q for txn %s", call.Args.Header().Key, txn) } sender.Send(ctx, call) if call.Reply.Header().Error != nil { log.Warningf("failed to cleanup %q intent: %s", call.Args.Header().Key, call.Reply.Header().GoError()) } }) } tm.keys.Clear() }