Skip to content
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

Simplify buffering in TCPConnection #3185

Merged
merged 1 commit into from
Jun 18, 2019
Merged

Conversation

SeanTAllen
Copy link
Member

Prior to this commit, the buffering in TCPConnection was overly
complicated. This was a carry over from the old style of buffer
handling. However, after Dipin Hora's commits to bring over the "faster
tcp" changes from Wallaroo, the buffer handling no longer makes sense.

Prior to this commit, a TCPConnection would be provided two variables to
control read buffering: init_size, max_size. The read buffer would start
at init_size and would grow to max_size.

Before "faster tcp", the buffer would only grow in size if we read the
entire buffer's worth of data in one recv call. After the data was read,
the buffer would be truncated and then handed off to the
TCPConnectionNotify. This was very wasteful. If you had a buffer of 128
allocated bytes and only read 4, then the 124 allocated bytes would be
truncated away and the buffer would be reallocated.

With "faster tcp", we allocate a large buffer and when reading data, we
chop it off the buffer as needed. This means, that "grow the buffer"
doesn't make much sense anymore. The buffer will continually shrink in
size as we slowly chop more and more off. This meant that with the code
as it is prior to change, we would always grow to max buffer size.

If we are always going to grow to max buffer size, it makes sense to
simplify the code and our interfaces and only allow for setting the
buffer size. This commit makes that simplication. Now we will:

  • allocate a buffer of size read_buffer_size
  • reallocate when the buffer is empty
  • reallocate if the caller is using expect and the buffer is too small
    to hold the expected payload

This is a breaking change for TPCConnection and TCPListener as it
changes the constructor interfaces.

@SeanTAllen SeanTAllen added the changelog - changed Automatically add "Changed" CHANGELOG entry on merge label Jun 18, 2019
@SeanTAllen SeanTAllen force-pushed the single-buffer-size-tcp branch from 74ec837 to 68a82a3 Compare June 18, 2019 03:40
@@ -657,7 +648,7 @@ actor TCPConnection
var bytes_to_send: USize = 0
var bytes_sent: USize = 0
while _writeable and (_pending_writev_total > 0) do
if bytes_sent >= _max_size then
if bytes_sent >= _read_buffer_size then
Copy link
Member Author

Choose a reason for hiding this comment

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

I opened an issue #3186 to address this issue. I think it should be a different PR.

