Move MulticastChannel and NIONetworkDevice to NIOCore (#1931)

Motivation:

MultcastChannel is a general abstraction for expressing multicast
capabilities on a given Channel. This abstraction doesn't have any
particularly tight tie to the POSIX layer, so it belongs in NIOCore.

This is also expressed in terms of NIONetworkDevice, so we need to move
that over. That also encourages us to bring over
System.enumerateDevices, and given that System.coreCount is also fairly
general-purpose we may as well bring it along too.

Modifications:

- Move MulticastChannel to NIOCore
- Move NIONetworkDevice to NIOCore
- Move System to NIOCore

Result:

More general-purpose abstractions in NIOCore.
This commit is contained in:
Cory Benfield 2021-08-06 13:42:29 +01:00 committed by GitHub
parent a7e064d185
commit c1e2f093e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 602 additions and 622 deletions

View File

@ -2,7 +2,7 @@
// //
// This source file is part of the SwiftNIO open source project // 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-2021 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0 // Licensed under Apache License v2.0
// //
// See LICENSE.txt for license information // See LICENSE.txt for license information
@ -118,9 +118,6 @@ internal enum Epoll {
} }
internal enum Linux { internal enum Linux {
static let cfsQuotaPath = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"
static let cfsPeriodPath = "/sys/fs/cgroup/cpu/cpu.cfs_period_us"
static let cpuSetPath = "/sys/fs/cgroup/cpuset/cpuset.cpus"
#if os(Android) #if os(Android)
static let SOCK_CLOEXEC = Glibc.SOCK_CLOEXEC static let SOCK_CLOEXEC = Glibc.SOCK_CLOEXEC
static let SOCK_NONBLOCK = Glibc.SOCK_NONBLOCK static let SOCK_NONBLOCK = Glibc.SOCK_NONBLOCK
@ -140,54 +137,5 @@ internal enum Linux {
} }
return fd return fd
} }
private static func firstLineOfFile(path: String) throws -> Substring {
let fh = try NIOFileHandle(path: path)
defer { try! fh.close() }
// linux doesn't properly report /sys/fs/cgroup/* files lengths so we use a reasonable limit
var buf = ByteBufferAllocator().buffer(capacity: 1024)
try buf.writeWithUnsafeMutableBytes(minimumWritableBytes: buf.capacity) { ptr in
let res = try fh.withUnsafeFileDescriptor { fd -> IOResult<ssize_t> in
return try Posix.read(descriptor: fd, pointer: ptr.baseAddress!, size: ptr.count)
}
switch res {
case .processed(let n):
return n
case .wouldBlock:
preconditionFailure("read returned EWOULDBLOCK despite a blocking fd")
}
}
return String(buffer: buf).prefix(while: { $0 != "\n" })
}
private static func countCoreIds(cores: Substring) -> Int {
let ids = cores.split(separator: "-", maxSplits: 1)
guard
let first = ids.first.flatMap({ Int($0, radix: 10) }),
let last = ids.last.flatMap({ Int($0, radix: 10) }),
last >= first
else { preconditionFailure("cpuset format is incorrect") }
return 1 + last - first
}
static func coreCount(cpuset cpusetPath: String) -> Int? {
guard
let cpuset = try? firstLineOfFile(path: cpusetPath).split(separator: ","),
!cpuset.isEmpty
else { return nil }
return cpuset.map(countCoreIds).reduce(0, +)
}
static func coreCount(quota quotaPath: String, period periodPath: String) -> Int? {
guard
let quota = try? Int(firstLineOfFile(path: quotaPath)),
quota > 0
else { return nil }
guard
let period = try? Int(firstLineOfFile(path: periodPath)),
period > 0
else { return nil }
return (quota - 1 + period) / period // always round up if fractional CPU quota requested
}
} }
#endif #endif

View File

