From dfe8c0b443cea5afc653eacbac06c5af36b8d436 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Mon, 6 May 2024 13:29:33 +0100 Subject: [PATCH 1/3] Add maxMessagePerRead setting for server and child channels --- Sources/Hummingbird/Configuration.swift | 34 +++++++++---------- Sources/HummingbirdCore/Server/Server.swift | 3 +- .../Server/ServerConfiguration.swift | 19 ++++++++--- .../HummingbirdTesting/TestApplication.swift | 6 +++- 4 files changed, 39 insertions(+), 23 deletions(-) diff --git a/Sources/Hummingbird/Configuration.swift b/Sources/Hummingbird/Configuration.swift index 9beb0551e..420b48709 100644 --- a/Sources/Hummingbird/Configuration.swift +++ b/Sources/Hummingbird/Configuration.swift @@ -31,6 +31,10 @@ public struct ApplicationConfiguration: Sendable { public var serverName: String? /// Defines the maximum length for the queue of pending connections public var backlog: Int + /// This will affect how many connections the server accepts at any one time + public let serverMaxMessagesPerRead: UInt + /// This will affect how much is read from a connection at any one time + public let childMaxMessagesPerRead: UInt /// Allows socket to be bound to an address that is already in use. public var reuseAddress: Bool #if canImport(Network) @@ -47,16 +51,23 @@ public struct ApplicationConfiguration: Sendable { /// - serverName: Server name to return in "server" header /// - backlog: the maximum length for the queue of pending connections. If a connection request arrives with the queue full, /// the client may receive an error with an indication of ECONNREFUSE + /// - serverMaxMessagesPerRead: This will affect how many connections the server accepts before waiting for notification of + /// more. Setting this too high can flood the server with too much work. + /// - childMaxMessagesPerRead: This will affect how much is read from a connection before waiting for notification of more /// - reuseAddress: Allows socket to be bound to an address that is already in use. public init( address: Address = .hostname(), serverName: String? = nil, backlog: Int = 256, + serverMaxMessagesPerRead: UInt = 8, + childMaxMessagesPerRead: UInt = 1, reuseAddress: Bool = true ) { self.address = address self.serverName = serverName self.backlog = backlog + self.serverMaxMessagesPerRead = serverMaxMessagesPerRead + self.childMaxMessagesPerRead = childMaxMessagesPerRead self.reuseAddress = reuseAddress #if canImport(Network) self.tlsOptions = .none @@ -79,35 +90,22 @@ public struct ApplicationConfiguration: Sendable { ) { self.address = address self.serverName = serverName - self.backlog = 256 // not used by Network framework self.reuseAddress = reuseAddress self.tlsOptions = tlsOptions + // The following are not used by Network framework + self.backlog = 256 + self.serverMaxMessagesPerRead = 8 + self.childMaxMessagesPerRead = 1 } #endif - /// Create new configuration struct with updated values - public func with( - address: Address? = nil, - serverName: String? = nil, - backlog: Int? = nil, - reuseAddress: Bool? = nil - ) -> Self { - return .init( - address: address ?? self.address, - serverName: serverName ?? self.serverName, - backlog: backlog ?? self.backlog, - reuseAddress: reuseAddress ?? self.reuseAddress - ) - } - /// return HTTP server configuration #if canImport(Network) var httpServer: ServerConfiguration { return .init( address: self.address, serverName: self.serverName, - backlog: self.backlog, reuseAddress: self.reuseAddress, tlsOptions: self.tlsOptions ) @@ -118,6 +116,8 @@ public struct ApplicationConfiguration: Sendable { address: self.address, serverName: self.serverName, backlog: self.backlog, + serverMaxMessagesPerRead: self.serverMaxMessagesPerRead, + childMaxMessagesPerRead: self.childMaxMessagesPerRead, reuseAddress: self.reuseAddress ) } diff --git a/Sources/HummingbirdCore/Server/Server.swift b/Sources/HummingbirdCore/Server/Server.swift index 5d9a44ec2..77899b70e 100644 --- a/Sources/HummingbirdCore/Server/Server.swift +++ b/Sources/HummingbirdCore/Server/Server.swift @@ -243,8 +243,9 @@ public actor Server: Service { // Specify backlog and enable SO_REUSEADDR for the server itself .serverChannelOption(ChannelOptions.backlog, value: numericCast(configuration.backlog)) .serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: configuration.reuseAddress ? 1 : 0) + .serverChannelOption(ChannelOptions.maxMessagesPerRead, value: configuration.serverMaxMessagesPerRead) .childChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: configuration.reuseAddress ? 1 : 0) - .childChannelOption(ChannelOptions.maxMessagesPerRead, value: 1) + .childChannelOption(ChannelOptions.maxMessagesPerRead, value: configuration.childMaxMessagesPerRead) .childChannelOption(ChannelOptions.allowRemoteHalfClosure, value: true) } diff --git a/Sources/HummingbirdCore/Server/ServerConfiguration.swift b/Sources/HummingbirdCore/Server/ServerConfiguration.swift index ae93a59fe..fa3769e85 100644 --- a/Sources/HummingbirdCore/Server/ServerConfiguration.swift +++ b/Sources/HummingbirdCore/Server/ServerConfiguration.swift @@ -22,6 +22,10 @@ public struct ServerConfiguration: Sendable { public let serverName: String? /// Defines the maximum length for the queue of pending connections public let backlog: Int + /// This will affect how many connections the server accepts at any one time + public let serverMaxMessagesPerRead: UInt + /// This will affect how much is read from a connection at any one time + public let childMaxMessagesPerRead: UInt /// Allows socket to be bound to an address that is already in use. public let reuseAddress: Bool #if canImport(Network) @@ -35,16 +39,23 @@ public struct ServerConfiguration: Sendable { /// - serverName: Server name to return in "server" header /// - backlog: the maximum length for the queue of pending connections. If a connection request arrives with the queue full, /// the client may receive an error with an indication of ECONNREFUSE + /// - serverMaxMessagesPerRead: This will affect how many connections the server accepts before waiting for notification of + /// more. Setting this too high can flood the server with too much work. + /// - childMaxMessagesPerRead: This will affect how much is read from a connection before waiting for notification of more /// - reuseAddress: Allows socket to be bound to an address that is already in use. public init( address: Address = .hostname(), serverName: String? = nil, backlog: Int = 256, + serverMaxMessagesPerRead: UInt = 8, + childMaxMessagesPerRead: UInt = 1, reuseAddress: Bool = true ) { self.address = address self.serverName = serverName self.backlog = backlog + self.serverMaxMessagesPerRead = serverMaxMessagesPerRead + self.childMaxMessagesPerRead = childMaxMessagesPerRead self.reuseAddress = reuseAddress #if canImport(Network) self.tlsOptions = .none @@ -55,23 +66,23 @@ public struct ServerConfiguration: Sendable { /// - Parameters: /// - address: Bind address for server /// - serverName: Server name to return in "server" header - /// - backlog: the maximum length for the queue of pending connections. If a connection request arrives with the queue full, - /// the client may receive an error with an indication of ECONNREFUSE /// - reuseAddress: Allows socket to be bound to an address that is already in use. /// - tlsOptions: TLS options for when you are using NIOTransportServices #if canImport(Network) public init( address: Address = .hostname(), serverName: String? = nil, - backlog: Int = 256, reuseAddress: Bool = true, tlsOptions: TSTLSOptions ) { self.address = address self.serverName = serverName - self.backlog = backlog self.reuseAddress = reuseAddress self.tlsOptions = tlsOptions + // The following are unsupported by transport services + self.backlog = 256 + self.serverMaxMessagesPerRead = 8 + self.childMaxMessagesPerRead = 1 } #endif } diff --git a/Sources/HummingbirdTesting/TestApplication.swift b/Sources/HummingbirdTesting/TestApplication.swift index 86dc4668b..f9066b005 100644 --- a/Sources/HummingbirdTesting/TestApplication.swift +++ b/Sources/HummingbirdTesting/TestApplication.swift @@ -37,7 +37,11 @@ internal struct TestApplication: ApplicationProtoc /// Event loop group used by application var eventLoopGroup: EventLoopGroup { self.base.eventLoopGroup } /// Configuration - var configuration: ApplicationConfiguration { self.base.configuration.with(address: .hostname("localhost", port: 0)) } + var configuration: ApplicationConfiguration { + var configuration = base.configuration + configuration.address = .hostname("localhost", port: 0) + return configuration + } /// Logger var logger: Logger { self.base.logger } /// On server running From 097a686d283e8e7471235e6956de87c63edd43f9 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Mon, 6 May 2024 14:51:32 +0100 Subject: [PATCH 2/3] swift format --- Sources/HummingbirdTesting/TestApplication.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/HummingbirdTesting/TestApplication.swift b/Sources/HummingbirdTesting/TestApplication.swift index f9066b005..8cb1a87ab 100644 --- a/Sources/HummingbirdTesting/TestApplication.swift +++ b/Sources/HummingbirdTesting/TestApplication.swift @@ -37,11 +37,12 @@ internal struct TestApplication: ApplicationProtoc /// Event loop group used by application var eventLoopGroup: EventLoopGroup { self.base.eventLoopGroup } /// Configuration - var configuration: ApplicationConfiguration { - var configuration = base.configuration + var configuration: ApplicationConfiguration { + var configuration = self.base.configuration configuration.address = .hostname("localhost", port: 0) return configuration } + /// Logger var logger: Logger { self.base.logger } /// On server running From fd0ccc52cdf9953860c7d67da8d5fe9f75394b47 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Wed, 8 May 2024 08:29:57 +0000 Subject: [PATCH 3/3] Use NIO defaults for maxMessagesPerRead unless overridden --- Sources/Hummingbird/Configuration.swift | 19 ++++++------ Sources/HummingbirdCore/Server/Server.swift | 11 +++++-- .../Server/ServerConfiguration.swift | 29 ++++++++++--------- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/Sources/Hummingbird/Configuration.swift b/Sources/Hummingbird/Configuration.swift index 420b48709..7aac3e85d 100644 --- a/Sources/Hummingbird/Configuration.swift +++ b/Sources/Hummingbird/Configuration.swift @@ -32,9 +32,9 @@ public struct ApplicationConfiguration: Sendable { /// Defines the maximum length for the queue of pending connections public var backlog: Int /// This will affect how many connections the server accepts at any one time - public let serverMaxMessagesPerRead: UInt + public var serverMaxMessagesPerRead: UInt? /// This will affect how much is read from a connection at any one time - public let childMaxMessagesPerRead: UInt + public var childMaxMessagesPerRead: UInt? /// Allows socket to be bound to an address that is already in use. public var reuseAddress: Bool #if canImport(Network) @@ -52,15 +52,16 @@ public struct ApplicationConfiguration: Sendable { /// - backlog: the maximum length for the queue of pending connections. If a connection request arrives with the queue full, /// the client may receive an error with an indication of ECONNREFUSE /// - serverMaxMessagesPerRead: This will affect how many connections the server accepts before waiting for notification of - /// more. Setting this too high can flood the server with too much work. - /// - childMaxMessagesPerRead: This will affect how much is read from a connection before waiting for notification of more - /// - reuseAddress: Allows socket to be bound to an address that is already in use. + /// more. Setting this too high can flood the server with too much work. DO NOT EDIT this unless you know what you are doing + /// - childMaxMessagesPerRead: This will affect how much is read from a connection before waiting for notification of more. DO + /// NOT EDIT this unless you know what you are doing + /// - reuseAddress: Allows socket to be bound to an address that is already in use. public init( address: Address = .hostname(), serverName: String? = nil, backlog: Int = 256, - serverMaxMessagesPerRead: UInt = 8, - childMaxMessagesPerRead: UInt = 1, + serverMaxMessagesPerRead: UInt? = nil, + childMaxMessagesPerRead: UInt? = nil, reuseAddress: Bool = true ) { self.address = address @@ -94,8 +95,8 @@ public struct ApplicationConfiguration: Sendable { self.tlsOptions = tlsOptions // The following are not used by Network framework self.backlog = 256 - self.serverMaxMessagesPerRead = 8 - self.childMaxMessagesPerRead = 1 + self.serverMaxMessagesPerRead = nil + self.childMaxMessagesPerRead = nil } #endif diff --git a/Sources/HummingbirdCore/Server/Server.swift b/Sources/HummingbirdCore/Server/Server.swift index 77899b70e..83228b362 100644 --- a/Sources/HummingbirdCore/Server/Server.swift +++ b/Sources/HummingbirdCore/Server/Server.swift @@ -239,14 +239,19 @@ public actor Server: Service { private nonisolated func createSocketsBootstrap( configuration: ServerConfiguration ) -> ServerBootstrap { - return ServerBootstrap(group: self.eventLoopGroup) + var bootstrap = ServerBootstrap(group: self.eventLoopGroup) // Specify backlog and enable SO_REUSEADDR for the server itself .serverChannelOption(ChannelOptions.backlog, value: numericCast(configuration.backlog)) .serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: configuration.reuseAddress ? 1 : 0) - .serverChannelOption(ChannelOptions.maxMessagesPerRead, value: configuration.serverMaxMessagesPerRead) .childChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: configuration.reuseAddress ? 1 : 0) - .childChannelOption(ChannelOptions.maxMessagesPerRead, value: configuration.childMaxMessagesPerRead) .childChannelOption(ChannelOptions.allowRemoteHalfClosure, value: true) + if let serverMaxMessagesPerRead = configuration.serverMaxMessagesPerRead { + bootstrap = bootstrap.serverChannelOption(ChannelOptions.maxMessagesPerRead, value: serverMaxMessagesPerRead) + } + if let childMaxMessagesPerRead = configuration.childMaxMessagesPerRead { + bootstrap = bootstrap.serverChannelOption(ChannelOptions.maxMessagesPerRead, value: childMaxMessagesPerRead) + } + return bootstrap } #if canImport(Network) diff --git a/Sources/HummingbirdCore/Server/ServerConfiguration.swift b/Sources/HummingbirdCore/Server/ServerConfiguration.swift index fa3769e85..da026857f 100644 --- a/Sources/HummingbirdCore/Server/ServerConfiguration.swift +++ b/Sources/HummingbirdCore/Server/ServerConfiguration.swift @@ -17,20 +17,20 @@ import NIOCore /// HTTP server configuration public struct ServerConfiguration: Sendable { /// Bind address for server - public let address: Address + public var address: Address /// Server name to return in "server" header - public let serverName: String? + public var serverName: String? /// Defines the maximum length for the queue of pending connections - public let backlog: Int + public var backlog: Int /// This will affect how many connections the server accepts at any one time - public let serverMaxMessagesPerRead: UInt + public var serverMaxMessagesPerRead: UInt? /// This will affect how much is read from a connection at any one time - public let childMaxMessagesPerRead: UInt + public var childMaxMessagesPerRead: UInt? /// Allows socket to be bound to an address that is already in use. - public let reuseAddress: Bool + public var reuseAddress: Bool #if canImport(Network) /// TLS options for NIO Transport services - public let tlsOptions: TSTLSOptions + public var tlsOptions: TSTLSOptions #endif /// Initialize server configuration @@ -40,15 +40,16 @@ public struct ServerConfiguration: Sendable { /// - backlog: the maximum length for the queue of pending connections. If a connection request arrives with the queue full, /// the client may receive an error with an indication of ECONNREFUSE /// - serverMaxMessagesPerRead: This will affect how many connections the server accepts before waiting for notification of - /// more. Setting this too high can flood the server with too much work. - /// - childMaxMessagesPerRead: This will affect how much is read from a connection before waiting for notification of more - /// - reuseAddress: Allows socket to be bound to an address that is already in use. + /// more. Setting this too high can flood the server with too much work. DO NOT EDIT this unless you know what you are doing + /// - childMaxMessagesPerRead: This will affect how much is read from a connection before waiting for notification of more. DO NOT + /// EDIT this unless you know what you are doing + /// - reuseAddress: Allows socket to be bound to an address that is already in use. public init( address: Address = .hostname(), serverName: String? = nil, backlog: Int = 256, - serverMaxMessagesPerRead: UInt = 8, - childMaxMessagesPerRead: UInt = 1, + serverMaxMessagesPerRead: UInt? = nil, + childMaxMessagesPerRead: UInt? = nil, reuseAddress: Bool = true ) { self.address = address @@ -81,8 +82,8 @@ public struct ServerConfiguration: Sendable { self.tlsOptions = tlsOptions // The following are unsupported by transport services self.backlog = 256 - self.serverMaxMessagesPerRead = 8 - self.childMaxMessagesPerRead = 1 + self.serverMaxMessagesPerRead = nil + self.childMaxMessagesPerRead = nil } #endif }