@@ -775,16 +766,13 @@ actor TCPConnection
This occurs only with IOCP on Windows.
"""
ifdef windows then
match len.usize()
| 0 =>
if len.usize() == 0 then
Copy link
Member Author

Choose a reason for hiding this comment

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

seems like this usize() probably isn't needed but I wanted to do the minimal change.

@@ -879,8 +861,8 @@ actor TCPConnection
end
end

if sum >= _max_size then
// If we've read _max_size, yield and read again later.
if sum >= _read_buffer_size then
Copy link
Member Author

Choose a reason for hiding this comment

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

we should consider freeing "yield after" from buffer size in which case #3186 should be updated.

@jemc
Copy link
Member

jemc commented Jun 18, 2019

Can you please add a link to PR #3178 in the commit and PR description?

Prior to this commit, the buffering in TCPConnection was overly
complicated. This was a carry over from the old style of buffer
handling. However, after Dipin Hora's commits to bring over the "faster
tcp" changes from Wallaroo, the buffer handling no longer makes sense.

Prior to this commit, a TCPConnection would be provided two variables to
control read buffering: init_size, max_size. The read buffer would start
at init_size and would grow to max_size.

Before "faster tcp", the buffer would only grow in size if we read the
entire buffer's worth of data in one recv call. After the data was read,
the buffer would be truncated and then handed off to the
TCPConnectionNotify. This was very wasteful. If you had a buffer of 128
allocated bytes and only read 4, then the 124 allocated bytes would be
truncated away and the buffer would be reallocated.

With "faster tcp", we allocate a large buffer and when reading data, we
chop it off the buffer as needed. This means, that "grow the buffer"
doesn't make much sense anymore. The buffer will continually shrink in
size as we slowly chop more and more off. This meant that with the code
as it is prior to change, we would always grow to max buffer size.

If we are always going to grow to max buffer size, it makes sense to
simplify the code and our interfaces and only allow for setting the
buffer size. This commit makes that simplication. Now we will:

- allocate a buffer of size `read_buffer_size`
- reallocate when the buffer is empty
- reallocate if the caller is using `expect` and the buffer is too small
to hold the expected payload

This is a breaking change for TPCConnection and TCPListener as it
changes the constructor interfaces.

Additional information on the "faster tcp" change can be found in the
following PR: #3178
@SeanTAllen SeanTAllen force-pushed the single-buffer-size-tcp branch from 68a82a3 to 2f9fdb2 Compare June 18, 2019 17:00
@SeanTAllen
Copy link
Member Author

@jemc comment amened.

@SeanTAllen SeanTAllen merged commit 2167fe3 into master Jun 18, 2019
@SeanTAllen SeanTAllen deleted the single-buffer-size-tcp branch June 18, 2019 17:03
@dipinhora
Copy link
Contributor

@SeanTAllen it seems like this change cannot handle the scenario when _expect > _read_buffer_size (or it can but i'm not following the logic correctly).

Can you please clarify/confirm?

@SeanTAllen
Copy link
Member Author

Correct. Expect is capped at whatever the buffer size is.

@SeanTAllen
Copy link
Member Author

And I forgot to open a PR for that change to expect.

@dipinhora
Copy link
Contributor

That seems like a significant functional change beyond the "simplify buffering" as the title of this PR suggests.

@SeanTAllen
Copy link
Member Author

I thought I had already opened a PR to address expect

@dipinhora
Copy link
Contributor

Okey dokey. I'll wait to see that PR. Thanks.

@SeanTAllen
Copy link
Member Author

The core change there is

  fun ref expect(qty: USize = 0) ?=>
    """
    A `received` call on the notifier must contain exactly `qty` bytes. If
    `qty` is zero, the call can contain any amount of data. This has no effect
    if called in the `sent` notifier callback.
    """
    if qty <= _read_buffer_size then
      if not _in_sent then
        _expect = _notify.expect(this, qty)
        _read_buf_size()
      end
    else
      error
    end

SeanTAllen added a commit that referenced this pull request Jun 18, 2019
Prior to this commit, `expect` could be used to create a denial of
service on a Pony application. If you used expect to handle a framed
protocol, then, you'd by default accept any sized `except` value which
could result in huge amounts of memory being allocated. Memory that
could vastly outstrip the max buffer size set for controlling memory
usage.

After this commit, `expect` is partial and will result in an error if
attempting to set an expect value that is greater than our max read
buffer size.

I intended to commit this prior to another PR, but forgot and thought
that I had. As it is, this PR works in conjunction with #3185 to prevent
expect from being passed that is greater than the max buffer size
thereby deadlocking the logic post #3185.
SeanTAllen added a commit that referenced this pull request Jun 18, 2019
Prior to this commit, `expect` could be used to create a denial of
service on a Pony application. If you used expect to handle a framed
protocol, then, you'd by default accept any sized `except` value which
could result in huge amounts of memory being allocated. Memory that
could vastly outstrip the max buffer size set for controlling memory
usage.

After this commit, `expect` is partial and will result in an error if
attempting to set an expect value that is greater than our max read
buffer size.

I intended to commit this prior to another PR, but forgot and thought
that I had. As it is, this PR works in conjunction with #3185 to prevent
expect from being passed that is greater than the max buffer size
thereby deadlocking the logic post #3185.
@SeanTAllen
Copy link
Member Author

@dipinhora #3197

SeanTAllen added a commit that referenced this pull request Jun 19, 2019
Prior to this commit, `expect` could be used to create a denial of
service on a Pony application. If you used expect to handle a framed
protocol, then, you'd by default accept any sized `except` value which
could result in huge amounts of memory being allocated. Memory that
could vastly outstrip the max buffer size set for controlling memory
usage.

After this commit, `expect` is partial and will result in an error if
attempting to set an expect value that is greater than our max read
buffer size.

I intended to commit this prior to another PR, but forgot and thought
that I had. As it is, this PR works in conjunction with #3185 to prevent
expect from being passed that is greater than the max buffer size
thereby deadlocking the logic post #3185.
SeanTAllen added a commit that referenced this pull request Jun 19, 2019
Prior to this commit, `expect` could be used to create a denial of
service on a Pony application. If you used expect to handle a framed
protocol, then, you'd by default accept any sized `except` value which
could result in huge amounts of memory being allocated. Memory that
could vastly outstrip the max buffer size set for controlling memory
usage.

After this commit, `expect` is partial and will result in an error if
attempting to set an expect value that is greater than our max read
buffer size.

I intended to commit this prior to another PR, but forgot and thought
that I had. As it is, this PR works in conjunction with #3185 to prevent
expect from being passed that is greater than the max buffer size
thereby deadlocking the logic post #3185.
patches11 pushed a commit to patches11/ponyc that referenced this pull request Jun 29, 2019
Prior to this commit, `expect` could be used to create a denial of
service on a Pony application. If you used expect to handle a framed
protocol, then, you'd by default accept any sized `except` value which
could result in huge amounts of memory being allocated. Memory that
could vastly outstrip the max buffer size set for controlling memory
usage.

After this commit, `expect` is partial and will result in an error if
attempting to set an expect value that is greater than our max read
buffer size.

I intended to commit this prior to another PR, but forgot and thought
that I had. As it is, this PR works in conjunction with ponylang#3185 to prevent
expect from being passed that is greater than the max buffer size
thereby deadlocking the logic post ponylang#3185.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
changelog - changed Automatically add "Changed" CHANGELOG entry on merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants