Move Channel abstractions to NIOCore. (#1920)

Motivation:

The most important API surface area in NIO are the Channel abstractions.
These are shared in all NIO programs, and are also used by several
projects to implement their I/O abstraction. There are several moving
parts to this abstraction, all of which are moving:

- Channel itself
- ChannelPipeline
- ChannelHandler

As these all move, they force several other pieces of API to move with
them. Most notably they force us to move NIOAny, which also forces us to
move FileHandle and FileRegion. That also forces us to bring over part
of our syscall abstraction. This duplication is acceptable due to its
minimal surface area, but it is definitely a flaw in our abstraction
design that we had to do that at all.

We also need to move the channel option abstraction, AddressedEnvelope,
and the DeadChannel.

Modifications:

- Moved a bunch of the Channel abstraction over.
- Moved Channel-associated types.

Result:

Channel will be part of NIOCore.
This commit is contained in:
Cory Benfield 2021-08-02 12:39:28 +01:00 committed by GitHub
parent 2a65f2b4be
commit ca136e8f80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 472 additions and 258 deletions

View File

@ -247,7 +247,7 @@ class BaseStreamSocketChannel<Socket: SocketProtocol>: BaseSocketChannel<Socket>
return
}
let data = data.forceAsIOData()
let data = self.unwrapData(data, as: IOData.self)
if !self.pendingWrites.add(data: data, promise: promise) {
self.pipeline.syncOperations.fireChannelWritabilityChanged()

View File

@ -362,7 +362,7 @@ public final class ServerBootstrap {
ctxEventLoop.assertInEventLoop()
future.flatMap { (_) -> EventLoopFuture<Void> in
ctxEventLoop.assertInEventLoop()
guard !context.pipeline.destroyed else {
guard context.channel.isActive else {
return context.eventLoop.makeFailedFuture(ChannelError.ioOnClosedChannel)
}
context.fireChannelRead(data)

View File

@ -237,7 +237,8 @@ class EmbeddedChannelCore: ChannelCore {
}
deinit {
assert(self.pipeline.destroyed, "leaked an open EmbeddedChannel, maybe forgot to call channel.finish()?")
assert(!self.isOpen && !self.isActive,
"leaked an open EmbeddedChannel, maybe forgot to call channel.finish()?")
isOpen = false
closePromise.succeed(())
}
@ -631,7 +632,7 @@ public final class EmbeddedChannel: Channel {
}
let elem = buffer.removeFirst()
guard let t = self._channelCore.tryUnwrapData(elem, as: T.self) else {
throw WrongTypeError(expected: T.self, actual: type(of: elem.forceAs(type: Any.self)))
throw WrongTypeError(expected: T.self, actual: type(of: self._channelCore.tryUnwrapData(elem, as: Any.self)!))
}
return t
}

View File

@ -2,7 +2,7 @@
//
// 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
//
// See LICENSE.txt for license information
@ -12,26 +12,6 @@
//
//===----------------------------------------------------------------------===//
public protocol FileDescriptor {
/// Will be called with the file descriptor if still open, if not it will
/// throw an `IOError`.
///
/// The ownership of the file descriptor must not escape the `body` as it's completely managed by the
/// implementation of the `FileDescriptor` protocol.
///
/// - parameters:
/// - body: The closure to execute if the `FileDescriptor` is still open.
/// - throws: If either the `FileDescriptor` was closed before or the closure throws by itself.
func withUnsafeFileDescriptor<T>(_ body: (CInt) throws -> T) throws -> T
/// `true` if this `FileDescriptor` is open (which means it was not closed yet).
var isOpen: Bool { get }
/// Close this `FileDescriptor`.
func close() throws
}
extension FileDescriptor {
internal static func setNonBlocking(fileDescriptor: CInt) throws {
let flags = try Posix.fcntl(descriptor: fileDescriptor, command: F_GETFL, value: 0)

View File

@ -2,7 +2,7 @@
//
// 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
//
// See LICENSE.txt for license information
@ -11,12 +11,6 @@
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//
// Interfaces.swift
// NIO
//
// Created by Cory Benfield on 27/02/2018.
//
#if os(Linux) || os(FreeBSD) || os(Android)
import CNIOLinux
@ -371,158 +365,3 @@ extension NIONetworkDevice: Hashable {
hasher.combine(self.interfaceIndex)
}
}
/// A representation of a single network interface on a system.
@available(*, deprecated, renamed: "NIONetworkDevice")
public final class NIONetworkInterface {
// This is a class because in almost all cases this will carry
// four structs that are backed by classes, and so will incur 4
// refcount operations each time it is copied.
/// The name of the network interface.
public let name: String
/// The address associated with the given network interface.
public let address: SocketAddress
/// The netmask associated with this address, if any.
public let netmask: SocketAddress?
/// The broadcast address associated with this socket interface, if it has one. Some
/// interfaces do not, especially those that have a `pointToPointDestinationAddress`.
public let 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.
public let pointToPointDestinationAddress: SocketAddress?
/// If the Interface supports Multicast
public let multicastSupported: Bool
/// The index of the interface, as provided by `if_nametoindex`.
public let 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)
guard let address = pAddress.pointee.Address.lpSockaddr.convert() else {
return nil
}
self.address = address
// 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)
case ADDRESS_FAMILY(AF_INET6):
self.netmask = v6mask(pAddress.pointee.OnLinkPrefixLength)
self.interfaceIndex = Int(pAdapter.pointee.Ipv6IfIndex)
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)
guard caddr.ifa_addr != nil else {
return nil
}
guard let address = caddr.ifa_addr!.convert() else {
return nil
}
self.address = address
if let netmask = caddr.ifa_netmask {
self.netmask = netmask.convert()
} else {
self.netmask = nil
}
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
}
if (caddr.ifa_flags & UInt32(IFF_MULTICAST)) != 0 {
self.multicastSupported = true
} else {
self.multicastSupported = false
}
do {
self.interfaceIndex = Int(try Posix.if_nametoindex(caddr.ifa_name))
} catch {
return nil
}
}
#endif
}
@available(*, deprecated, renamed: "NIONetworkDevice")
extension NIONetworkInterface: CustomDebugStringConvertible {
public var debugDescription: String {
let baseString = "Interface \(self.name): address \(self.address)"
let maskString = self.netmask != nil ? " netmask \(self.netmask!)" : ""
return baseString + maskString
}
}
@available(*, deprecated, renamed: "NIONetworkDevice")
extension NIONetworkInterface: Equatable {
public static func ==(lhs: NIONetworkInterface, rhs: NIONetworkInterface) -> 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
}
}

