Skip to content

Commit 1a70915

Browse files
committed
#13 Add SocketDescriptor.Event
1 parent 3f485ab commit 1a70915

File tree

4 files changed

+210
-1
lines changed

4 files changed

+210
-1
lines changed

Sources/Socket/System/Constants.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,17 @@ internal var _MSG_CONFIRM: CInt { numericCast(MSG_CONFIRM) }
452452
internal var _MSG_MORE: CInt { numericCast(MSG_MORE) }
453453
#endif
454454

455+
#if os(Linux) || os(Android)
456+
@_alwaysEmitIntoClient
457+
internal var _EFD_CLOEXEC: Int { EFD_CLOEXEC }
458+
459+
@_alwaysEmitIntoClient
460+
internal var _EFD_NONBLOCK: Int { EFD_NONBLOCK }
461+
462+
@_alwaysEmitIntoClient
463+
internal var _EFD_SEMAPHORE: Int { EFD_SEMAPHORE }
464+
#endif
465+
455466
@_alwaysEmitIntoClient
456467
internal var _fd_set_count: Int {
457468
#if canImport(Darwin)
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#if os(Linux) || os(Android)
2+
import CSocket
3+
4+
public extension SocketDescriptor {
5+
6+
/// File descriptor for event notification
7+
///
8+
/// 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.
9+
/// The object contains an unsigned 64-bit integer counter that is maintained by the kernel.
10+
struct Event: RawRepresentable, Equatable, Hashable, Sendable {
11+
12+
public typealias RawValue = FileDescriptor.RawValue
13+
14+
public init(rawValue: RawValue) {
15+
self.rawValue = rawValue
16+
}
17+
18+
public let rawValue: RawValue
19+
}
20+
}
21+
22+
// MARK: - Supporting Types
23+
24+
public extension SocketDescriptor.Event {
25+
26+
/// Flags when opening sockets.
27+
@frozen
28+
struct Flags: OptionSet, Hashable, Codable, Sendable {
29+
30+
/// The raw C file events.
31+
@_alwaysEmitIntoClient
32+
public let rawValue: CInt
33+
34+
/// Create a strongly-typed file events from a raw C value.
35+
@_alwaysEmitIntoClient
36+
public init(rawValue: CInt) { self.rawValue = rawValue }
37+
38+
@_alwaysEmitIntoClient
39+
private init(_ cValue: Int) {
40+
self.init(rawValue: numericCast(cValue))
41+
}
42+
}
43+
}
44+
45+
public extension SocketDescriptor.Event.Flags {
46+
47+
/// Set the close-on-exec (`FD_CLOEXEC`) flag on the new file descriptor.
48+
///
49+
/// See the description of the `O_CLOEXEC` flag in `open(2)` for reasons why this may be useful.
50+
@_alwaysEmitIntoClient
51+
static var nonBlocking: SocketDescriptor.Event.Flags { SocketDescriptor.Event.Flags(_EFD_CLOEXEC) }
52+
53+
/// Set the `O_NONBLOCK` file status flag on the new open file description.
54+
///
55+
/// Using this flag saves extra calls to `fcntl(2)` to achieve the same result.
56+
@_alwaysEmitIntoClient
57+
static var closeOnExec: SocketDescriptor.Event.Flags { SocketDescriptor.Event.Flags(_EFD_NONBLOCK) }
58+
59+
/// Provide semaphore-like semantics for reads from the new file descriptor.
60+
@_alwaysEmitIntoClient
61+
static var semaphore: SocketDescriptor.Event.Flags { SocketDescriptor.Event.Flags(_EFD_SEMAPHORE) }
62+
}
63+
64+
// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *)
65+
extension SocketDescriptor.Event.Flags: CustomStringConvertible, CustomDebugStringConvertible
66+
{
67+
/// A textual representation of the open options.
68+
@inline(never)
69+
public var description: String {
70+
let descriptions: [(Element, StaticString)] = [
71+
(.nonBlocking, ".nonBlocking"),
72+
(.closeOnExec, ".closeOnExec"),
73+
(.semaphore, ".semaphore"),
74+
]
75+
return _buildDescription(descriptions)
76+
}
77+
78+
/// A textual representation of the open options, suitable for debugging.
79+
public var debugDescription: String { self.description }
80+
}
81+
82+
public extension SocketDescriptor.Event {
83+
84+
@frozen
85+
struct Counter: RawRepresentable, Equatable, Hashable, Sendable {
86+
87+
public typealias RawValue = CUnsignedInt
88+
89+
@_alwaysEmitIntoClient
90+
public var rawValue: RawValue
91+
92+
@_alwaysEmitIntoClient
93+
public init(rawValue: RawValue = 0) {
94+
self.rawValue = rawValue
95+
}
96+
}
97+
}
98+
99+
extension SocketDescriptor.Event.Counter: ExpressibleByIntegerLiteral {
100+
101+
public init(integerLiteral value: RawValue) {
102+
self.init(rawValue: value)
103+
}
104+
}
105+
106+
extension SocketDescriptor.Event.Counter: CustomStringConvertible, CustomDebugStringConvertible {
107+
108+
public var description: String { rawValue.description }
109+
110+
public var debugDescription: String { description }
111+
}
112+
113+
// MARK: - Operations
114+
115+
extension SocketDescriptor.Event {
116+
117+
/**
118+
`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.
119+
The object contains an unsigned 64-bit integer (uint64_t) counter that is maintained by the kernel.
120+
This counter is initialized with the value specified in the argument initval.
121+
*/
122+
@usableFromInline
123+
internal static func _events(
124+
_ counter: SocketDescriptor.Event.Counter,
125+
_ flags: SocketDescriptor.Event.Flags,
126+
retryOnInterrupt: Bool
127+
) -> Result<SocketDescriptor.Event, Errno> {
128+
valueOrErrno(retryOnInterrupt: retryOnInterrupt) {
129+
system_eventfd(counter.rawValue, flags.rawValue)
130+
}.map({ SocketDescriptor.Event(rawValue: $0) })
131+
}
132+
133+
@_alwaysEmitIntoClient
134+
public init(
135+
_ counter: SocketDescriptor.Event.Counter = 0,
136+
_ flags: SocketDescriptor.Event.Flags = [],
137+
retryOnInterrupt: Bool = true
138+
) throws(Errno) {
139+
self = try Self._events(counter, flags, retryOnInterrupt: retryOnInterrupt).get()
140+
}
141+
142+
/// Deletes a file descriptor.
143+
///
144+
/// Deletes the file descriptor from the per-process object reference table.
145+
/// If this is the last reference to the underlying object,
146+
/// the object will be deactivated.
147+
///
148+
/// The corresponding C function is `close`.
149+
@_alwaysEmitIntoClient
150+
public func close() throws(Errno) { try _close().get() }
151+
152+
@usableFromInline
153+
internal func _close() -> Result<(), Errno> {
154+
nothingOrErrno(retryOnInterrupt: false) { system_close(self.rawValue) }
155+
}
156+
157+
@usableFromInline
158+
internal func _read(
159+
retryOnInterrupt: Bool
160+
) -> Result<Counter, Errno> {
161+
var counter = Counter()
162+
return withUnsafeMutableBytes(of: &counter.rawValue) { buffer in
163+
valueOrErrno(retryOnInterrupt: retryOnInterrupt) {
164+
system_read(self.rawValue, buffer.baseAddress, buffer.count)
165+
}
166+
}.map { assert($0 == 8) }.map { _ in counter }
167+
}
168+
169+
/**
170+
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.
171+
The value returned by read(2) is in host byte order, i.e., the native byte order for integers on the host machine.
172+
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:
173+
174+
- 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.
175+
- 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.
176+
177+
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.
178+
*/
179+
@_alwaysEmitIntoClient
180+
public func read(
181+
retryOnInterrupt: Bool = true
182+
) throws(Errno) -> Counter {
183+
try _read(retryOnInterrupt: retryOnInterrupt).get()
184+
}
185+
}
186+
#endif

Sources/Socket/System/SocketFlags.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#if os(Linux)
1+
#if os(Linux) || os(Android)
22
import CSocket
33

44
/// Flags when opening sockets.

Sources/Socket/System/Syscalls.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,18 @@ internal func system_recvmsg(
384384
return recvmsg(socket, message, flags)
385385
}
386386

387+
#if os(Linux) || os(Android)
388+
internal func system_eventfd(
389+
_ initval: CUnsignedInt,
390+
_ flags: CInt
391+
) -> CInt {
392+
#if ENABLE_MOCKING
393+
if mockingEnabled { return _mock(initval, flags) }
394+
#endif
395+
return eventfd(initval, flags)
396+
}
397+
#endif
398+
387399
internal func system_fcntl(
388400
_ fd: Int32,
389401
_ cmd: Int32

0 commit comments

Comments
 (0)