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

implement lazy selector registration #388

Merged
merged 1 commit into from
May 4, 2018

Conversation

weissi
Copy link
Member

@weissi weissi commented May 3, 2018

Motivation:

Previously we would eagerly register any fd with the selector
(epoll/kqueue) which works well with kqueue. With epoll however, you'll
get EPOLLHUP immediately if you supplied a socket that hasn't had
connect/bind called on it.
We got away with this because we took quite a bit of care to always make
sure we call connect/bind pretty much immediately after we registered
it. And crucially before we ever asked the selector to tell us about
new events.

Modifications:

made selector registration lazy (when we try activating the channel by
calling connect/bind)

Result:

the user can now register and connect whenever they feel like it, fixes #380

Copy link
Contributor

@Lukasa Lukasa left a comment

Choose a reason for hiding this comment

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

Cool, mostly looks really good! Some notes!

@@ -18,14 +18,16 @@ private struct SocketChannelLifecycleManager {
// MARK: Types
private enum State {
case fresh
case registered
case lazilyRegistered // register() has been run but the selector doesn't know about it yet
case fullyRegistered // fully registered, ie. the selector knows about it
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: I don't love these names. Can we call the new state preRegistered, maybe?

case activated
case closed
}

private enum Event {
case activate
case register
case registerLazily
case registerFully
Copy link
Contributor

Choose a reason for hiding this comment

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

Similarly, these names aren't necessarily right. Maybe beginRegistration and completeRegistration?

if self.lifecycleManager.isRegisteredLazily {
try! becomeFullyRegistered0()
if self.lifecycleManager.isRegisteredFully {
becomeActive0(promise: promise)
Copy link
Contributor

Choose a reason for hiding this comment

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

Do these callouts not get triggered by register0?

Copy link
Member Author

Choose a reason for hiding this comment

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

@Lukasa right now register0 only does the pre-registration. I didn't change its name as it's on ChannelCore. But we could obviously rename it to preRegister0

guard self.selectableEventLoop.isOpen else {
let error = EventLoopError.shutdown
self.pipeline.fireErrorCaught0(error: error)
self.close0(error: error, mode: .all, promise: nil)
promise?.fail(error: error)
Copy link
Member

Choose a reason for hiding this comment

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

I think we should just pass the promise into self.close0(...) which will ensure correct ordering as well.

So basically:

self.close0(error: error, mode: .all, promise: promise)

@@ -337,6 +337,11 @@ internal final class SelectableEventLoop: EventLoop {
_addresses.deallocate()
}

/// Is this `SelectableEventLoop` still open (ie. not shutting down or shut down)
public var isOpen: Bool {
Copy link
Member

Choose a reason for hiding this comment

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

@weissi this is not safe as lifecycleState is not thread-safe. Maybe change to internal and assert that the caller is the EventLoop thread ?

Copy link
Member Author

Choose a reason for hiding this comment

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

@normanmaurer it's fine as SelectableEventLoop is an internal type (not public) but regardless I changed this to internal and added an assertion

XCTAssertNoThrow(try server.register().wait())
XCTAssertNoThrow(try server.eventLoop.submit {
XCTAssertFalse(server.isActive)
}.wait())
Copy link
Member

Choose a reason for hiding this comment

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

remove 4 spaces.

@@ -2317,6 +2317,43 @@ public class ChannelTests: XCTestCase {

}

func testLazyRegistrationWorksForServerSockets() throws {
let group = MultiThreadedEventLoopGroup(numThreads: 1)
Copy link
Member

Choose a reason for hiding this comment

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

add:

defer {
    XCTAssertNoThrow(try group.syncShutdownGracefully())
}

Copy link
Member Author

Choose a reason for hiding this comment

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

done

}

func testLazyRegistrationWorksForClientSockets() throws {
let group = MultiThreadedEventLoopGroup(numThreads: 1)
Copy link
Member

Choose a reason for hiding this comment

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

add:

defer {
    XCTAssertNoThrow(try group.syncShutdownGracefully())
}

Copy link
Member Author

Choose a reason for hiding this comment

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

done

Motivation:

Previously we would eagerly register any fd with the selector
(epoll/kqueue) which works well with kqueue. With epoll however, you'll
get `EPOLLHUP` immediately if you supplied a socket that hasn't had
connect/bind called on it.
We got away with this because we took quite a bit of care to always make
sure we call connect/bind pretty much immediately after we registered
it.  And crucially before we ever asked the selector to tell us about
new events.

Modifications:

made selector registration lazy (when we try activating the channel by
calling connect/bind)

Result:

the user can now register and connect whenever they feel like it.
Copy link
Contributor

@Lukasa Lukasa left a comment

Choose a reason for hiding this comment

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

This LGTM, great job @weissi!

@Lukasa Lukasa added the semver/patch No public API change. label May 4, 2018
@Lukasa Lukasa added this to the 1.7.0 milestone May 4, 2018
@normanmaurer normanmaurer merged commit 2f55baf into apple:master May 4, 2018
@normanmaurer
Copy link
Member

Thanks @weissi ... merged :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver/patch No public API change.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

implement selector late registration
3 participants