mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-08-06 22:22:38 +03:00
Write a buffer's meta data into QUIC
Summary: Instead of writing real data into the transport, we want to support a use case where only its metadata is written to the transport. Sending of the real data is delegated to another entity in such setup. Reviewed By: mjoras Differential Revision: D26131772 fbshipit-source-id: 4fcfa3a1626203f63c61898e6de089a3079d043d
This commit is contained in:
committed by
Facebook GitHub Bot
parent
968b39f4a9
commit
adc1e15eff
@@ -955,6 +955,15 @@ class QuicSocket {
|
|||||||
bool eof,
|
bool eof,
|
||||||
DeliveryCallback* cb = nullptr) = 0;
|
DeliveryCallback* cb = nullptr) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a data representation in the form of BufferMeta to the given stream.
|
||||||
|
*/
|
||||||
|
virtual WriteResult writeBufMeta(
|
||||||
|
StreamId id,
|
||||||
|
const BufferMeta& data,
|
||||||
|
bool eof,
|
||||||
|
DeliveryCallback* cb = nullptr) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a callback to be invoked when the peer has acknowledged the
|
* Register a callback to be invoked when the peer has acknowledged the
|
||||||
* given offset on the given stream.
|
* given offset on the given stream.
|
||||||
|
@@ -1968,6 +1968,79 @@ QuicSocket::WriteResult QuicTransportBase::writeChain(
|
|||||||
return folly::unit;
|
return folly::unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QuicSocket::WriteResult QuicTransportBase::writeBufMeta(
|
||||||
|
StreamId id,
|
||||||
|
const BufferMeta& data,
|
||||||
|
bool eof,
|
||||||
|
DeliveryCallback* cb) {
|
||||||
|
if (isReceivingStream(conn_->nodeType, id)) {
|
||||||
|
return folly::makeUnexpected(LocalErrorCode::INVALID_OPERATION);
|
||||||
|
}
|
||||||
|
if (closeState_ != CloseState::OPEN) {
|
||||||
|
return folly::makeUnexpected(LocalErrorCode::CONNECTION_CLOSED);
|
||||||
|
}
|
||||||
|
FOLLY_MAYBE_UNUSED auto self = sharedGuard();
|
||||||
|
try {
|
||||||
|
// Check whether stream exists before calling getStream to avoid
|
||||||
|
// creating a peer stream if it does not exist yet.
|
||||||
|
if (!conn_->streamManager->streamExists(id)) {
|
||||||
|
return folly::makeUnexpected(LocalErrorCode::STREAM_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
auto stream = conn_->streamManager->getStream(id);
|
||||||
|
if (!stream->writable()) {
|
||||||
|
return folly::makeUnexpected(LocalErrorCode::STREAM_CLOSED);
|
||||||
|
}
|
||||||
|
if (stream->currentWriteOffset == 0 && stream->writeBuffer.empty()) {
|
||||||
|
// If nothing has been written to writeBuffer ever, meta writing isn't
|
||||||
|
// allowed.
|
||||||
|
return folly::makeUnexpected(LocalErrorCode::INVALID_OPERATION);
|
||||||
|
}
|
||||||
|
// Register DeliveryCallback for the data + eof offset.
|
||||||
|
if (cb) {
|
||||||
|
auto dataLength = data.length + (eof ? 1 : 0);
|
||||||
|
if (dataLength) {
|
||||||
|
auto currentLargestWriteOffset = getLargestWriteOffsetSeen(*stream);
|
||||||
|
registerDeliveryCallback(
|
||||||
|
id, currentLargestWriteOffset + dataLength - 1, cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool wasAppLimitedOrIdle = false;
|
||||||
|
if (conn_->congestionController) {
|
||||||
|
wasAppLimitedOrIdle = conn_->congestionController->isAppLimited();
|
||||||
|
wasAppLimitedOrIdle |= conn_->streamManager->isAppIdle();
|
||||||
|
}
|
||||||
|
writeBufMetaToQuicStream(*stream, data, eof);
|
||||||
|
// If we were previously app limited restart pacing with the current rate.
|
||||||
|
if (wasAppLimitedOrIdle && conn_->pacer) {
|
||||||
|
conn_->pacer->reset();
|
||||||
|
}
|
||||||
|
updateWriteLooper(true);
|
||||||
|
} catch (const QuicTransportException& ex) {
|
||||||
|
VLOG(4) << __func__ << " streamId=" << id << " " << ex.what() << " "
|
||||||
|
<< *this;
|
||||||
|
exceptionCloseWhat_ = ex.what();
|
||||||
|
closeImpl(std::make_pair(
|
||||||
|
QuicErrorCode(ex.errorCode()), std::string("writeChain() error")));
|
||||||
|
return folly::makeUnexpected(LocalErrorCode::TRANSPORT_ERROR);
|
||||||
|
} catch (const QuicInternalException& ex) {
|
||||||
|
VLOG(4) << __func__ << " streamId=" << id << " " << ex.what() << " "
|
||||||
|
<< *this;
|
||||||
|
exceptionCloseWhat_ = ex.what();
|
||||||
|
closeImpl(std::make_pair(
|
||||||
|
QuicErrorCode(ex.errorCode()), std::string("writeChain() error")));
|
||||||
|
return folly::makeUnexpected(ex.errorCode());
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
VLOG(4) << __func__ << " streamId=" << id << " " << ex.what() << " "
|
||||||
|
<< *this;
|
||||||
|
exceptionCloseWhat_ = ex.what();
|
||||||
|
closeImpl(std::make_pair(
|
||||||
|
QuicErrorCode(TransportErrorCode::INTERNAL_ERROR),
|
||||||
|
std::string("writeChain() error")));
|
||||||
|
return folly::makeUnexpected(LocalErrorCode::INTERNAL_ERROR);
|
||||||
|
}
|
||||||
|
return folly::unit;
|
||||||
|
}
|
||||||
|
|
||||||
folly::Expected<folly::Unit, LocalErrorCode>
|
folly::Expected<folly::Unit, LocalErrorCode>
|
||||||
QuicTransportBase::registerDeliveryCallback(
|
QuicTransportBase::registerDeliveryCallback(
|
||||||
StreamId id,
|
StreamId id,
|
||||||
|
@@ -178,6 +178,12 @@ class QuicTransportBase : public QuicSocket {
|
|||||||
bool eof,
|
bool eof,
|
||||||
DeliveryCallback* cb = nullptr) override;
|
DeliveryCallback* cb = nullptr) override;
|
||||||
|
|
||||||
|
WriteResult writeBufMeta(
|
||||||
|
StreamId id,
|
||||||
|
const BufferMeta& data,
|
||||||
|
bool eof,
|
||||||
|
DeliveryCallback* cb = nullptr) override;
|
||||||
|
|
||||||
folly::Expected<folly::Unit, LocalErrorCode> registerDeliveryCallback(
|
folly::Expected<folly::Unit, LocalErrorCode> registerDeliveryCallback(
|
||||||
StreamId id,
|
StreamId id,
|
||||||
uint64_t offset,
|
uint64_t offset,
|
||||||
|
@@ -198,6 +198,9 @@ class MockQuicSocket : public QuicSocket {
|
|||||||
MOCK_METHOD4(
|
MOCK_METHOD4(
|
||||||
writeChain,
|
writeChain,
|
||||||
WriteResult(StreamId, SharedBuf, bool, DeliveryCallback*));
|
WriteResult(StreamId, SharedBuf, bool, DeliveryCallback*));
|
||||||
|
MOCK_METHOD4(
|
||||||
|
writeBufMeta,
|
||||||
|
WriteResult(StreamId, const BufferMeta&, bool, DeliveryCallback*));
|
||||||
MOCK_METHOD3(
|
MOCK_METHOD3(
|
||||||
registerDeliveryCallback,
|
registerDeliveryCallback,
|
||||||
folly::Expected<folly::Unit, LocalErrorCode>(
|
folly::Expected<folly::Unit, LocalErrorCode>(
|
||||||
|
@@ -3279,5 +3279,31 @@ TEST_F(QuicTransportTest, PrioritySetAndGet) {
|
|||||||
EXPECT_EQ(LocalErrorCode::CONNECTION_CLOSED, closedConnStreamPri.error());
|
EXPECT_EQ(LocalErrorCode::CONNECTION_CLOSED, closedConnStreamPri.error());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(QuicTransportTest, WriteBufMetaIntoStream) {
|
||||||
|
auto streamId = transport_->createBidirectionalStream().value();
|
||||||
|
size_t bufferLength = 2000;
|
||||||
|
BufferMeta meta(bufferLength);
|
||||||
|
auto buf = buildRandomInputData(20);
|
||||||
|
// Some amount of real data needs to be written first:
|
||||||
|
transport_->writeChain(streamId, std::move(buf), false);
|
||||||
|
transport_->writeBufMeta(streamId, meta, true);
|
||||||
|
auto& stream =
|
||||||
|
*transport_->getConnectionState().streamManager->findStream(streamId);
|
||||||
|
EXPECT_GE(stream.writeBufMeta.offset, 20);
|
||||||
|
EXPECT_EQ(stream.writeBufMeta.length, bufferLength);
|
||||||
|
EXPECT_TRUE(stream.writeBufMeta.eof);
|
||||||
|
EXPECT_EQ(
|
||||||
|
*stream.finalWriteOffset,
|
||||||
|
stream.writeBufMeta.offset + stream.writeBufMeta.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(QuicTransportTest, WriteBufMetaWithoutRealData) {
|
||||||
|
auto streamId = transport_->createBidirectionalStream().value();
|
||||||
|
size_t bufferLength = 2000;
|
||||||
|
BufferMeta meta(bufferLength);
|
||||||
|
auto result = transport_->writeBufMeta(streamId, meta, true);
|
||||||
|
EXPECT_TRUE(result.hasError());
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace test
|
} // namespace test
|
||||||
} // namespace quic
|
} // namespace quic
|
||||||
|
@@ -29,6 +29,9 @@ void prependToBuf(quic::Buf& buf, quic::Buf toAppend) {
|
|||||||
namespace quic {
|
namespace quic {
|
||||||
|
|
||||||
void writeDataToQuicStream(QuicStreamState& stream, Buf data, bool eof) {
|
void writeDataToQuicStream(QuicStreamState& stream, Buf data, bool eof) {
|
||||||
|
// Once data is written to writeBufMeta, no more data can be written to
|
||||||
|
// writeBuffer.
|
||||||
|
CHECK_EQ(0, stream.writeBufMeta.offset);
|
||||||
uint64_t len = 0;
|
uint64_t len = 0;
|
||||||
if (data) {
|
if (data) {
|
||||||
len = data->computeChainDataLength();
|
len = data->computeChainDataLength();
|
||||||
@@ -48,6 +51,28 @@ void writeDataToQuicStream(QuicStreamState& stream, Buf data, bool eof) {
|
|||||||
stream.conn.streamManager->updateWritableStreams(stream);
|
stream.conn.streamManager->updateWritableStreams(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void writeBufMetaToQuicStream(
|
||||||
|
QuicStreamState& stream,
|
||||||
|
const BufferMeta& data,
|
||||||
|
bool eof) {
|
||||||
|
if (data.length > 0) {
|
||||||
|
maybeWriteBlockAfterAPIWrite(stream);
|
||||||
|
}
|
||||||
|
if (stream.writeBufMeta.offset == 0) {
|
||||||
|
CHECK(!stream.finalWriteOffset.has_value());
|
||||||
|
stream.writeBufMeta.offset =
|
||||||
|
stream.currentWriteOffset + stream.writeBuffer.chainLength();
|
||||||
|
}
|
||||||
|
stream.writeBufMeta.length += data.length;
|
||||||
|
if (eof) {
|
||||||
|
stream.finalWriteOffset =
|
||||||
|
stream.writeBufMeta.offset + stream.writeBufMeta.length;
|
||||||
|
stream.writeBufMeta.eof = true;
|
||||||
|
}
|
||||||
|
updateFlowControlOnWriteToStream(stream, data.length);
|
||||||
|
stream.conn.streamManager->updateWritableStreams(stream);
|
||||||
|
}
|
||||||
|
|
||||||
void writeDataToQuicStream(QuicCryptoStream& stream, Buf data) {
|
void writeDataToQuicStream(QuicCryptoStream& stream, Buf data) {
|
||||||
stream.writeBuffer.append(std::move(data));
|
stream.writeBuffer.append(std::move(data));
|
||||||
}
|
}
|
||||||
|
@@ -21,6 +21,17 @@ namespace quic {
|
|||||||
*/
|
*/
|
||||||
void writeDataToQuicStream(QuicStreamState& stream, Buf data, bool eof);
|
void writeDataToQuicStream(QuicStreamState& stream, Buf data, bool eof);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds data represented in the form of BufferMeta to the end of the Buffer
|
||||||
|
* Meta queue of the stream.
|
||||||
|
*
|
||||||
|
* TODO: move to dsr directory.
|
||||||
|
*/
|
||||||
|
void writeBufMetaToQuicStream(
|
||||||
|
QuicStreamState& stream,
|
||||||
|
const BufferMeta& data,
|
||||||
|
bool eof);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds data to the end of the write buffer of the QUIC crypto stream. This
|
* Adds data to the end of the write buffer of the QUIC crypto stream. This
|
||||||
* data will be written onto the socket.
|
* data will be written onto the socket.
|
||||||
|
@@ -16,6 +16,71 @@
|
|||||||
|
|
||||||
namespace quic {
|
namespace quic {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A buffer representation without the actual data. This is part of the public
|
||||||
|
* facing interface.
|
||||||
|
*
|
||||||
|
* This is experimental.
|
||||||
|
*/
|
||||||
|
struct BufferMeta {
|
||||||
|
size_t length;
|
||||||
|
|
||||||
|
explicit BufferMeta(size_t lengthIn) : length(lengthIn) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A write buffer representation without the actual data. This is used for
|
||||||
|
* write buffer management in a stream.
|
||||||
|
*
|
||||||
|
* This is experimental.
|
||||||
|
*/
|
||||||
|
struct WriteBufferMeta {
|
||||||
|
size_t length{0};
|
||||||
|
size_t offset{0};
|
||||||
|
bool eof{false};
|
||||||
|
|
||||||
|
WriteBufferMeta() = default;
|
||||||
|
|
||||||
|
struct Builder {
|
||||||
|
Builder& setLength(size_t lengthIn) {
|
||||||
|
length_ = lengthIn;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder& setOffset(size_t offsetIn) {
|
||||||
|
offset_ = offsetIn;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder& setEOF(bool val) {
|
||||||
|
eof_ = val;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteBufferMeta build() {
|
||||||
|
return WriteBufferMeta(length_, offset_, eof_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t length_{0};
|
||||||
|
size_t offset_{0};
|
||||||
|
bool eof_{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
WriteBufferMeta split(size_t splitLen) {
|
||||||
|
CHECK_GE(length, splitLen);
|
||||||
|
auto splitEof = splitLen == length && eof;
|
||||||
|
WriteBufferMeta splitOf(splitLen, offset, splitEof);
|
||||||
|
offset += splitLen;
|
||||||
|
length -= splitLen;
|
||||||
|
return splitOf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit WriteBufferMeta(size_t lengthIn, size_t offsetIn, bool eofIn)
|
||||||
|
: length(lengthIn), offset(offsetIn), eof(eofIn) {}
|
||||||
|
};
|
||||||
|
|
||||||
struct StreamBuffer {
|
struct StreamBuffer {
|
||||||
BufQueue data;
|
BufQueue data;
|
||||||
uint64_t offset;
|
uint64_t offset;
|
||||||
@@ -97,6 +162,17 @@ struct QuicStreamLike {
|
|||||||
// egress packets that contains a *new* STREAM frame for this stream.
|
// egress packets that contains a *new* STREAM frame for this stream.
|
||||||
uint64_t numPacketsTxWithNewData{0};
|
uint64_t numPacketsTxWithNewData{0};
|
||||||
|
|
||||||
|
// BufferMeta that has been writen to the QUIC layer.
|
||||||
|
// When offset is 0, nothing has been written to it. On first write, its
|
||||||
|
// starting offset will be currentWriteOffset + writeBuffer.chainLength().
|
||||||
|
WriteBufferMeta writeBufMeta;
|
||||||
|
|
||||||
|
// A map to store sent WriteBufferMetas for potential retransmission.
|
||||||
|
folly::F14FastMap<uint64_t, WriteBufferMeta> retransmissionBufMetas;
|
||||||
|
|
||||||
|
// WriteBufferMetas that's already marked lost. They will be retransmitted.
|
||||||
|
std::deque<WriteBufferMeta> lossBufMetas;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Either insert a new entry into the loss buffer, or merge the buffer with
|
* Either insert a new entry into the loss buffer, or merge the buffer with
|
||||||
* an existing entry.
|
* an existing entry.
|
||||||
|
Reference in New Issue
Block a user