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
}