@ -1,367 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
#if os(Linux) || os(FreeBSD) || os(Android)
import CNIOLinux
#endif
#if os(Windows)
import let WinSDK.AF_INET
import let WinSDK.AF_INET6
import let WinSDK.INET_ADDRSTRLEN
import let WinSDK.INET6_ADDRSTRLEN
import struct WinSDK.ADDRESS_FAMILY
import struct WinSDK.IP_ADAPTER_ADDRESSES
import struct WinSDK.IP_ADAPTER_UNICAST_ADDRESS
import typealias WinSDK.UINT8
#endif
#if !os(Windows)
private extension ifaddrs {
var dstaddr: UnsafeMutablePointer<sockaddr>? {
#if os(Linux) || os(Android)
return self.ifa_ifu.ifu_dstaddr
#elseif os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
return self.ifa_dstaddr
#endif
}
var broadaddr: UnsafeMutablePointer<sockaddr>? {
#if os(Linux) || os(Android)
return self.ifa_ifu.ifu_broadaddr
#elseif os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
return self.ifa_dstaddr
#endif
}
}
#endif
/// A representation of a single network device on a system.
public struct NIONetworkDevice {
private var backing: Backing
/// The name of the network device.
public var name: String {
get {
return self.backing.name
}
set {
self.uniquifyIfNeeded()
self.backing.name = newValue
}
}
/// The address associated with the given network device.
public var address: SocketAddress? {
get {
return self.backing.address
}
set {
self.uniquifyIfNeeded()
self.backing.address = newValue
}
}
/// The netmask associated with this address, if any.
public var netmask: SocketAddress? {
get {
return self.backing.netmask
}
set {
self.uniquifyIfNeeded()
self.backing.netmask = newValue
}
}
/// The broadcast address associated with this socket interface, if it has one. Some
/// interfaces do not, especially those that have a `pointToPointDestinationAddress`.
public var broadcastAddress: SocketAddress? {
get {
return self.backing.broadcastAddress
}
set {
self.uniquifyIfNeeded()
self.backing.broadcastAddress = newValue
}
}
/// The address of the peer on a point-to-point interface, if this is one. Some
/// interfaces do not have such an address: most of those have a `broadcastAddress`
/// instead.
public var pointToPointDestinationAddress: SocketAddress? {
get {
return self.backing.pointToPointDestinationAddress
}
set {
self.uniquifyIfNeeded()
self.backing.pointToPointDestinationAddress = newValue
}
}
/// If the Interface supports Multicast
public var multicastSupported: Bool {
get {
return self.backing.multicastSupported
}
set {
self.uniquifyIfNeeded()
self.backing.multicastSupported = newValue
}
}
/// The index of the interface, as provided by `if_nametoindex`.
public var interfaceIndex: Int {
get {
return self.backing.interfaceIndex
}
set {
self.uniquifyIfNeeded()
self.backing.interfaceIndex = newValue
}
}
/// Create a brand new network interface.
///
/// This constructor will fail if NIO does not understand the format of the underlying
/// socket address family. This is quite common: for example, Linux will return AF_PACKET
/// addressed interfaces on most platforms, which NIO does not currently understand.
#if os(Windows)
internal init?(_ pAdapter: UnsafeMutablePointer<IP_ADAPTER_ADDRESSES>,
_ pAddress: UnsafeMutablePointer<IP_ADAPTER_UNICAST_ADDRESS>) {
guard let backing = Backing(pAdapter, pAddress) else {
return nil
}
self.backing = backing
}
#else
internal init?(_ caddr: ifaddrs) {
guard let backing = Backing(caddr) else {
return nil
}
self.backing = backing
}
#endif
#if !os(Windows)
/// Convert a `NIONetworkInterface` to a `NIONetworkDevice`. As `NIONetworkDevice`s are a superset of `NIONetworkInterface`s,
/// it is always possible to perform this conversion.
@available(*, deprecated, message: "This is a compatibility helper, and will be removed in a future release")
public init(_ interface: NIONetworkInterface) {
self.backing = Backing(
name: interface.name,
address: interface.address,
netmask: interface.netmask,
broadcastAddress: interface.broadcastAddress,
pointToPointDestinationAddress: interface.pointToPointDestinationAddress,
multicastSupported: interface.multicastSupported,
interfaceIndex: interface.interfaceIndex
)
}
#endif
public init(name: String,
address: SocketAddress?,
netmask: SocketAddress?,
broadcastAddress: SocketAddress?,
pointToPointDestinationAddress: SocketAddress,
multicastSupported: Bool,
interfaceIndex: Int) {
self.backing = Backing(
name: name,
address: address,
netmask: netmask,
broadcastAddress: broadcastAddress,
pointToPointDestinationAddress: pointToPointDestinationAddress,
multicastSupported: multicastSupported,
interfaceIndex: interfaceIndex
)
}
private mutating func uniquifyIfNeeded() {
if !isKnownUniquelyReferenced(&self.backing) {
self.backing = Backing(copying: self.backing)
}
}
}
extension NIONetworkDevice {
fileprivate final class Backing {
/// The name of the network interface.
var name: String
/// The address associated with the given network interface.
var address: SocketAddress?
/// The netmask associated with this address, if any.
var netmask: SocketAddress?
/// The broadcast address associated with this socket interface, if it has one. Some
/// interfaces do not, especially those that have a `pointToPointDestinationAddress`.
var broadcastAddress: SocketAddress?
/// The address of the peer on a point-to-point interface, if this is one. Some
/// interfaces do not have such an address: most of those have a `broadcastAddress`
/// instead.
var pointToPointDestinationAddress: SocketAddress?
/// If the Interface supports Multicast
var multicastSupported: Bool
/// The index of the interface, as provided by `if_nametoindex`.
var interfaceIndex: Int
/// Create a brand new network interface.
///
/// This constructor will fail if NIO does not understand the format of the underlying
/// socket address family. This is quite common: for example, Linux will return AF_PACKET
/// addressed interfaces on most platforms, which NIO does not currently understand.
#if os(Windows)
internal init?(_ pAdapter: UnsafeMutablePointer<IP_ADAPTER_ADDRESSES>,
_ pAddress: UnsafeMutablePointer<IP_ADAPTER_UNICAST_ADDRESS>) {
self.name = String(decodingCString: pAdapter.pointee.FriendlyName,
as: UTF16.self)
self.address = pAddress.pointee.Address.lpSockaddr.convert()
// TODO: convert the prefix length to the mask itself
let v4mask: (UINT8) -> SocketAddress? = { _ in
var buffer: [CChar] =
Array<CChar>(repeating: 0, count: Int(INET_ADDRSTRLEN))
var mask: sockaddr_in = sockaddr_in()
mask.sin_family = ADDRESS_FAMILY(AF_INET)
_ = buffer.withUnsafeMutableBufferPointer {
try! NIOBSDSocket.inet_ntop(af: .inet, src: &mask,
dst: $0.baseAddress!,
size: INET_ADDRSTRLEN)
}
return SocketAddress(mask)
}
let v6mask: (UINT8) -> SocketAddress? = { _ in
var buffer: [CChar] =
Array<CChar>(repeating: 0, count: Int(INET6_ADDRSTRLEN))
var mask: sockaddr_in6 = sockaddr_in6()
mask.sin6_family = ADDRESS_FAMILY(AF_INET6)
_ = buffer.withUnsafeMutableBufferPointer {
try! NIOBSDSocket.inet_ntop(af: .inet6, src: &mask,
dst: $0.baseAddress!,
size: INET6_ADDRSTRLEN)
}
return SocketAddress(mask)
}
switch pAddress.pointee.Address.lpSockaddr.pointee.sa_family {
case ADDRESS_FAMILY(AF_INET):
self.netmask = v4mask(pAddress.pointee.OnLinkPrefixLength)
self.interfaceIndex = Int(pAdapter.pointee.IfIndex)
break
case ADDRESS_FAMILY(AF_INET6):
self.netmask = v6mask(pAddress.pointee.OnLinkPrefixLength)
self.interfaceIndex = Int(pAdapter.pointee.Ipv6IfIndex)
break
default:
return nil
}
// TODO(compnerd) handle broadcast/ppp/multicast information
self.broadcastAddress = nil
self.pointToPointDestinationAddress = nil
self.multicastSupported = false
}
#else
internal init?(_ caddr: ifaddrs) {
self.name = String(cString: caddr.ifa_name)
self.address = caddr.ifa_addr.flatMap { $0.convert() }
self.netmask = caddr.ifa_netmask.flatMap { $0.convert() }
if (caddr.ifa_flags & UInt32(IFF_BROADCAST)) != 0, let addr = caddr.broadaddr {
self.broadcastAddress = addr.convert()
self.pointToPointDestinationAddress = nil
} else if (caddr.ifa_flags & UInt32(IFF_POINTOPOINT)) != 0, let addr = caddr.dstaddr {
self.broadcastAddress = nil
self.pointToPointDestinationAddress = addr.convert()
} else {
self.broadcastAddress = nil
self.pointToPointDestinationAddress = nil
}
self.multicastSupported = (caddr.ifa_flags & UInt32(IFF_MULTICAST)) != 0
do {
self.interfaceIndex = Int(try Posix.if_nametoindex(caddr.ifa_name))
} catch {
return nil
}
}
#endif
init(copying original: Backing) {
self.name = original.name
self.address = original.address
self.netmask = original.netmask
self.broadcastAddress = original.broadcastAddress
self.pointToPointDestinationAddress = original.pointToPointDestinationAddress
self.multicastSupported = original.multicastSupported
self.interfaceIndex = original.interfaceIndex
}
init(name: String,
address: SocketAddress?,
netmask: SocketAddress?,
broadcastAddress: SocketAddress?,
pointToPointDestinationAddress: SocketAddress?,
multicastSupported: Bool,
interfaceIndex: Int) {
self.name = name
self.address = address
self.netmask = netmask
self.broadcastAddress = broadcastAddress
self.pointToPointDestinationAddress = pointToPointDestinationAddress
self.multicastSupported = multicastSupported
self.interfaceIndex = interfaceIndex
}
}
}
extension NIONetworkDevice: CustomDebugStringConvertible {
public var debugDescription: String {
let baseString = "Device \(self.name): address \(String(describing: self.address))"
let maskString = self.netmask != nil ? " netmask \(self.netmask!)" : ""
return baseString + maskString
}
}
// Sadly, as this is class-backed we cannot synthesise the implementation.
extension NIONetworkDevice: Equatable {
public static func ==(lhs: NIONetworkDevice, rhs: NIONetworkDevice) -> Bool {
return lhs.name == rhs.name &&
lhs.address == rhs.address &&
lhs.netmask == rhs.netmask &&
lhs.broadcastAddress == rhs.broadcastAddress &&
lhs.pointToPointDestinationAddress == rhs.pointToPointDestinationAddress &&
lhs.interfaceIndex == rhs.interfaceIndex
}
}
extension NIONetworkDevice: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(self.name)
hasher.combine(self.address)
hasher.combine(self.netmask)
hasher.combine(self.broadcastAddress)
hasher.combine(self.pointToPointDestinationAddress)
hasher.combine(self.interfaceIndex)
}
}

