Skip to content

Commit

Permalink
bootstraps: offer ELG validation (#1464)
Browse files Browse the repository at this point in the history
Motivation:

Today, we just expect the ELGs passed to the bootstraps to be the
correct ones, if not, we crash.

Modifications:

Offer an alternative `validatingGroup:` `init` that just returns `nil`
if the ELGs are of the wrong types.

Result:

Easier to work with multi-stack systems for example when the user might
pass an ELG for either NIO on Sockets or NIO on Network.framework.
  • Loading branch information
weissi authored Apr 1, 2020
1 parent b2d937b commit e876fb3
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 10 deletions.
121 changes: 112 additions & 9 deletions Sources/NIO/Bootstrap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
// Copyright (c) 2017-2020 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
Expand All @@ -15,6 +15,13 @@
/// The type of all `channelInitializer` callbacks.
internal typealias ChannelInitializerCallback = (Channel) -> EventLoopFuture<Void>

/// Common functionality for all NIO on sockets bootstraps.
internal enum NIOOnSocketsBootstraps {
internal static func isCompatible(group: EventLoopGroup) -> Bool {
return group is SelectableEventLoop || group is MultiThreadedEventLoopGroup
}
}

/// A `ServerBootstrap` is an easy way to bootstrap a `ServerSocketChannel` when creating network servers.
///
/// Example:
Expand Down Expand Up @@ -66,20 +73,55 @@ public final class ServerBootstrap {
@usableFromInline
internal var _childChannelOptions: ChannelOptions.Storage

/// Create a `ServerBootstrap` for the `EventLoopGroup` `group`.
/// Create a `ServerBootstrap` on the `EventLoopGroup` `group`.
///
/// The `EventLoopGroup` `group` must be compatible, otherwise the program will crash. `ServerBootstrap` is
/// compatible only with `MultiThreadedEventLoopGroup` as well as the `EventLoop`s returned by
/// `MultiThreadedEventLoopGroup.next`. See `init(validatingGroup:childGroup:)` for a fallible initializer for
/// situations where it's impossible to tell ahead of time if the `EventLoopGroup`s are compatible or not.
///
/// - parameters:
/// - group: The `EventLoopGroup` to use for the `ServerSocketChannel`.
/// - group: The `EventLoopGroup` to use for the `bind` of the `ServerSocketChannel` and to accept new `SocketChannel`s with.
public convenience init(group: EventLoopGroup) {
self.init(group: group, childGroup: group)
guard NIOOnSocketsBootstraps.isCompatible(group: group) else {
preconditionFailure("ServerBootstrap is only compatible with MultiThreadedEventLoopGroup and " +
"SelectableEventLoop. You tried constructing one with \(group) which is incompatible.")
}
self.init(validatingGroup: group, childGroup: group)!
}

/// Create a `ServerBootstrap`.
/// Create a `ServerBootstrap` on the `EventLoopGroup` `group` which accepts `Channel`s on `childGroup`.
///
/// The `EventLoopGroup`s `group` and `childGroup` must be compatible, otherwise the program will crash.
/// `ServerBootstrap` is compatible only with `MultiThreadedEventLoopGroup` as well as the `EventLoop`s returned by
/// `MultiThreadedEventLoopGroup.next`. See `init(validatingGroup:childGroup:)` for a fallible initializer for
/// situations where it's impossible to tell ahead of time if the `EventLoopGroup`s are compatible or not.
///
/// - parameters:
/// - group: The `EventLoopGroup` to use for the `bind` of the `ServerSocketChannel` and to accept new `SocketChannel`s with.
/// - childGroup: The `EventLoopGroup` to run the accepted `SocketChannel`s on.
public init(group: EventLoopGroup, childGroup: EventLoopGroup) {
public convenience init(group: EventLoopGroup, childGroup: EventLoopGroup) {
guard NIOOnSocketsBootstraps.isCompatible(group: group) && NIOOnSocketsBootstraps.isCompatible(group: childGroup) else {
preconditionFailure("ServerBootstrap is only compatible with MultiThreadedEventLoopGroup and " +
"SelectableEventLoop. You tried constructing one with group: \(group) and " +
"childGroup: \(childGroup) at least one of which is incompatible.")
}
self.init(validatingGroup: group, childGroup: childGroup)!

}

/// Create a `ServerBootstrap` on the `EventLoopGroup` `group` which accepts `Channel`s on `childGroup`, validating
/// that the `EventLoopGroup`s are compatible with `ServerBootstrap`.
///
/// - parameters:
/// - group: The `EventLoopGroup` to use for the `bind` of the `ServerSocketChannel` and to accept new `SocketChannel`s with.
/// - childGroup: The `EventLoopGroup` to run the accepted `SocketChannel`s on. If `nil`, `group` is used.
public init?(validatingGroup group: EventLoopGroup, childGroup: EventLoopGroup? = nil) {
let childGroup = childGroup ?? group
guard NIOOnSocketsBootstraps.isCompatible(group: group) && NIOOnSocketsBootstraps.isCompatible(group: childGroup) else {
return nil
}

self.group = group
self.childGroup = childGroup
self._serverChannelOptions = ChannelOptions.Storage()
Expand Down Expand Up @@ -386,9 +428,29 @@ public final class ClientBootstrap: NIOClientTCPBootstrapProtocol {

/// Create a `ClientBootstrap` on the `EventLoopGroup` `group`.
///
/// The `EventLoopGroup` `group` must be compatible, otherwise the program will crash. `ClientBootstrap` is
/// compatible only with `MultiThreadedEventLoopGroup` as well as the `EventLoop`s returned by
/// `MultiThreadedEventLoopGroup.next`. See `init(validatingGroup:)` for a fallible initializer for
/// situations where it's impossible to tell ahead of time if the `EventLoopGroup` is compatible or not.
///
/// - parameters:
/// - group: The `EventLoopGroup` to use.
public convenience init(group: EventLoopGroup) {
guard NIOOnSocketsBootstraps.isCompatible(group: group) else {
preconditionFailure("ClientBootstrap is only compatible with MultiThreadedEventLoopGroup and " +
"SelectableEventLoop. You tried constructing one with \(group) which is incompatible.")
}
self.init(validatingGroup: group)!
}

/// Create a `ClientBootstrap` on the `EventLoopGroup` `group`, validating that `group` is compatible.
///
/// - parameters:
/// - group: The `EventLoopGroup` to use.
public init(group: EventLoopGroup) {
public init?(validatingGroup group: EventLoopGroup) {
guard NIOOnSocketsBootstraps.isCompatible(group: group) else {
return nil
}
self.group = group
self._channelOptions = ChannelOptions.Storage()
self._channelOptions.append(key: ChannelOptions.socket(SocketOptionLevel(Posix.IPPROTO_TCP), TCP_NODELAY), value: 1)
Expand Down Expand Up @@ -621,9 +683,29 @@ public final class DatagramBootstrap {

/// Create a `DatagramBootstrap` on the `EventLoopGroup` `group`.
///
/// The `EventLoopGroup` `group` must be compatible, otherwise the program will crash. `DatagramBootstrap` is
/// compatible only with `MultiThreadedEventLoopGroup` as well as the `EventLoop`s returned by
/// `MultiThreadedEventLoopGroup.next`. See `init(validatingGroup:)` for a fallible initializer for
/// situations where it's impossible to tell ahead of time if the `EventLoopGroup` is compatible or not.
///
/// - parameters:
/// - group: The `EventLoopGroup` to use.
public convenience init(group: EventLoopGroup) {
guard NIOOnSocketsBootstraps.isCompatible(group: group) else {
preconditionFailure("DatagramBootstrap is only compatible with MultiThreadedEventLoopGroup and " +
"SelectableEventLoop. You tried constructing one with \(group) which is incompatible.")
}
self.init(validatingGroup: group)!
}

/// Create a `DatagramBootstrap` on the `EventLoopGroup` `group`, validating that `group` is compatible.
///
/// - parameters:
/// - group: The `EventLoopGroup` to use.
public init(group: EventLoopGroup) {
public init?(validatingGroup group: EventLoopGroup) {
guard NIOOnSocketsBootstraps.isCompatible(group: group) else {
return nil
}
self._channelOptions = ChannelOptions.Storage()
self.group = group
self.channelInitializer = nil
Expand Down Expand Up @@ -767,9 +849,30 @@ public final class NIOPipeBootstrap {

/// Create a `NIOPipeBootstrap` on the `EventLoopGroup` `group`.
///
/// The `EventLoopGroup` `group` must be compatible, otherwise the program will crash. `NIOPipeBootstrap` is
/// compatible only with `MultiThreadedEventLoopGroup` as well as the `EventLoop`s returned by
/// `MultiThreadedEventLoopGroup.next`. See `init(validatingGroup:)` for a fallible initializer for
/// situations where it's impossible to tell ahead of time if the `EventLoopGroup`s are compatible or not.
///
/// - parameters:
/// - group: The `EventLoopGroup` to use.
public init(group: EventLoopGroup) {
public convenience init(group: EventLoopGroup) {
guard NIOOnSocketsBootstraps.isCompatible(group: group) else {
preconditionFailure("NIOPipeBootstrap is only compatible with MultiThreadedEventLoopGroup and " +
"SelectableEventLoop. You tried constructing one with \(group) which is incompatible.")
}
self.init(validatingGroup: group)!
}

/// Create a `NIOPipeBootstrap` on the `EventLoopGroup` `group`, validating that `group` is compatible.
///
/// - parameters:
/// - group: The `EventLoopGroup` to use.
public init?(validatingGroup group: EventLoopGroup) {
guard NIOOnSocketsBootstraps.isCompatible(group: group) else {
return nil
}

self._channelOptions = ChannelOptions.Storage()
self.group = group
self.channelInitializer = nil
Expand Down
8 changes: 8 additions & 0 deletions Tests/NIOTests/BootstrapTest+XCTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ extension BootstrapTest {
("testDatagramBootstrapSetsChannelOptionsBeforeChannelInitializer", testDatagramBootstrapSetsChannelOptionsBeforeChannelInitializer),
("testPipeBootstrapSetsChannelOptionsBeforeChannelInitializer", testPipeBootstrapSetsChannelOptionsBeforeChannelInitializer),
("testServerBootstrapAddsAcceptHandlerAfterServerChannelInitialiser", testServerBootstrapAddsAcceptHandlerAfterServerChannelInitialiser),
("testClientBootstrapValidatesWorkingELGsCorrectly", testClientBootstrapValidatesWorkingELGsCorrectly),
("testClientBootstrapRejectsNotWorkingELGsCorrectly", testClientBootstrapRejectsNotWorkingELGsCorrectly),
("testServerBootstrapValidatesWorkingELGsCorrectly", testServerBootstrapValidatesWorkingELGsCorrectly),
("testServerBootstrapRejectsNotWorkingELGsCorrectly", testServerBootstrapRejectsNotWorkingELGsCorrectly),
("testDatagramBootstrapValidatesWorkingELGsCorrectly", testDatagramBootstrapValidatesWorkingELGsCorrectly),
("testDatagramBootstrapRejectsNotWorkingELGsCorrectly", testDatagramBootstrapRejectsNotWorkingELGsCorrectly),
("testNIOPipeBootstrapValidatesWorkingELGsCorrectly", testNIOPipeBootstrapValidatesWorkingELGsCorrectly),
("testNIOPipeBootstrapRejectsNotWorkingELGsCorrectly", testNIOPipeBootstrapRejectsNotWorkingELGsCorrectly),
]
}
}
Expand Down
109 changes: 108 additions & 1 deletion Tests/NIOTests/BootstrapTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
// Copyright (c) 2017-2020 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
Expand Down Expand Up @@ -422,6 +422,113 @@ class BootstrapTest: XCTestCase {
XCTAssertNoThrow(_ = try server.pipeline.context(name: "AcceptHandler").wait())
XCTAssertNoThrow(try server.close().wait())
}

func testClientBootstrapValidatesWorkingELGsCorrectly() {
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
XCTAssertNoThrow(try elg.syncShutdownGracefully())
}
let el = elg.next()

XCTAssertNotNil(ClientBootstrap(validatingGroup: elg))
XCTAssertNotNil(ClientBootstrap(validatingGroup: el))
}

func testClientBootstrapRejectsNotWorkingELGsCorrectly() {
let elg = EmbeddedEventLoop()
defer {
XCTAssertNoThrow(try elg.syncShutdownGracefully())
}
let el = elg.next()

XCTAssertNil(ClientBootstrap(validatingGroup: elg))
XCTAssertNil(ClientBootstrap(validatingGroup: el))
}

func testServerBootstrapValidatesWorkingELGsCorrectly() {
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
XCTAssertNoThrow(try elg.syncShutdownGracefully())
}
let el = elg.next()

XCTAssertNotNil(ServerBootstrap(validatingGroup: elg))
XCTAssertNotNil(ServerBootstrap(validatingGroup: el))
XCTAssertNotNil(ServerBootstrap(validatingGroup: elg, childGroup: elg))
XCTAssertNotNil(ServerBootstrap(validatingGroup: el, childGroup: el))
}

func testServerBootstrapRejectsNotWorkingELGsCorrectly() {
let correctELG = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
XCTAssertNoThrow(try correctELG.syncShutdownGracefully())
}

let wrongELG = EmbeddedEventLoop()
defer {
XCTAssertNoThrow(try wrongELG.syncShutdownGracefully())
}
let wrongEL = wrongELG.next()
let correctEL = correctELG.next()

// both wrong
XCTAssertNil(ServerBootstrap(validatingGroup: wrongELG))
XCTAssertNil(ServerBootstrap(validatingGroup: wrongEL))
XCTAssertNil(ServerBootstrap(validatingGroup: wrongELG, childGroup: wrongELG))
XCTAssertNil(ServerBootstrap(validatingGroup: wrongEL, childGroup: wrongEL))

// group correct, child group wrong
XCTAssertNil(ServerBootstrap(validatingGroup: correctELG, childGroup: wrongELG))
XCTAssertNil(ServerBootstrap(validatingGroup: correctEL, childGroup: wrongEL))

// group wrong, child group correct
XCTAssertNil(ServerBootstrap(validatingGroup: wrongELG, childGroup: correctELG))
XCTAssertNil(ServerBootstrap(validatingGroup: wrongEL, childGroup: correctEL))
}

func testDatagramBootstrapValidatesWorkingELGsCorrectly() {
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
XCTAssertNoThrow(try elg.syncShutdownGracefully())
}
let el = elg.next()

XCTAssertNotNil(DatagramBootstrap(validatingGroup: elg))
XCTAssertNotNil(DatagramBootstrap(validatingGroup: el))
}

func testDatagramBootstrapRejectsNotWorkingELGsCorrectly() {
let elg = EmbeddedEventLoop()
defer {
XCTAssertNoThrow(try elg.syncShutdownGracefully())
}
let el = elg.next()

XCTAssertNil(DatagramBootstrap(validatingGroup: elg))
XCTAssertNil(DatagramBootstrap(validatingGroup: el))
}

func testNIOPipeBootstrapValidatesWorkingELGsCorrectly() {
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
XCTAssertNoThrow(try elg.syncShutdownGracefully())
}
let el = elg.next()

XCTAssertNotNil(NIOPipeBootstrap(validatingGroup: elg))
XCTAssertNotNil(NIOPipeBootstrap(validatingGroup: el))
}

func testNIOPipeBootstrapRejectsNotWorkingELGsCorrectly() {
let elg = EmbeddedEventLoop()
defer {
XCTAssertNoThrow(try elg.syncShutdownGracefully())
}
let el = elg.next()

XCTAssertNil(NIOPipeBootstrap(validatingGroup: elg))
XCTAssertNil(NIOPipeBootstrap(validatingGroup: el))
}
}

private final class MakeSureAutoReadIsOffInChannelInitializer: ChannelInboundHandler {
Expand Down

0 comments on commit e876fb3

Please sign in to comment.