From 3f485ab82fcd3ca1f5c28f062ca081925ff30441 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Wed, 9 Jul 2025 00:43:14 -0400 Subject: [PATCH 1/2] Update dependencies --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 7e73f0f..d3c57f8 100644 --- a/Package.swift +++ b/Package.swift @@ -24,7 +24,7 @@ var package = Package( dependencies: [ .package( url: "https://github.com/apple/swift-system", - from: "1.4.0" + from: "1.5.0" ) ], targets: [ From 1a7091523b005c5ea8a3fe12b9754a4639dc5faa Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Wed, 9 Jul 2025 07:22:23 +0000 Subject: [PATCH 2/2] #13 Add `SocketDescriptor.Event` --- Sources/Socket/System/Constants.swift | 11 ++ Sources/Socket/System/SocketEvent.swift | 186 ++++++++++++++++++++++++ Sources/Socket/System/SocketFlags.swift | 2 +- Sources/Socket/System/Syscalls.swift | 12 ++ 4 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 Sources/Socket/System/SocketEvent.swift diff --git a/Sources/Socket/System/Constants.swift b/Sources/Socket/System/Constants.swift index dea5c9e..7f44ebb 100644 --- a/Sources/Socket/System/Constants.swift +++ b/Sources/Socket/System/Constants.swift @@ -452,6 +452,17 @@ internal var _MSG_CONFIRM: CInt { numericCast(MSG_CONFIRM) } internal var _MSG_MORE: CInt { numericCast(MSG_MORE) } #endif +#if os(Linux) || os(Android) +@_alwaysEmitIntoClient +internal var _EFD_CLOEXEC: Int { EFD_CLOEXEC } + +@_alwaysEmitIntoClient +internal var _EFD_NONBLOCK: Int { EFD_NONBLOCK } + +@_alwaysEmitIntoClient +internal var _EFD_SEMAPHORE: Int { EFD_SEMAPHORE } +#endif + @_alwaysEmitIntoClient internal var _fd_set_count: Int { #if canImport(Darwin) diff --git a/Sources/Socket/System/SocketEvent.swift b/Sources/Socket/System/SocketEvent.swift new file mode 100644 index 0000000..a445ad2 --- /dev/null +++ b/Sources/Socket/System/SocketEvent.swift @@ -0,0 +1,186 @@ +#if os(Linux) || os(Android) +import CSocket + +public extension SocketDescriptor { + + /// File descriptor for event notification + /// + /// An "eventfd object" can be used as an event wait/notify mechanism by user-space applications, and by the kernel to notify user-space applications of events. + /// The object contains an unsigned 64-bit integer counter that is maintained by the kernel. + struct Event: RawRepresentable, Equatable, Hashable, Sendable { + + public typealias RawValue = FileDescriptor.RawValue + + public init(rawValue: RawValue) { + self.rawValue = rawValue + } + + public let rawValue: RawValue + } +} + +// MARK: - Supporting Types + +public extension SocketDescriptor.Event { + + /// Flags when opening sockets. + @frozen + struct Flags: OptionSet, Hashable, Codable, Sendable { + + /// The raw C file events. + @_alwaysEmitIntoClient + public let rawValue: CInt + + /// Create a strongly-typed file events from a raw C value. + @_alwaysEmitIntoClient + public init(rawValue: CInt) { self.rawValue = rawValue } + + @_alwaysEmitIntoClient + private init(_ cValue: Int) { + self.init(rawValue: numericCast(cValue)) + } + } +} + +public extension SocketDescriptor.Event.Flags { + + /// Set the close-on-exec (`FD_CLOEXEC`) flag on the new file descriptor. + /// + /// See the description of the `O_CLOEXEC` flag in `open(2)` for reasons why this may be useful. + @_alwaysEmitIntoClient + static var nonBlocking: SocketDescriptor.Event.Flags { SocketDescriptor.Event.Flags(_EFD_CLOEXEC) } + + /// Set the `O_NONBLOCK` file status flag on the new open file description. + /// + /// Using this flag saves extra calls to `fcntl(2)` to achieve the same result. + @_alwaysEmitIntoClient + static var closeOnExec: SocketDescriptor.Event.Flags { SocketDescriptor.Event.Flags(_EFD_NONBLOCK) } + + /// Provide semaphore-like semantics for reads from the new file descriptor. + @_alwaysEmitIntoClient + static var semaphore: SocketDescriptor.Event.Flags { SocketDescriptor.Event.Flags(_EFD_SEMAPHORE) } +} + +// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +extension SocketDescriptor.Event.Flags: CustomStringConvertible, CustomDebugStringConvertible +{ + /// A textual representation of the open options. + @inline(never) + public var description: String { + let descriptions: [(Element, StaticString)] = [ + (.nonBlocking, ".nonBlocking"), + (.closeOnExec, ".closeOnExec"), + (.semaphore, ".semaphore"), + ] + return _buildDescription(descriptions) + } + + /// A textual representation of the open options, suitable for debugging. + public var debugDescription: String { self.description } +} + +public extension SocketDescriptor.Event { + + @frozen + struct Counter: RawRepresentable, Equatable, Hashable, Sendable { + + public typealias RawValue = CUnsignedInt + + @_alwaysEmitIntoClient + public var rawValue: RawValue + + @_alwaysEmitIntoClient + public init(rawValue: RawValue = 0) { + self.rawValue = rawValue + } + } +} + +extension SocketDescriptor.Event.Counter: ExpressibleByIntegerLiteral { + + public init(integerLiteral value: RawValue) { + self.init(rawValue: value) + } +} + +extension SocketDescriptor.Event.Counter: CustomStringConvertible, CustomDebugStringConvertible { + + public var description: String { rawValue.description } + + public var debugDescription: String { description } +} + +// MARK: - Operations + +extension SocketDescriptor.Event { + + /** + `eventfd()` creates an "eventfd object" that can be used as an event wait/notify mechanism by user-space applications, and by the kernel to notify user-space applications of events. + The object contains an unsigned 64-bit integer (uint64_t) counter that is maintained by the kernel. + This counter is initialized with the value specified in the argument initval. + */ + @usableFromInline + internal static func _events( + _ counter: SocketDescriptor.Event.Counter, + _ flags: SocketDescriptor.Event.Flags, + retryOnInterrupt: Bool + ) -> Result { + valueOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_eventfd(counter.rawValue, flags.rawValue) + }.map({ SocketDescriptor.Event(rawValue: $0) }) + } + + @_alwaysEmitIntoClient + public init( + _ counter: SocketDescriptor.Event.Counter = 0, + _ flags: SocketDescriptor.Event.Flags = [], + retryOnInterrupt: Bool = true + ) throws(Errno) { + self = try Self._events(counter, flags, retryOnInterrupt: retryOnInterrupt).get() + } + + /// Deletes a file descriptor. + /// + /// Deletes the file descriptor from the per-process object reference table. + /// If this is the last reference to the underlying object, + /// the object will be deactivated. + /// + /// The corresponding C function is `close`. + @_alwaysEmitIntoClient + public func close() throws(Errno) { try _close().get() } + + @usableFromInline + internal func _close() -> Result<(), Errno> { + nothingOrErrno(retryOnInterrupt: false) { system_close(self.rawValue) } + } + + @usableFromInline + internal func _read( + retryOnInterrupt: Bool + ) -> Result { + var counter = Counter() + return withUnsafeMutableBytes(of: &counter.rawValue) { buffer in + valueOrErrno(retryOnInterrupt: retryOnInterrupt) { + system_read(self.rawValue, buffer.baseAddress, buffer.count) + } + }.map { assert($0 == 8) }.map { _ in counter } + } + + /** + Each successful `read(2)` returns an 8-byte integer. A read(2) will fail with the error EINVAL if the size of the supplied buffer is less than 8 bytes. + The value returned by read(2) is in host byte order, i.e., the native byte order for integers on the host machine. + The semantics of read(2) depend on whether the eventfd counter currently has a nonzero value and whether the EFD_SEMAPHORE flag was specified when creating the eventfd file descriptor: + + - If EFD_SEMAPHORE was not specified and the eventfd counter has a nonzero value, then a read(2) returns 8 bytes containing that value, and the counter's value is reset to zero. + - If EFD_SEMAPHORE was specified and the eventfd counter has a nonzero value, then a read(2) returns 8 bytes containing the value 1, and the counter's value is decremented by 1. + + If the eventfd counter is zero at the time of the call to read(2), then the call either blocks until the counter becomes nonzero (at which time, the read(2) proceeds as described above) or fails with the error EAGAIN if the file descriptor has been made nonblocking. + */ + @_alwaysEmitIntoClient + public func read( + retryOnInterrupt: Bool = true + ) throws(Errno) -> Counter { + try _read(retryOnInterrupt: retryOnInterrupt).get() + } +} +#endif diff --git a/Sources/Socket/System/SocketFlags.swift b/Sources/Socket/System/SocketFlags.swift index 4fe320a..d0c0f91 100644 --- a/Sources/Socket/System/SocketFlags.swift +++ b/Sources/Socket/System/SocketFlags.swift @@ -1,4 +1,4 @@ -#if os(Linux) +#if os(Linux) || os(Android) import CSocket /// Flags when opening sockets. diff --git a/Sources/Socket/System/Syscalls.swift b/Sources/Socket/System/Syscalls.swift index e5b6b12..35a573a 100644 --- a/Sources/Socket/System/Syscalls.swift +++ b/Sources/Socket/System/Syscalls.swift @@ -384,6 +384,18 @@ internal func system_recvmsg( return recvmsg(socket, message, flags) } +#if os(Linux) || os(Android) +internal func system_eventfd( + _ initval: CUnsignedInt, + _ flags: CInt +) -> CInt { +#if ENABLE_MOCKING + if mockingEnabled { return _mock(initval, flags) } +#endif + return eventfd(initval, flags) +} +#endif + internal func system_fcntl( _ fd: Int32, _ cmd: Int32