View File

@ -49,17 +49,3 @@ internal protocol SelectableChannel: Channel {
func reregister(selector: Selector<NIORegistration>, interested: SelectorEventSet) throws func reregister(selector: Selector<NIORegistration>, interested: SelectorEventSet) throws
} }
/// Multicast is not supported on this interface.
public struct NIOMulticastNotSupportedError: Error {
public var device: NIONetworkDevice
public init(device: NIONetworkDevice) {
self.device = device
}
}
/// Multicast has not been properly implemented on this channel.
public struct NIOMulticastNotImplementedError: Error {
public init() {}
}

View File

@ -99,7 +99,6 @@ private let sysDup: @convention(c) (CInt) -> CInt = dup
#if !os(Windows) #if !os(Windows)
private let sysGetpeername: @convention(c) (CInt, UnsafeMutablePointer<sockaddr>?, UnsafeMutablePointer<socklen_t>?) -> CInt = getpeername private let sysGetpeername: @convention(c) (CInt, UnsafeMutablePointer<sockaddr>?, UnsafeMutablePointer<socklen_t>?) -> CInt = getpeername
private let sysGetsockname: @convention(c) (CInt, UnsafeMutablePointer<sockaddr>?, UnsafeMutablePointer<socklen_t>?) -> CInt = getsockname private let sysGetsockname: @convention(c) (CInt, UnsafeMutablePointer<sockaddr>?, UnsafeMutablePointer<socklen_t>?) -> CInt = getsockname
private let sysGetifaddrs: @convention(c) (UnsafeMutablePointer<UnsafeMutablePointer<ifaddrs>?>?) -> CInt = getifaddrs
#endif #endif
private let sysFreeifaddrs: @convention(c) (UnsafeMutablePointer<ifaddrs>?) -> Void = freeifaddrs private let sysFreeifaddrs: @convention(c) (UnsafeMutablePointer<ifaddrs>?) -> Void = freeifaddrs
private let sysIfNameToIndex: @convention(c) (UnsafePointer<CChar>?) -> CUnsignedInt = if_nametoindex private let sysIfNameToIndex: @convention(c) (UnsafePointer<CChar>?) -> CUnsignedInt = if_nametoindex
@ -488,13 +487,6 @@ internal enum Posix {
return sysGetsockname(socket, address, addressLength) return sysGetsockname(socket, address, addressLength)
} }
} }
@inline(never)
internal static func getifaddrs(_ addrs: UnsafeMutablePointer<UnsafeMutablePointer<ifaddrs>?>) throws {
_ = try syscall(blocking: false) {
sysGetifaddrs(addrs)
}
}
#endif #endif
@inline(never) @inline(never)

View File

