bool IOBufQuicBatch::flushInternal()

in quic/api/IoBufQuicBatch.cpp [72:177]


bool IOBufQuicBatch::flushInternal() {
  if (batchWriter_->empty()) {
    return true;
  }

  bool written = false;
  folly::Optional<int> firstSocketErrno;
  if (!happyEyeballsState_ || happyEyeballsState_->shouldWriteToFirstSocket) {
    auto consumed = batchWriter_->write(sock_, peerAddress_);
    if (consumed < 0) {
      firstSocketErrno = errno;
    }
    written = (consumed >= 0);
    if (happyEyeballsState_) {
      happyEyeballsState_->shouldWriteToFirstSocket =
          (consumed >= 0 || isRetriableError(errno));

      if (!happyEyeballsState_->shouldWriteToFirstSocket) {
        sock_.pauseRead();
      }
    }
  }

  // If error occured on first socket, kick off second socket immediately
  if (!written && happyEyeballsState_ &&
      happyEyeballsState_->connAttemptDelayTimeout &&
      happyEyeballsState_->connAttemptDelayTimeout->isScheduled()) {
    happyEyeballsState_->connAttemptDelayTimeout->timeoutExpired();
    happyEyeballsState_->connAttemptDelayTimeout->cancelTimeout();
  }

  folly::Optional<int> secondSocketErrno;
  if (happyEyeballsState_ && happyEyeballsState_->shouldWriteToSecondSocket) {
    auto consumed = batchWriter_->write(
        *happyEyeballsState_->secondSocket,
        happyEyeballsState_->secondPeerAddress);
    if (consumed < 0) {
      secondSocketErrno = errno;
    }

    // written is marked true if either socket write succeeds
    written |= (consumed >= 0);
    happyEyeballsState_->shouldWriteToSecondSocket =
        (consumed >= 0 || isRetriableError(errno));
    if (!happyEyeballsState_->shouldWriteToSecondSocket) {
      happyEyeballsState_->secondSocket->pauseRead();
    }
  }

  if (!written && statsCallback_) {
    if (firstSocketErrno.has_value()) {
      QUIC_STATS(
          statsCallback_,
          onUDPSocketWriteError,
          QuicTransportStatsCallback::errnoToSocketErrorType(
              firstSocketErrno.value()));
    }
    if (secondSocketErrno.has_value()) {
      QUIC_STATS(
          statsCallback_,
          onUDPSocketWriteError,
          QuicTransportStatsCallback::errnoToSocketErrorType(
              secondSocketErrno.value()));
    }
  }

  // If we have no happy eyeballs state, we only care if the first socket had
  // an error. Otherwise we check both.
  if ((!happyEyeballsState_ && firstSocketErrno.has_value() &&
       !isRetriableError(firstSocketErrno.value())) ||
      (happyEyeballsState_ && !happyEyeballsState_->shouldWriteToFirstSocket &&
       !happyEyeballsState_->shouldWriteToSecondSocket)) {
    auto firstSocketErrorMsg = firstSocketErrno.has_value()
        ? folly::to<std::string>(
              folly::errnoStr(firstSocketErrno.value()), ", ")
        : "";
    auto secondSocketErrorMsg = secondSocketErrno.has_value()
        ? folly::errnoStr(secondSocketErrno.value())
        : "";
    auto errorMsg =
        folly::to<std::string>(firstSocketErrorMsg, secondSocketErrorMsg);
    // Both sockets becomes fatal, close connection
    VLOG(4) << "Error writing to the socket " << errorMsg << " "
            << peerAddress_;

    // We can get write error for any reason, close the conn only if network
    // is unreachable, for all others, we throw a transport exception
    if (isNetworkUnreachable(errno)) {
      throw QuicInternalException(
          folly::to<std::string>("Error on socket write ", errorMsg),
          LocalErrorCode::CONNECTION_ABANDONED);
    } else {
      throw QuicTransportException(
          folly::to<std::string>("Error on socket write ", errorMsg),
          TransportErrorCode::INTERNAL_ERROR);
    }
  }

  if (!written) {
    // This can happen normally, so ignore. Now we treat most errors same
    // as a loss to avoid looping.
    return false; // done
  }

  return true; // success, not done yet
}