-
Notifications
You must be signed in to change notification settings - Fork 643
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
Add AsyncChannel
based ServerBootstrap.bind()
methods
#2403
Add AsyncChannel
based ServerBootstrap.bind()
methods
#2403
Conversation
f2e4d05
to
7f2a1de
Compare
Sources/NIOCore/AsyncChannel/AsyncChannelInboundStreamChannelHandler.swift
Outdated
Show resolved
Hide resolved
Sources/NIOCore/AsyncChannel/AsyncChannelInboundStreamChannelHandler.swift
Outdated
Show resolved
Hide resolved
Sources/NIOCore/AsyncChannel/AsyncChannelInboundStreamChannelHandler.swift
Outdated
Show resolved
Hide resolved
}.flatMap { | ||
$0 |
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 if you use eventLoop.flatSubmit
you can drop this
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.
Where would I use the flatSubmit
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.
Instead of the submit
on L699
}.flatMap { | ||
$0 |
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.
same here re: flat submit
let switchFuture = self.completionHandler(result, context.channel) | ||
switchFuture | ||
.whenComplete { result in | ||
// We must be in the event loop here to make sure no hops have happened |
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.
Is this an API requirement? Can't we just hop back to the right event-loop?
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.
It is a tricky one. In theory, yes it is fine to hop here since the user can add the handlers to the pipeline in their closure and then hop to another future.
I am going to remove that preconditions. Users can screw up with weird construction anyhow but hopefully they don't have to wrap into NIOAsyncChannel
themselves often and can just use our configureXXX
methods.
7f2a1de
to
e8795a0
Compare
Sources/NIOCore/AsyncChannel/AsyncChannelInboundStreamChannelHandler.swift
Outdated
Show resolved
Hide resolved
) where InboundIn == ProducerElement { | ||
self.eventLoop = eventLoop | ||
self.closeRatchet = closeRatchet | ||
self.transformation = .sync { $0 } |
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.
Would it make sense to have transformation
be an optional instead of having to have this identity transformation here? Maybe the compiler sees through this and optimises it, but if not, I'm thinking having to call transformation(unwrapped)
on each channelRead
could cause some unnecessary overhead when there's no actual transformation to be 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.
I tried this initially but this doesn't work because the compiler can't infer that if the transformation
is none
that InboundIn == ProducerElement
. The compiler should be smart enough here though to see that the closure is a no-op in this case and be able to optimise it away.
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.
There's no way the compiler makes that optimization, but it's ok, the identity function is going to be pretty cheap, and this is an implementation detail we can fix.
Sources/NIOPosix/Bootstrap.swift
Outdated
/// Bind the `ServerSocketChannel` to the `unixDomainSocketPath` parameter. | ||
/// | ||
/// - Parameters: | ||
/// - unixDomainSocketPath: The _Unix domain socket_ path to bind to. `unixDomainSocketPath` must not exist, it will be created by the system. |
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.
unixDomainSocketPath
must not exist, it will be created by the system.
I don't think this applies to this version of the init, as you can set the cleanupExistingSocketFile
flag to clean up the socket if it exists already, correct?
synchronouslyWrapping channel: Channel, | ||
backpressureStrategy: NIOAsyncSequenceProducerBackPressureStrategies.HighLowWatermark? = nil, | ||
isOutboundHalfClosureEnabled: Bool = false, | ||
protocolNegotiationClosure: @escaping (Channel) -> EventLoopFuture<Inbound> |
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'm a bit nervous about having these two methods differ only on the label of a trailing closure, which is commonly omitted.
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 plan was to keep these methods spi
forever and never make them truly public since we only really use them from the bootstrap. Does that make sense to you and remove some of the nervousness?
) where InboundIn == ProducerElement { | ||
self.eventLoop = eventLoop | ||
self.closeRatchet = closeRatchet | ||
self.transformation = .sync { $0 } |
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.
There's no way the compiler makes that optimization, but it's ok, the identity function is going to be pretty cheap, and this is an implementation detail we can fix.
Sources/NIOTLS/NIOTypedApplicationProtocolNegotiationHandler.swift
Outdated
Show resolved
Hide resolved
0ccfa6f
to
09941a4
Compare
Sources/NIOPosix/Bootstrap.swift
Outdated
synchronouslyWrapping: serverChannel, | ||
backpressureStrategy: serverBackpressureStrategy, | ||
transformationClosure: { channel in | ||
try NIOAsyncChannel( |
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.
It appears this is being called on the server channel eventloop, but it expects to be called on the child channel eventloop.
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.
Thanks. Good catch!
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.
You should run your tests with an EventLoopGroup with more than one thread or at least have one test with this. Three is a good number to ensure you don't have a server and a client interleaving EventLoop usage. I was running with two threads and only caught this because I had a test which had two client requests in it which opened two child channels
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 a very good piece of advice. I also recommend adding preconditionInEventLoop
checks for any code that expects it, because they're fairly cheap and they flush out these bugs really early.
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.
Yeah the preconditions were already in place and our tests caught this once I added more than on thread to the ELG. I also just pushed some commits that refactors the bootstrapping more to make all of this properly work.
95fb851
to
53d553c
Compare
# Motivation In my previous PR, we added a new async bridge from a NIO `Channel` to Swift Concurrency primitives in the from of the `NIOAsyncChannel`. This type alone is already helpful in bridging `Channel`s to Concurrency; however, it is hard to use since it requires to wrap the `Channel` at the right time otherwise we will drop reads. Furthermore, in the case of protocol negotiation this becomes even trickier since we need to wait until it finishes and then wrap the `Channel`. # Modification This PR introduces a few things: 1. New methods on the `ServerBootstrap` which allow the creation of `NIOAsyncChannel` based channels. This can be used in all cases where no protocol negotiation is involved. 2. A new protocol and type called `NIOProtocolNegotiationHandler` and `NIOProtocolNegotiationResult` which is used to identify channel handlers that are doing protocol negotiation. 3. New methods on the `ServerBootstrap` that are aware of protocol negotiation. # Result We can now easily and safely create new `AsyncChannel`s from the `ServerBootstrap`
53d553c
to
0f91297
Compare
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've left some notes in the diff.
I also have a broader structural question I want us to think about: is searching for the handler by type the right thing to do here? This forces a lot of runtime type checking in more complex pipelines, which may not be the right thing to do.
Our other options are:
- Search by name. It's a little-known feature of NIO that handlers can be named, and can be searched for by name. This is a little cheaper, and might help us.
- Require that the handler be passed to the
bind
call. This is tricky, because most handlers aren'tSendable
. - Update the
childChannelInitializer
to require that the user returns the negotiation handler, instead ofVoid
.
Do you have thoughts here?
Sources/NIOCore/AsyncChannel/AsyncChannelInboundStreamChannelHandler.swift
Show resolved
Hide resolved
Sources/NIOCore/AsyncChannel/AsyncChannelInboundStreamChannelHandler.swift
Show resolved
Hide resolved
Tests/NIOTLSTests/NIOTypedApplicationProtocolNegotiationHandlerTests+XCTest.swift
Outdated
Show resolved
Hide resolved
Tests/NIOTLSTests/NIOTypedApplicationProtocolNegotiationHandlerTests.swift
Outdated
Show resolved
Hide resolved
Sources/NIOPosix/Bootstrap.swift
Outdated
} | ||
|
||
private typealias MakeServerChannel = (_ eventLoop: SelectableEventLoop, _ childGroup: EventLoopGroup, _ enableMPTCP: Bool) throws -> ServerSocketChannel | ||
private typealias Register = (EventLoop, ServerSocketChannel) -> EventLoopFuture<Void> |
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.
Do either of these need @Sendable
?
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.
Yes Register
needs to Sendable
since it is run on the server socket EL. However, MakeServerChannel
is run inline and not escaping.
Good question!
WDYT? |
I wonder if it's a good time for us to explore (3). However, as this is SPI we can merge this without that change and investigate it later. |
I would prefer to merge this now to unblock the HTTP2 work and will track this as an open item before we make the APIs public |
Motivation
In my previous PR, we added a new async bridge from a NIO
Channel
to Swift Concurrency primitives in the from of theNIOAsyncChannel
. This type alone is already helpful in bridgingChannel
s to Concurrency; however, it is hard to use since it requires to wrap theChannel
at the right time otherwise we will drop reads. Furthermore, in the case of protocol negotiation this becomes even trickier since we need to wait until it finishes and then wrap theChannel
.Modification
This PR introduces a few things:
ServerBootstrap
which allow the creation ofNIOAsyncChannel
based channels. This can be used in all cases where no protocol negotiation is involved.NIOProtocolNegotiationHandler
andNIOProtocolNegotiationResult
which is used to identify channel handlers that are doing protocol negotiation.ServerBootstrap
that are aware of protocol negotiation.Result
We can now easily and safely create new
AsyncChannel
s from theServerBootstrap