-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
coalesce writes #54
coalesce writes #54
Conversation
test failures are #47 |
I’m not sure if this belongs in a stream multiplexer. The stream multiplexer’s responsibility is to, well, multiplex streams. |
Maybe so, but we do have a problem with doing small writes all over the place. |
An alternative is to have a generic coalescer (not a protocol specific one, like here), and put it under the multiplexer, perhaps at a user's preference. |
Then let’s fix it there. Small writes are not an mplex-specific problem, we
have the same problem when using QUIC.
…On Sat, May 11, 2019 at 21:56 vyzo ***@***.***> wrote:
Maybe so, but we do have a problem with doing small writes all over the
place.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#54 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AALI6V3K5XZ35BHZNOLNN33PU27AJANCNFSM4HMIBMUQ>
.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an interesting finding and experiment, but we really don’t want this at this layer.
I agree that this doesn’t belong in a multiplexer. Even if we pushed it down the stack, it would belong in the transport.
But let’s not add a delay to everything. There’s a reason why people disable Nagle in most backend processes.
The application protocol is the one that knows its write pattern, and whether its use case is latency-sensitive or not. Ideally we should expose metadata about the transport at play: is fragmentation costly or not? In UDP the answer might be no, in TCP it would be yes, all of this omitting the cost of the syscall.
Providing a toolbox utility that protocols can opt in, like Marten suggests, is a reasonable tradeoff.
I’m also worried we’re fine-tuning a lot for the specific usecase of relay infrastructure.
@raulk I wasn't really intending to merge this in mplex, it was an experiment to open the conversation. |
Marked as |
Here is a proposal: We can add the coaslescer under the multiplexer and also extend our |
Continuation of discussion in libp2p/go-libp2p#633 Will close it now, but do not delete the branch as it is in use in relays. |
reopened this so that we avoid deleting the branch by accident. |
@raulk would you be comfortable merging with a configurable coalesce delay, that defaults to 1ms? Note that we are adding write coalescing to yamux as well in whyrusleeping/yamux#28 |
Where is the main source of small writes? I’d still prefer to fix it there. |
1ms delay will hardly break anything. |
In short, we can't fix it above the multiplexer, it either has to be in the multiplexer or below it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can simplify this a bit but it should work. The only thing we really need to change is the default delay.
multiplex.go
Outdated
@@ -37,6 +37,8 @@ var ErrInvalidState = errors.New("received an unexpected message from the peer") | |||
var ( | |||
NewStreamTimeout = time.Minute | |||
ResetStreamTimeout = 2 * time.Minute | |||
|
|||
WriteCoalesceDelay = 1 * time.Millisecond |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's default to 100us for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, is that the default in yamux too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah it's 10us in yamux.
n := copy(buf, data) | ||
pool.Put(data) | ||
|
||
if !mp.writeTimerFired { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd just combine this with handleOutgoing
and avoid storing the timer on the instance (makes it clear that it's local only).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then we'd have to explicitly pass it to writeMsg
and mutate in there, which makes it a little messy.
It seemed nicer to just have the timer in the instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My suggestion was to ditch writeMsg
and combine everything. But we can do that later.
defer pool.Put(buf) | ||
|
||
n := copy(buf, data) | ||
pool.Put(data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be simpler to just use a pool of buffered writers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hrm, maybe. I find it simpler to reason with the buffer actually.
Well, except that the tests are failing. |
multiplex.go
Outdated
case data := <-mp.writeCh: | ||
err := mp.writeMsg(data) | ||
if err != nil { | ||
log.Warningf("Error writing data: %s", err.Error()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we return here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably should. There is an implicit return because the write failure closes the connection, which triggers the shutdown channel, but better to be explicit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
The test failure is #47 -- this test has been failing for a while. |
The test passes if I add a small delay. |
Fixed the test as well. |
main objection (delay too large) has been lifted, raul unavailable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only got to it after the fact.
// only return this error if we don't *already* have an error from the write. | ||
if err2 := mp.con.SetWriteDeadline(time.Time{}); err == nil && err2 != nil { | ||
return err2 | ||
buf := pool.Get(4096) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we hold on to a static byte slice to avoid the pool operation? This method is not called concurrently, so it should be safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure, that's probably a worthwhile optimization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That'll take 4KiB when idle which can add up pretty quickly.
This is an experiment in adding a write coalescer to mplex. Although I do think we need a generic one that sites between the security module and the multiplexer, perhaps instantiated at user's choice.