Skip to content

Moving PooledRecvBufferAllocator from NIOPosix to NIOCore #3110

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

Merged
merged 14 commits into from
May 7, 2025

Conversation

rafaelcepeda
Copy link
Contributor

Moving PooledRecvBufferAllocator from NIOPosix to NIOCore, making it part of its public interface.

Motivation:

We want to introduce the capability of reusing receiving buffers from a pool into swift-nio-transport-services project. To prevent duplicating this functionality there, this change is making the existing functionality from NIO part of its public API.

Modifications:

Moved the type PooledRecvBufferAllocator from NIOPosix module to NIOCore and turned it into a public type.

Result:

PooledRecvBufferAllocator is part of NIOCore public API.

@Lukasa Lukasa added the 🆕 semver/minor Adds new public API. label Feb 5, 2025
@@ -12,10 +12,8 @@
//
//===----------------------------------------------------------------------===//

import NIOCore

/// A receive buffer allocator which cycles through a pool of buffers.
Copy link
Contributor

Choose a reason for hiding this comment

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

We should extend this to express when users might want to use this type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this addition to the comment accurate and sufficient: This type is useful when allocating new buffers for every IO read is expensive or undesirable.?

Copy link
Contributor

Choose a reason for hiding this comment

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

Allocating is always expensive, so I'm not sure this is particularly insightful. Maybe something like:

Channels can read multiple times per cycle (based on ChannelOptions.maxMessagesPerRead), and they reuse the inbound buffer for each read. If a ChannelHandler holds onto this buffer, then CoWing will be needed. A PooledRecvBufferAllocator cycles through preallocated buffers to avoid CoWs during the same read cycle.

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 added your suggestion in the documentation.

mutating func record(actualReadBytes: Int) {
/// - Parameters:
/// - actualReadBytes: Number of bytes being recorded
/// - Returns: whether the next buffer will be larger than the last.
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't actually returning anything so I'd get rid of this line

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch. Removed from the code.

@@ -12,10 +12,8 @@
//
//===----------------------------------------------------------------------===//

import NIOCore

/// A receive buffer allocator which cycles through a pool of buffers.
Copy link
Contributor

Choose a reason for hiding this comment

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

Allocating is always expensive, so I'm not sure this is particularly insightful. Maybe something like:

Channels can read multiple times per cycle (based on ChannelOptions.maxMessagesPerRead), and they reuse the inbound buffer for each read. If a ChannelHandler holds onto this buffer, then CoWing will be needed. A PooledRecvBufferAllocator cycles through preallocated buffers to avoid CoWs during the same read cycle.

internal struct PooledRecvBufferAllocator {
///
/// This type is useful when allocating new buffers for every IO read is expensive or undesirable.
public struct PooledRecvBufferAllocator {
Copy link
Contributor

Choose a reason for hiding this comment

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

This'll need a "NIO" prefix now that it's coming into a public namespace, see https://github.com/apple/swift-nio/blob/main/docs/public-api.md#1-no-global-namespace-additions-that-arent-prefixed

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 added the prefix. Great catch.

/// - allocator: `ByteBufferAllocator` used to construct a new buffer if needed
/// - body: Closure where the caller can use the new or existing buffer
/// - Returns: A tuple containing the `ByteBuffer` used and the `Result` yielded by the closure provided.
public mutating func buffer<Result>(
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be @inlinable. In turn it will mean a bunch of other functions and properties will need to be as well. For anything that's private that needs to be @inlinable/@usableFromInline they can become internal and be prefixed with an _.

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 changed this and related functions/properties to be @inlinable/@usableFromInline as you suggested.

Copy link
Contributor

@glbrntt glbrntt left a comment

Choose a reason for hiding this comment

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

One nit but LGTM otherwise

/// Channels can read multiple times per cycle (based on `ChannelOptions.maxMessagesPerRead`), and they reuse
/// the inbound buffer for each read. If a `ChannelHandler` holds onto this buffer, then CoWing will be needed.
/// A `NIOPooledRecvBufferAllocator` cycles through preallocated buffers to avoid CoWs during the same read cycle.
public struct NIOPooledRecvBufferAllocator {
Copy link
Contributor

Choose a reason for hiding this comment

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

As a public type we should annotate whether this is Sendable or not. It looks like it is.

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 introduced the change requested.

@rafaelcepeda rafaelcepeda requested a review from glbrntt May 7, 2025 12:11
@glbrntt glbrntt enabled auto-merge (squash) May 7, 2025 12:47
@glbrntt glbrntt merged commit afa7d4f into apple:main May 7, 2025
40 of 45 checks passed
@rafaelcepeda rafaelcepeda deleted the pooledbuffer-niocore branch May 7, 2025 12:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🆕 semver/minor Adds new public API.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants