in quic/api/QuicPacketScheduler.cpp [799:919]
SchedulingResult CloningScheduler::scheduleFramesForPacket(
PacketBuilderInterface&& builder,
uint32_t writableBytes) {
// The writableBytes in this function shouldn't be limited by cwnd, since
// we only use CloningScheduler for the cases that we want to bypass cwnd for
// now.
bool hasData = frameScheduler_.hasData();
if (conn_.version.has_value() &&
conn_.version.value() == QuicVersion::MVFST_EXPERIMENTAL2) {
hasData = frameScheduler_.hasImmediateData();
}
if (hasData) {
// Note that there is a possibility that we end up writing nothing here. But
// if frameScheduler_ hasData() to write, we shouldn't invoke the cloning
// path if the write fails.
return frameScheduler_.scheduleFramesForPacket(
std::move(builder), writableBytes);
}
// TODO: We can avoid the copy & rebuild of the header by creating an
// independent header builder.
auto header = builder.getPacketHeader();
std::move(builder).releaseOutputBuffer();
// Look for an outstanding packet that's no larger than the writableBytes
for (auto& outstandingPacket : conn_.outstandings.packets) {
if (outstandingPacket.declaredLost ||
outstandingPacket.metadata.isD6DProbe ||
outstandingPacket.isDSRPacket) {
continue;
}
auto opPnSpace = outstandingPacket.packet.header.getPacketNumberSpace();
// Reusing the RegularQuicPacketBuilder throughout loop bodies will lead to
// frames belong to different original packets being written into the same
// clone packet. So re-create a RegularQuicPacketBuilder every time.
// TODO: We can avoid the copy & rebuild of the header by creating an
// independent header builder.
auto builderPnSpace = builder.getPacketHeader().getPacketNumberSpace();
if (opPnSpace != builderPnSpace) {
continue;
}
size_t prevSize = 0;
if (conn_.transportSettings.dataPathType ==
DataPathType::ContinuousMemory) {
ScopedBufAccessor scopedBufAccessor(conn_.bufAccessor);
prevSize = scopedBufAccessor.buf()->length();
}
// Reusing the same builder throughout loop bodies will lead to frames
// belong to different original packets being written into the same clone
// packet. So re-create a builder every time.
std::unique_ptr<PacketBuilderInterface> internalBuilder;
if (conn_.transportSettings.dataPathType == DataPathType::ChainedMemory) {
internalBuilder = std::make_unique<RegularQuicPacketBuilder>(
conn_.udpSendPacketLen,
header,
getAckState(conn_, builderPnSpace).largestAckedByPeer.value_or(0));
} else {
CHECK(conn_.bufAccessor && conn_.bufAccessor->ownsBuffer());
internalBuilder = std::make_unique<InplaceQuicPacketBuilder>(
*conn_.bufAccessor,
conn_.udpSendPacketLen,
header,
getAckState(conn_, builderPnSpace).largestAckedByPeer.value_or(0));
}
// If the packet is already a clone that has been processed, we don't clone
// it again.
if (outstandingPacket.associatedEvent &&
conn_.outstandings.packetEvents.count(
*outstandingPacket.associatedEvent) == 0) {
continue;
}
// I think this only fail if udpSendPacketLen somehow shrinks in the middle
// of a connection.
if (outstandingPacket.metadata.encodedSize >
writableBytes + cipherOverhead_) {
continue;
}
internalBuilder->accountForCipherOverhead(cipherOverhead_);
internalBuilder->encodePacketHeader();
PacketRebuilder rebuilder(*internalBuilder, conn_);
// TODO: It's possible we write out a packet that's larger than the packet
// size limit. For example, when the packet sequence number has advanced to
// a point where we need more bytes to encoded it than that of the original
// packet. In that case, if the original packet is already at the packet
// size limit, we will generate a packet larger than the limit. We can
// either ignore the problem, hoping the packet will be able to travel the
// network just fine; Or we can throw away the built packet and send a ping.
// Rebuilder will write the rest of frames
auto rebuildResult = rebuilder.rebuildFromPacket(outstandingPacket);
if (rebuildResult) {
if (conn_.version.has_value() &&
conn_.version.value() == QuicVersion::MVFST_EXPERIMENTAL2) {
// Check if we have any acks pending and write those into the cloned
// packet as well.
if (frameScheduler_.hasPendingAcks()) {
frameScheduler_.writeNextAcks(*internalBuilder);
}
}
return SchedulingResult(
std::move(rebuildResult), std::move(*internalBuilder).buildPacket());
} else if (
conn_.transportSettings.dataPathType ==
DataPathType::ContinuousMemory) {
// When we use Inplace packet building and reuse the write buffer, even if
// the packet rebuild has failed, there might be some bytes already
// written into the buffer and the buffer tail pointer has already moved.
// We need to roll back the tail pointer to the position before the packet
// building to exclude those bytes. Otherwise these bytes will be sitting
// in between legit packets inside the buffer and will either cause errors
// further down the write path, or be sent out and then dropped at peer
// when peer fail to parse them.
internalBuilder.reset();
CHECK(conn_.bufAccessor && conn_.bufAccessor->ownsBuffer());
ScopedBufAccessor scopedBufAccessor(conn_.bufAccessor);
auto& buf = scopedBufAccessor.buf();
buf->trimEnd(buf->length() - prevSize);
}
}
return SchedulingResult(folly::none, folly::none);
}