@ -2,7 +2,7 @@
// //
// This source file is part of the SwiftNIO open source project // 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-2021 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0 // Licensed under Apache License v2.0
// //
// See LICENSE.txt for license information // See LICENSE.txt for license information
@ -12,28 +12,6 @@
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
#if os(Linux) || os(FreeBSD) || os(Android)
import CNIOLinux
#endif
#if os(Windows)
import let WinSDK.RelationProcessorCore
import let WinSDK.AF_UNSPEC
import let WinSDK.ERROR_SUCCESS
import func WinSDK.GetAdaptersAddresses
import func WinSDK.GetLastError
import func WinSDK.GetLogicalProcessorInformation
import struct WinSDK.IP_ADAPTER_ADDRESSES
import struct WinSDK.IP_ADAPTER_UNICAST_ADDRESS
import struct WinSDK.SYSTEM_LOGICAL_PROCESSOR_INFORMATION
import struct WinSDK.ULONG
import typealias WinSDK.DWORD
#endif
/// A utility function that runs the body code only in debug builds, without /// A utility function that runs the body code only in debug builds, without
/// emitting compiler warnings. /// emitting compiler warnings.
/// ///
@ -49,151 +27,3 @@ final class Box<T> {
let value: T let value: T
init(_ value: T) { self.value = value } init(_ value: T) { self.value = value }
} }
public enum System {
/// A utility function that returns an estimate of the number of *logical* cores
/// on the system.
///
/// This value can be used to help provide an estimate of how many threads to use with
/// the `MultiThreadedEventLoopGroup`. The exact ratio between this number and the number
/// of threads to use is a matter for the programmer, and can be determined based on the
/// specific execution behaviour of the program.
///
/// - returns: The logical core count on the system.
public static var coreCount: Int {
#if os(Windows)
var dwLength: DWORD = 0
_ = GetLogicalProcessorInformation(nil, &dwLength)
let alignment: Int =
MemoryLayout<SYSTEM_LOGICAL_PROCESSOR_INFORMATION>.alignment
let pBuffer: UnsafeMutableRawPointer =
UnsafeMutableRawPointer.allocate(byteCount: Int(dwLength),
alignment: alignment)
defer {
pBuffer.deallocate()
}
let dwSLPICount: Int =
Int(dwLength) / MemoryLayout<SYSTEM_LOGICAL_PROCESSOR_INFORMATION>.stride
let pSLPI: UnsafeMutablePointer<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> =
pBuffer.bindMemory(to: SYSTEM_LOGICAL_PROCESSOR_INFORMATION.self,
capacity: dwSLPICount)
let bResult: Bool = GetLogicalProcessorInformation(pSLPI, &dwLength)
precondition(bResult, "GetLogicalProcessorInformation: \(GetLastError())")
return UnsafeBufferPointer<SYSTEM_LOGICAL_PROCESSOR_INFORMATION>(start: pSLPI,
count: dwSLPICount)
.filter { $0.Relationship == RelationProcessorCore }
.map { $0.ProcessorMask.nonzeroBitCount }
.reduce(0, +)
#elseif os(Linux) || os(Android)
if let quota = Linux.coreCount(quota: Linux.cfsQuotaPath, period: Linux.cfsPeriodPath) {
return quota
} else if let cpusetCount = Linux.coreCount(cpuset: Linux.cpuSetPath) {
return cpusetCount
} else {
return sysconf(CInt(_SC_NPROCESSORS_ONLN))
}
#else
return sysconf(CInt(_SC_NPROCESSORS_ONLN))
#endif
}
#if !os(Windows)
/// A utility function that enumerates the available network interfaces on this machine.
///
/// This function returns values that are true for a brief snapshot in time. These results can
/// change, and the returned values will not change to reflect them. This function must be called
/// again to get new results.
///
/// - returns: An array of network interfaces available on this machine.
/// - throws: If an error is encountered while enumerating interfaces.
@available(*, deprecated, renamed: "enumerateDevices")
public static func enumerateInterfaces() throws -> [NIONetworkInterface] {
var interfaces: [NIONetworkInterface] = []
interfaces.reserveCapacity(12) // Arbitrary choice.
var interface: UnsafeMutablePointer<ifaddrs>? = nil
try Posix.getifaddrs(&interface)
let originalInterface = interface
defer {
freeifaddrs(originalInterface)
}
while let concreteInterface = interface {
if let nioInterface = NIONetworkInterface._construct(from: concreteInterface.pointee) {
interfaces.append(nioInterface)
}
interface = concreteInterface.pointee.ifa_next
}
return interfaces
}
#endif
/// A utility function that enumerates the available network devices on this machine.
///
/// This function returns values that are true for a brief snapshot in time. These results can
/// change, and the returned values will not change to reflect them. This function must be called
/// again to get new results.
///
/// - returns: An array of network devices available on this machine.
/// - throws: If an error is encountered while enumerating interfaces.
public static func enumerateDevices() throws -> [NIONetworkDevice] {
var devices: [NIONetworkDevice] = []
devices.reserveCapacity(12) // Arbitrary choice.
#if os(Windows)
var ulSize: ULONG = 0
_ = GetAdaptersAddresses(ULONG(AF_UNSPEC), 0, nil, nil, &ulSize)
let stride: Int = MemoryLayout<IP_ADAPTER_ADDRESSES>.stride
let pBuffer: UnsafeMutableBufferPointer<IP_ADAPTER_ADDRESSES> =
UnsafeMutableBufferPointer.allocate(capacity: Int(ulSize) / stride)
defer {
pBuffer.deallocate()
}
let ulResult: ULONG =
GetAdaptersAddresses(ULONG(AF_UNSPEC), 0, nil, pBuffer.baseAddress,
&ulSize)
guard ulResult == ERROR_SUCCESS else {
throw IOError(windows: ulResult, reason: "GetAdaptersAddresses")
}
var pAdapter: UnsafeMutablePointer<IP_ADAPTER_ADDRESSES>? =
UnsafeMutablePointer(pBuffer.baseAddress)
while pAdapter != nil {
let pUnicastAddresses: UnsafeMutablePointer<IP_ADAPTER_UNICAST_ADDRESS>? =
pAdapter!.pointee.FirstUnicastAddress
var pUnicastAddress: UnsafeMutablePointer<IP_ADAPTER_UNICAST_ADDRESS>? =
pUnicastAddresses
while pUnicastAddress != nil {
if let device = NIONetworkDevice(pAdapter!, pUnicastAddress!) {
devices.append(device)
}
pUnicastAddress = pUnicastAddress!.pointee.Next
}
pAdapter = pAdapter!.pointee.Next
}
#else
var interface: UnsafeMutablePointer<ifaddrs>? = nil
try Posix.getifaddrs(&interface)
let originalInterface = interface
defer {
freeifaddrs(originalInterface)
}
while let concreteInterface = interface {
if let nioInterface = NIONetworkDevice(concreteInterface.pointee) {
devices.append(nioInterface)
}
interface = concreteInterface.pointee.ifa_next
}
#endif
return devices
}
}

