Skip to content

Tcp flow control#1217

Merged
mattklein123 merged 35 commits intoenvoyproxy:masterfrom
alyssawilk:tcp-flow-control
Jul 18, 2017
Merged

Tcp flow control#1217
mattklein123 merged 35 commits intoenvoyproxy:masterfrom
alyssawilk:tcp-flow-control

Conversation

@alyssawilk
Copy link
Contributor

Early steps for #150, starting with something "simple" (heh)

Adding watermark functions to the Connection and ConnectionCallbacks
Having both the base network ConnectionImpl and the SSL version call Watermark callbacks
Having the tcp filter react to Watermark callbacks by enabling/disabling reads

This includes tests of *::ConnectionImpl and new TCP/TLS tests which exert the watermark code thanks to our read limits being willfully ignored (we read 16384 rather than min(16834, read_buffer_limit_))

Copy link
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

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

Looks good, nice progress!

}

void ConnectionImpl::checkForHighWatermark() {
if (above_high_watermark_called_ || high_watermark_ == 0 ||
Copy link
Member

Choose a reason for hiding this comment

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

There's some asymmetry here that is probably fine, but might be worth calling out somewhere. You'll never receive an onBelowWBLowWatermark() unless onAboveWBHighWatermark() has been called, but the other direction doesn't hold.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, per my other comment, I would add as many ASSERTs as make sense to code like this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've called it out more explicitly in include/envoy/network/connection.h let me know if you think it should go elsewhere as well!

Buffer::Instance& getWriteBuffer() override { return *current_write_buffer_; }

void replaceWriteBufferForTest(std::unique_ptr<Buffer::OwnedImpl> new_buffer) {
write_buffer_ = std::move(new_buffer);
Copy link
Member

Choose a reason for hiding this comment

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

It feels a bit scary reaching in and mutating internal state from a test (as opposed to helper methods to just inspect state). I wonder if there is a way to factory-ify this to allow mocking via dependency injection...

Copy link
Member

Choose a reason for hiding this comment

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

Agreed. The general pattern we have been doing is to make something like BufferFactory which returns a buffer. Then in a test you can hold on to the pointer to do things. Search for "Factory" and you will see many examples.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

SGTM. This turns out to involve touching a handful of new files (and the test refactors were already pretty large) so if no one objects I'm going to the test changes and the buffer factory out into their own separate patch since this one is already pretty large.

Copy link
Member

Choose a reason for hiding this comment

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

That's fine with me if that is easier.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, over in #1234. Feel free to ignore this one until that is merge in, or I can attempt to rebase if you'd prefer to review things in parallel.

uint64_t last_write_buffer_size_{};
std::unique_ptr<BufferStats> buffer_stats_;
// Tracks the number of times reads have been disabled. If N different components call
// readDisabled(true) this allows the connection to only resume reasd when readDisabled(false)
Copy link
Member

Choose a reason for hiding this comment

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

Nit: s/reasd/reads/

if (options_.per_connection_buffer_limit_bytes_ > 0) {
new_connection->setWriteBufferWatermarks(options_.per_connection_buffer_limit_bytes_ / 2,
options_.per_connection_buffer_limit_bytes_ + 1);
}
Copy link
Member

Choose a reason for hiding this comment

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

Can we refactor this logic to be in one place?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops - missed this one. I'd be inclined to merge setReadBufferLimit and setWriteBufferWatermarks into setConnectionBufferLimits which just does both under the hood. I'll see if that works cleanly in code and docs tomorrow.

TEST_P(ConnectionImplDeathTest, BadFd) {
Event::DispatcherImpl dispatcher;
EXPECT_DEATH(ConnectionImpl(dispatcher, -1,
Event::DispatcherImpl dispatcher_;
Copy link
Member

Choose a reason for hiding this comment

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

_ suffix for local variables?

Copy link
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.

Overall looks great. A few quick pile on comments.

* @param high_watermark if the connection has more bytes than this buffered,
* onAboveWriteBufferHighWatermark will be called.
*/
virtual void setWriteBufferWatermarks(size_t low_watermark, size_t high_watermark) PURE;
Copy link
Member

Choose a reason for hiding this comment

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

nit: prefer fixed sizes in Envoy code (probably uint32_t here but uint64_t is fine if you want).

read_callbacks_->connection().readDisable(disable);
}

void TcpProxy::DownstreamCallbacks::onAboveWriteBufferHighWatermark() {
Copy link
Member

Choose a reason for hiding this comment

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

Is it worth it here to add ASSERTS in all of these functions that basically assert that we aren't getting multiple watermark callbacks? Or can that happen? I'm thinking something along the lines of ASSERT(!parent_.upstreamReadDisabled()). (Similar in other places).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

connections can have the read disabled by other means so if we check the actual state of the connection we can only verify that we enable actually disabled sockets (not that we disable enabled sockets) We could track local state by adding 2 booleans to the tcp proxy filter which are only used for debug, which I'd be happy to do - flow control is tricky enough to get right I'm happy to have extra asserts too!

Copy link
Member

Choose a reason for hiding this comment

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

I would be in favor of adding the booleans (at least for now, with a TODO to clean them up once we have production experience) since this code is super tricky to get right and more checking is better IMO. I will leave it up to you to decide either way.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Works for me. Flow control changes are inherently dangerous as you point out.
Added asserts and the the tests pass 5k runs without flakes once I got the waitForDisconnect fix merged in.

}

void ConnectionImpl::setWriteBufferWatermarks(size_t new_low_watermark, size_t new_high_watermark) {
ENVOY_CONN_LOG(trace, "Setting watermarks: {} {}", *this, new_low_watermark, new_high_watermark);
Copy link
Member

Choose a reason for hiding this comment

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

nit: I would probably do this one at debug level. (We should codify this but in general I've been doing trace level for anything that is in the data path and would happen during the connection, like writing bytes, reading bytes, etc., but this is more of a one time thing).

}

void ConnectionImpl::checkForHighWatermark() {
if (above_high_watermark_called_ || high_watermark_ == 0 ||
Copy link
Member

Choose a reason for hiding this comment

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

Yeah, per my other comment, I would add as many ASSERTs as make sense to code like this.

Buffer::Instance& getWriteBuffer() override { return *current_write_buffer_; }

void replaceWriteBufferForTest(std::unique_ptr<Buffer::OwnedImpl> new_buffer) {
write_buffer_ = std::move(new_buffer);
Copy link
Member

Choose a reason for hiding this comment

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

Agreed. The general pattern we have been doing is to make something like BufferFactory which returns a buffer. Then in a test you can hold on to the pointer to do things. Search for "Factory" and you will see many examples.

Address::InstanceConstSharedPtr local_address_;
Buffer::OwnedImpl read_buffer_;
Buffer::OwnedImpl write_buffer_;
std::unique_ptr<Buffer::OwnedImpl> write_buffer_{new Buffer::OwnedImpl};
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Buffer::InstancePtr

// buffer passes |high_watermark_|, onAboveWriteBufferHighWatermark will be called to disable
// reading further data. When the buffer drains below |low_watermark_|,
// onBelowWriteBufferLowWatermark will be called to resume reads.
size_t high_watermark_{0};
Copy link
Member

Choose a reason for hiding this comment

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

same comment about uint32_t or uint64_t

connection->setReadBufferLimit(cluster.perConnectionBufferLimitBytes());
uint32_t buffer_limit = cluster.perConnectionBufferLimitBytes();
connection->setReadBufferLimit(buffer_limit);
connection->setWriteBufferWatermarks(buffer_limit / 2, buffer_limit + 1);
Copy link
Member

Choose a reason for hiding this comment

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

Same here per @htuch other comment. I would recommend adding some type of watermark utility class that can be used to setup nice defaults on a connection.

@rshriram
Copy link
Member

May be I missed this somewhere, but is there an option to configure the watermark (or the buffer size for upstream) for plain TCP connections?x

@alyssawilk
Copy link
Contributor Author

alyssawilk commented Jul 10, 2017 via email

Copy link
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.

looks awesome modulo a few small things and the remaining comment of centralizing the / 2 setup logic somewhere.

* and onBelowWriteBufferHighWatermark callbacks.
* The connection is assumed to start out with less than high_watermark
* worth of data buffered, so onAboveWriteBufferHighWatermark will always be
* called before onAboveWriteBufferHighWatermark
Copy link
Member

Choose a reason for hiding this comment

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

s/onAboveWriteBufferHighWatermark/onBelowWriteBufferLowWatermark ?


/**
* Sets the high and low watermarks which trigger onAboveWriteBufferHighWatermark
* and onBelowWriteBufferHighWatermark callbacks.
Copy link
Member

Choose a reason for hiding this comment

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

s/onBelowWriteBufferHighWatermark/onBelowWriteBufferLowWatermark ?

* worth of data buffered, so onAboveWriteBufferHighWatermark will always be
* called before onAboveWriteBufferHighWatermark
* @param low_watermark if the connection was above the high watermark and the
* connection buffer is drained below this many bytes, onBelowWriteBufferHighWatermark will be
Copy link
Member

Choose a reason for hiding this comment

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

s/onBelowWriteBufferHighWatermark/onBelowWriteBufferLowWatermark ?


Network::ListenerPtr listener = dispatcher.createSslListener(
connection_handler, *server_ctx, socket, listener_callbacks, stats_store,
void Initialize(uint32_t read_buffer_limit) {
Copy link
Member

Choose a reason for hiding this comment

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

nit: initialize (I think part of this change is in the other change also, so might be best to get one of these in first and then rebase/focus on the other one).


namespace Envoy {

class MockBuffer : public Buffer::OwnedImpl {
Copy link
Member

Choose a reason for hiding this comment

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

same comments about method name casing in this PR (also same comment about picking one of the PRs to get in first and then we can do the other one).

@mattklein123
Copy link
Member

@alyssawilk before I forget, I think it would be worthwhile to add a stat for tcp_proxy high watermark pause and low watermark resume. Probably:
https://github.com/lyft/envoy/blob/master/source/common/filter/tcp_proxy.h#L27

and

https://github.com/lyft/envoy/blob/master/include/envoy/upstream/upstream.h#L185

If possible I would like to do this in this PR vs. follow up so that when we deploy we can watch the stats.

Copy link
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

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

LGTM modulo remaining comments from @mattklein123.

Network::MockConnectionCallbacks server_callbacks_;
std::shared_ptr<MockReadFilter> read_filter_;
std::unique_ptr<MockBuffer> buffer_ptr_{new MockBuffer()};
MockBuffer& buffer_{*buffer_ptr_};
Copy link
Member

Choose a reason for hiding this comment

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

You can make this an actual pointer as per earlier thread and idiom discussion if you want.

return -1;
}

int bytes_written() const { return bytes_written_; }
Copy link
Member

Choose a reason for hiding this comment

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

Also I'd reiterate the general preference for fixed sized types for quantities here, e.g. uint32_t.

// enabling/disabling reads from the socket, or allowing more data, but then not triggering
// based on watermarks until 2x the data is buffered in the common case. Given these are all soft
// limits we err on the side of buffering more and having better performace.
// If the connection class is changed to write-and-flush the high watermark should be changed to
Copy link
Member

Choose a reason for hiding this comment

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

Can you elaborate on what "write-and-flush" means? Is this a flush after every write? Why does this affect high watermark?

@alyssawilk
Copy link
Contributor Author

alyssawilk commented Jul 12, 2017 via email

@mattklein123
Copy link
Member

COUNTER(downstream_cx_total) \
COUNTER(downstream_cx_no_route)
COUNTER(downstream_cx_no_route) \
COUNTER(upstream_pause_reading) \
Copy link
Member

Choose a reason for hiding this comment

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

It would be better IMO if these were defined as upstream cluster stats (see the link I originally referenced). They can then also be used in the HTTP flow and for other proxy scenarios.

P.S. please also document the new stats in the relevant parts of the docs.

Copy link
Contributor Author

@alyssawilk alyssawilk Jul 12, 2017

Choose a reason for hiding this comment

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

Is it reasonable to put upstream/downstream in the filter stats and upstream-pauses (and resumes) in the upstream cluster stats? Adding downstream-pauses in the upstream stats seems odd to me but maybe that's me not having fully absorbed what goes where in envoy yet :-)

Also thanks for the pointer to stats! I'm much happier with (my local, soon upstream) tcp proxy integration tests validating them.

Copy link
Member

Choose a reason for hiding this comment

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

If you take a look at generally the way we do stats, where possible we use cluster for the "upstream" stats. "Downstream" stats are taken care of by the filter itself. In this case its TCP proxy, but in future PRs you will obviously be doing the same type of thing in the HTTP connection manager. This is why today you see only downstream in the above stats and all the upstream stats are in the cluster stats. If you look at existing tcp proxy filter code you will see lots of uses of cluster stats for this purpose. I would just replicate that.

The main reason to do this is for consistency across protocols where possible.

Copy link
Contributor Author

@alyssawilk alyssawilk Jul 12, 2017

Choose a reason for hiding this comment

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

Edit: ah, I think your comment answered this for me. We can only track what the filter does in the filter so I'll just track flow_control related enable/disable

Actually I have a one more questions on this (sorry for the back and forth).

Added upstream_pause_reading_total to cluster stats in my local branch but then it only gets watermark enables/disables. Any preference for a rename for flow_control_[paused|resumed]_reads vs tracking stats in readDisable on the connection?

Given there's already some readEnabling/Disabling going on on the http side I'm not sure if you'd prefer to track the flow control changes separately or if tracking all enables/disables is more useful.

Copy link
Member

Choose a reason for hiding this comment

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

No problem at all with the questions.

I haven't thought through it completely, but I agree that explicitly tracking flow control pause/resume sounds useful and probably what we want. So renaming sounds good. We can always add connection level read disable/enable stats later on if we want. I guess that would be my vote (just rename of what you are doing now). But will defer to your judgement.

Copy link
Contributor Author

@alyssawilk alyssawilk Jul 12, 2017

Choose a reason for hiding this comment

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

Please to not merge yet - I think I had a test flake locally and I'm trying to sort out if it's related to this patch set.

Edit: Ah, annoyingly it's not guaranteed that the large write test backs up - it almost always does on debug builds but very occasionally fails and often fails on tsan.

* the write butter. When enough data is drained from the write buffer,
* onBelowWriteBufferHighWatermark is called which results in resuming reads.
* onAboveWriteBufferHighWatermark, which allows subscribers to enforce flow control by disabling
* reads on the socket funneling data to the write butter. When enough data is drained from the
Copy link
Member

Choose a reason for hiding this comment

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

s/butter/buffer :)

// buffer is resized (written to or drained), the watermarks are checked. As the buffer size
// transitions from under the low watermark to above the high watermark, the above_high_watermark
// function is called one time. It will not be called again until the buffer is drained below the
// low watermark, at which point the below_low_watermark function is called.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Matt's earlier comment re:utilities was spot on - once I started on h2 flow control I found myself copy-pasting watermark checks so pulled this out. I believe it will be reusable for at least some of the codec buffers. Either way it guards us from screwing up and missing buffer size changes changes (I missed one in ssl/connection_impl.cc before my own tests caught me) because all edits are done through the wrapper.

@htuch let me know what you think before I go too crazy with the unit tests :-)

@mattklein123
Copy link
Member

@alyssawilk I merged the other change so once you merge master on this one I think we should be good to go!

// transitions from under the low watermark to above the high watermark, the above_high_watermark
// function is called one time. It will not be called again until the buffer is drained below the
// low watermark, at which point the below_low_watermark function is called.
class WatermarkBuffer : public Instance {
Copy link
Member

Choose a reason for hiding this comment

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

This is really nice @alyssawilk. The only question I had is whether it makes sense to make this a subclass of OwnedImpl or not? It's probably better as a wrapper, since it has nothing really to do with the OwnedImpl implementation, OTOH you can avoid a few lines of wrapper calls where it would just chain to the parent class. I think what you have is good, if you can add the tests to provide coverage I think we're good to go.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's basically what I thought, but I'm happy to go either way if @mattklein123 would prefer the perf boost.
Should be up to 100% coverage on the new watermark code now.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe just throw in a TODO about potential perf boost? This change is big enough as it is so if it's working my preference would be to just ship it and iterate later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sticking with things as is SGTM.
Having thought more I agree with Harvey that this is more of a code cleanliness choice rather than a perf issue so I'd lean towards adding a TODO iff it shows up in profiles if that's OK

Copy link
Member

Choose a reason for hiding this comment

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

Sure that's fine. I will take a final pass through this change later today.

}
int write(int fd) override;
Event::Libevent::BufferPtr& buffer() override {
return static_cast<LibEventInstance&>(*wrapped_buffer_).buffer();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@htuch as discussed this solves the problem where move didn't work in both directions (now tested) because OwnedImpl assumed it was moving another OwnedImpl. The down-side is that WatermarkBuffer now only can do move()s if it wraps an OwnedImpl. That said the Envoy code base already assumes all buffers are OwnedImpls so I don't think we're losing any flexibility, it's just ugly from a purist perspective.

Copy link
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.

Looks really awesome. Few small comments. Will let @htuch finish out the review and merge.

// transitions from under the low watermark to above the high watermark, the above_high_watermark
// function is called one time. It will not be called again until the buffer is drained below the
// low watermark, at which point the below_low_watermark function is called.
class WatermarkBuffer : public LibEventInstance {
Copy link
Member

Choose a reason for hiding this comment

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

If this is a wrapper around a buffer, does it have to derive from LibEventInstance ? Are the changes to introduce LibEventInstance necessary? (If they are the below static_cast should probably be a dynamic_cast, but it's not clear to me that it's necessary).

Copy link
Member

Choose a reason for hiding this comment

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

Maybe you just answered my comment with your other comment. If so, maybe add more code comments?

#include "envoy/network/listen_socket.h"
#include "envoy/network/listener.h"

#include "common/buffer/buffer_impl.h"
Copy link
Member

Choose a reason for hiding this comment

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

Is this needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For OwnedImplFactory.

write_buffer_(dispatcher.getBufferFactory().create()), dispatcher_(dispatcher), fd_(fd),
id_(++next_global_id_) {
write_buffer_(Buffer::InstancePtr{dispatcher.getBufferFactory().create()},
[&]() -> void { this->onLowWatermark(); },
Copy link
Member

Choose a reason for hiding this comment

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

nit: capture this, not & (everything). Same next line.

// based on watermarks until 2x the data is buffered in the common case. Given these are all soft
// limits we err on the side of buffering more and having better performace.
// If the connection class is changed to write to the buffer and flush to the socket in the same
// stack, the high watermark should be changed to the buffer limit without the + 1
Copy link
Member

Choose a reason for hiding this comment

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

Question: Not immediately obvious what the +1 is about. Can you explain a bit more in comment?

cluster.sslContext() ? dispatcher.createSslClientConnection(*cluster.sslContext(), address)
: dispatcher.createClientConnection(address);
connection->setReadBufferLimit(cluster.perConnectionBufferLimitBytes());
uint32_t buffer_limit = cluster.perConnectionBufferLimitBytes();
Copy link
Member

Choose a reason for hiding this comment

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

nit: const uint32_t, or just lose the local variable and do it inline like it was before (might need static_cast).

connect();
client_connection_->setBufferLimits(10);

Runtime::RandomGeneratorImpl rand;
Copy link
Member

Choose a reason for hiding this comment

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

Can we use a seed that can be printed and supplied on the command-line to repeat failed tests? I worry about failures we catch in CI flakes that we can't reproduce otherwise. Maybe the gtest seed (https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md#shuffling-the-tests) ?

Copy link
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 see an ability to pass seeds to our rng.
I do see a TODO in test/test_common/network_utility.cc for such a feature :-/

Copy link
Member

Choose a reason for hiding this comment

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

This is true, but should be fixed. At a minimum file an issue, add a TODO and reference. We do use RandomGeneratorImpl in a few places in test/ today, but I feel this one in particular is likely to bite us, as the particular read/write schedule that might trigger a bug could be rare.

Copy link
Member

Choose a reason for hiding this comment

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

FWIW, the code is trivial: https://github.com/lyft/envoy/blob/master/source/common/runtime/runtime_impl.h#L31

I would just add a TestRandomGeneratorImpl that doesn't use thread local stuff and allows a seed.

// based on watermarks until 2x the data is buffered in the common case. Given these are all soft
// limits we err on the side of buffering more and having better performace.
// If the connection class is changed to write to the buffer and flush to the socket in the same
// stack, the high watermark should be changed from |limit + 1| to |limit|.
Copy link
Member

Choose a reason for hiding this comment

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

I think I'm still not smart enough to understand why this is the case :(

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Confusing you and Matt means I'm clearly not explaining it well :-/
Here's one more pass - if this doesn't do it I'd lean towards explaining in person and getting your help clarifying it

// enabling/disabling reads from the socket, or allowing more data, but then not triggering
// based on watermarks until 2x the data is buffered in the common case. Given these are all soft
// limits we err on the side of buffering more and having better performace.
// limits we err on the side of buffering more triggering watermark callbacks less often.
Copy link
Member

Choose a reason for hiding this comment

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

"buffering more and ..."

OK this makes sense to me now, thanks. I guess my follow up question is whether this is needed though. The assumption is that we will be writing limit bytes in the typical case. I kind of doubt that will happen (limits would typically be set with some margin above the typical back and forth sizes). Given that, does the extra complexity and groking time provide enough value? (I don't really feel that strongly about it, mostly asking the question).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Heh, just had the same conversation with Harvey. It is the common case for tcp proxying. I'd updated the comment to this effect but yeah, I'm just as happy removing.

Copy link
Member

Choose a reason for hiding this comment

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

I'm fine keeping it if we think it will provide value in a real scenario (seems like it will).

Copy link
Member

@htuch htuch 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 fixing this, a few comments.

EXPECT_EQ(message, std::string(e.what())); \
}

class TestRandomGenerator {
Copy link
Member

Choose a reason for hiding this comment

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

Is there any value in this implementing the RandomGenerator interface?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not currently. It would require faking out the uuid string which no one needs. No callers actually want the RandomGenerator interface they just want a random number.
I can of course do it anyway if we anticipate the use case.

TestRandomGenerator::TestRandomGenerator() : generator_(SEED) {
std::cerr << "TestRandomGenerator running with seed " << SEED;
TestRandomGenerator::TestRandomGenerator()
: generator_(GTEST_FLAG(random_seed) == 0 ? SEED : GTEST_FLAG(random_seed)) {
Copy link
Member

Choose a reason for hiding this comment

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

google/googletest#331 says we should be able to get the current seed with UnitTest::random_seed().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can but it appears to always be 0 as it was when I tried accessing it via flag.
It appears to only be set if you set --gtest_shuffle which I find to be an uncomfortable hidden dependency I'd rather not depend on.

http://docs.ros.org/api/self_test/html/gtest-all_8cc_source.html -> 5752

std::cerr << "TestRandomGenerator running with seed " << SEED;
TestRandomGenerator::TestRandomGenerator()
: generator_(GTEST_FLAG(random_seed) == 0 ? SEED : GTEST_FLAG(random_seed)) {
const int32_t seed = GTEST_FLAG(random_seed) == 0 ? SEED : GTEST_FLAG(random_seed);
Copy link
Member

Choose a reason for hiding this comment

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

Might be cleaner to make this a const member variable and initialize it once in the member initializor list, then initialize generator_ with seed_.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, it's test code, we can burn the extra memory for a temp varaible.

@mattklein123 mattklein123 merged commit 1580db3 into envoyproxy:master Jul 18, 2017
@alyssawilk alyssawilk deleted the tcp-flow-control branch July 20, 2017 14:58
rshriram pushed a commit to rshriram/envoy that referenced this pull request Oct 30, 2018
Automatic merge from submit-queue.

Add support for v1 config.

**What this PR does / why we need it**:

**Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes envoyproxy#1217 

**Special notes for your reviewer**:

**Release note**:

```release-note
NONE
```
jpsim pushed a commit that referenced this pull request Nov 28, 2022
Description: This PR contains only the header changes from #1209 which were made while developing.
Risk Level: Low
Testing: N/A, no behavior changes
Docs Changes: N/A
Release Notes: N/A

Signed-off-by: Cerek Hillen <chillen@lyft.com>
Signed-off-by: JP Simard <jp@jpsim.com>
jpsim pushed a commit that referenced this pull request Nov 29, 2022
Description: This PR contains only the header changes from #1209 which were made while developing.
Risk Level: Low
Testing: N/A, no behavior changes
Docs Changes: N/A
Release Notes: N/A

Signed-off-by: Cerek Hillen <chillen@lyft.com>
Signed-off-by: JP Simard <jp@jpsim.com>
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.

4 participants