View File

@ -0,0 +1,65 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
import NIOConcurrencyHelpers
/// A `SelectableChannel` is a `Channel` that can be used with a `Selector` which notifies a user when certain events
/// are possible. On UNIX a `Selector` is usually an abstraction of `select`, `poll`, `epoll` or `kqueue`.
///
/// - warning: `SelectableChannel` methods and properties are _not_ thread-safe (unless they also belong to `Channel`).
internal protocol SelectableChannel: Channel {
/// The type of the `Selectable`. A `Selectable` is usually wrapping a file descriptor that can be registered in a
/// `Selector`.
associatedtype SelectableType: Selectable
var isOpen: Bool { get }
/// The event(s) of interest.
var interestedEvent: SelectorEventSet { get }
/// Called when the `SelectableChannel` is ready to be written.
func writable()
/// Called when the `SelectableChannel` is ready to be read.
func readable()
/// Called when the read side of the `SelectableChannel` hit EOF.
func readEOF()
/// Called when the write side of the `SelectableChannel` hit EOF.
func writeEOF()
/// Called when the `SelectableChannel` was reset (ie. is now unusable)
func reset()
func register(selector: Selector<NIORegistration>, interested: SelectorEventSet) throws
func deregister(selector: Selector<NIORegistration>, mode: CloseMode) 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

@ -296,7 +296,7 @@ final class ServerSocketChannel: BaseSocketChannel<ServerSocket> {
override public func channelRead0(_ data: NIOAny) {
self.eventLoop.assertInEventLoop()
let ch = data.forceAsOther() as SocketChannel
let ch = self.unwrapData(data, as: SocketChannel.self)
ch.eventLoop.execute {
ch.register().flatMapThrowing {
guard ch.isOpen else {
@ -669,7 +669,7 @@ final class DatagramChannel: BaseSocketChannel<Socket> {
}
/// Buffer a write in preparation for a flush.
override func bufferPendingWrite(data: NIOAny, promise: EventLoopPromise<Void>?) {
let data = data.forceAsByteEnvelope()
let data = self.unwrapData(data, as: AddressedEnvelope<ByteBuffer>.self)
if !self.pendingWrites.add(envelope: data, promise: promise) {
assert(self.isActive)

View File

@ -123,7 +123,7 @@ public enum System {
}
while let concreteInterface = interface {
if let nioInterface = NIONetworkInterface(concreteInterface.pointee) {
if let nioInterface = NIONetworkInterface._construct(from: concreteInterface.pointee) {
interfaces.append(nioInterface)
}
interface = concreteInterface.pointee.ifa_next

View File

@ -2,7 +2,7 @@
//
// 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
//
// See LICENSE.txt for license information
@ -166,42 +166,6 @@ public protocol NIOSynchronousChannelOptions {
func getOption<Option: ChannelOption>(_ option: Option) throws -> Option.Value
}
/// A `SelectableChannel` is a `Channel` that can be used with a `Selector` which notifies a user when certain events
/// are possible. On UNIX a `Selector` is usually an abstraction of `select`, `poll`, `epoll` or `kqueue`.
///
/// - warning: `SelectableChannel` methods and properties are _not_ thread-safe (unless they also belong to `Channel`).
internal protocol SelectableChannel: Channel {
/// The type of the `Selectable`. A `Selectable` is usually wrapping a file descriptor that can be registered in a
/// `Selector`.
associatedtype SelectableType: Selectable
var isOpen: Bool { get }
/// The event(s) of interest.
var interestedEvent: SelectorEventSet { get }
/// Called when the `SelectableChannel` is ready to be written.
func writable()
/// Called when the `SelectableChannel` is ready to be read.
func readable()
/// Called when the read side of the `SelectableChannel` hit EOF.
func readEOF()
/// Called when the write side of the `SelectableChannel` hit EOF.
func writeEOF()
/// Called when the `SelectableChannel` was reset (ie. is now unusable)
func reset()
func register(selector: Selector<NIORegistration>, interested: SelectorEventSet) throws
func deregister(selector: Selector<NIORegistration>, mode: CloseMode) throws
func reregister(selector: Selector<NIORegistration>, interested: SelectorEventSet) throws
}
/// Default implementations which will start on the head of the `ChannelPipeline`.
extension Channel {
@ -414,20 +378,6 @@ extension ChannelError: Equatable { }
/// The removal of a `ChannelHandler` using `ChannelPipeline.removeHandler` has been attempted more than once.
public struct NIOAttemptedToRemoveHandlerMultipleTimesError: Error {}
/// 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() {}
}
/// An `Channel` related event that is passed through the `ChannelPipeline` to notify the user.
public enum ChannelEvent: Equatable {
/// `ChannelOptions.allowRemoteHalfClosure` is `true` and input portion of the `Channel` was closed.

View File

@ -0,0 +1,34 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
public protocol FileDescriptor {
/// Will be called with the file descriptor if still open, if not it will
/// throw an `IOError`.
///
/// The ownership of the file descriptor must not escape the `body` as it's completely managed by the
/// implementation of the `FileDescriptor` protocol.
///
/// - parameters:
/// - body: The closure to execute if the `FileDescriptor` is still open.
/// - throws: If either the `FileDescriptor` was closed before or the closure throws by itself.
func withUnsafeFileDescriptor<T>(_ body: (CInt) throws -> T) throws -> T
/// `true` if this `FileDescriptor` is open (which means it was not closed yet).
var isOpen: Bool { get }
/// Close this `FileDescriptor`.
func close() throws
}

View File

@ -11,6 +11,13 @@
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
#if os(Windows)
import ucrt
#elseif os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import Darwin
#elseif os(Linux) || os(Android)
import Glibc
#endif
/// A `NIOFileHandle` is a handle to an open file.
///
@ -45,7 +52,7 @@ public final class NIOFileHandle: FileDescriptor {
/// - returns: A new `NIOFileHandle` with a fresh underlying file descriptor but shared seek pointer.
public func duplicate() throws -> NIOFileHandle {
return try withUnsafeFileDescriptor { fd in
NIOFileHandle(descriptor: try Posix.dup(descriptor: fd))
NIOFileHandle(descriptor: try SystemCalls.dup(descriptor: fd))
}
}
@ -66,7 +73,7 @@ public final class NIOFileHandle: FileDescriptor {
public func close() throws {
try withUnsafeFileDescriptor { fd in
try Posix.close(descriptor: fd)
try SystemCalls.close(descriptor: fd)
}
self.isOpen = false
@ -141,7 +148,7 @@ extension NIOFileHandle {
/// - mode: Access mode. Default mode is `.read`.
/// - flags: Additional POSIX flags.
public convenience init(path: String, mode: Mode = .read, flags: Flags = .default) throws {
let fd = try Posix.open(file: path, oFlag: mode.posixFlags | O_CLOEXEC | flags.posixFlags, mode: flags.posixMode)
let fd = try SystemCalls.open(file: path, oFlag: mode.posixFlags | O_CLOEXEC | flags.posixFlags, mode: flags.posixMode)
self.init(descriptor: fd)
}

View File

@ -11,6 +11,14 @@
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
#if os(Windows)
import ucrt
#elseif os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import Darwin
#elseif os(Linux) || os(Android)
import Glibc
#endif
/// A `FileRegion` represent a readable portion usually created to be sent over the network.
///
@ -80,8 +88,8 @@ extension FileRegion {
/// - fileHandle: An open `NIOFileHandle` to the file.
public init(fileHandle: NIOFileHandle) throws {
let eof = try fileHandle.withUnsafeFileDescriptor { (fd: CInt) throws -> off_t in
let eof = try Posix.lseek(descriptor: fd, offset: 0, whence: SEEK_END)
try Posix.lseek(descriptor: fd, offset: 0, whence: SEEK_SET)
let eof = try SystemCalls.lseek(descriptor: fd, offset: 0, whence: SEEK_END)
try SystemCalls.lseek(descriptor: fd, offset: 0, whence: SEEK_SET)
return eof
}
self.init(fileHandle: fileHandle, readerIndex: 0, endIndex: Int(eof))

View File

@ -115,3 +115,26 @@ extension IOError: CustomStringConvertible {
return reasonForError(errnoCode: self.errnoCode, reason: self.failureDescription)
}
}
// FIXME: Duplicated with NIO.
/// An result for an IO operation that was done on a non-blocking resource.
enum CoreIOResult<T: Equatable>: Equatable {
/// Signals that the IO operation could not be completed as otherwise we would need to block.
case wouldBlock(T)
/// Signals that the IO operation was completed.
case processed(T)
}
internal extension CoreIOResult where T: FixedWidthInteger {
var result: T {
switch self {
case .processed(let value):
return value
case .wouldBlock(_):
fatalError("cannot unwrap CoreIOResult")
}
}
}

View File

@ -0,0 +1,175 @@
//===----------------------------------------------------------------------===//
//
// 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 Glibc
import CNIOLinux
#elseif os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import Darwin
#elseif 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 interface on a system.
@available(*, deprecated, renamed: "NIONetworkDevice")
public final class NIONetworkInterface {
// This is a class because in almost all cases this will carry
// four structs that are backed by classes, and so will incur 4
// refcount operations each time it is copied.
/// The name of the network interface.
public let name: String
/// The address associated with the given network interface.
public let address: SocketAddress
/// The netmask associated with this address, if any.
public let netmask: SocketAddress?
/// The broadcast address associated with this socket interface, if it has one. Some
/// interfaces do not, especially those that have a `pointToPointDestinationAddress`.
public let 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.
public let pointToPointDestinationAddress: SocketAddress?
/// If the Interface supports Multicast
public let multicastSupported: Bool
/// The index of the interface, as provided by `if_nametoindex`.
public let interfaceIndex: Int
fileprivate init?(_ caddr: ifaddrs) {
self.name = String(cString: caddr.ifa_name)
guard caddr.ifa_addr != nil else {
return nil
}
guard let address = caddr.ifa_addr!.convert() else {
return nil
}
self.address = address
if let netmask = caddr.ifa_netmask {
self.netmask = netmask.convert()
} else {
self.netmask = nil
}
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
}
if (caddr.ifa_flags & UInt32(IFF_MULTICAST)) != 0 {
self.multicastSupported = true
} else {
self.multicastSupported = false
}
do {
self.interfaceIndex = Int(try SystemCalls.if_nametoindex(caddr.ifa_name))
} catch {
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")
extension NIONetworkInterface: CustomDebugStringConvertible {
public var debugDescription: String {
let baseString = "Interface \(self.name): address \(self.address)"
let maskString = self.netmask != nil ? " netmask \(self.netmask!)" : ""
return baseString + maskString
}
}
@available(*, deprecated, renamed: "NIONetworkDevice")
extension NIONetworkInterface: Equatable {
public static func ==(lhs: NIONetworkInterface, rhs: NIONetworkInterface) -> 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
}
}
/// A helper extension for working with sockaddr pointers.
extension UnsafeMutablePointer where Pointee == sockaddr {
/// Converts the `sockaddr` to a `SocketAddress`.
fileprivate func convert() -> SocketAddress? {
switch NIOBSDSocket.AddressFamily(rawValue: CInt(pointee.sa_family)) {
case .inet:
return self.withMemoryRebound(to: sockaddr_in.self, capacity: 1) {
SocketAddress($0.pointee)
}
case .inet6:
return self.withMemoryRebound(to: sockaddr_in6.self, capacity: 1) {
SocketAddress($0.pointee)
}
case .unix:
return self.withMemoryRebound(to: sockaddr_un.self, capacity: 1) {
SocketAddress($0.pointee)
}
default:
return nil
}
}
}

View File

@ -0,0 +1,130 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
// This file contains code that ensures errno is captured correctly when doing syscalls and no ARC traffic can happen inbetween that *could* change the errno
// value before we were able to read it.
// It's important that all static methods are declared with `@inline(never)` so it's not possible any ARC traffic happens while we need to read errno.
//
// Created by Norman Maurer on 11/10/17.
//
// This file arguably shouldn't be here in NIOCore, but due to early design decisions we accidentally exposed a few types that
// know about system calls into the core API (looking at you, FileHandle). As a result we need support for a small number of system calls.
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
import Darwin.C
#elseif os(Linux) || os(FreeBSD) || os(Android)
import Glibc
#elseif os(Windows)
import CNIOWindows
#else
#error("bad os")
#endif
private let sysDup: @convention(c) (CInt) -> CInt = dup
private let sysClose: @convention(c) (CInt) -> CInt = close
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 sysIfNameToIndex: @convention(c) (UnsafePointer<CChar>?) -> CUnsignedInt = if_nametoindex
private func isUnacceptableErrno(_ code: Int32) -> Bool {
switch code {
case EFAULT, EBADF:
return true
default:
return false
}
}
private func preconditionIsNotUnacceptableErrno(err: CInt, where function: String) -> Void {
// strerror is documented to return "Unknown error: ..." for illegal value so it won't ever fail
precondition(!isUnacceptableErrno(err), "unacceptable errno \(err) \(String(cString: strerror(err)!)) in \(function))")
}
/*
* Sorry, we really try hard to not use underscored attributes. In this case
* however we seem to break the inlining threshold which makes a system call
* take twice the time, ie. we need this exception.
*/
@inline(__always)
@discardableResult
internal func syscall<T: FixedWidthInteger>(blocking: Bool,
where function: String = #function,
_ body: () throws -> T)
throws -> CoreIOResult<T> {
while true {
let res = try body()
if res == -1 {
let err = errno
switch (err, blocking) {
case (EINTR, _):
continue
case (EWOULDBLOCK, true):
return .wouldBlock(0)
default:
preconditionIsNotUnacceptableErrno(err: err, where: function)
throw IOError(errnoCode: err, reason: function)
}
}
return .processed(res)
}
}
enum SystemCalls {
@discardableResult
@inline(never)
internal static func dup(descriptor: CInt) throws -> CInt {
return try syscall(blocking: false) {
sysDup(descriptor)
}.result
}
@inline(never)
internal static func close(descriptor: CInt) throws {
let res = sysClose(descriptor)
if res == -1 {
let err = errno
// There is really nothing "sane" we can do when EINTR was reported on close.
// So just ignore it and "assume" everything is fine == we closed the file descriptor.
//
// For more details see:
// - https://bugs.chromium.org/p/chromium/issues/detail?id=269623
// - https://lwn.net/Articles/576478/
if err != EINTR {
preconditionIsNotUnacceptableErrno(err: err, where: #function)
throw IOError(errnoCode: err, reason: "close")
}
}
}
@inline(never)
internal static func open(file: UnsafePointer<CChar>, oFlag: CInt, mode: mode_t) throws -> CInt {
return try syscall(blocking: false) {
sysOpenWithMode(file, oFlag, mode)
}.result
}
@discardableResult
@inline(never)
internal static func lseek(descriptor: CInt, offset: off_t, whence: CInt) throws -> off_t {
return try syscall(blocking: false) {
sysLseek(descriptor, offset, whence)
}.result
}
@inline(never)
internal static func if_nametoindex(_ name: UnsafePointer<CChar>?) throws -> CUnsignedInt {
return try syscall(blocking: false) {
sysIfNameToIndex(name)
}.result
}
}

View File

@ -2,7 +2,7 @@
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2019 Apple Inc. and the SwiftNIO project authors
// Copyright (c) 2019-2021 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
@ -12,7 +12,8 @@
//
//===----------------------------------------------------------------------===//
@testable import NIO
@testable import NIOCore
import NIO
import NIOTestUtils
import XCTest

View File

@ -2,7 +2,7 @@
//
// 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
//
// See LICENSE.txt for license information
@ -13,7 +13,7 @@
//===----------------------------------------------------------------------===//
import XCTest
@testable import NIO
@testable import NIOCore
class BaseObjectTest: XCTestCase {
func testNIOByteBufferConversion() {

View File

@ -2,7 +2,7 @@
//
// 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
//
// See LICENSE.txt for license information
@ -14,7 +14,8 @@
import XCTest
import NIOConcurrencyHelpers
@testable import NIO
@testable import NIOCore
import NIO
import NIOTestUtils
private final class IndexWritingHandler: ChannelDuplexHandler {

View File

@ -254,7 +254,7 @@ public final class ChannelTests: XCTestCase {
expectedSingleWritabilities: [Int]?,
expectedVectorWritabilities: [[Int]]?,
expectedFileWritabilities: [(Int, Int)]?,
returns: [IOResult<Int>],
returns: [NIO.IOResult<Int>],
promiseStates: [[Bool]],
file: StaticString = #file,
line: UInt = #line) throws -> OverallWriteResult {
@ -2028,7 +2028,7 @@ public final class ChannelTests: XCTestCase {
init(protocolFamily: NIOBSDSocket.ProtocolFamily) throws {
try super.init(protocolFamily: protocolFamily, type: .stream, setNonBlocking: true)
}
override func read(pointer: UnsafeMutableRawBufferPointer) throws -> IOResult<Int> {
override func read(pointer: UnsafeMutableRawBufferPointer) throws -> NIO.IOResult<Int> {
defer {
self.firstReadHappened = true
}
@ -2443,11 +2443,11 @@ public final class ChannelTests: XCTestCase {
try super.init(protocolFamily: .inet, type: .stream, setNonBlocking: true)
}
override func write(pointer: UnsafeRawBufferPointer) throws -> IOResult<Int> {
override func write(pointer: UnsafeRawBufferPointer) throws -> NIO.IOResult<Int> {
throw IOError(errnoCode: ETXTBSY, reason: "WriteAlwaysFailingSocket.write fake error")
}
override func writev(iovecs: UnsafeBufferPointer<IOVector>) throws -> IOResult<Int> {
override func writev(iovecs: UnsafeBufferPointer<IOVector>) throws -> NIO.IOResult<Int> {
throw IOError(errnoCode: ETXTBSY, reason: "WriteAlwaysFailingSocket.writev fake error")
}
}

View File

@ -2,7 +2,7 @@
//
// 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
//
// See LICENSE.txt for license information
@ -109,7 +109,7 @@ class PendingDatagramWritesManagerTests: XCTestCase {
promises: [EventLoopPromise<Void>],
expectedSingleWritabilities: [(Int, SocketAddress)]?,
expectedVectorWritabilities: [[(Int, SocketAddress)]]?,
returns: [Result<IOResult<Int>, Error>],
returns: [Result<NIO.IOResult<Int>, Error>],
promiseStates: [[Bool]],
file: StaticString = #file,
line: UInt = #line) throws -> OverallWriteResult {