View File

@ -81,7 +81,7 @@ public final class NIONetworkInterface {
/// The index of the interface, as provided by `if_nametoindex`. /// The index of the interface, as provided by `if_nametoindex`.
public let interfaceIndex: Int public let interfaceIndex: Int
fileprivate init?(_ caddr: ifaddrs) { internal init?(_ caddr: ifaddrs) {
self.name = String(cString: caddr.ifa_name) self.name = String(cString: caddr.ifa_name)
guard caddr.ifa_addr != nil else { guard caddr.ifa_addr != nil else {
@ -122,12 +122,6 @@ public final class NIONetworkInterface {
return nil return nil
} }
} }
// This is public just so we can avoid needing to pull over any of the System helpers: they can
// construct this type directly. Ideally, we'd have avoided even needing this in NIOCore.
public static func _construct(from caddr: ifaddrs) -> NIONetworkInterface? {
return NIONetworkInterface(caddr)
}
} }
@available(*, deprecated, renamed: "NIONetworkDevice") @available(*, deprecated, renamed: "NIONetworkDevice")
@ -173,3 +167,320 @@ extension UnsafeMutablePointer where Pointee == sockaddr {
} }
} }
} }
/// A representation of a single network device on a system.
public struct NIONetworkDevice {
private var backing: Backing
/// The name of the network device.
public var name: String {
get {
return self.backing.name
}
set {
self.uniquifyIfNeeded()
self.backing.name = newValue
}
}
/// The address associated with the given network device.
public var address: SocketAddress? {
get {
return self.backing.address
}
set {
self.uniquifyIfNeeded()
self.backing.address = newValue
}
}
/// The netmask associated with this address, if any.
public var netmask: SocketAddress? {
get {
return self.backing.netmask
}
set {
self.uniquifyIfNeeded()
self.backing.netmask = newValue
}
}
/// The broadcast address associated with this socket interface, if it has one. Some
/// interfaces do not, especially those that have a `pointToPointDestinationAddress`.
public var broadcastAddress: SocketAddress? {
get {
return self.backing.broadcastAddress
}
set {
self.uniquifyIfNeeded()
self.backing.broadcastAddress = newValue
}
}
/// The address of the peer on a point-to-point interface, if this is one. Some
/// interfaces do not have such an address: most of those have a `broadcastAddress`
/// instead.
public var pointToPointDestinationAddress: SocketAddress? {
get {
return self.backing.pointToPointDestinationAddress
}
set {
self.uniquifyIfNeeded()
self.backing.pointToPointDestinationAddress = newValue
}
}
/// If the Interface supports Multicast
public var multicastSupported: Bool {
get {
return self.backing.multicastSupported
}
set {
self.uniquifyIfNeeded()
self.backing.multicastSupported = newValue
}
}
/// The index of the interface, as provided by `if_nametoindex`.
public var interfaceIndex: Int {
get {
return self.backing.interfaceIndex
}
set {
self.uniquifyIfNeeded()
self.backing.interfaceIndex = newValue
}
}
/// Create a brand new network interface.
///
/// This constructor will fail if NIO does not understand the format of the underlying
/// socket address family. This is quite common: for example, Linux will return AF_PACKET
/// addressed interfaces on most platforms, which NIO does not currently understand.
#if os(Windows)
internal init?(_ pAdapter: UnsafeMutablePointer<IP_ADAPTER_ADDRESSES>,
_ pAddress: UnsafeMutablePointer<IP_ADAPTER_UNICAST_ADDRESS>) {
guard let backing = Backing(pAdapter, pAddress) else {
return nil
}
self.backing = backing
}
#else
internal init?(_ caddr: ifaddrs) {
guard let backing = Backing(caddr) else {
return nil
}
self.backing = backing
}
#endif
#if !os(Windows)
/// Convert a `NIONetworkInterface` to a `NIONetworkDevice`. As `NIONetworkDevice`s are a superset of `NIONetworkInterface`s,
/// it is always possible to perform this conversion.
@available(*, deprecated, message: "This is a compatibility helper, and will be removed in a future release")
public init(_ interface: NIONetworkInterface) {
self.backing = Backing(
name: interface.name,
address: interface.address,
netmask: interface.netmask,
broadcastAddress: interface.broadcastAddress,
pointToPointDestinationAddress: interface.pointToPointDestinationAddress,
multicastSupported: interface.multicastSupported,
interfaceIndex: interface.interfaceIndex
)
}
#endif
public init(name: String,
address: SocketAddress?,
netmask: SocketAddress?,
broadcastAddress: SocketAddress?,
pointToPointDestinationAddress: SocketAddress,
multicastSupported: Bool,
interfaceIndex: Int) {
self.backing = Backing(
name: name,
address: address,
netmask: netmask,
broadcastAddress: broadcastAddress,
pointToPointDestinationAddress: pointToPointDestinationAddress,
multicastSupported: multicastSupported,
interfaceIndex: interfaceIndex
)
}
private mutating func uniquifyIfNeeded() {
if !isKnownUniquelyReferenced(&self.backing) {
self.backing = Backing(copying: self.backing)
}
}
}
extension NIONetworkDevice {
fileprivate final class Backing {
/// The name of the network interface.
var name: String
/// The address associated with the given network interface.
var address: SocketAddress?
/// The netmask associated with this address, if any.
var netmask: SocketAddress?
/// The broadcast address associated with this socket interface, if it has one. Some
/// interfaces do not, especially those that have a `pointToPointDestinationAddress`.
var broadcastAddress: SocketAddress?
/// The address of the peer on a point-to-point interface, if this is one. Some
/// interfaces do not have such an address: most of those have a `broadcastAddress`
/// instead.
var pointToPointDestinationAddress: SocketAddress?
/// If the Interface supports Multicast
var multicastSupported: Bool
/// The index of the interface, as provided by `if_nametoindex`.
var interfaceIndex: Int
/// Create a brand new network interface.
///
/// This constructor will fail if NIO does not understand the format of the underlying
/// socket address family. This is quite common: for example, Linux will return AF_PACKET
/// addressed interfaces on most platforms, which NIO does not currently understand.
#if os(Windows)
internal init?(_ pAdapter: UnsafeMutablePointer<IP_ADAPTER_ADDRESSES>,
_ pAddress: UnsafeMutablePointer<IP_ADAPTER_UNICAST_ADDRESS>) {
self.name = String(decodingCString: pAdapter.pointee.FriendlyName,
as: UTF16.self)
self.address = pAddress.pointee.Address.lpSockaddr.convert()
// TODO: convert the prefix length to the mask itself
let v4mask: (UINT8) -> SocketAddress? = { _ in
var buffer: [CChar] =
Array<CChar>(repeating: 0, count: Int(INET_ADDRSTRLEN))
var mask: sockaddr_in = sockaddr_in()
mask.sin_family = ADDRESS_FAMILY(AF_INET)
_ = buffer.withUnsafeMutableBufferPointer {
try! NIOBSDSocket.inet_ntop(af: .inet, src: &mask,
dst: $0.baseAddress!,
size: INET_ADDRSTRLEN)
}
return SocketAddress(mask)
}
let v6mask: (UINT8) -> SocketAddress? = { _ in
var buffer: [CChar] =
Array<CChar>(repeating: 0, count: Int(INET6_ADDRSTRLEN))
var mask: sockaddr_in6 = sockaddr_in6()
mask.sin6_family = ADDRESS_FAMILY(AF_INET6)
_ = buffer.withUnsafeMutableBufferPointer {
try! NIOBSDSocket.inet_ntop(af: .inet6, src: &mask,
dst: $0.baseAddress!,
size: INET6_ADDRSTRLEN)
}
return SocketAddress(mask)
}
switch pAddress.pointee.Address.lpSockaddr.pointee.sa_family {
case ADDRESS_FAMILY(AF_INET):
self.netmask = v4mask(pAddress.pointee.OnLinkPrefixLength)
self.interfaceIndex = Int(pAdapter.pointee.IfIndex)
break
case ADDRESS_FAMILY(AF_INET6):
self.netmask = v6mask(pAddress.pointee.OnLinkPrefixLength)
self.interfaceIndex = Int(pAdapter.pointee.Ipv6IfIndex)
break
default:
return nil
}
// TODO(compnerd) handle broadcast/ppp/multicast information
self.broadcastAddress = nil
self.pointToPointDestinationAddress = nil
self.multicastSupported = false
}
#else
internal init?(_ caddr: ifaddrs) {
self.name = String(cString: caddr.ifa_name)
self.address = caddr.ifa_addr.flatMap { $0.convert() }
self.netmask = caddr.ifa_netmask.flatMap { $0.convert() }
if (caddr.ifa_flags & UInt32(IFF_BROADCAST)) != 0, let addr = caddr.broadaddr {
self.broadcastAddress = addr.convert()
self.pointToPointDestinationAddress = nil
} else if (caddr.ifa_flags & UInt32(IFF_POINTOPOINT)) != 0, let addr = caddr.dstaddr {
self.broadcastAddress = nil
self.pointToPointDestinationAddress = addr.convert()
} else {
self.broadcastAddress = nil
self.pointToPointDestinationAddress = nil
}
self.multicastSupported = (caddr.ifa_flags & UInt32(IFF_MULTICAST)) != 0
do {
self.interfaceIndex = Int(try SystemCalls.if_nametoindex(caddr.ifa_name))
} catch {
return nil
}
}
#endif
init(copying original: Backing) {
self.name = original.name
self.address = original.address
self.netmask = original.netmask
self.broadcastAddress = original.broadcastAddress
self.pointToPointDestinationAddress = original.pointToPointDestinationAddress
self.multicastSupported = original.multicastSupported
self.interfaceIndex = original.interfaceIndex
}
init(name: String,
address: SocketAddress?,
netmask: SocketAddress?,
broadcastAddress: SocketAddress?,
pointToPointDestinationAddress: SocketAddress?,
multicastSupported: Bool,
interfaceIndex: Int) {
self.name = name
self.address = address
self.netmask = netmask
self.broadcastAddress = broadcastAddress
self.pointToPointDestinationAddress = pointToPointDestinationAddress
self.multicastSupported = multicastSupported
self.interfaceIndex = interfaceIndex
}
}
}
extension NIONetworkDevice: CustomDebugStringConvertible {
public var debugDescription: String {
let baseString = "Device \(self.name): address \(String(describing: self.address))"
let maskString = self.netmask != nil ? " netmask \(self.netmask!)" : ""
return baseString + maskString
}
}
// Sadly, as this is class-backed we cannot synthesise the implementation.
extension NIONetworkDevice: Equatable {
public static func ==(lhs: NIONetworkDevice, rhs: NIONetworkDevice) -> Bool {
return lhs.name == rhs.name &&
lhs.address == rhs.address &&
lhs.netmask == rhs.netmask &&
lhs.broadcastAddress == rhs.broadcastAddress &&
lhs.pointToPointDestinationAddress == rhs.pointToPointDestinationAddress &&
lhs.interfaceIndex == rhs.interfaceIndex
}
}
extension NIONetworkDevice: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(self.name)
hasher.combine(self.address)
hasher.combine(self.netmask)
hasher.combine(self.broadcastAddress)
hasher.combine(self.pointToPointDestinationAddress)
hasher.combine(self.interfaceIndex)
}
}

View File

@ -0,0 +1,74 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
// This is a companion to System.swift that provides only Linux specials: either things that exist
// only on Linux, or things that have Linux-specific extensions.
#if os(Linux) || os(Android)
import CNIOLinux
enum Linux {
static let cfsQuotaPath = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"
static let cfsPeriodPath = "/sys/fs/cgroup/cpu/cpu.cfs_period_us"
static let cpuSetPath = "/sys/fs/cgroup/cpuset/cpuset.cpus"
private static func firstLineOfFile(path: String) throws -> Substring {
let fh = try NIOFileHandle(path: path)
defer { try! fh.close() }
// linux doesn't properly report /sys/fs/cgroup/* files lengths so we use a reasonable limit
var buf = ByteBufferAllocator().buffer(capacity: 1024)
try buf.writeWithUnsafeMutableBytes(minimumWritableBytes: buf.capacity) { ptr in
let res = try fh.withUnsafeFileDescriptor { fd -> CoreIOResult<ssize_t> in
return try SystemCalls.read(descriptor: fd, pointer: ptr.baseAddress!, size: ptr.count)
}
switch res {
case .processed(let n):
return n
case .wouldBlock:
preconditionFailure("read returned EWOULDBLOCK despite a blocking fd")
}
}
return String(buffer: buf).prefix(while: { $0 != "\n" })
}
private static func countCoreIds(cores: Substring) -> Int {
let ids = cores.split(separator: "-", maxSplits: 1)
guard
let first = ids.first.flatMap({ Int($0, radix: 10) }),
let last = ids.last.flatMap({ Int($0, radix: 10) }),
last >= first
else { preconditionFailure("cpuset format is incorrect") }
return 1 + last - first
}
static func coreCount(cpuset cpusetPath: String) -> Int? {
guard
let cpuset = try? firstLineOfFile(path: cpusetPath).split(separator: ","),
!cpuset.isEmpty
else { return nil }
return cpuset.map(countCoreIds).reduce(0, +)
}
static func coreCount(quota quotaPath: String, period periodPath: String) -> Int? {
guard
let quota = try? Int(firstLineOfFile(path: quotaPath)),
quota > 0
else { return nil }
guard
let period = try? Int(firstLineOfFile(path: periodPath)),
period > 0
else { return nil }
return (quota - 1 + period) / period // always round up if fractional CPU quota requested
}
}
#endif

View File

@ -162,3 +162,18 @@ extension MulticastChannel {
promise?.fail(NIOMulticastNotImplementedError()) promise?.fail(NIOMulticastNotImplementedError())
} }
} }
/// Multicast is not supported on this interface.
public struct NIOMulticastNotSupportedError: Error {
public var device: NIONetworkDevice
public init(device: NIONetworkDevice) {
self.device = device
}
}
/// Multicast has not been properly implemented on this channel.
public struct NIOMulticastNotImplementedError: Error {
public init() {}
}

View File

@ -33,8 +33,13 @@ private let sysDup: @convention(c) (CInt) -> CInt = dup
private let sysClose: @convention(c) (CInt) -> CInt = close private let sysClose: @convention(c) (CInt) -> CInt = close
private let sysOpenWithMode: @convention(c) (UnsafePointer<CChar>, CInt, mode_t) -> CInt = open private let sysOpenWithMode: @convention(c) (UnsafePointer<CChar>, CInt, mode_t) -> CInt = open
private let sysLseek: @convention(c) (CInt, off_t, CInt) -> off_t = lseek private let sysLseek: @convention(c) (CInt, off_t, CInt) -> off_t = lseek
private let sysRead: @convention(c) (CInt, UnsafeMutableRawPointer?, size_t) -> size_t = read
private let sysIfNameToIndex: @convention(c) (UnsafePointer<CChar>?) -> CUnsignedInt = if_nametoindex private let sysIfNameToIndex: @convention(c) (UnsafePointer<CChar>?) -> CUnsignedInt = if_nametoindex
#if !os(Windows)
private let sysGetifaddrs: @convention(c) (UnsafeMutablePointer<UnsafeMutablePointer<ifaddrs>?>?) -> CInt = getifaddrs
#endif
private func isUnacceptableErrno(_ code: Int32) -> Bool { private func isUnacceptableErrno(_ code: Int32) -> Bool {
switch code { switch code {
case EFAULT, EBADF: case EFAULT, EBADF:
@ -121,10 +126,26 @@ enum SystemCalls {
}.result }.result
} }
@inline(never)
internal static func read(descriptor: CInt, pointer: UnsafeMutableRawPointer, size: size_t) throws -> CoreIOResult<ssize_t> {
return try syscall(blocking: true) {
sysRead(descriptor, pointer, size)
}
}
@inline(never) @inline(never)
internal static func if_nametoindex(_ name: UnsafePointer<CChar>?) throws -> CUnsignedInt { internal static func if_nametoindex(_ name: UnsafePointer<CChar>?) throws -> CUnsignedInt {
return try syscall(blocking: false) { return try syscall(blocking: false) {
sysIfNameToIndex(name) sysIfNameToIndex(name)
}.result }.result
} }
#if !os(Windows)
@inline(never)
internal static func getifaddrs(_ addrs: UnsafeMutablePointer<UnsafeMutablePointer<ifaddrs>?>) throws {
_ = try syscall(blocking: false) {
sysGetifaddrs(addrs)
}
}
#endif
} }

View File

@ -11,6 +11,28 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
#if os(Linux) || os(FreeBSD) || os(Android)
import CNIOLinux
import Glibc
#elseif os(Windows)
import let WinSDK.RelationProcessorCore
import let WinSDK.AF_UNSPEC
import let WinSDK.ERROR_SUCCESS
import func WinSDK.GetAdaptersAddresses
import func WinSDK.GetLastError
import func WinSDK.GetLogicalProcessorInformation
import struct WinSDK.IP_ADAPTER_ADDRESSES
import struct WinSDK.IP_ADAPTER_UNICAST_ADDRESS
import struct WinSDK.SYSTEM_LOGICAL_PROCESSOR_INFORMATION
import struct WinSDK.ULONG
import typealias WinSDK.DWORD
#elseif os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import Darwin
#endif
/// A utility function that runs the body code only in debug builds, without /// A utility function that runs the body code only in debug builds, without
/// emitting compiler warnings. /// emitting compiler warnings.
@ -30,3 +52,151 @@ final class Box<T> {
init(_ value: T) { self.value = value } init(_ value: T) { self.value = value }
} }
public enum System {
/// A utility function that returns an estimate of the number of *logical* cores
/// on the system.
///
/// This value can be used to help provide an estimate of how many threads to use with
/// the `MultiThreadedEventLoopGroup`. The exact ratio between this number and the number
/// of threads to use is a matter for the programmer, and can be determined based on the
/// specific execution behaviour of the program.
///
/// - returns: The logical core count on the system.
public static var coreCount: Int {
#if os(Windows)
var dwLength: DWORD = 0
_ = GetLogicalProcessorInformation(nil, &dwLength)
let alignment: Int =
MemoryLayout<SYSTEM_LOGICAL_PROCESSOR_INFORMATION>.alignment
let pBuffer: UnsafeMutableRawPointer =
UnsafeMutableRawPointer.allocate(byteCount: Int(dwLength),
alignment: alignment)
defer {
pBuffer.deallocate()
}
let dwSLPICount: Int =
Int(dwLength) / MemoryLayout<SYSTEM_LOGICAL_PROCESSOR_INFORMATION>.stride
let pSLPI: UnsafeMutablePointer<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> =
pBuffer.bindMemory(to: SYSTEM_LOGICAL_PROCESSOR_INFORMATION.self,
capacity: dwSLPICount)
let bResult: Bool = GetLogicalProcessorInformation(pSLPI, &dwLength)
precondition(bResult, "GetLogicalProcessorInformation: \(GetLastError())")
return UnsafeBufferPointer<SYSTEM_LOGICAL_PROCESSOR_INFORMATION>(start: pSLPI,
count: dwSLPICount)
.filter { $0.Relationship == RelationProcessorCore }
.map { $0.ProcessorMask.nonzeroBitCount }
.reduce(0, +)
#elseif os(Linux) || os(Android)
if let quota = Linux.coreCount(quota: Linux.cfsQuotaPath, period: Linux.cfsPeriodPath) {
return quota
} else if let cpusetCount = Linux.coreCount(cpuset: Linux.cpuSetPath) {
return cpusetCount
} else {
return sysconf(CInt(_SC_NPROCESSORS_ONLN))
}
#else
return sysconf(CInt(_SC_NPROCESSORS_ONLN))
#endif
}
#if !os(Windows)
/// A utility function that enumerates the available network interfaces on this machine.
///
/// This function returns values that are true for a brief snapshot in time. These results can
/// change, and the returned values will not change to reflect them. This function must be called
/// again to get new results.
///
/// - returns: An array of network interfaces available on this machine.
/// - throws: If an error is encountered while enumerating interfaces.
@available(*, deprecated, renamed: "enumerateDevices")
public static func enumerateInterfaces() throws -> [NIONetworkInterface] {
var interfaces: [NIONetworkInterface] = []
interfaces.reserveCapacity(12) // Arbitrary choice.
var interface: UnsafeMutablePointer<ifaddrs>? = nil
try SystemCalls.getifaddrs(&interface)
let originalInterface = interface
defer {
freeifaddrs(originalInterface)
}
while let concreteInterface = interface {
if let nioInterface = NIONetworkInterface(concreteInterface.pointee) {
interfaces.append(nioInterface)
}
interface = concreteInterface.pointee.ifa_next
}
return interfaces
}
#endif
/// A utility function that enumerates the available network devices on this machine.
///
/// This function returns values that are true for a brief snapshot in time. These results can
/// change, and the returned values will not change to reflect them. This function must be called
/// again to get new results.
///
/// - returns: An array of network devices available on this machine.
/// - throws: If an error is encountered while enumerating interfaces.
public static func enumerateDevices() throws -> [NIONetworkDevice] {
var devices: [NIONetworkDevice] = []
devices.reserveCapacity(12) // Arbitrary choice.
#if os(Windows)
var ulSize: ULONG = 0
_ = GetAdaptersAddresses(ULONG(AF_UNSPEC), 0, nil, nil, &ulSize)
let stride: Int = MemoryLayout<IP_ADAPTER_ADDRESSES>.stride
let pBuffer: UnsafeMutableBufferPointer<IP_ADAPTER_ADDRESSES> =
UnsafeMutableBufferPointer.allocate(capacity: Int(ulSize) / stride)
defer {
pBuffer.deallocate()
}
let ulResult: ULONG =
GetAdaptersAddresses(ULONG(AF_UNSPEC), 0, nil, pBuffer.baseAddress,
&ulSize)
guard ulResult == ERROR_SUCCESS else {
throw IOError(windows: ulResult, reason: "GetAdaptersAddresses")
}
var pAdapter: UnsafeMutablePointer<IP_ADAPTER_ADDRESSES>? =
UnsafeMutablePointer(pBuffer.baseAddress)
while pAdapter != nil {
let pUnicastAddresses: UnsafeMutablePointer<IP_ADAPTER_UNICAST_ADDRESS>? =
pAdapter!.pointee.FirstUnicastAddress
var pUnicastAddress: UnsafeMutablePointer<IP_ADAPTER_UNICAST_ADDRESS>? =
pUnicastAddresses
while pUnicastAddress != nil {
if let device = NIONetworkDevice(pAdapter!, pUnicastAddress!) {
devices.append(device)
}
pUnicastAddress = pUnicastAddress!.pointee.Next
}
pAdapter = pAdapter!.pointee.Next
}
#else
var interface: UnsafeMutablePointer<ifaddrs>? = nil
try SystemCalls.getifaddrs(&interface)
let originalInterface = interface
defer {
freeifaddrs(originalInterface)
}
while let concreteInterface = interface {
if let nioInterface = NIONetworkDevice(concreteInterface.pointee) {
devices.append(nioInterface)
}
interface = concreteInterface.pointee.ifa_next
}
#endif
return devices
}
}

View File

@ -2,7 +2,7 @@
// //
// This source file is part of the SwiftNIO open source project // 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-2021 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0 // Licensed under Apache License v2.0
// //
// See LICENSE.txt for license information // See LICENSE.txt for license information
@ -13,7 +13,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
import XCTest import XCTest
@testable import NIO @testable import NIOCore
class LinuxTest: XCTestCase { class LinuxTest: XCTestCase {
func testCoreCountQuota() { func testCoreCountQuota() {