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

Add api to access eventloopGroup in Kitura-NIO #225

Merged
merged 12 commits into from
Oct 23, 2019
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ let package = Package(
targets: ["KituraNet"])
],
dependencies: [
// FIXME: remove version constraint once IBM-Swift/Kitura-NIO#225 is merged
.package(url: "https://github.com/apple/swift-nio.git", "2.3.0"..."2.8.0"),
// Dependencies declare other packages that this package depends on.
.package(url: "https://github.com/apple/swift-nio.git", from: "2.8.0"),
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.0.0"),
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.0.0"),
.package(url: "https://github.com/IBM-Swift/BlueSSLService.git", from: "1.0.0"),
Expand Down
80 changes: 63 additions & 17 deletions Sources/KituraNet/HTTP/HTTPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ An HTTP server that listens for connections on a socket.
server.stop()
````
*/

#if os(Linux)
let numberOfCores = Int(linux_sched_getaffinity())
fileprivate let globalELG = MultiThreadedEventLoopGroup(numberOfThreads: numberOfCores > 0 ? numberOfCores : System.coreCount)
#else
fileprivate let globalELG = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
#endif

public class HTTPServer: Server {

public typealias ServerType = HTTPServer
Expand Down Expand Up @@ -88,7 +96,6 @@ public class HTTPServer: Server {
return self._state
}
}

set {
self.syncQ.sync {
self._state = newValue
Expand Down Expand Up @@ -124,8 +131,28 @@ public class HTTPServer: Server {
/// Maximum number of pending connections
private let maxPendingConnections = 100

/// The event loop group on which the HTTP handler runs
private let eventLoopGroup: MultiThreadedEventLoopGroup
// A lazily initialized EventLoopGroup, accessed via `eventLoopGroup`
private var _eventLoopGroup: EventLoopGroup?

/// The EventLoopGroup used by this HTTPServer. This property may be assigned
/// once and once only, by calling `setEventLoopGroup(value:)` before `listen()` is called.
/// Server runs on `eventLoopGroup` which it is initialized to i.e. when user explicitly provides `eventLoopGroup` for server,
/// public variable `eventLoopGroup` will return value stored private variable `_eventLoopGroup` when `ServerBootstrap` is called in `listen()`
/// making the server run of userdefined EventLoopGroup. If the `setEventLoopGroup(value:)` is not called, `nil` in variable `_eventLoopGroup` forces
/// Server to run in `globalELG` since value of `eventLoopGroup` in `ServerBootstrap(group: eventLoopGroup)` gets initialzed to value `globalELG`
/// if `setEventLoopGroup(value:)` is not called before `listen()`
/// If you are using Kitura-NIO and need to access EventLoopGroup that Kitura uses, you can do so like this:
///
/// ```swift
/// let eventLoopGroup = server.eventLoopGroup
/// ```
///
public var eventLoopGroup: EventLoopGroup {
if let value = self._eventLoopGroup { return value }
let value = globalELG
self._eventLoopGroup = value
return value
}

var quiescingHelper: ServerQuiescingHelper?

Expand All @@ -146,12 +173,6 @@ public class HTTPServer: Server {
````
*/
public init(options: ServerOptions = ServerOptions()) {
#if os(Linux)
let numberOfCores = Int(linux_sched_getaffinity())
self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: numberOfCores > 0 ? numberOfCores : System.coreCount)
#else
self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
#endif
self.options = options
}

Expand Down Expand Up @@ -288,6 +309,23 @@ public class HTTPServer: Server {
try listen(.tcp(port, address))
}

/// Sets the EventLoopGroup to be used by this HTTPServer. This may be called once
/// and once only, and must be called prior to `listen()`.
/// - Throws: If the EventLoopGroup has already been assigned.
/// If you are using Kitura-NIO and need to set EventLoopGroup that Kitura uses, you can do so like this:
///
/// ```swift
/// server.setEventLoopGroup(EventLoopGroup)
/// ```
///
/// - Parameter : this function is supplied with user defined EventLoopGroup as arguement
public func setEventLoopGroup(_ value: EventLoopGroup) throws {
guard _eventLoopGroup == nil else {
throw HTTPServerError.eventLoopGroupAlreadyInitialized
}
_eventLoopGroup = value
}

private func listen(_ socket: SocketType) throws {

if let tlsConfig = tlsConfig {
Expand Down Expand Up @@ -464,14 +502,6 @@ public class HTTPServer: Server {
return server
}

deinit {
do {
try eventLoopGroup.syncShutdownGracefully()
} catch {
Log.error("Failed to shutdown eventLoopGroup")
}
}

/**
Stop listening for new connections.

Expand Down Expand Up @@ -684,3 +714,19 @@ enum KituraWebSocketUpgradeError: Error {
// Unknown upgrade error
case unknownUpgradeError
}

/// Errors thrown by HTTPServer
public struct HTTPServerError: Error, Equatable {

internal enum HTTPServerErrorType: Error {
case eventLoopGroupAlreadyInitialized
}

private var _httpServerError: HTTPServerErrorType

private init(value: HTTPServerErrorType){
self._httpServerError = value
}

public static var eventLoopGroupAlreadyInitialized = HTTPServerError(value: .eventLoopGroupAlreadyInitialized)
}
87 changes: 85 additions & 2 deletions Tests/KituraNetTests/RegressionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import NIO
import NIOHTTP1
import NIOSSL
import LoggerAPI
import CLinuxHelpers

class RegressionTests: KituraNetTest {

Expand All @@ -31,7 +32,9 @@ class RegressionTests: KituraNetTest {
("testServersCollidingOnPort", testServersCollidingOnPort),
("testServersSharingPort", testServersSharingPort),
("testBadRequest", testBadRequest),
("testBadRequestFollowingGoodRequest", testBadRequestFollowingGoodRequest)
("testBadRequestFollowingGoodRequest", testBadRequestFollowingGoodRequest),
("testCustomEventLoopGroup", testCustomEventLoopGroup),
("testFailEventLoopGroupReinitialization", testFailEventLoopGroupReinitialization),
]
}

Expand Down Expand Up @@ -172,7 +175,6 @@ class RegressionTests: KituraNetTest {
defer {
server.stop()
}

guard let serverPort = server.port else {
XCTFail("Server port was not initialized")
return
Expand All @@ -190,6 +192,87 @@ class RegressionTests: KituraNetTest {
}
}

func testCustomEventLoopGroup() {
do {
#if os(Linux)
let numberOfCores = Int(linux_sched_getaffinity())
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: numberOfCores > 0 ? numberOfCores : System.coreCount)
#else
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
#endif
let server = HTTPServer()
do {
try server.setEventLoopGroup(eventLoopGroup)
} catch {
XCTFail("Unable to initialize EventLoopGroup: \(error)")
}
let serverPort: Int = 8091
defer {
server.stop()
}
do {
try server.listen(on: serverPort)
} catch {
XCTFail("Unable to start the server \(error)")
}
var goodClient = try GoodClient()
// Connect a 'good' (SSL enabled) client to the server
try goodClient.connect(serverPort, expectation: self.expectation(description: "Connecting a bad client"))
XCTAssertEqual(goodClient.connectedPort, serverPort, "GoodClient not connected to expected server port")

// Start a server using eventLoopGroup api provided by HTPPServer()
let server2 = HTTPServer()
do {
try server2.setEventLoopGroup(server.eventLoopGroup)
} catch {
XCTFail("Unable to initialize EventLoopGroup: \(error)")
}

let serverPort2: Int = 8092
defer {
server2.stop()
}
do {
try server2.listen(on: serverPort2)
} catch {
XCTFail("Unable to start the server \(error)")
}
var goodClient2 = try GoodClient()
// Connect a 'good' (SSL enabled) client to the server
try goodClient2.connect(serverPort2, expectation: self.expectation(description: "Connecting a bad client"))
XCTAssertEqual(goodClient2.connectedPort, serverPort2, "GoodClient not connected to expected server port")
} catch {
XCTFail("Error: \(error)")
}
waitForExpectations(timeout: 10)
}

// Tests eventLoopGroup initialization in server after starting the server
// If server `setEventLoopGroup` is called after function `listen()` server should throw
// error HTTPServerError.eventLoopGroupAlreadyInitialized
func testFailEventLoopGroupReinitialization() {
harish1992 marked this conversation as resolved.
Show resolved Hide resolved
do {
#if os(Linux)
let numberOfCores = Int(linux_sched_getaffinity())
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: numberOfCores > 0 ? numberOfCores : System.coreCount)
#else
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
#endif
let server = HTTPServer()
do {
try server.listen(on: 8093)
} catch {
XCTFail("Unable to start the server \(error)")
}
do {
try server.setEventLoopGroup(eventLoopGroup)
} catch {
let httpError = error as? HTTPServerError
XCTAssertEqual(httpError, HTTPServerError.eventLoopGroupAlreadyInitialized)
}
}
}

/// A simple client which connects to a port but sends no data
struct BadClient {
let clientBootstrap: ClientBootstrap
Expand Down