Skip to content

quiche: implement http stream interfaces #8556

Merged
alyssawilk merged 41 commits intoenvoyproxy:masterfrom
danzh2010:quicencode
Nov 5, 2019
Merged

quiche: implement http stream interfaces #8556
alyssawilk merged 41 commits intoenvoyproxy:masterfrom
danzh2010:quicencode

Conversation

@danzh2010
Copy link
Copy Markdown
Contributor

@danzh2010 danzh2010 commented Oct 9, 2019

Implement encodeHeader|Data|Trailer.

Implement watermark buffer for QUIC stream and session to limit data buffered in stream send buffer.

Implement readDisable() for QUIC stream to block pushing data when upper stream receive buffer is full.

Risk Level: low, not in use
Testing: added tests in stream test.
Part of #2557

Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
This reverts commit 3e39fbe.

Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
@danzh2010 danzh2010 changed the title Quicencode quiche: implement http stream interfaces Oct 9, 2019
@alyssawilk alyssawilk self-assigned this Oct 10, 2019
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
@danzh2010
Copy link
Copy Markdown
Contributor Author

This one is ready for review. PTAL

Copy link
Copy Markdown
Contributor

@alyssawilk alyssawilk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tackling this? Got partway through and I'll tackle the rest once we've worked out some docs


void QuicHttpServerConnectionImpl::onUnderlyingConnectionAboveWriteBufferHighWatermark() {
for (auto& it : quic_server_session_.stream_map()) {
if (!it.second->is_static()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am completely failing to find what is_static checks. can you clue me in?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_static is true for static streams, for IETF QUIC, it's crypto stream, and for GQuic, also headers stream.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nowadays, all the streams are managed in one place: QuicSession::stream_map_

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we're willing to infinite buffer on headers stream? We had a recent attack on the H2 stack where when an incoming request could generate an immediate response (like a 400 or 404) the flow control "didn't work" because we had assumed downstream buffers were fed by upstream (backends) not downstream (new bad requests) and adversary could keep feeding bad requests, generating header-only responses and adding more and more to the downstream connection. I want to make sure we don't reintroduce that flaw for HTTP/3

One workaround would be #7619 to have an upper upper bound but that hasn't landed yet. The other was the series of PRs @yanavlasov did to cap outbound frames (search for "flood" in the h2 codec). I think we need one or the other here if headers are otherwise unbounded. TODO is fine.

If we leave this as is for now I'd suggest a comment with the TODO
"we do not yet cap bytes for static streams, like the gQUIC headers stream or the crypto stream" to make it clear what is_static means

Copy link
Copy Markdown
Contributor Author

@danzh2010 danzh2010 Oct 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QuicHeaderStream always buffer data, its WriteHeaders() doesn't check this threshold. In IETF QUIC, there is no headers stream, and headers will be serialized into stream payload. That would solve this problem, right?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not having a headers stream helps, but it'd be good to have hard bounds on the crypto stream too.
Again maybe we can have a TODO which admits it will be solved by #1719 but as that's closed and not actively being worked on, I think we need something tracking it as an active problem

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we need a hard bound on crypto stream, it needs to be in QUICHE because the handshake is hidden from envoy.

But I still don't understand how can crypto stream subject to this sort of attack?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked QUICHE code, the IETF quic data stream should be robust against this sort of attack because the buffered headers in data stream's send buffer should have prevented stream from being closed still the header is ACK'ed. So the totally data buffered is capped by max allowed header size * max allowed stream number.

For GQuic, because all the headers are sent on header stream and stream is closed once header is buffered in send buffer, the header stream can buffer infinite number of response headers. We need a hard bound for headers stream. But this probably not worth to be added in QUICHE now as QUICHE is moving to IETF quic.

crypto stream also has infinite send buffer, if handshake implementation is wrong, it can also buffer infinitely. I added TODO in EnvoyQuicServerStream::encodeHeaders() about this vulnerability.

// This is counting not serialized bytes in the send buffer.
uint64_t bytes_to_send_old = BufferedDataBytes();
// QUIC stream must take all.
WriteBodySlices(quic::QuicMemSliceSpan(quic::QuicMemSliceSpanImpl(data)), end_stream);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused about how this works.
I thought that WriteBodySlices consumed some amount of data, and returned how many bytes are consumed. Where are the rest going? Are they getting drained from data and then deleted as the transient QuicMemSlice bits go away or OnCanWrite?
I think this could benefit from some doccing up like in source/docs/flow_control.md talking about how things should work, and I can take another pass once we have it written up?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added docs/quiche_integration.md. PTAL

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems dangerous to ignore the return value of WriteBodySlices. Can we consider the follow behavior instead:

  1. Use a very large buffered_data_threshold. This threshold has little to do with upstream flow control window etc, it is only used to prevent pathological conditions.
  2. When we WriteBodySlices, we only consider it success if all data is consumed. Otherwise close the connection.
  3. After a successful WriteBodySlices, run the watermark callbacks if needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added Reset(QUIC_BAD_APPLICATION_PAYLOAD) and early return.

local_end_stream_ = end_stream;
// This is counting not serialized bytes in the send buffer.
uint64_t bytes_to_send_old = BufferedDataBytes();
// QUIC stream must take all.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we setting buffered_data_threshold_ high enough we think this is guaranteed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, and that's why I assume WriteBodySlices() always consumes all the data.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the only relevant thing I find here is
EnvoyQuicStream(2 * GetQuicFlag(FLAGS_quic_buffered_data_threshold) ...which is
EnvoyQuicStream(2 * 8 * 1024
which doesn't look remotely high enough
what am I missing? maybe the docs can call out where it's set since it's not (to me) obvious?

Copy link
Copy Markdown
Contributor Author

@danzh2010 danzh2010 Oct 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't adjust that flag yet, because I did't have a good sense of how much we want to buffer. I set the flag to be 2 * max flow control window during QuicDispatcher construction now, considering that QUIC will continue encodeData() for up to 1 flow control window of data.

EnvoyQuicStream() here takes in the high watermark actually, it should be less than what the threshold allows to block upper stream before the buffer rejects taking more data. I changed it to be 32k for now.

if (bytes_to_send_new > bytes_to_send_old) {
// If buffered bytes changed, update stream and session's watermark book
// keeping.
sendBufferSimulation().checkHighWatermark(bytes_to_send_new);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we're going to adjust this when we try to encode new data, but do we also adjust it when the QUIC stack drains data from the buffer? Like if pacing is on, will we trigger low watermrks as we ship data? I'm not clear on where this happens.

Copy link
Copy Markdown
Contributor Author

@danzh2010 danzh2010 Oct 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the write is driven by OnCanWrite() where we call checkLowWatermark()

// Network::WriteBufferSource
Network::StreamBuffer getWriteBuffer() override { NOT_REACHED_GCOVR_EXCL_LINE; }

void adjustBytesToSend(int64_t delta);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment please

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Signed-off-by: Dan Zhang <danzh@google.com>
Copy link
Copy Markdown
Contributor

@alyssawilk alyssawilk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, the docs are awesome! Sent docs fixups offline but here's another round for review.


void QuicHttpServerConnectionImpl::onUnderlyingConnectionAboveWriteBufferHighWatermark() {
for (auto& it : quic_server_session_.stream_map()) {
if (!it.second->is_static()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we're willing to infinite buffer on headers stream? We had a recent attack on the H2 stack where when an incoming request could generate an immediate response (like a 400 or 404) the flow control "didn't work" because we had assumed downstream buffers were fed by upstream (backends) not downstream (new bad requests) and adversary could keep feeding bad requests, generating header-only responses and adding more and more to the downstream connection. I want to make sure we don't reintroduce that flaw for HTTP/3

One workaround would be #7619 to have an upper upper bound but that hasn't landed yet. The other was the series of PRs @yanavlasov did to cap outbound frames (search for "flood" in the h2 codec). I think we need one or the other here if headers are otherwise unbounded. TODO is fine.

If we leave this as is for now I'd suggest a comment with the TODO
"we do not yet cap bytes for static streams, like the gQUIC headers stream or the crypto stream" to make it clear what is_static means

local_end_stream_ = end_stream;
// This is counting not serialized bytes in the send buffer.
uint64_t bytes_to_send_old = BufferedDataBytes();
// QUIC stream must take all.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the only relevant thing I find here is
EnvoyQuicStream(2 * GetQuicFlag(FLAGS_quic_buffered_data_threshold) ...which is
EnvoyQuicStream(2 * 8 * 1024
which doesn't look remotely high enough
what am I missing? maybe the docs can call out where it's set since it's not (to me) obvious?

ASSERT(data.length() == 0);

uint64_t bytes_to_send_new = BufferedDataBytes();
ASSERT(bytes_to_send_old <= bytes_to_send_new);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sanity check on quic stack functionality - if we'd had buffered data above the high watermark, and QUIC pacing allowed us to write more (OnCanWrite was queued up in the dispatcher loop) and we get data from a backend and send WriteBodySlices, could we immediately write, and so drain, and have fewer bytes to send?

As an aside, it's weird to me we'd have an assert and immediately after we'd have the if. I wonder if we should have a wrapper function checkWatermarks(bytes_to_send_old, bytes_to_send_new)
which simply returns if they're equal, and checks high watermarks if new > old and low if new < old and we can call checkWatermarks in all the places we're manually checking. Thoughts?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sanity check on quic stack functionality - if we'd had buffered data above the high watermark, and QUIC pacing allowed us to write more (OnCanWrite was queued up in the dispatcher loop) and we get data from a backend and send WriteBodySlices, could we immediately write, and so drain, and have fewer bytes to send?

Yes, WriteBodySlices() will still write before OnCanWrite() get called via send_alarm in the queue. And OnCanWrite() might not able to write anymore afterwards as WriteBodySlices() already takes the chance to write.

As an aside, it's weird to me we'd have an assert and immediately after we'd have the if. I wonder if we should have a wrapper function checkWatermarks(bytes_to_send_old, bytes_to_send_new)
which simply returns if they're equal, and checks high watermarks if new > old and low if new < old and we can call checkWatermarks in all the places we're manually checking. Thoughts?

Yes, makes sense.


void checkHighWatermark(uint32_t bytes_buffered) {
if (high_watermark_ > 0 && !is_above_high_watermark_ && bytes_buffered > high_watermark_) {
// Just exceeds high watermark.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exceeds -> exceeded the

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


void checkLowWatermark(uint32_t bytes_buffered) {
if (low_watermark_ > 0 && !is_below_low_watermark_ && bytes_buffered < low_watermark_) {
// Just cross low watermark.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cross -> crossed the

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// A class, together with a stand alone buffer, used to achieve the purpose of WatermarkBuffer.
// Itself doesn't have buffer or do bookeeping of buffered bytes. But provided with buffered_bytes,
// it re-acts upon crossing high/low watermarks.
class EnvoyQuicSimulatedWatermarkBuffer {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unit tests for this please

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding it now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

bool status_changed{false};
if (disable) {
++read_disable_counter_;
ASSERT(read_disable_counter_ == 1);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs (and Envoy flow control) allows readDisable(true) to be called multiple times. I'd think the second time, read_disable_counter would be 2.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, this assert is added for debugging. Removed.

// TODO(danzh): add interface to quic for connection level buffer throttling.
// Currently read buffer is capped by connection level flow control. And
// write buffer is not capped.
// write buffer limit is set during construction. Change buffer limit during
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change -> Changing the
connection -> the connection

Copy link
Copy Markdown
Contributor

@wu-bin wu-bin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are some comments so far, I'll continue tomorrow.

FUNCTION(misc) \
FUNCTION(mongo) \
FUNCTION(quic) \
FUNCTION(quic_stream) \
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need 'quic_stream'? Looking at the list, most dependent libraries only use one logger id.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EnvoyQuicSimulatedWatermarkBuffer is used in both session and stream, to make the log more clear, I pass in logger instance from its owner. And the logger needs a different id to distinguish each other.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I'm inclined to replace EnvoyQuicSimulatedWatermarkBuffer.logger_ by EnvoyQuicSimulatedWatermarkBuffer.stream_id, use QuicUtils::GetInvalidStreamId() if it's for a session. That way you can log to the 'quic' logger with a stream id in it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EnvoyQuicSimulatedWatermarkBuffer.stream_id is a good idea. That way I can use ENVOY_STREAM_LOG.

But I'm not sure if moving all the quic related log under 'quic' logger is what we want. Quic session and connections are using 'connection' logger currently in parallel to Network::ConnectionImpl

: quic::QuicServerSessionBase(config, supported_versions, connection.get(), visitor, helper,
crypto_config, compressed_certs_cache),
QuicFilterManagerConnectionImpl(std::move(connection), dispatcher) {}
QuicFilterManagerConnectionImpl(connection.get(), dispatcher, send_buffer_limit),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we talked offline, let's try simplify the lifetimes by changing QuicFilterManagerConnectionImpl to a member of EnvoyQuicServerSession, such that the Impl doesn't need to check if quic_connection_ is nullptr. Feel free to TODO it in a future PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defer to #8496 so the refactoring can be tested. TODO added.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

#include "quiche/quic/core/quic_session.h"
#include "quiche/spdy/core/spdy_header_block.h"

#include "extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.h"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we include platform/api/quic_mem_slice_span.h instead of impl?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, the weird dependency of QuicMemSlice API is app -> impl -> api

public Http::StreamCallbackHelper,
protected Logger::Loggable<Logger::Id::quic_stream> {
public:
EnvoyQuicStream(uint32_t buffer_limit, std::function<void()> below_low_watermark,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment for |buffer_limit|.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// This is counting not serialized bytes in the send buffer.
uint64_t bytes_to_send_old = BufferedDataBytes();
// QUIC stream must take all.
WriteBodySlices(quic::QuicMemSliceSpan(quic::QuicMemSliceSpanImpl(data)), end_stream);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems dangerous to ignore the return value of WriteBodySlices. Can we consider the follow behavior instead:

  1. Use a very large buffered_data_threshold. This threshold has little to do with upstream flow control window etc, it is only used to prevent pathological conditions.
  2. When we WriteBodySlices, we only consider it success if all data is consumed. Otherwise close the connection.
  3. After a successful WriteBodySlices, run the watermark callbacks if needed.

void EnvoyQuicServerStream::resetStream(Http::StreamResetReason /*reason*/) {
NOT_IMPLEMENTED_GCOVR_EXCL_LINE;
void EnvoyQuicServerStream::resetStream(Http::StreamResetReason reason) {
// Higher layers expect calling resetStream() to immediately raise reset callbacks.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Higher layers => Upper layers

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


void EnvoyQuicServerStream::readDisable(bool /*disable*/) { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; }
void EnvoyQuicServerStream::switchStreamBlockState(bool should_block) {
ASSERT(FinishedReadingHeaders(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It certainly sounds unlikely, but if we can handle block before finishing headers, we should not ASSERT, a warning log seems not appropriate to me.
Also, can we add some information to the error message? Like the upstream buffer limit and bytes read for headers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stream blocking and unblocking can only happen to request body. decodeHeaders() doesn't have buffer limit. The ASSERT here is just for debugging purpose.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SG

if (should_block) {
sequencer()->SetBlockedUntilFlush();
} else {
ASSERT(read_disable_counter_ == 0, "readDisable called in btw.");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does 'btw' mean?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw->between

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, I thought it is 'by the way'.

Copy link
Copy Markdown
Contributor

@wu-bin wu-bin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still reviewing it. Here are the comments before quic_filter_manager_connection_impl.cc.

bool empty_payload_with_fin = buffer->length() == 0 && finished_reading;
if (!empty_payload_with_fin || !end_stream_decoded_) {
ASSERT(decoder() != nullptr);
decoder()->decodeData(*buffer, finished_reading);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if empty_payload_with_fin==false and end_stream_decoded_==true?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's impossible. If fin is already delivered, no new data should arrive.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Consider add an assert for that so we can catch it when the assumption is violated.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the logic here to avoid decoding empty payload with end of stream in case of trailer. ASSERT doesn't apply any more.

in_decode_data_callstack_ = false;
if (read_disable_counter_ > 0) {
// If readDisable() was ever called during decodeData() and it meant to disable
// reading from downstream, the call must have been deferred. Call it now.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: Can you elaborate (in this comment) on why the switch call needs to be deferred?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented in EnvoyQuicStream::readDisable() when deferring switch call.

if (BufferedDataBytes() > 0) {
// If the stream is closed without sending out all bufferred data, regard
// them as sent now and adjust connection buffer book keeping.
dynamic_cast<QuicFilterManagerConnectionImpl*>(session())->adjustBytesToSend(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: add a filter_manager_connection_impl() function to return the QuicFilterManagerConnectionImpl*. (Feel free to choose a different function name)

// EnvoyQuicStream
void switchStreamBlockState(bool should_block) override;
uint32_t streamId() override;
Network::Connection* connection() override;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: How about network_connection(), to distinguish from quic connection.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for ENVOY_CONN_LOG which assumes passed in object has connection() interface.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SG


// A class, together with a stand alone buffer, used to achieve the purpose of WatermarkBuffer.
// Itself doesn't have buffer or do bookeeping of buffered bytes. But provided with buffered_bytes,
// it re-acts upon crossing high/low watermarks.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Document the (no-op) behavior when both high and low watermarks are 0.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


void checkHighWatermark(uint32_t bytes_buffered) {
if (high_watermark_ > 0 && !is_above_high_watermark_ && bytes_buffered > high_watermark_) {
// This is the first time for the buffer to cross the high watermark
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: How about: // Transitioning from below to above high watermark.

Similar for checkLowWatermark.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +65 to +67
bool is_below_low_watermark_{true};
uint32_t high_watermark_{0};
bool is_above_high_watermark_{false};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like is_below_low_watermark_ and is_above_high_watermark_ can be replaced by a single last_exceeded_watermark_ which is a tri-state: {NONE, LOW_WATERMARK, HIGH_WATERMARK}. Your call.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The NONE state seems not necessary. I simplified to two state indicated by a bool.

} else {
send_buffer_simulation_.checkLowWatermark(buffered_data_new);
}
connection.adjustBytesToSend(buffered_data_new - buffered_data_old);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add some code to handle integer overflow/unsigned wraparound? (buffered_data_new - buffered_data_old may not be representable as a int64_t)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's possible to over overflow or underflow as the delta is capped by flow control and congestion control.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SG. (I agree it's extremely unlikely, I just feel that we can still handle it gracefully even if it happens.)

return decoder_;
}

// True once end of stream is propergated to Envoy. Envoy doesn't expect to be
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

propergated => propagated

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

void QuicFilterManagerConnectionImpl::adjustBytesToSend(int64_t delta) {
bytes_to_send_ += delta;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bytes_to_send_ is uint32_t but delta is int64. Can we make bytes_to_send_ 64 bit?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bytes_to_send_ is used to compare with buffer limits which are all uint32_t. delta is converted from size_t which is very unlikely to overflow given QUIC's flow control and congestion control window.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SG

// of trying to map it into a 64-bit space.
stream_info_(dispatcher.timeSource()), id_(quic_connection_->connection_id().Hash()) {
// TODO(danzh): Use QUIC specific enum value.
stream_info_(dispatcher.timeSource()), id_(quic_connection_->connection_id().Hash()),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: How is id_ used? What happens if there is a hash collision?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used by Network::Connection::id() which is just for logging and stats as far as I observed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SG. Please add a comment for id_ to prevent it from being used as a connection id in the future.

// Derive to have simpler priority mechanism.
class TestEnvoyQuicServerSession : public EnvoyQuicServerSession {
public:
TestEnvoyQuicServerSession(const quic::QuicConfig& config,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: You can do using EnvoyQuicServerSession::EnvoyQuicServerSession; to "inherit" constructors. Similar for TestQuicCryptoServerStream.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

quic::ENCRYPTION_FORWARD_SECURE,
std::make_unique<quic::NullEncrypter>(quic::Perspective::IS_SERVER));
// Drive congestion control manually.
auto send_algorithm = new testing::StrictMock<quic::test::MockSendAlgorithm>;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Consider a NiceMock and only do EXPECT_CALL only the interesting calls.(CanSend, GetCongestionWindow, ...)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

decoded_headers->Method()->value().getStringView());
}));
EXPECT_CALL(request_decoder, decodeData(_, true))
.Times(testing::AtMost(1))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since end_stream is already true in decodeHeaders, can we make sure decodeData is not called?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please change to .Times(0) for decodeData, or just remove the EXPECT_CALL.

EXPECT_EQ(Http::Headers::get().MethodValues.Get,
headers->Method()->value().getStringView());
}));
if (quic_version_.transport_version == quic::QUIC_VERSION_99) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about if (VersionUsesHttp3(quic_version_.transport_version))?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QUICHE deps is behind, switch to VersionUsesQpack for now. And it should be updated when next QUICHE update.

}
}));
std::string data = payload;
if (quic_version_.transport_version == quic::QUIC_VERSION_99) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +114 to +122
if (quic_version_.transport_version == quic::QUIC_VERSION_99) {
std::unique_ptr<char[]> data_buffer;
quic::HttpEncoder encoder;
quic::QuicByteCount data_frame_header_length =
encoder.SerializeDataFrameHeader(payload.length(), &data_buffer);
quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length);
data = absl::StrCat(data_frame_header, payload);
}
quic::QuicStreamFrame frame(stream_id_, fin, 0, data);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern is used multiple times in the PR, consider wrap it in a function

quic::QuicStreamFrame MakeQuicStreamFrame(version, stream_id, fin, offset, payload) {
  std::string frame_data = VersionUsesHttp3(version) ? frame_header + payload : payload;
  return quic::QuicStreamFrame(stream_id, fin, offset, frame_data);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QuicStreamFrame keeps a string view, so it can't be returned. I moved the string manipulating logic into a helper function.

namespace Envoy {
namespace Quic {

class MockEnvoyQuicSession : public quic::QuicSpdySession, public QuicFilterManagerConnectionImpl {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: if MockEnvoyQuicSession inherits MockQuicSpdySession, can we remove the MOCK_METHODs?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to inherit from MockQuicSpdySession because it uses quic::test::DefaultQuicConfig() which sets flow control window to very large value.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SG

Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
@danzh2010
Copy link
Copy Markdown
Contributor Author

ping?

wu-bin
wu-bin previously approved these changes Oct 28, 2019
namespace Envoy {
namespace Quic {

class MockEnvoyQuicSession : public quic::QuicSpdySession, public QuicFilterManagerConnectionImpl {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SG

decoded_headers->Method()->value().getStringView());
}));
EXPECT_CALL(request_decoder, decodeData(_, true))
.Times(testing::AtMost(1))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please change to .Times(0) for decodeData, or just remove the EXPECT_CALL.

}

void QuicFilterManagerConnectionImpl::adjustBytesToSend(int64_t delta) {
bytes_to_send_ += delta;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SG

} else {
send_buffer_simulation_.checkLowWatermark(buffered_data_new);
}
connection.adjustBytesToSend(buffered_data_new - buffered_data_old);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SG. (I agree it's extremely unlikely, I just feel that we can still handle it gracefully even if it happens.)


// True after the buffer goes below low watermark and hasn't come up above high
// watermark yet, even though the buffered data might be between high and low
// watermarks.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider add a comment for the behavior when neither high nor low watermarks have been crossed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the beginning state of the buffer. Comment added.

// EnvoyQuicStream
void switchStreamBlockState(bool should_block) override;
uint32_t streamId() override;
Network::Connection* connection() override;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SG


void EnvoyQuicServerStream::readDisable(bool /*disable*/) { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; }
void EnvoyQuicServerStream::switchStreamBlockState(bool should_block) {
ASSERT(FinishedReadingHeaders(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SG

Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Copy link
Copy Markdown
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super awesome to see this coming together. Just flushing out a few small nits/questions from a skim through. Generally LGTM and will defer to @alyssawilk @wu-bin on the detailed review.

/wait

@@ -0,0 +1,33 @@
### Overview
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can we move to https://github.com/envoyproxy/envoy/tree/master/source/docs? This is where we currently have our dev docs. The top level docs directory is for user docs.

for (auto& it : quic_server_session_.stream_map()) {
if (!it.second->is_static()) {
ENVOY_LOG(debug, "runHighWatermarkCallbacks on stream {}", it.first);
dynamic_cast<EnvoyQuicServerStream*>(it.second.get())->runHighWatermarkCallbacks();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need the dynamic cast here and below? Can the stored stream just derive from the right interface?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stream_map() is a QUICHE interface, for sure it doesn't know about Envoy stream interface.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see so this map is storing an opaque pointer of some type? Could it eventually just store a common interface that our classes derive from? Can this be a TODO here and below?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QuicSession::stream_map() store all QuicStream objects. Our envoy quic stream also derives from that class and from EnvoyQuicStream as well. And the latter has runHighWatermarkCallbacks() interface. So we need a cross cast here I believe.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy with the TODO there. Can we have some comment the first time we reference is_static just explaining it's crypto and gQUIC headers?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// flow control window of upstream. The per-stream high watermark should be
// smaller than max flow control window to make sure upper stream can be flow
// control blocked early enough not to send more than the threshold allows.
SetQuicFlag(FLAGS_quic_buffered_data_threshold, 2 * 16 * 1024 * 1024); // 32MB
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems pretty huge compared to what we typically recommend for HTTP/2 at the edge. Will we eventually make this configurable?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This threshold needs to be large enough to hold all the data from upstream even after readDisable(). So it should be larger than HTTP2 per stream flow control window whose negotiated value is not accessible at this level. I will add a TODO to plumb through to get that value.

But before that, for QUICHE to work correctly, we need this threshold to be large enough what each encodeData() can consume all the data passed in. This doesn't mean that we are actually buffering that much data because upstream's flow control window probably is not that large after negotiation.

// the stream to buffer all the data.
// Ideally this limit should also corelate to peer's receive window
// but not fully depends on that.
16 * 1024, [this]() { runLowWatermarkCallbacks(); },
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment above. IMO for QUIC we should start with good edge defaults, since this is really practically only going to be used at the edge. So yeah 256MiB default is way too large. Can we just use different defaults for QUIC and get them right now? If we want to TODO this for discussion during a production readiness pass that is fine with me.

Network::Connection* EnvoyQuicServerStream::connection() { return filterManagerConnection(); }

QuicFilterManagerConnectionImpl* EnvoyQuicServerStream::filterManagerConnection() {
return dynamic_cast<QuicFilterManagerConnectionImpl*>(session());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question about the dynamic cast?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my previous comment.

// notified more than once about end of stream. So once this is true, no need
// to set it in the callback to Envoy stream any more.
bool end_stream_decoded_{false};
int32_t read_disable_counter_{0};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: unsigned

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
@danzh2010
Copy link
Copy Markdown
Contributor Author

@alyssawilk Comments and TODOs about buffer size are added. PTAL!

Signed-off-by: Dan Zhang <danzh@google.com>
mattklein123
mattklein123 previously approved these changes Oct 30, 2019
Copy link
Copy Markdown
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, LGTM modulo format fix and some nits. Will defer to @alyssawilk @wu-bin for further review.

@danzh2010 can you please open an explicit issue for tuning/hardening QUIC buffer sizes before we call the MVP ready? I want to make sure we are tracking that. Thank you!


void EnvoyQuicServerStream::encodeHeaders(const Http::HeaderMap& headers, bool end_stream) {
ENVOY_STREAM_LOG(debug, "encodeHeaders (end_stream={}) {}.", *this, end_stream, headers);
// QUICHE guarantees to take all the headers. This could cause inifit data to
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo "inifit"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

for (auto& it : quic_server_session_.stream_map()) {
if (!it.second->is_static()) {
ENVOY_LOG(debug, "runHighWatermarkCallbacks on stream {}", it.first);
dynamic_cast<EnvoyQuicServerStream*>(it.second.get())->runHighWatermarkCallbacks();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see so this map is storing an opaque pointer of some type? Could it eventually just store a common interface that our classes derive from? Can this be a TODO here and below?

Signed-off-by: Dan Zhang <danzh@google.com>
Copy link
Copy Markdown
Contributor Author

@danzh2010 danzh2010 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#8826 is added for tracking stream send buffer size concerns.


void EnvoyQuicServerStream::encodeHeaders(const Http::HeaderMap& headers, bool end_stream) {
ENVOY_STREAM_LOG(debug, "encodeHeaders (end_stream={}) {}.", *this, end_stream, headers);
// QUICHE guarantees to take all the headers. This could cause inifit data to
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

for (auto& it : quic_server_session_.stream_map()) {
if (!it.second->is_static()) {
ENVOY_LOG(debug, "runHighWatermarkCallbacks on stream {}", it.first);
dynamic_cast<EnvoyQuicServerStream*>(it.second.get())->runHighWatermarkCallbacks();
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QuicSession::stream_map() store all QuicStream objects. Our envoy quic stream also derives from that class and from EnvoyQuicStream as well. And the latter has runHighWatermarkCallbacks() interface. So we need a cross cast here I believe.

@danzh2010
Copy link
Copy Markdown
Contributor Author

@alyssawilk mind take another look?

Copy link
Copy Markdown
Contributor

@alyssawilk alyssawilk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha, I was already halfway through! It's looking great - only a few nits left on my end

runResetCallbacks(reason);
if (local_end_stream_ && !reading_stopped()) {
// This is after 200 early response. Reset with QUIC_STREAM_NO_ERROR instead
// of propagating original reset reason.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get a bit more detail here? It isn't obvious how this relates to 200 early response, or how StopReading() translates into Reset(QUIC_STREAM_NO_ERROR)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// the stream to buffer all the data.
// Ideally this limit should also corelate to peer's receive window
// but not fully depends on that.
16 * 1024, [this]() { runLowWatermarkCallbacks(); },
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I thought this one was tied to the upstream limit, i.e. if we set it low we'd end up advertising to the peer we were willing to buffer data when we wouldn't locally. Sounds like I had the wrong field, they can be decoupled and we can start with smaller limits, which SGTM.

for (auto& it : quic_server_session_.stream_map()) {
if (!it.second->is_static()) {
ENVOY_LOG(debug, "runHighWatermarkCallbacks on stream {}", it.first);
dynamic_cast<EnvoyQuicServerStream*>(it.second.get())->runHighWatermarkCallbacks();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy with the TODO there. Can we have some comment the first time we reference is_static just explaining it's crypto and gQUIC headers?


// A class, together with a stand alone buffer, used to achieve the purpose of WatermarkBuffer.
// Itself doesn't have buffer or bookkeep buffered bytes. But provided with buffered_bytes,
// it re-acts upon crossing high/low watermarks.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rea-acts -> reacts

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Signed-off-by: Dan Zhang <danzh@google.com>
@danzh2010
Copy link
Copy Markdown
Contributor Author

Haha, I was already halfway through! It's looking great - only a few nits left on my end

Commented about QUIC_STREAM_NO_ERROR and static_stream. PTAL!

Copy link
Copy Markdown
Contributor

@alyssawilk alyssawilk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wohoo!

@alyssawilk alyssawilk merged commit b589cf6 into envoyproxy:master Nov 5, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants