HTTPSession::detach(HTTPTransaction* txn) noexcept {
  DestructorGuard guard(this);
  HTTPCodec::StreamID streamID = txn->getID();
  auto txnSeqn = txn->getSequenceNumber();
  auto it = transactions_.find(txn->getID());
  DCHECK(it != transactions_.end());

  if (txn->isIngressPaused()) {
    // Someone detached a transaction that was paused.  Make the resumeIngress
    // call to keep liveTransactions_ in order
    VLOG(4) << *this << " detached paused transaction=" << streamID;
    resumeIngress(txn);
  }

  VLOG(4) << *this << " removing streamID=" << streamID <<
    ", liveTransactions was " << liveTransactions_;
  CHECK_GT(liveTransactions_, 0);
  liveTransactions_--;

  if (txn->isPushed()) {
    auto assocTxn = findTransaction(*txn->getAssocTxnId());
    if (assocTxn) {
      assocTxn->removePushedTransaction(streamID);
    }
  }
  if (txn->getControlStream()) {
    auto controlTxn = findTransaction(*txn->getControlStream());
    if (controlTxn) {
      controlTxn->removeExTransaction(streamID);
    }
  }

  auto oldStreamCount = getPipelineStreamCount();
  decrementTransactionCount(txn, true, true);
  transactions_.erase(it);

  if (transactions_.empty()) {
    HTTPSessionBase::setLatestActive();
    if (infoCallback_) {
      infoCallback_->onDeactivateConnection(*this);
    }
    if (getConnectionManager()) {
      getConnectionManager()->onDeactivated(*this);
    }
  } else {
    if (infoCallback_) {
      infoCallback_->onTransactionDetached(*this);
    }
  }

  if (!readsShutdown()) {
    if (maybeResumePausedPipelinedTransaction(oldStreamCount, txnSeqn)) {
      return;
    } else {
      // this will resume reads if they were paused (eg: 0 HTTP transactions)
      resumeReads();
    }
  }

  if (liveTransactions_ == 0 && transactions_.empty() && !isScheduled()) {
    resetTimeout();
  }

  // It's possible that this is the last transaction in the session,
  // so check whether the conditions for shutdown are satisfied.
  if (transactions_.empty()) {
    if (shouldShutdown()) {
      writesDraining_ = true;
    }
    // Handle the case where we are draining writes but all remaining
    // transactions terminated with no egress.
    if (writesDraining_ && !writesShutdown() && !hasMoreWrites()) {
      shutdownTransport(false, true);
      return;
    }
  }
  checkForShutdown();
}
HTTPSession::detach(HTTPTransaction* txn) noexcept {
  DestructorGuard guard(this);
  HTTPCodec::StreamID streamID = txn->getID();
  auto txnSeqn = txn->getSequenceNumber();
  auto it = transactions_.find(txn->getID());
  DCHECK(it != transactions_.end());

  if (txn->isIngressPaused()) {
     
     
    VLOG(4) << *this << " detached paused transaction=" << streamID;
    resumeIngress(txn);
  }

  VLOG(4) << *this << " removing streamID=" << streamID <<
    ", liveTransactions was " << liveTransactions_;
  CHECK_GT(liveTransactions_, 0);
  liveTransactions_--;

  if (txn->isPushed()) {
    auto assocTxn = findTransaction(*txn->getAssocTxnId());
    if (assocTxn) {
      assocTxn->removePushedTransaction(streamID);
    }
  }
  if (txn->getControlStream()) {
    auto controlTxn = findTransaction(*txn->getControlStream());
    if (controlTxn) {
      controlTxn->removeExTransaction(streamID);
    }
  }

  auto oldStreamCount = getPipelineStreamCount();
  decrementTransactionCount(txn, true, true);
  transactions_.erase(it);

  if (transactions_.empty()) {
    HTTPSessionBase::setLatestActive();
    if (infoCallback_) {
      infoCallback_->onDeactivateConnection(*this);
    }
    if (getConnectionManager()) {
      getConnectionManager()->onDeactivated(*this);
    }
  } else {
    if (infoCallback_) {
      infoCallback_->onTransactionDetached(*this);
    }
  }

  if (!readsShutdown()) {
    if (maybeResumePausedPipelinedTransaction(oldStreamCount, txnSeqn)) {
      return;
    } else {
       
      resumeReads();
    }
  }

  if (liveTransactions_ == 0 && transactions_.empty() && !isScheduled()) {
    resetTimeout();
  }

   
   
  if (transactions_.empty()) {
    if (shouldShutdown()) {
      writesDraining_ = true;
    }
     
     
    if (writesDraining_ && !writesShutdown() && !hasMoreWrites()) {
      shutdownTransport(false, true);
      return;
    }
  }
  checkForShutdown();
}