From 3fe2f8e16eafe6d8924263d08f4c23e203275c0a Mon Sep 17 00:00:00 2001 From: Johannes Weiss Date: Thu, 21 Nov 2024 11:15:35 +0000 Subject: [PATCH] give common blocking functions a clear name --- Sources/NIOCore/EventLoopFuture.swift | 7 +- Sources/NIOCore/SystemCallHelpers.swift | 13 +- Sources/NIOPosix/BSDSocketAPICommon.swift | 2 + Sources/NIOPosix/BaseSocket.swift | 1 + Sources/NIOPosix/IO.swift | 1 + Sources/NIOPosix/Linux.swift | 34 ++++ .../MultiThreadedEventLoopGroup.swift | 4 + Sources/NIOPosix/NIOThreadPool.swift | 161 ++++++++++-------- Sources/NIOPosix/SelectableEventLoop.swift | 31 +++- Sources/NIOPosix/SelectorEpoll.swift | 5 +- Sources/NIOPosix/SelectorGeneric.swift | 49 +++++- Sources/NIOPosix/SelectorKqueue.swift | 13 +- Sources/NIOPosix/System.swift | 119 ++++++++----- Sources/NIOPosix/Thread.swift | 1 + 14 files changed, 311 insertions(+), 130 deletions(-) diff --git a/Sources/NIOCore/EventLoopFuture.swift b/Sources/NIOCore/EventLoopFuture.swift index ea5632e5c1..793d3a7ed5 100644 --- a/Sources/NIOCore/EventLoopFuture.swift +++ b/Sources/NIOCore/EventLoopFuture.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftNIO open source project // -// Copyright (c) 2017-2020 Apple Inc. and the SwiftNIO project authors +// Copyright (c) 2017-2024 Apple Inc. and the SwiftNIO project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -1048,11 +1048,12 @@ extension EventLoopFuture { @preconcurrency @inlinable public func wait(file: StaticString = #file, line: UInt = #line) throws -> Value where Value: Sendable { - try self._wait(file: file, line: line) + try self._blockingWaitForFutureCompletion(file: file, line: line) } @inlinable - func _wait(file: StaticString, line: UInt) throws -> Value where Value: Sendable { + @inline(never) + func _blockingWaitForFutureCompletion(file: StaticString, line: UInt) throws -> Value where Value: Sendable { self.eventLoop._preconditionSafeToWait(file: file, line: line) let v: UnsafeMutableTransferBox?> = .init(nil) diff --git a/Sources/NIOCore/SystemCallHelpers.swift b/Sources/NIOCore/SystemCallHelpers.swift index 159b2f10b2..4e192b37ff 100644 --- a/Sources/NIOCore/SystemCallHelpers.swift +++ b/Sources/NIOCore/SystemCallHelpers.swift @@ -60,7 +60,8 @@ private let sysGetifaddrs: @convention(c) (UnsafeMutablePointer Bool { +@inlinable +internal func isUnacceptableErrno(_ code: Int32) -> Bool { switch code { case EFAULT, EBADF: return true @@ -69,7 +70,8 @@ private func isUnacceptableErrno(_ code: Int32) -> Bool { } } -private func preconditionIsNotUnacceptableErrno(err: CInt, where function: String) { +@inlinable +internal func preconditionIsNotUnacceptableErrno(err: CInt, where function: String) { // strerror is documented to return "Unknown error: ..." for illegal value so it won't ever fail precondition( !isUnacceptableErrno(err), @@ -126,6 +128,7 @@ enum SystemCalls { #endif @inline(never) + @usableFromInline internal static func close(descriptor: CInt) throws { let res = sysClose(descriptor) if res == -1 { @@ -150,6 +153,7 @@ enum SystemCalls { } @inline(never) + @usableFromInline internal static func open( file: UnsafePointer, oFlag: CInt, @@ -170,6 +174,7 @@ enum SystemCalls { @discardableResult @inline(never) + @usableFromInline internal static func lseek(descriptor: CInt, offset: off_t, whence: CInt) throws -> off_t { try syscall(blocking: false) { sysLseek(descriptor, offset, whence) @@ -178,6 +183,7 @@ enum SystemCalls { #if os(Windows) @inline(never) + @usableFromInline internal static func read( descriptor: CInt, pointer: UnsafeMutableRawPointer, @@ -189,6 +195,7 @@ enum SystemCalls { } #elseif !os(WASI) @inline(never) + @usableFromInline internal static func read( descriptor: CInt, pointer: UnsafeMutableRawPointer, @@ -202,6 +209,7 @@ enum SystemCalls { #if !os(WASI) @inline(never) + @usableFromInline internal static func if_nametoindex(_ name: UnsafePointer?) throws -> CUnsignedInt { try syscall(blocking: false) { sysIfNameToIndex(name!) @@ -210,6 +218,7 @@ enum SystemCalls { #if !os(Windows) @inline(never) + @usableFromInline internal static func getifaddrs(_ addrs: UnsafeMutablePointer?>) throws { _ = try syscall(blocking: false) { sysGetifaddrs(addrs) diff --git a/Sources/NIOPosix/BSDSocketAPICommon.swift b/Sources/NIOPosix/BSDSocketAPICommon.swift index 0465ed54af..ad4fbb8e5b 100644 --- a/Sources/NIOPosix/BSDSocketAPICommon.swift +++ b/Sources/NIOPosix/BSDSocketAPICommon.swift @@ -31,6 +31,7 @@ protocol _SocketShutdownProtocol { var cValue: CInt { get } } +@usableFromInline internal enum Shutdown: _SocketShutdownProtocol { case RD case WR @@ -47,6 +48,7 @@ extension NIOBSDSocket { extension NIOBSDSocket { /// Specifies the type of socket. + @usableFromInline internal struct SocketType: RawRepresentable { public typealias RawValue = CInt public var rawValue: RawValue diff --git a/Sources/NIOPosix/BaseSocket.swift b/Sources/NIOPosix/BaseSocket.swift index bc6663445f..43ed45f8bc 100644 --- a/Sources/NIOPosix/BaseSocket.swift +++ b/Sources/NIOPosix/BaseSocket.swift @@ -22,6 +22,7 @@ import let WinSDK.EBADF import struct WinSDK.socklen_t #endif +@usableFromInline protocol Registration { /// The `SelectorEventSet` in which the `Registration` is interested. var interested: SelectorEventSet { get set } diff --git a/Sources/NIOPosix/IO.swift b/Sources/NIOPosix/IO.swift index 5a9a581583..dae5c3f63e 100644 --- a/Sources/NIOPosix/IO.swift +++ b/Sources/NIOPosix/IO.swift @@ -24,6 +24,7 @@ extension IOResult where T: FixedWidthInteger { } /// An result for an IO operation that was done on a non-blocking resource. +@usableFromInline enum IOResult: Equatable { /// Signals that the IO operation could not be completed as otherwise we would need to block. diff --git a/Sources/NIOPosix/Linux.swift b/Sources/NIOPosix/Linux.swift index 07e48815ec..91c817ae16 100644 --- a/Sources/NIOPosix/Linux.swift +++ b/Sources/NIOPosix/Linux.swift @@ -18,11 +18,13 @@ #if os(Linux) || os(Android) import CNIOLinux +@usableFromInline internal enum TimerFd { internal static let TFD_CLOEXEC = CNIOLinux.TFD_CLOEXEC internal static let TFD_NONBLOCK = CNIOLinux.TFD_NONBLOCK @inline(never) + @usableFromInline internal static func timerfd_settime( fd: CInt, flags: CInt, @@ -35,6 +37,7 @@ internal enum TimerFd { } @inline(never) + @usableFromInline internal static func timerfd_create(clockId: CInt, flags: CInt) throws -> CInt { try syscall(blocking: false) { CNIOLinux.timerfd_create(clockId, flags) @@ -42,9 +45,13 @@ internal enum TimerFd { } } +@usableFromInline internal enum EventFd { + @usableFromInline internal static let EFD_CLOEXEC = CNIOLinux.EFD_CLOEXEC + @usableFromInline internal static let EFD_NONBLOCK = CNIOLinux.EFD_NONBLOCK + @usableFromInline internal typealias eventfd_t = CNIOLinux.eventfd_t @inline(never) @@ -55,6 +62,7 @@ internal enum EventFd { } @inline(never) + @usableFromInline internal static func eventfd_read(fd: CInt, value: UnsafeMutablePointer) throws -> CInt { try syscall(blocking: false) { CNIOLinux.eventfd_read(fd, value) @@ -73,40 +81,65 @@ internal enum EventFd { } } +@usableFromInline internal enum Epoll { + @usableFromInline internal typealias epoll_event = CNIOLinux.epoll_event + @usableFromInline internal static let EPOLL_CTL_ADD: CInt = numericCast(CNIOLinux.EPOLL_CTL_ADD) + @usableFromInline internal static let EPOLL_CTL_MOD: CInt = numericCast(CNIOLinux.EPOLL_CTL_MOD) + @usableFromInline internal static let EPOLL_CTL_DEL: CInt = numericCast(CNIOLinux.EPOLL_CTL_DEL) #if canImport(Android) || canImport(Musl) + @usableFromInline internal static let EPOLLIN: CUnsignedInt = numericCast(CNIOLinux.EPOLLIN) + @usableFromInline internal static let EPOLLOUT: CUnsignedInt = numericCast(CNIOLinux.EPOLLOUT) + @usableFromInline internal static let EPOLLERR: CUnsignedInt = numericCast(CNIOLinux.EPOLLERR) + @usableFromInline internal static let EPOLLRDHUP: CUnsignedInt = numericCast(CNIOLinux.EPOLLRDHUP) + @usableFromInline internal static let EPOLLHUP: CUnsignedInt = numericCast(CNIOLinux.EPOLLHUP) #if canImport(Android) + @usableFromInline internal static let EPOLLET: CUnsignedInt = 2_147_483_648 // C macro not imported by ClangImporter #else + @usableFromInline internal static let EPOLLET: CUnsignedInt = numericCast(CNIOLinux.EPOLLET) #endif #elseif os(Android) + @usableFromInline internal static let EPOLLIN: CUnsignedInt = 1 //numericCast(CNIOLinux.EPOLLIN) + @usableFromInline internal static let EPOLLOUT: CUnsignedInt = 4 //numericCast(CNIOLinux.EPOLLOUT) + @usableFromInline internal static let EPOLLERR: CUnsignedInt = 8 // numericCast(CNIOLinux.EPOLLERR) + @usableFromInline internal static let EPOLLRDHUP: CUnsignedInt = 8192 //numericCast(CNIOLinux.EPOLLRDHUP) + @usableFromInline internal static let EPOLLHUP: CUnsignedInt = 16 //numericCast(CNIOLinux.EPOLLHUP) + @usableFromInline internal static let EPOLLET: CUnsignedInt = 2_147_483_648 //numericCast(CNIOLinux.EPOLLET) #else + @usableFromInline internal static let EPOLLIN: CUnsignedInt = numericCast(CNIOLinux.EPOLLIN.rawValue) + @usableFromInline internal static let EPOLLOUT: CUnsignedInt = numericCast(CNIOLinux.EPOLLOUT.rawValue) + @usableFromInline internal static let EPOLLERR: CUnsignedInt = numericCast(CNIOLinux.EPOLLERR.rawValue) + @usableFromInline internal static let EPOLLRDHUP: CUnsignedInt = numericCast(CNIOLinux.EPOLLRDHUP.rawValue) + @usableFromInline internal static let EPOLLHUP: CUnsignedInt = numericCast(CNIOLinux.EPOLLHUP.rawValue) + @usableFromInline internal static let EPOLLET: CUnsignedInt = numericCast(CNIOLinux.EPOLLET.rawValue) #endif + @usableFromInline internal static let ENOENT: CUnsignedInt = numericCast(CNIOLinux.ENOENT) @inline(never) @@ -130,6 +163,7 @@ internal enum Epoll { } @inline(never) + @usableFromInline internal static func epoll_wait( epfd: CInt, events: UnsafeMutablePointer, diff --git a/Sources/NIOPosix/MultiThreadedEventLoopGroup.swift b/Sources/NIOPosix/MultiThreadedEventLoopGroup.swift index c09f9fe8d3..010a81817d 100644 --- a/Sources/NIOPosix/MultiThreadedEventLoopGroup.swift +++ b/Sources/NIOPosix/MultiThreadedEventLoopGroup.swift @@ -20,6 +20,7 @@ import NIOCore import Dispatch #endif +@usableFromInline struct NIORegistration: Registration { enum ChannelType { case serverSocketChannel(ServerSocketChannel) @@ -31,9 +32,11 @@ struct NIORegistration: Registration { var channel: ChannelType /// The `SelectorEventSet` in which this `NIORegistration` is interested in. + @usableFromInline var interested: SelectorEventSet /// The registration ID for this `NIORegistration` used by the `Selector`. + @usableFromInline var registrationID: SelectorRegistrationID } @@ -568,6 +571,7 @@ extension ScheduledTask: Comparable { } extension NIODeadline { + @inlinable func readyIn(_ target: NIODeadline) -> TimeAmount { if self < target { return .nanoseconds(0) diff --git a/Sources/NIOPosix/NIOThreadPool.swift b/Sources/NIOPosix/NIOThreadPool.swift index 7854cbff7b..ab1ef17fcd 100644 --- a/Sources/NIOPosix/NIOThreadPool.swift +++ b/Sources/NIOPosix/NIOThreadPool.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftNIO open source project // -// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors +// Copyright (c) 2017-2024 Apple Inc. and the SwiftNIO project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -63,12 +63,17 @@ public final class NIOThreadPool { /// The work that should be done by the `NIOThreadPool`. public typealias WorkItem = @Sendable (WorkItemState) -> Void - private struct IdentifiableWorkItem: Sendable { + @usableFromInline + struct IdentifiableWorkItem: Sendable { + @usableFromInline var workItem: WorkItem + + @usableFromInline var id: Int? } - private enum State { + @usableFromInline + internal enum State { /// The `NIOThreadPool` is already stopped. case stopped /// The `NIOThreadPool` is shutting down, the array has one boolean entry for each thread indicating if it has shut down already. @@ -81,7 +86,8 @@ public final class NIOThreadPool { } /// Whether threads in the pool have work. - private enum WorkState: Hashable { + @usableFromInline + internal enum _WorkState: Hashable { case hasWork case hasNoWork } @@ -93,9 +99,11 @@ public final class NIOThreadPool { // to wait for a given value. The value indicates whether the thread has some work to do. Work // in this case can be either processing a work item or exiting the threads processing // loop (i.e. shutting down). - private let conditionLock: ConditionLock + @usableFromInline + internal let _conditionLock: ConditionLock<_WorkState> private var threads: [NIOThread]? = nil // protected by `conditionLock` - private var state: State = .stopped + @usableFromInline + internal var _state: State = .stopped // WorkItems don't have a handle so they can't be cancelled directly. Instead an ID is assigned // to each cancellable work item and the IDs of each work item to cancel is stored in this set. @@ -113,7 +121,8 @@ public final class NIOThreadPool { // be removed. // // Note: protected by 'lock'. - private var cancelledWorkIDs: Set = [] + @usableFromInline + internal var _cancelledWorkIDs: Set = [] private let nextWorkID = ManagedAtomic(0) public let numberOfThreads: Int @@ -137,16 +146,16 @@ public final class NIOThreadPool { return } - let threadsToJoin = self.conditionLock.withLock { - switch self.state { + let threadsToJoin = self._conditionLock.withLock { + switch self._state { case .running(let items): - self.state = .modifying + self._state = .modifying queue.async { for item in items { item.workItem(.cancelled) } } - self.state = .shuttingDown(Array(repeating: true, count: self.numberOfThreads)) + self._state = .shuttingDown(Array(repeating: true, count: self.numberOfThreads)) let threads = self.threads! self.threads = nil @@ -184,15 +193,15 @@ public final class NIOThreadPool { } private func _submit(id: Int?, _ body: @escaping WorkItem) { - let submitted = self.conditionLock.withLock { - let workState: WorkState + let submitted = self._conditionLock.withLock { + let workState: _WorkState let submitted: Bool - switch self.state { + switch self._state { case .running(var items): - self.state = .modifying + self._state = .modifying items.append(.init(workItem: body, id: id)) - self.state = .running(items) + self._state = .running(items) workState = items.isEmpty ? .hasNoWork : .hasWork submitted = true @@ -233,60 +242,74 @@ public final class NIOThreadPool { private init(numberOfThreads: Int, canBeStopped: Bool) { self.numberOfThreads = numberOfThreads self.canBeStopped = canBeStopped - self.conditionLock = ConditionLock(value: .hasNoWork) + self._conditionLock = ConditionLock(value: .hasNoWork) } - private func process(identifier: Int) { - var itemAndState: (item: WorkItem, state: WorkItemState)? = nil + // Do not rename or remove this function. + // + // When doing on-/off-CPU analysis, for example with continuous profiling, it's + // important to recognise certain functions that are purely there to wait. + // + // This function is one of those and giving it a consistent name makes it much easier to remove from the profiles + // when only interested in on-CPU work. + @inline(never) + @inlinable + internal func _blockingWaitForWork(identifier: Int) -> (item: WorkItem, state: WorkItemState)? { + self._conditionLock.withLock(when: .hasWork) { + let workState: _WorkState + let result: (WorkItem, WorkItemState)? - repeat { - itemAndState = nil // ensure previous work item is not retained while waiting for the condition - itemAndState = self.conditionLock.withLock(when: .hasWork) { - let workState: WorkState - let result: (WorkItem, WorkItemState)? - - switch self.state { - case .running(var items): - self.state = .modifying - let itemAndID = items.removeFirst() - - let state: WorkItemState - if let id = itemAndID.id, !self.cancelledWorkIDs.isEmpty { - state = self.cancelledWorkIDs.remove(id) == nil ? .active : .cancelled - } else { - state = .active - } + switch self._state { + case .running(var items): + self._state = .modifying + let itemAndID = items.removeFirst() - self.state = .running(items) + let state: WorkItemState + if let id = itemAndID.id, !self._cancelledWorkIDs.isEmpty { + state = self._cancelledWorkIDs.remove(id) == nil ? .active : .cancelled + } else { + state = .active + } - workState = items.isEmpty ? .hasNoWork : .hasWork - result = (itemAndID.workItem, state) + self._state = .running(items) - case .shuttingDown(var aliveStates): - self.state = .modifying - assert(aliveStates[identifier]) - aliveStates[identifier] = false - self.state = .shuttingDown(aliveStates) + workState = items.isEmpty ? .hasNoWork : .hasWork + result = (itemAndID.workItem, state) - // Unlock with '.hasWork' to resume any other threads waiting to shutdown. - workState = .hasWork - result = nil + case .shuttingDown(var aliveStates): + self._state = .modifying + assert(aliveStates[identifier]) + aliveStates[identifier] = false + self._state = .shuttingDown(aliveStates) - case .stopped: - // Unreachable: 'stopped' is the initial state which is left when starting the - // thread pool, and before any thread calls this function. - fatalError("Invalid state") + // Unlock with '.hasWork' to resume any other threads waiting to shutdown. + workState = .hasWork + result = nil - case .modifying: - fatalError(".modifying state misuse") - } + case .stopped: + // Unreachable: 'stopped' is the initial state which is left when starting the + // thread pool, and before any thread calls this function. + fatalError("Invalid state") - return (unlockWith: workState, result: result) + case .modifying: + fatalError(".modifying state misuse") } - // if there was a work item popped, run it - itemAndState.map { item, state in item(state) } - } while itemAndState != nil + return (unlockWith: workState, result: result) + } + } + + private func process(identifier: Int) { + repeat { + let itemAndState = self._blockingWaitForWork(identifier: identifier) + + if let (item, state) = itemAndState { + // if there was a work item popped, run it + item(state) + } else { + break // Otherwise, we're done + } + } while true } /// Start the `NIOThreadPool` if not already started. @@ -295,8 +318,8 @@ public final class NIOThreadPool { } public func _start(threadNamePrefix: String) { - let alreadyRunning = self.conditionLock.withLock { - switch self.state { + let alreadyRunning = self._conditionLock.withLock { + switch self._state { case .running: // Already running, this has no effect on whether there is more work for the // threads to run. @@ -307,7 +330,7 @@ public final class NIOThreadPool { fatalError("start() called while in shuttingDown") case .stopped: - self.state = .running(Deque(minimumCapacity: 16)) + self._state = .running(Deque(minimumCapacity: 16)) assert(self.threads == nil) self.threads = [] self.threads!.reserveCapacity(self.numberOfThreads) @@ -330,11 +353,11 @@ public final class NIOThreadPool { // We should keep thread names under 16 characters because Linux doesn't allow more. NIOThread.spawnAndRun(name: "\(threadNamePrefix)\(id)", detachThread: false) { thread in readyThreads.withLock { - let threadCount = self.conditionLock.withLock { + let threadCount = self._conditionLock.withLock { self.threads!.append(thread) - let workState: WorkState + let workState: _WorkState - switch self.state { + switch self._state { case .running(let items): workState = items.isEmpty ? .hasNoWork : .hasWork case .shuttingDown: @@ -364,7 +387,7 @@ public final class NIOThreadPool { readyThreads.unlock() func threadCount() -> Int { - self.conditionLock.withLock { + self._conditionLock.withLock { (unlockWith: nil, result: self.threads?.count ?? -1) } } @@ -376,11 +399,11 @@ public final class NIOThreadPool { self.canBeStopped, "Perpetual NIOThreadPool has been deinited, you must make sure that perpetual pools don't deinit" ) - switch self.state { + switch self._state { case .stopped, .shuttingDown: () default: - assertionFailure("wrong state \(self.state)") + assertionFailure("wrong state \(self._state)") } } } @@ -440,8 +463,8 @@ extension NIOThreadPool { } } } onCancel: { - self.conditionLock.withLock { - self.cancelledWorkIDs.insert(workID) + self._conditionLock.withLock { + self._cancelledWorkIDs.insert(workID) return (unlockWith: nil, result: ()) } } diff --git a/Sources/NIOPosix/SelectableEventLoop.swift b/Sources/NIOPosix/SelectableEventLoop.swift index 20fe17fc41..89fc4737f9 100644 --- a/Sources/NIOPosix/SelectableEventLoop.swift +++ b/Sources/NIOPosix/SelectableEventLoop.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftNIO open source project // -// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors +// Copyright (c) 2017-2024 Apple Inc. and the SwiftNIO project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -93,6 +93,7 @@ internal final class SelectableEventLoop: EventLoop { case exitingThread } + @usableFromInline internal let _selector: NIOPosix.Selector private let thread: NIOThread @usableFromInline @@ -477,7 +478,8 @@ internal final class SelectableEventLoop: EventLoop { } } - private func currentSelectorStrategy(nextReadyDeadline: NIODeadline?) -> SelectorStrategy { + @inlinable + internal func _currentSelectorStrategy(nextReadyDeadline: NIODeadline?) -> SelectorStrategy { guard let deadline = nextReadyDeadline else { // No tasks to handle so just block. If any tasks were added in the meantime wakeup(...) was called and so this // will directly unblock. @@ -679,6 +681,26 @@ internal final class SelectableEventLoop: EventLoop { } } + // Do not rename or remove this function. + // + // When doing on-/off-CPU analysis, for example with continuous profiling, it's + // important to recognise certain functions that are purely there to wait. + // + // This function is one of those and giving it a consistent name makes it much easier to remove from the profiles + // when only interested in on-CPU work. + @inline(never) + @inlinable + internal func _blockingWaitForWork( + nextReadyDeadline: NIODeadline?, + _ body: (SelectorEvent) -> Void + ) throws { + try self._selector.whenReady( + strategy: self._currentSelectorStrategy(nextReadyDeadline: nextReadyDeadline), + onLoopBegin: { self._tasksLock.withLock { () -> Void in self._pendingTaskPop = true } }, + body + ) + } + /// Start processing I/O and tasks for this `SelectableEventLoop`. This method will continue running (and so block) until the `SelectableEventLoop` is closed. internal func run() throws { self.preconditionInEventLoop() @@ -745,10 +767,7 @@ internal final class SelectableEventLoop: EventLoop { // Block until there are events to handle or the selector was woken up // for macOS: in case any calls we make to Foundation put objects into an autoreleasepool try withAutoReleasePool { - try self._selector.whenReady( - strategy: currentSelectorStrategy(nextReadyDeadline: nextReadyDeadline), - onLoopBegin: { self._tasksLock.withLock { () -> Void in self._pendingTaskPop = true } } - ) { ev in + try self._blockingWaitForWork(nextReadyDeadline: nextReadyDeadline) { ev in switch ev.registration.channel { case .serverSocketChannel(let chan): self.handleEvent(ev.io, channel: chan) diff --git a/Sources/NIOPosix/SelectorEpoll.swift b/Sources/NIOPosix/SelectorEpoll.swift index 208aa3a950..db907ed25b 100644 --- a/Sources/NIOPosix/SelectorEpoll.swift +++ b/Sources/NIOPosix/SelectorEpoll.swift @@ -17,6 +17,7 @@ import NIOCore #if !SWIFTNIO_USE_IO_URING #if os(Linux) || os(Android) +import CNIOLinux /// Represents the `epoll` filters/events we might use: /// @@ -79,7 +80,8 @@ extension SelectorEventSet { return filter } - fileprivate init(epollEvent: Epoll.epoll_event) { + @inlinable + internal init(epollEvent: Epoll.epoll_event) { var selectorEventSet: SelectorEventSet = ._none if epollEvent.events & Epoll.EPOLLIN != 0 { selectorEventSet.formUnion(.read) @@ -207,6 +209,7 @@ extension Selector: _SelectorBackendProtocol { /// - Parameters: /// - strategy: The `SelectorStrategy` to apply /// - body: The function to execute for each `SelectorEvent` that was produced. + @inlinable func whenReady0( strategy: SelectorStrategy, onLoopBegin loopStart: () -> Void, diff --git a/Sources/NIOPosix/SelectorGeneric.swift b/Sources/NIOPosix/SelectorGeneric.swift index d97496d6a6..fd70728c7f 100644 --- a/Sources/NIOPosix/SelectorGeneric.swift +++ b/Sources/NIOPosix/SelectorGeneric.swift @@ -15,6 +15,11 @@ import NIOConcurrencyHelpers import NIOCore +#if os(Linux) +import CNIOLinux +#endif + +@usableFromInline internal enum SelectorLifecycleState { case open case closing @@ -22,6 +27,7 @@ internal enum SelectorLifecycleState { } extension Optional { + @inlinable internal func withUnsafeOptionalPointer(_ body: (UnsafePointer?) throws -> T) rethrows -> T { if var this = self { return try withUnsafePointer(to: &this) { x in @@ -35,6 +41,7 @@ extension Optional { #if !os(Windows) extension timespec { + @inlinable init(timeAmount amount: TimeAmount) { let nsecPerSec: Int64 = 1_000_000_000 let ns = amount.nanoseconds @@ -52,36 +59,47 @@ extension timespec { /// receives a connection reset, express interest with `[.read, .write, .reset]`. /// If then suddenly the socket becomes both readable and writable, the eventing mechanism will tell you about that /// fact using `[.read, .write]`. +@usableFromInline struct SelectorEventSet: OptionSet, Equatable { + @usableFromInline typealias RawValue = UInt8 + @usableFromInline let rawValue: RawValue /// It's impossible to actually register for no events, therefore `_none` should only be used to bootstrap a set /// of flags or to compare against spurious wakeups. + @usableFromInline static let _none = SelectorEventSet([]) /// Connection reset. + @usableFromInline static let reset = SelectorEventSet(rawValue: 1 << 0) /// EOF at the read/input end of a `Selectable`. + @usableFromInline static let readEOF = SelectorEventSet(rawValue: 1 << 1) /// Interest in/availability of data to be read + @usableFromInline static let read = SelectorEventSet(rawValue: 1 << 2) /// Interest in/availability of data to be written + @usableFromInline static let write = SelectorEventSet(rawValue: 1 << 3) /// EOF at the write/output end of a `Selectable`. /// /// - Note: This is rarely used because in many cases, there is no signal that this happened. + @usableFromInline static let writeEOF = SelectorEventSet(rawValue: 1 << 4) /// Error encountered. + @usableFromInline static let error = SelectorEventSet(rawValue: 1 << 5) + @inlinable init(rawValue: SelectorEventSet.RawValue) { self.rawValue = rawValue } @@ -144,40 +162,59 @@ protocol _SelectorBackendProtocol { /// There are specific subclasses per API type with a shared common superclass providing overall scaffolding. // this is deliberately not thread-safe, only the wakeup() function may be called unprotectedly +@usableFromInline internal class Selector { + @usableFromInline var lifecycleState: SelectorLifecycleState + @usableFromInline var registrations = [Int: R]() + @usableFromInline var registrationID: SelectorRegistrationID = .initialRegistrationID + @usableFromInline let myThread: NIOThread // The rules for `self.selectorFD`, `self.eventFD`, and `self.timerFD`: // reads: `self.externalSelectorFDLock` OR access from the EventLoop thread // writes: `self.externalSelectorFDLock` AND access from the EventLoop thread let externalSelectorFDLock = NIOLock() + @usableFromInline var selectorFD: CInt = -1 // -1 == we're closed // Here we add the stored properties that are used by the specific backends #if canImport(Darwin) + @usableFromInline typealias EventType = kevent #elseif os(Linux) || os(Android) #if !SWIFTNIO_USE_IO_URING + @usableFromInline typealias EventType = Epoll.epoll_event + @usableFromInline var earliestTimer: NIODeadline = .distantFuture + @usableFromInline var eventFD: CInt = -1 // -1 == we're closed + @usableFromInline var timerFD: CInt = -1 // -1 == we're closed #else + @usableFromInline typealias EventType = URingEvent + @usableFromInline var eventFD: CInt = -1 // -1 == we're closed + @usableFromInline var ring = URing() + @usableFromInline let multishot = URing.io_uring_use_multishot_poll // if true, we run with streaming multishot polls + @usableFromInline let deferReregistrations = true // if true we only flush once at reentring whenReady() - saves syscalls + @usableFromInline var deferredReregistrationsPending = false // true if flush needed when reentring whenReady() #endif #else #error("Unsupported platform, no suitable selector backend (we need kqueue or epoll support)") #endif + @usableFromInline var events: UnsafeMutablePointer + @usableFromInline var eventsCapacity = 64 internal func testsOnly_withUnsafeSelectorFD(_ body: (CInt) throws -> T) throws -> T { @@ -205,17 +242,20 @@ internal class Selector { Selector.deallocateEventsArray(events: events, capacity: eventsCapacity) } - private static func allocateEventsArray(capacity: Int) -> UnsafeMutablePointer { + @inlinable + internal static func allocateEventsArray(capacity: Int) -> UnsafeMutablePointer { let events: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: capacity) events.initialize(to: EventType()) return events } - private static func deallocateEventsArray(events: UnsafeMutablePointer, capacity: Int) { + @inlinable + internal static func deallocateEventsArray(events: UnsafeMutablePointer, capacity: Int) { events.deinitialize(count: capacity) events.deallocate() } + @inlinable func growEventArrayIfNeeded(ready: Int) { assert(self.myThread == NIOThread.current) guard ready == eventsCapacity else { @@ -317,6 +357,7 @@ internal class Selector { /// - strategy: The `SelectorStrategy` to apply /// - onLoopBegin: A function executed after the selector returns, just before the main loop begins.. /// - body: The function to execute for each `SelectorEvent` that was produced. + @inlinable func whenReady( strategy: SelectorStrategy, onLoopBegin loopStart: () -> Void, @@ -345,6 +386,7 @@ internal class Selector { } extension Selector: CustomStringConvertible { + @usableFromInline var description: String { func makeDescription() -> String { "Selector { descriptor = \(self.selectorFD) }" @@ -361,6 +403,7 @@ extension Selector: CustomStringConvertible { } /// An event that is triggered once the `Selector` was able to select something. +@usableFromInline struct SelectorEvent { public let registration: R public var io: SelectorEventSet @@ -370,6 +413,7 @@ struct SelectorEvent { /// - Parameters: /// - io: The `SelectorEventSet` that triggered this event. /// - registration: The registration that belongs to the event. + @inlinable init(io: SelectorEventSet, registration: R) { self.io = io self.registration = registration @@ -426,6 +470,7 @@ extension Selector where R == NIORegistration { } /// The strategy used for the `Selector`. +@usableFromInline enum SelectorStrategy { /// Block until there is some IO ready to be processed or the `Selector` is explicitly woken up. case block diff --git a/Sources/NIOPosix/SelectorKqueue.swift b/Sources/NIOPosix/SelectorKqueue.swift index 49a264d730..543c1ae9c0 100644 --- a/Sources/NIOPosix/SelectorKqueue.swift +++ b/Sources/NIOPosix/SelectorKqueue.swift @@ -99,6 +99,7 @@ extension KQueueEventFilterSet { } extension SelectorRegistrationID { + @inlinable init(kqueueUData: UnsafeMutableRawPointer?) { self = .init(rawValue: UInt32(truncatingIfNeeded: UInt(bitPattern: kqueueUData))) } @@ -106,7 +107,8 @@ extension SelectorRegistrationID { // this is deliberately not thread-safe, only the wakeup() function may be called unprotectedly extension Selector: _SelectorBackendProtocol { - private static func toKQueueTimeSpec(strategy: SelectorStrategy) -> timespec? { + @inlinable + internal static func toKQueueTimeSpec(strategy: SelectorStrategy) -> timespec? { switch strategy { case .block: return nil @@ -251,6 +253,7 @@ extension Selector: _SelectorBackendProtocol { /// - Parameters: /// - strategy: The `SelectorStrategy` to apply /// - body: The function to execute for each `SelectorEvent` that was produced. + @inlinable func whenReady0( strategy: SelectorStrategy, onLoopBegin loopStart: () -> Void, @@ -268,8 +271,8 @@ extension Selector: _SelectorBackendProtocol { kq: self.selectorFD, changelist: nil, nchanges: 0, - eventlist: events, - nevents: Int32(eventsCapacity), + eventlist: self.events, + nevents: Int32(self.eventsCapacity), timeout: ts ) ) @@ -287,7 +290,7 @@ extension Selector: _SelectorBackendProtocol { reason: "kevent returned with EV_ERROR set: \(String(describing: ev))" ) } - guard filter != EVFILT_USER, let registration = registrations[Int(ev.ident)] else { + guard filter != EVFILT_USER, let registration = self.registrations[Int(ev.ident)] else { continue } guard eventRegistrationID == registration.registrationID else { @@ -327,7 +330,7 @@ extension Selector: _SelectorBackendProtocol { try body((SelectorEvent(io: selectorEvent, registration: registration))) } - growEventArrayIfNeeded(ready: ready) + self.growEventArrayIfNeeded(ready: ready) } /// Close the `Selector`. diff --git a/Sources/NIOPosix/System.swift b/Sources/NIOPosix/System.swift index 33b3be73d3..30b4bd0ddd 100644 --- a/Sources/NIOPosix/System.swift +++ b/Sources/NIOPosix/System.swift @@ -172,7 +172,8 @@ private let sysRecvMmsg = CNIODarwin_recvmmsg private let sysIoctl: @convention(c) (CInt, CUnsignedLong, UnsafeMutableRawPointer) -> CInt = ioctl #endif // !os(Windows) -private func isUnacceptableErrno(_ code: Int32) -> Bool { +@inlinable +func isUnacceptableErrno(_ code: CInt) -> Bool { // On iOS, EBADF is a possible result when a file descriptor has been reaped in the background. // In particular, it's possible to get EBADF from accept(), where the underlying accept() FD // is valid but the accepted one is not. The right solution here is to perform a check for @@ -195,7 +196,8 @@ private func isUnacceptableErrno(_ code: Int32) -> Bool { #endif } -private func isUnacceptableErrnoOnClose(_ code: Int32) -> Bool { +@inlinable +public func isUnacceptableErrnoOnClose(_ code: CInt) -> Bool { // We treat close() differently to all other FDs: we still want to catch EBADF here. switch code { case EFAULT, EBADF: @@ -205,7 +207,8 @@ private func isUnacceptableErrnoOnClose(_ code: Int32) -> Bool { } } -private func isUnacceptableErrnoForbiddingEINVAL(_ code: Int32) -> Bool { +@inlinable +internal func isUnacceptableErrnoForbiddingEINVAL(_ code: CInt) -> Bool { // We treat read() and pread() differently since we also want to catch EINVAL. #if canImport(Darwin) && !os(macOS) switch code { @@ -225,6 +228,7 @@ private func isUnacceptableErrnoForbiddingEINVAL(_ code: Int32) -> Bool { } #if os(Windows) +@inlinable internal func strerror(_ errno: CInt) -> String { withUnsafeTemporaryAllocation(of: CChar.self, capacity: 95) { let result = strerror_s($0.baseAddress, $0.count, errno) @@ -234,7 +238,8 @@ internal func strerror(_ errno: CInt) -> String { } #endif -private func preconditionIsNotUnacceptableErrno(err: CInt, where function: String) { +@inlinable +internal func preconditionIsNotUnacceptableErrno(err: CInt, where function: String) { // strerror is documented to return "Unknown error: ..." for illegal value so it won't ever fail #if os(Windows) precondition(!isUnacceptableErrno(err), "unacceptable errno \(err) \(strerror(err)) in \(function))") @@ -246,7 +251,8 @@ private func preconditionIsNotUnacceptableErrno(err: CInt, where function: Strin #endif } -private func preconditionIsNotUnacceptableErrnoOnClose(err: CInt, where function: String) { +@inlinable +internal func preconditionIsNotUnacceptableErrnoOnClose(err: CInt, where function: String) { // strerror is documented to return "Unknown error: ..." for illegal value so it won't ever fail #if os(Windows) precondition(!isUnacceptableErrnoOnClose(err), "unacceptable errno \(err) \(strerror(err)) in \(function))") @@ -258,7 +264,8 @@ private func preconditionIsNotUnacceptableErrnoOnClose(err: CInt, where function #endif } -private func preconditionIsNotUnacceptableErrnoForbiddingEINVAL(err: CInt, where function: String) { +@inlinable +internal func preconditionIsNotUnacceptableErrnoForbiddingEINVAL(err: CInt, where function: String) { // strerror is documented to return "Unknown error: ..." for illegal value so it won't ever fail #if os(Windows) precondition( @@ -278,6 +285,7 @@ private func preconditionIsNotUnacceptableErrnoForbiddingEINVAL(err: CInt, where // take twice the time, ie. we need this exception. @inline(__always) @discardableResult +@inlinable internal func syscall( blocking: Bool, where function: String = #function, @@ -310,6 +318,7 @@ internal func syscall( #if canImport(Darwin) @inline(__always) +@inlinable @discardableResult internal func syscall( where function: String = #function, @@ -334,6 +343,7 @@ internal func syscall( } #elseif os(Linux) || os(Android) @inline(__always) +@inlinable @discardableResult internal func syscall( where function: String = #function, @@ -360,6 +370,7 @@ internal func syscall( #if !os(Windows) @inline(__always) +@inlinable @discardableResult internal func syscallOptional( where function: String = #function, @@ -391,6 +402,7 @@ internal func syscallOptional( // however we seem to break the inlining threshold which makes a system call // take twice the time, ie. we need this exception. @inline(__always) +@inlinable @discardableResult internal func syscallForbiddingEINVAL( where function: String = #function, @@ -421,39 +433,60 @@ internal func syscallForbiddingEINVAL( } } +@usableFromInline internal enum Posix { #if canImport(Darwin) + @usableFromInline static let UIO_MAXIOV: Int = 1024 + @usableFromInline static let SHUT_RD: CInt = CInt(Darwin.SHUT_RD) + @usableFromInline static let SHUT_WR: CInt = CInt(Darwin.SHUT_WR) + @usableFromInline static let SHUT_RDWR: CInt = CInt(Darwin.SHUT_RDWR) #elseif os(Linux) || os(FreeBSD) || os(Android) #if canImport(Glibc) + @usableFromInline static let UIO_MAXIOV: Int = Int(Glibc.UIO_MAXIOV) + @usableFromInline static let SHUT_RD: CInt = CInt(Glibc.SHUT_RD) + @usableFromInline static let SHUT_WR: CInt = CInt(Glibc.SHUT_WR) + @usableFromInline static let SHUT_RDWR: CInt = CInt(Glibc.SHUT_RDWR) #elseif canImport(Musl) + @usableFromInline static let UIO_MAXIOV: Int = Int(Musl.UIO_MAXIOV) + @usableFromInline static let SHUT_RD: CInt = CInt(Musl.SHUT_RD) + @usableFromInline static let SHUT_WR: CInt = CInt(Musl.SHUT_WR) + @usableFromInline static let SHUT_RDWR: CInt = CInt(Musl.SHUT_RDWR) #elseif canImport(Android) + @usableFromInline static let UIO_MAXIOV: Int = Int(Android.UIO_MAXIOV) + @usableFromInline static let SHUT_RD: CInt = CInt(Android.SHUT_RD) + @usableFromInline static let SHUT_WR: CInt = CInt(Android.SHUT_WR) + @usableFromInline static let SHUT_RDWR: CInt = CInt(Android.SHUT_RDWR) #endif #else + @usableFromInline static var UIO_MAXIOV: Int { fatalError("unsupported OS") } + @usableFromInline static var SHUT_RD: Int { fatalError("unsupported OS") } + @usableFromInline static var SHUT_WR: Int { fatalError("unsupported OS") } + @usableFromInline static var SHUT_RDWR: Int { fatalError("unsupported OS") } @@ -503,14 +536,14 @@ internal enum Posix { #if !os(Windows) @inline(never) - internal static func shutdown(descriptor: CInt, how: Shutdown) throws { + public static func shutdown(descriptor: CInt, how: Shutdown) throws { _ = try syscall(blocking: false) { sysShutdown(descriptor, how.cValue) } } @inline(never) - internal static func close(descriptor: CInt) throws { + public static func close(descriptor: CInt) throws { let res = sysClose(descriptor) if res == -1 { #if os(Windows) @@ -534,7 +567,7 @@ internal enum Posix { } @inline(never) - internal static func bind(descriptor: CInt, ptr: UnsafePointer, bytes: Int) throws { + public static func bind(descriptor: CInt, ptr: UnsafePointer, bytes: Int) throws { _ = try syscall(blocking: false) { sysBind(descriptor, ptr, socklen_t(bytes)) } @@ -542,6 +575,7 @@ internal enum Posix { @inline(never) @discardableResult + @usableFromInline // TODO: Allow varargs internal static func fcntl(descriptor: CInt, command: CInt, value: CInt) throws -> CInt { try syscall(blocking: false) { @@ -550,7 +584,7 @@ internal enum Posix { } @inline(never) - internal static func socket( + public static func socket( domain: NIOBSDSocket.ProtocolFamily, type: NIOBSDSocket.SocketType, protocolSubtype: NIOBSDSocket.ProtocolSubtype @@ -561,7 +595,7 @@ internal enum Posix { } @inline(never) - internal static func setsockopt( + public static func setsockopt( socket: CInt, level: CInt, optionName: CInt, @@ -574,7 +608,7 @@ internal enum Posix { } @inline(never) - internal static func getsockopt( + public static func getsockopt( socket: CInt, level: CInt, optionName: CInt, @@ -587,14 +621,14 @@ internal enum Posix { } @inline(never) - internal static func listen(descriptor: CInt, backlog: CInt) throws { + public static func listen(descriptor: CInt, backlog: CInt) throws { _ = try syscall(blocking: false) { sysListen(descriptor, backlog) } } @inline(never) - internal static func accept( + public static func accept( descriptor: CInt, addr: UnsafeMutablePointer?, len: UnsafeMutablePointer? @@ -611,7 +645,7 @@ internal enum Posix { } @inline(never) - internal static func connect(descriptor: CInt, addr: UnsafePointer, size: socklen_t) throws -> Bool { + public static func connect(descriptor: CInt, addr: UnsafePointer, size: socklen_t) throws -> Bool { do { _ = try syscall(blocking: false) { sysConnect(descriptor, addr, size) @@ -626,14 +660,14 @@ internal enum Posix { } @inline(never) - internal static func open(file: UnsafePointer, oFlag: CInt, mode: mode_t) throws -> CInt { + public static func open(file: UnsafePointer, oFlag: CInt, mode: mode_t) throws -> CInt { try syscall(blocking: false) { sysOpenWithMode(file, oFlag, mode) }.result } @inline(never) - internal static func open(file: UnsafePointer, oFlag: CInt) throws -> CInt { + public static func open(file: UnsafePointer, oFlag: CInt) throws -> CInt { try syscall(blocking: false) { sysOpen(file, oFlag) }.result @@ -641,21 +675,21 @@ internal enum Posix { @inline(never) @discardableResult - internal static func ftruncate(descriptor: CInt, size: off_t) throws -> CInt { + public static func ftruncate(descriptor: CInt, size: off_t) throws -> CInt { try syscall(blocking: false) { sysFtruncate(descriptor, size) }.result } @inline(never) - internal static func write(descriptor: CInt, pointer: UnsafeRawPointer, size: Int) throws -> IOResult { + public static func write(descriptor: CInt, pointer: UnsafeRawPointer, size: Int) throws -> IOResult { try syscall(blocking: true) { sysWrite(descriptor, pointer, size) } } @inline(never) - internal static func pwrite( + public static func pwrite( descriptor: CInt, pointer: UnsafeRawPointer, size: Int, @@ -668,7 +702,7 @@ internal enum Posix { #if !os(Windows) @inline(never) - internal static func writev(descriptor: CInt, iovecs: UnsafeBufferPointer) throws -> IOResult { + public static func writev(descriptor: CInt, iovecs: UnsafeBufferPointer) throws -> IOResult { try syscall(blocking: true) { sysWritev(descriptor, iovecs.baseAddress!, CInt(iovecs.count)) } @@ -676,7 +710,7 @@ internal enum Posix { #endif @inline(never) - internal static func read( + public static func read( descriptor: CInt, pointer: UnsafeMutableRawPointer, size: size_t @@ -687,7 +721,7 @@ internal enum Posix { } @inline(never) - internal static func pread( + public static func pread( descriptor: CInt, pointer: UnsafeMutableRawPointer, size: size_t, @@ -699,7 +733,7 @@ internal enum Posix { } @inline(never) - internal static func recvmsg( + public static func recvmsg( descriptor: CInt, msgHdr: UnsafeMutablePointer, flags: CInt @@ -710,7 +744,7 @@ internal enum Posix { } @inline(never) - internal static func sendmsg( + public static func sendmsg( descriptor: CInt, msgHdr: UnsafePointer, flags: CInt @@ -722,7 +756,7 @@ internal enum Posix { @discardableResult @inline(never) - internal static func lseek(descriptor: CInt, offset: off_t, whence: CInt) throws -> off_t { + public static func lseek(descriptor: CInt, offset: off_t, whence: CInt) throws -> off_t { try syscall(blocking: false) { sysLseek(descriptor, offset, whence) }.result @@ -731,7 +765,7 @@ internal enum Posix { @discardableResult @inline(never) - internal static func dup(descriptor: CInt) throws -> CInt { + public static func dup(descriptor: CInt) throws -> CInt { try syscall(blocking: false) { sysDup(descriptor) }.result @@ -740,7 +774,7 @@ internal enum Posix { #if !os(Windows) // It's not really posix but exists on Linux and MacOS / BSD so just put it here for now to keep it simple @inline(never) - internal static func sendfile(descriptor: CInt, fd: CInt, offset: off_t, count: size_t) throws -> IOResult { + public static func sendfile(descriptor: CInt, fd: CInt, offset: off_t, count: size_t) throws -> IOResult { var written: off_t = 0 do { _ = try syscall(blocking: false) { () -> ssize_t in @@ -778,7 +812,7 @@ internal enum Posix { } @inline(never) - internal static func sendmmsg( + public static func sendmmsg( sockfd: CInt, msgvec: UnsafeMutablePointer, vlen: CUnsignedInt, @@ -790,7 +824,7 @@ internal enum Posix { } @inline(never) - internal static func recvmmsg( + public static func recvmmsg( sockfd: CInt, msgvec: UnsafeMutablePointer, vlen: CUnsignedInt, @@ -803,7 +837,7 @@ internal enum Posix { } @inline(never) - internal static func getpeername( + public static func getpeername( socket: CInt, address: UnsafeMutablePointer, addressLength: UnsafeMutablePointer @@ -814,7 +848,7 @@ internal enum Posix { } @inline(never) - internal static func getsockname( + public static func getsockname( socket: CInt, address: UnsafeMutablePointer, addressLength: UnsafeMutablePointer @@ -826,7 +860,7 @@ internal enum Posix { #endif @inline(never) - internal static func if_nametoindex(_ name: UnsafePointer?) throws -> CUnsignedInt { + public static func if_nametoindex(_ name: UnsafePointer?) throws -> CUnsignedInt { try syscall(blocking: false) { sysIfNameToIndex(name!) }.result @@ -834,28 +868,28 @@ internal enum Posix { #if !os(Windows) @inline(never) - internal static func poll(fds: UnsafeMutablePointer, nfds: nfds_t, timeout: CInt) throws -> CInt { + public static func poll(fds: UnsafeMutablePointer, nfds: nfds_t, timeout: CInt) throws -> CInt { try syscall(blocking: false) { sysPoll(fds, nfds, timeout) }.result } @inline(never) - internal static func fstat(descriptor: CInt, outStat: UnsafeMutablePointer) throws { + public static func fstat(descriptor: CInt, outStat: UnsafeMutablePointer) throws { _ = try syscall(blocking: false) { sysFstat(descriptor, outStat) } } @inline(never) - internal static func stat(pathname: String, outStat: UnsafeMutablePointer) throws { + public static func stat(pathname: String, outStat: UnsafeMutablePointer) throws { _ = try syscall(blocking: false) { sysStat(pathname, outStat) } } @inline(never) - internal static func lstat(pathname: String, outStat: UnsafeMutablePointer) throws { + public static func lstat(pathname: String, outStat: UnsafeMutablePointer) throws { _ = try syscall(blocking: false) { sysLstat(pathname, outStat) } @@ -959,7 +993,7 @@ internal enum Posix { } @inline(never) - internal static func socketpair( + public static func socketpair( domain: NIOBSDSocket.ProtocolFamily, type: NIOBSDSocket.SocketType, protocolSubtype: NIOBSDSocket.ProtocolSubtype, @@ -972,7 +1006,7 @@ internal enum Posix { #endif #if !os(Windows) @inline(never) - internal static func ioctl(fd: CInt, request: CUnsignedLong, ptr: UnsafeMutableRawPointer) throws { + public static func ioctl(fd: CInt, request: CUnsignedLong, ptr: UnsafeMutableRawPointer) throws { _ = try syscall(blocking: false) { /// `numericCast` to support musl which accepts `CInt` (cf. `CUnsignedLong`). sysIoctl(fd, numericCast(request), ptr) @@ -997,7 +1031,7 @@ public struct NIOFailedToSetSocketNonBlockingError: Error {} #if !os(Windows) extension Posix { - static func setNonBlocking(socket: CInt) throws { + public static func setNonBlocking(socket: CInt) throws { let flags = try Posix.fcntl(descriptor: socket, command: F_GETFL, value: 0) do { let ret = try Posix.fcntl(descriptor: socket, command: F_SETFL, value: flags | O_NONBLOCK) @@ -1014,12 +1048,13 @@ extension Posix { #endif #if canImport(Darwin) +@usableFromInline internal enum KQueue { // TODO: Figure out how to specify a typealias to the kevent struct without run into trouble with the swift compiler @inline(never) - internal static func kqueue() throws -> CInt { + public static func kqueue() throws -> CInt { try syscall(blocking: false) { Darwin.kqueue() }.result @@ -1027,7 +1062,7 @@ internal enum KQueue { @inline(never) @discardableResult - internal static func kevent( + public static func kevent( kq: CInt, changelist: UnsafePointer?, nchanges: CInt, diff --git a/Sources/NIOPosix/Thread.swift b/Sources/NIOPosix/Thread.swift index f61ab29e1d..a902a1cfa9 100644 --- a/Sources/NIOPosix/Thread.swift +++ b/Sources/NIOPosix/Thread.swift @@ -40,6 +40,7 @@ protocol ThreadOps { /// A Thread that executes some runnable block. /// /// All methods exposed are thread-safe. +@usableFromInline final class NIOThread { internal typealias ThreadBoxValue = (body: (NIOThread) -> Void, name: String?) internal typealias ThreadBox = Box