Skip to content

Commit 82af47f

Browse files
committed
thread configuration (names & QoS on Darwin)
1 parent be823e6 commit 82af47f

9 files changed

+330
-63
lines changed

Sources/NIOPosix/MultiThreadedEventLoopGroup.swift

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public final class MultiThreadedEventLoopGroup: EventLoopGroup {
7070
private let index = ManagedAtomic<Int>(0)
7171
private var eventLoops: [SelectableEventLoop]
7272
private let shutdownLock: NIOLock = NIOLock()
73-
private let threadNamePrefix: String
73+
private let threadNamePrefix: Optional<String>
7474
private var runState: RunState = .running
7575
private let canBeShutDown: Bool
7676

@@ -108,7 +108,8 @@ public final class MultiThreadedEventLoopGroup: EventLoopGroup {
108108
}
109109

110110
private static func setupThreadAndEventLoop(
111-
name: String,
111+
name: String?,
112+
threadConfiguration: NIOThreadConfiguration,
112113
parentGroup: MultiThreadedEventLoopGroup,
113114
selectorFactory: @escaping () throws -> NIOPosix.Selector<NIORegistration>,
114115
initializer: @escaping ThreadInitializer,
@@ -119,7 +120,7 @@ public final class MultiThreadedEventLoopGroup: EventLoopGroup {
119120
// synchronised by `lock`
120121
var _loop: SelectableEventLoop! = nil
121122

122-
NIOThread.spawnAndRun(name: name, detachThread: false) { t in
123+
NIOThread.spawnAndRun(name: name, configuration: threadConfiguration, detachThread: false) { t in
123124
MultiThreadedEventLoopGroup.runTheLoop(
124125
thread: t,
125126
parentGroup: parentGroup,
@@ -150,6 +151,7 @@ public final class MultiThreadedEventLoopGroup: EventLoopGroup {
150151
public convenience init(numberOfThreads: Int) {
151152
self.init(
152153
numberOfThreads: numberOfThreads,
154+
threadConfiguration: .defaultForEventLoopGroups,
153155
canBeShutDown: true,
154156
metricsDelegate: nil,
155157
selectorFactory: NIOPosix.Selector<NIORegistration>.init
@@ -169,6 +171,32 @@ public final class MultiThreadedEventLoopGroup: EventLoopGroup {
169171
public convenience init(numberOfThreads: Int, metricsDelegate: NIOEventLoopMetricsDelegate) {
170172
self.init(
171173
numberOfThreads: numberOfThreads,
174+
threadConfiguration: .defaultForEventLoopGroups,
175+
canBeShutDown: true,
176+
metricsDelegate: metricsDelegate,
177+
selectorFactory: NIOPosix.Selector<NIORegistration>.init
178+
)
179+
}
180+
181+
/// Creates a `MultiThreadedEventLoopGroup` instance which uses `numberOfThreads`.
182+
///
183+
/// - note: Don't forget to call `shutdownGracefully` or `syncShutdownGracefully` when you no longer need this
184+
/// `EventLoopGroup`. If you forget to shut the `EventLoopGroup` down you will leak `numberOfThreads`
185+
/// (kernel) threads which are costly resources. This is especially important in unit tests where one
186+
/// `MultiThreadedEventLoopGroup` is started per test case.
187+
///
188+
/// - Parameters:
189+
/// - numberOfThreads: The number of `Threads` to use.
190+
/// - threadConfiguration: Configuration for the threads to spawn.
191+
/// - metricsDelegate: Delegate for collecting information from this eventloop
192+
public convenience init(
193+
numberOfThreads: Int,
194+
threadConfiguration: NIOThreadConfiguration,
195+
metricsDelegate: NIOEventLoopMetricsDelegate? = nil
196+
) {
197+
self.init(
198+
numberOfThreads: numberOfThreads,
199+
threadConfiguration: threadConfiguration,
172200
canBeShutDown: true,
173201
metricsDelegate: metricsDelegate,
174202
selectorFactory: NIOPosix.Selector<NIORegistration>.init
@@ -179,20 +207,21 @@ public final class MultiThreadedEventLoopGroup: EventLoopGroup {
179207
///
180208
/// This is only useful for global singletons.
181209
public static func _makePerpetualGroup(
182-
threadNamePrefix: String,
183-
numberOfThreads: Int
210+
numberOfThreads: Int,
211+
threadConfiguration: NIOThreadConfiguration
184212
) -> MultiThreadedEventLoopGroup {
185213
self.init(
186214
numberOfThreads: numberOfThreads,
215+
threadConfiguration: threadConfiguration,
187216
canBeShutDown: false,
188-
threadNamePrefix: threadNamePrefix,
189217
metricsDelegate: nil,
190218
selectorFactory: NIOPosix.Selector<NIORegistration>.init
191219
)
192220
}
193221

194222
internal convenience init(
195223
numberOfThreads: Int,
224+
threadConfiguration: NIOThreadConfiguration,
196225
metricsDelegate: NIOEventLoopMetricsDelegate?,
197226
selectorFactory: @escaping () throws -> NIOPosix.Selector<NIORegistration>
198227
) {
@@ -201,31 +230,15 @@ public final class MultiThreadedEventLoopGroup: EventLoopGroup {
201230
self.init(
202231
threadInitializers: initializers,
203232
canBeShutDown: true,
233+
threadConfiguration: threadConfiguration,
204234
metricsDelegate: metricsDelegate,
205235
selectorFactory: selectorFactory
206236
)
207237
}
208238

209239
internal convenience init(
210240
numberOfThreads: Int,
211-
canBeShutDown: Bool,
212-
threadNamePrefix: String,
213-
metricsDelegate: NIOEventLoopMetricsDelegate?,
214-
selectorFactory: @escaping () throws -> NIOPosix.Selector<NIORegistration>
215-
) {
216-
precondition(numberOfThreads > 0, "numberOfThreads must be positive")
217-
let initializers: [ThreadInitializer] = Array(repeating: { _ in }, count: numberOfThreads)
218-
self.init(
219-
threadInitializers: initializers,
220-
canBeShutDown: canBeShutDown,
221-
threadNamePrefix: threadNamePrefix,
222-
metricsDelegate: metricsDelegate,
223-
selectorFactory: selectorFactory
224-
)
225-
}
226-
227-
internal convenience init(
228-
numberOfThreads: Int,
241+
threadConfiguration: NIOThreadConfiguration,
229242
canBeShutDown: Bool,
230243
metricsDelegate: NIOEventLoopMetricsDelegate?,
231244
selectorFactory: @escaping () throws -> NIOPosix.Selector<NIORegistration>
@@ -235,20 +248,23 @@ public final class MultiThreadedEventLoopGroup: EventLoopGroup {
235248
self.init(
236249
threadInitializers: initializers,
237250
canBeShutDown: canBeShutDown,
251+
threadConfiguration: threadConfiguration,
238252
metricsDelegate: metricsDelegate,
239253
selectorFactory: selectorFactory
240254
)
241255
}
242256

243257
internal convenience init(
244258
threadInitializers: [ThreadInitializer],
259+
threadConfiguration: NIOThreadConfiguration,
245260
metricsDelegate: NIOEventLoopMetricsDelegate?,
246261
selectorFactory: @escaping () throws -> NIOPosix.Selector<NIORegistration> = NIOPosix.Selector<NIORegistration>
247262
.init
248263
) {
249264
self.init(
250265
threadInitializers: threadInitializers,
251266
canBeShutDown: true,
267+
threadConfiguration: threadConfiguration,
252268
metricsDelegate: metricsDelegate,
253269
selectorFactory: selectorFactory
254270
)
@@ -261,12 +277,12 @@ public final class MultiThreadedEventLoopGroup: EventLoopGroup {
261277
internal init(
262278
threadInitializers: [ThreadInitializer],
263279
canBeShutDown: Bool,
264-
threadNamePrefix: String = "NIO-ELT-",
280+
threadConfiguration: NIOThreadConfiguration,
265281
metricsDelegate: NIOEventLoopMetricsDelegate?,
266282
selectorFactory: @escaping () throws -> NIOPosix.Selector<NIORegistration> = NIOPosix.Selector<NIORegistration>
267283
.init
268284
) {
269-
self.threadNamePrefix = threadNamePrefix
285+
self.threadNamePrefix = threadConfiguration.threadNamePrefix
270286
let myGroupID = nextEventLoopGroupID.loadThenWrappingIncrement(ordering: .relaxed)
271287
self.myGroupID = myGroupID
272288
var idx = 0
@@ -275,7 +291,8 @@ public final class MultiThreadedEventLoopGroup: EventLoopGroup {
275291
self.eventLoops = threadInitializers.map { initializer in
276292
// Maximum name length on linux is 16 by default.
277293
let ev = MultiThreadedEventLoopGroup.setupThreadAndEventLoop(
278-
name: "\(threadNamePrefix)\(myGroupID)-#\(idx)",
294+
name: self.threadNamePrefix.map { "\($0)\(myGroupID)-#\(idx)" },
295+
threadConfiguration: threadConfiguration,
279296
parentGroup: self,
280297
selectorFactory: selectorFactory,
281298
initializer: initializer,

Sources/NIOPosix/NIOThreadPool.swift

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ public final class NIOThreadPool {
7979
/// It should never be "leaked" outside of the lock block.
8080
case modifying
8181
}
82+
private let threadConfiguration: NIOThreadConfiguration
8283
private let semaphore = DispatchSemaphore(value: 0)
8384
private let lock = NIOLock()
8485
private var threads: [NIOThread]? = nil // protected by `lock`
@@ -194,21 +195,57 @@ public final class NIOThreadPool {
194195
/// - parameters:
195196
/// - numberOfThreads: The number of threads to use for the thread pool.
196197
public convenience init(numberOfThreads: Int) {
197-
self.init(numberOfThreads: numberOfThreads, canBeStopped: true)
198+
self.init(
199+
numberOfThreads: numberOfThreads,
200+
threadConfiguration: .defaultForOffloadThreadPool,
201+
canBeStopped: true
202+
)
203+
}
204+
205+
/// Initialize a `NIOThreadPool` thread pool with `numberOfThreads` threads.
206+
///
207+
/// - parameters:
208+
/// - numberOfThreads: The number of threads to use for the thread pool.
209+
public convenience init(numberOfThreads: Int, threadConfiguration: NIOThreadConfiguration) {
210+
self.init(
211+
numberOfThreads: numberOfThreads,
212+
threadConfiguration: .defaultForOffloadThreadPool,
213+
canBeStopped: true
214+
)
198215
}
199216

200217
/// Create a ``NIOThreadPool`` that is already started, cannot be shut down and must not be `deinit`ed.
201218
///
202219
/// This is only useful for global singletons.
220+
@available(*, deprecated, renamed: "_makePerpetualStartedPool(numberOfThreads:threadConfiguration:threadNamePrefix:)")
203221
public static func _makePerpetualStartedPool(numberOfThreads: Int, threadNamePrefix: String) -> NIOThreadPool {
204-
let pool = self.init(numberOfThreads: numberOfThreads, canBeStopped: false)
205-
pool._start(threadNamePrefix: threadNamePrefix)
222+
var threadConfig = NIOThreadConfiguration.defaultForOffloadThreadPool
223+
threadConfig.threadNamePrefix = threadNamePrefix
224+
let pool = self.init(numberOfThreads: numberOfThreads, threadConfiguration: threadConfig, canBeStopped: false)
225+
pool.start()
206226
return pool
207227
}
208228

209-
private init(numberOfThreads: Int, canBeStopped: Bool) {
229+
/// Create a ``NIOThreadPool`` that is already started, cannot be shut down and must not be `deinit`ed.
230+
///
231+
/// This is only useful for global singletons.
232+
public static func _makePerpetualStartedPool(
233+
numberOfThreads: Int,
234+
threadConfiguration: NIOThreadConfiguration
235+
) -> NIOThreadPool {
236+
let pool = self.init(
237+
numberOfThreads: numberOfThreads,
238+
threadConfiguration: threadConfiguration,
239+
canBeStopped: false
240+
)
241+
pool.start()
242+
return pool
243+
}
244+
245+
private init(numberOfThreads: Int, threadConfiguration: NIOThreadConfiguration, canBeStopped: Bool) {
210246
self.numberOfThreads = numberOfThreads
211247
self.canBeStopped = canBeStopped
248+
self.threadConfiguration = threadConfiguration
212249
}
213250

214251
private func process(identifier: Int) {
@@ -252,10 +289,6 @@ public final class NIOThreadPool {
252289

253290
/// Start the `NIOThreadPool` if not already started.
254291
public func start() {
255-
self._start(threadNamePrefix: "TP-#")
256-
}
257-
258-
public func _start(threadNamePrefix: String) {
259292
let alreadyRunning: Bool = self.lock.withLock {
260293
switch self.state {
261294
case .running(_):
@@ -286,9 +319,14 @@ public final class NIOThreadPool {
286319
self.threads?.reserveCapacity(self.numberOfThreads)
287320
}
288321

322+
let threadNamePrefix = self.threadConfiguration.threadNamePrefix
289323
for id in 0..<self.numberOfThreads {
290324
// We should keep thread names under 16 characters because Linux doesn't allow more.
291-
NIOThread.spawnAndRun(name: "\(threadNamePrefix)\(id)", detachThread: false) { thread in
325+
NIOThread.spawnAndRun(
326+
name: "\(threadNamePrefix)\(id)",
327+
configuration: self.threadConfiguration,
328+
detachThread: false
329+
) { thread in
292330
self.lock.withLock {
293331
self.threads!.append(thread)
294332
cond.lock()

Sources/NIOPosix/PosixSingletons.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,11 @@ private let singletonMTELG: MultiThreadedEventLoopGroup = {
9696
)
9797
}
9898
let threadCount = NIOSingletons.groupLoopCountSuggestion
99+
var threadConfig = NIOThreadConfiguration.defaultForEventLoopGroups
100+
threadConfig.threadNamePrefix = "NIO-SGLTN-"
99101
let group = MultiThreadedEventLoopGroup._makePerpetualGroup(
100-
threadNamePrefix: "NIO-SGLTN-",
101-
numberOfThreads: threadCount
102+
numberOfThreads: threadCount,
103+
threadConfiguration: threadConfig
102104
)
103105
_ = Unmanaged.passUnretained(group).retain() // Never gonna give you up,
104106
return group
@@ -113,9 +115,11 @@ private let globalPosixBlockingPool: NIOThreadPool = {
113115
"""
114116
)
115117
}
118+
var threadConfig = NIOThreadConfiguration.defaultForOffloadThreadPool
119+
threadConfig.threadNamePrefix = "SGLTN-TP-#"
116120
let pool = NIOThreadPool._makePerpetualStartedPool(
117121
numberOfThreads: NIOSingletons.blockingPoolThreadCountSuggestion,
118-
threadNamePrefix: "SGLTN-TP-#"
122+
threadConfiguration: threadConfig
119123
)
120124
_ = Unmanaged.passUnretained(pool).retain() // never gonna let you down.
121125
return pool

Sources/NIOPosix/Thread.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ protocol ThreadOps {
4141
///
4242
/// All methods exposed are thread-safe.
4343
final class NIOThread {
44-
internal typealias ThreadBoxValue = (body: (NIOThread) -> Void, name: String?)
44+
internal typealias ThreadBoxValue = (body: (NIOThread) -> Void, name: String?, configuration: NIOThreadConfiguration)
4545
internal typealias ThreadBox = Box<ThreadBoxValue>
4646

4747
private let desiredName: String?
@@ -78,22 +78,31 @@ final class NIOThread {
7878
ThreadOpsSystem.joinThread(self.handle)
7979
}
8080

81+
static func spawnAndRunBasic(
82+
body: @escaping (NIOThread) -> Void
83+
) {
84+
var threadConfig = NIOThreadConfiguration.defaultForEventLoopGroups
85+
threadConfig.threadNamePrefix = "UnitTest-"
86+
self.spawnAndRun(name: nil, configuration: threadConfig, detachThread: true, body: body)
87+
}
88+
8189
/// Spawns and runs some task in a `NIOThread`.
8290
///
8391
/// - arguments:
8492
/// - name: The name of the `NIOThread` or `nil` if no specific name should be set.
8593
/// - body: The function to execute within the spawned `NIOThread`.
8694
/// - detach: Whether to detach the thread. If the thread is not detached it must be `join`ed.
8795
static func spawnAndRun(
88-
name: String? = nil,
96+
name: String?,
97+
configuration: NIOThreadConfiguration,
8998
detachThread: Bool = true,
9099
body: @escaping (NIOThread) -> Void
91100
) {
92101
var handle: ThreadOpsSystem.ThreadHandle? = nil
93102

94103
// Store everything we want to pass into the c function in a Box so we
95104
// can hand-over the reference.
96-
let tuple: ThreadBoxValue = (body: body, name: name)
105+
let tuple: ThreadBoxValue = (body: body, name: name, configuration: configuration)
97106
let box = ThreadBox(tuple)
98107

99108
ThreadOpsSystem.run(handle: &handle, args: box, detachThread: detachThread)

0 commit comments

Comments
 (0)