Skip to content

Deframer implementation resizes buffer when not necessary #211

Closed
@bugreport1234

Description

@bugreport1234

Hello,
I'm using this library in a project. First of all, thanks, this library has made working with TLS very straightforward for me.

I'm using this library in the context of non-blocking I/O. Due to the specifics of my project I'm unable to use an OS polling library like mio, and instead manually check sockets for readiness in a loop, sleeping for a short period between iterations. This means that the vast majority of read calls return WouldBlock. This has worked well with unencrypted TcpStreams, however once I implemented an encrypted version with this library I started seeing unreasonable CPU usage even though the connection was idle.

I've been able to modify the simpleclient.rs example to serve as a minimal testcase: https://pastebin.com/S0SPraDN. Executing this code results in consistent CPU usage of ~2% for me.

Upon investigation, it appears the code in deframer.rs#L45 first resizes the deframer buffer to maximum length, attempts a read, and truncates the buffer back to its original size if an error occurred. It seems these operations were the cause of the CPU usage I was seeing, possibly because of the allocation size of >18KB. Unfortunately these operations execute in almost every iteration of my loop, due to the returned WouldBlock from the underlying socket.

I've been able to confirm that these operations are indeed the cause of the CPU usage through a workaround. If you insert the following at the start of the read function the CPU usage drops back to 0%:

let mut empty_buf = [0; 0];
rd.read(&mut empty_buf)?;

The code attempts to read to an empty buffer which means it shouldn't affect the functionality of the success case. In the error case, the function immediately returns without getting to the resizing part, thus avoiding the costly operations.

However I feel like it should be possible to solve this without a workaround like this one. From PR #9 and PR #11 I gather that the current behavior and the large buffer size were introduced to avoid too many read calls to make reading more efficient. Perhaps std::io::BufReader could be used here, which seems to have been made to solve this problem, and appears not to be using resize internally.
However, I have no experience with either TLS nor BufReader internals, so I apologize if this would not be a workable solution.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions