/src/swift-nio/Sources/NIOCore/Utilities.swift
Line | Count | Source |
1 | | //===----------------------------------------------------------------------===// |
2 | | // |
3 | | // This source file is part of the SwiftNIO open source project |
4 | | // |
5 | | // Copyright (c) 2021-2023 Apple Inc. and the SwiftNIO project authors |
6 | | // Licensed under Apache License v2.0 |
7 | | // |
8 | | // See LICENSE.txt for license information |
9 | | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors |
10 | | // |
11 | | // SPDX-License-Identifier: Apache-2.0 |
12 | | // |
13 | | //===----------------------------------------------------------------------===// |
14 | | #if os(Linux) || os(FreeBSD) || os(Android) |
15 | | import CNIOLinux |
16 | | #if canImport(Glibc) |
17 | | @preconcurrency import Glibc |
18 | | #elseif canImport(Musl) |
19 | | @preconcurrency import Musl |
20 | | #elseif canImport(Android) |
21 | | @preconcurrency import Android |
22 | | #endif |
23 | | #elseif os(OpenBSD) |
24 | | import CNIOOpenBSD |
25 | | @preconcurrency import Glibc |
26 | | #elseif os(Windows) |
27 | | import ucrt |
28 | | import let WinSDK.RelationProcessorCore |
29 | | |
30 | | import let WinSDK.AF_UNSPEC |
31 | | import let WinSDK.ERROR_BUFFER_OVERFLOW |
32 | | import let WinSDK.ERROR_SUCCESS |
33 | | |
34 | | import func WinSDK.GetAdaptersAddresses |
35 | | import func WinSDK.GetLastError |
36 | | import func WinSDK.GetLogicalProcessorInformation |
37 | | |
38 | | import struct WinSDK.IP_ADAPTER_ADDRESSES |
39 | | import struct WinSDK.IP_ADAPTER_UNICAST_ADDRESS |
40 | | import struct WinSDK.SYSTEM_LOGICAL_PROCESSOR_INFORMATION |
41 | | import struct WinSDK.ULONG |
42 | | |
43 | | import typealias WinSDK.DWORD |
44 | | #elseif canImport(Darwin) |
45 | | import Darwin |
46 | | #elseif canImport(WASILibc) |
47 | | @preconcurrency import WASILibc |
48 | | #else |
49 | | #error("The Core utilities module was unable to identify your C library.") |
50 | | #endif |
51 | | |
52 | | /// A utility function that runs the body code only in debug builds, without |
53 | | /// emitting compiler warnings. |
54 | | /// |
55 | | /// This is currently the only way to do this in Swift: see |
56 | | /// https://forums.swift.org/t/support-debug-only-code/11037 for a discussion. |
57 | | @inlinable |
58 | 6.51G | internal func debugOnly(_ body: () -> Void) { |
59 | 6.51G | // FIXME: duplicated with NIO. |
60 | 6.51G | assert( |
61 | 6.51G | { |
62 | 2.08M | body() |
63 | 2.08M | return true |
64 | 2.08M | }() $s7NIOCore9debugOnlyyyyyXEFSbyXEfu_ Line | Count | Source | 61 | 2.08M | { | 62 | 2.08M | body() | 63 | 2.08M | return true | 64 | 2.08M | }() |
$s7NIOCore9debugOnlyyyyyXEFSbyXEfu_SbyXEfU_ Line | Count | Source | 61 | 2.08M | { | 62 | 2.08M | body() | 63 | 2.08M | return true | 64 | 2.08M | }() |
|
65 | 6.51G | ) |
66 | 6.51G | } |
67 | | |
68 | | /// Allows to "box" another value. |
69 | | final class Box<T> { |
70 | | // FIXME: Duplicated with NIO. |
71 | | let value: T |
72 | 0 | init(_ value: T) { self.value = value } |
73 | | } |
74 | | |
75 | | extension Box: Sendable where T: Sendable {} |
76 | | |
77 | | public enum System: Sendable { |
78 | | /// A utility function that returns an estimate of the number of *logical* cores |
79 | | /// on the system available for use. |
80 | | /// |
81 | | /// This value can be used to help provide an estimate of how many threads to use with |
82 | | /// the `MultiThreadedEventLoopGroup`. The exact ratio between this number and the number |
83 | | /// of threads to use is a matter for the programmer, and can be determined based on the |
84 | | /// specific execution behaviour of the program. |
85 | | /// |
86 | | /// On Linux the value returned will take account of cgroup or cpuset restrictions. |
87 | | /// The result will be rounded up to the nearest whole number where fractional CPUs have been assigned. |
88 | | /// |
89 | | /// - Returns: The logical core count on the system. |
90 | 0 | public static var coreCount: Int { |
91 | | #if os(Windows) |
92 | | var dwLength: DWORD = 0 |
93 | | _ = GetLogicalProcessorInformation(nil, &dwLength) |
94 | | |
95 | | let alignment: Int = |
96 | | MemoryLayout<SYSTEM_LOGICAL_PROCESSOR_INFORMATION>.alignment |
97 | | let pBuffer: UnsafeMutableRawPointer = |
98 | | UnsafeMutableRawPointer.allocate( |
99 | | byteCount: Int(dwLength), |
100 | | alignment: alignment |
101 | | ) |
102 | | defer { |
103 | | pBuffer.deallocate() |
104 | | } |
105 | | |
106 | | let dwSLPICount: Int = |
107 | | Int(dwLength) / MemoryLayout<SYSTEM_LOGICAL_PROCESSOR_INFORMATION>.stride |
108 | | let pSLPI: UnsafeMutablePointer<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> = |
109 | | pBuffer.bindMemory( |
110 | | to: SYSTEM_LOGICAL_PROCESSOR_INFORMATION.self, |
111 | | capacity: dwSLPICount |
112 | | ) |
113 | | |
114 | | let bResult: Bool = GetLogicalProcessorInformation(pSLPI, &dwLength) |
115 | | precondition(bResult, "GetLogicalProcessorInformation: \(GetLastError())") |
116 | | |
117 | | return UnsafeBufferPointer<SYSTEM_LOGICAL_PROCESSOR_INFORMATION>( |
118 | | start: pSLPI, |
119 | | count: dwSLPICount |
120 | | ) |
121 | | .filter { $0.Relationship == RelationProcessorCore } |
122 | | .map { $0.ProcessorMask.nonzeroBitCount } |
123 | | .reduce(0, +) |
124 | | #elseif os(Linux) || os(Android) |
125 | 0 | var cpuSetPath: String? |
126 | 0 |
|
127 | 0 | switch Linux.cgroupVersion { |
128 | 0 | case .v1: |
129 | 0 | if let quota = Linux.coreCountCgroup1Restriction() { |
130 | 0 | return quota |
131 | 0 | } |
132 | 0 | cpuSetPath = Linux.cpuSetPathV1 |
133 | 0 | case .v2: |
134 | 0 | if let quota = Linux.coreCountCgroup2Restriction() { |
135 | 0 | return quota |
136 | 0 | } |
137 | 0 | cpuSetPath = Linux.cpuSetPathV2 |
138 | 0 | case .none: |
139 | 0 | break |
140 | 0 | } |
141 | 0 |
|
142 | 0 | if let cpuSetPath, |
143 | 0 | let cpusetCount = Linux.coreCount(cpuset: cpuSetPath) |
144 | 0 | { |
145 | 0 | return cpusetCount |
146 | 0 | } else { |
147 | 0 | return sysconf(CInt(_SC_NPROCESSORS_ONLN)) |
148 | 0 | } |
149 | | #else |
150 | | return sysconf(CInt(_SC_NPROCESSORS_ONLN)) |
151 | | #endif |
152 | 0 | } |
153 | | |
154 | | #if !os(Windows) && !os(WASI) |
155 | | /// A utility function that enumerates the available network interfaces on this machine. |
156 | | /// |
157 | | /// This function returns values that are true for a brief snapshot in time. These results can |
158 | | /// change, and the returned values will not change to reflect them. This function must be called |
159 | | /// again to get new results. |
160 | | /// |
161 | | /// - Returns: An array of network interfaces available on this machine. |
162 | | /// - Throws: If an error is encountered while enumerating interfaces. |
163 | | @available(*, deprecated, renamed: "enumerateDevices") |
164 | | #if compiler(>=6.3) |
165 | | @available(Android 24, *) |
166 | | #endif |
167 | 0 | public static func enumerateInterfaces() throws -> [NIONetworkInterface] { |
168 | 0 | var interfaces: [NIONetworkInterface] = [] |
169 | 0 | interfaces.reserveCapacity(12) // Arbitrary choice. |
170 | 0 |
|
171 | 0 | var interface: UnsafeMutablePointer<ifaddrs>? = nil |
172 | 0 | try SystemCalls.getifaddrs(&interface) |
173 | 0 | let originalInterface = interface |
174 | 0 | defer { |
175 | 0 | freeifaddrs(originalInterface) |
176 | 0 | } |
177 | 0 |
|
178 | 0 | while let concreteInterface = interface { |
179 | 0 | if let nioInterface = NIONetworkInterface(concreteInterface.pointee) { |
180 | 0 | interfaces.append(nioInterface) |
181 | 0 | } |
182 | 0 | interface = concreteInterface.pointee.ifa_next |
183 | 0 | } |
184 | 0 |
|
185 | 0 | return interfaces |
186 | 0 | } |
187 | | #endif |
188 | | |
189 | | /// A utility function that enumerates the available network devices on this machine. |
190 | | /// |
191 | | /// This function returns values that are true for a brief snapshot in time. These results can |
192 | | /// change, and the returned values will not change to reflect them. This function must be called |
193 | | /// again to get new results. |
194 | | /// |
195 | | /// - Returns: An array of network devices available on this machine. |
196 | | /// - Throws: If an error is encountered while enumerating interfaces. |
197 | | #if compiler(>=6.3) |
198 | | @available(Android 24, *) |
199 | | #endif |
200 | 0 | public static func enumerateDevices() throws -> [NIONetworkDevice] { |
201 | 0 | var devices: [NIONetworkDevice] = [] |
202 | 0 | devices.reserveCapacity(12) // Arbitrary choice. |
203 | 0 |
|
204 | | #if os(Windows) |
205 | | // `GetAdaptersAddresses` writes a linked list of variably sized records into the |
206 | | // caller's buffer, so the buffer must be sized in *bytes*, not in whole |
207 | | // `IP_ADAPTER_ADDRESSES` records. 15KB is the initial allocation recommended by the |
208 | | // documentation; if that is not enough (or if the adapter information grows between |
209 | | // two calls) the call reports `ERROR_BUFFER_OVERFLOW` and updates `ulSize` with the |
210 | | // size now required, so the enumeration is retried a small number of times. |
211 | | var ulSize: ULONG = 15 * 1024 |
212 | | let ulBufferOverflow: ULONG = ULONG(ERROR_BUFFER_OVERFLOW) |
213 | | var ulResult: ULONG = ulBufferOverflow |
214 | | var attemptsRemaining: Int = 3 |
215 | | |
216 | | while ulResult == ulBufferOverflow && attemptsRemaining > 0 { |
217 | | attemptsRemaining -= 1 |
218 | | |
219 | | let pBuffer: UnsafeMutableRawPointer = |
220 | | UnsafeMutableRawPointer.allocate( |
221 | | byteCount: Int(ulSize), |
222 | | alignment: MemoryLayout<IP_ADAPTER_ADDRESSES>.alignment |
223 | | ) |
224 | | defer { |
225 | | pBuffer.deallocate() |
226 | | } |
227 | | |
228 | | let pAdapters: UnsafeMutablePointer<IP_ADAPTER_ADDRESSES> = |
229 | | pBuffer.bindMemory(to: IP_ADAPTER_ADDRESSES.self, capacity: 1) |
230 | | |
231 | | ulResult = GetAdaptersAddresses(ULONG(AF_UNSPEC), 0, nil, pAdapters, &ulSize) |
232 | | guard ulResult == ERROR_SUCCESS else { |
233 | | continue |
234 | | } |
235 | | |
236 | | var pAdapter: UnsafeMutablePointer<IP_ADAPTER_ADDRESSES>? = pAdapters |
237 | | while let adapter = pAdapter { |
238 | | var pUnicastAddress: UnsafeMutablePointer<IP_ADAPTER_UNICAST_ADDRESS>? = |
239 | | adapter.pointee.FirstUnicastAddress |
240 | | while let unicastAddress = pUnicastAddress { |
241 | | if let device = NIONetworkDevice(adapter, unicastAddress) { |
242 | | devices.append(device) |
243 | | } |
244 | | pUnicastAddress = unicastAddress.pointee.Next |
245 | | } |
246 | | pAdapter = adapter.pointee.Next |
247 | | } |
248 | | } |
249 | | |
250 | | guard ulResult == ERROR_SUCCESS else { |
251 | | throw IOError(windows: ulResult, reason: "GetAdaptersAddresses") |
252 | | } |
253 | | #elseif !os(WASI) |
254 | 0 | var interface: UnsafeMutablePointer<ifaddrs>? = nil |
255 | 0 | try SystemCalls.getifaddrs(&interface) |
256 | 0 | let originalInterface = interface |
257 | 0 | defer { |
258 | 0 | freeifaddrs(originalInterface) |
259 | 0 | } |
260 | 0 |
|
261 | 0 | while let concreteInterface = interface { |
262 | 0 | if let nioInterface = NIONetworkDevice(concreteInterface.pointee) { |
263 | 0 | devices.append(nioInterface) |
264 | 0 | } |
265 | 0 | interface = concreteInterface.pointee.ifa_next |
266 | 0 | } |
267 | 0 |
|
268 | | #endif |
269 | 0 | return devices |
270 | 0 | } |
271 | | } |
272 | | |
273 | | extension System { |
274 | | #if os(Linux) |
275 | | /// Returns true if the platform supports `UDP_SEGMENT` (GSO). |
276 | | /// |
277 | | /// The option can be enabled by setting the ``ChannelOptions/Types/DatagramSegmentSize`` channel option. |
278 | | public static let supportsUDPSegmentationOffload: Bool = CNIOLinux_supports_udp_segment() |
279 | | #else |
280 | | /// Returns true if the platform supports `UDP_SEGMENT` (GSO). |
281 | | /// |
282 | | /// The option can be enabled by setting the ``ChannelOptions/Types/DatagramSegmentSize`` channel option. |
283 | | public static let supportsUDPSegmentationOffload: Bool = false |
284 | | #endif |
285 | | |
286 | | #if os(Linux) |
287 | | /// Returns true if the platform supports `UDP_GRO`. |
288 | | /// |
289 | | /// The option can be enabled by setting the ``ChannelOptions/Types/DatagramReceiveOffload`` channel option. |
290 | | public static let supportsUDPReceiveOffload: Bool = CNIOLinux_supports_udp_gro() |
291 | | #else |
292 | | /// Returns true if the platform supports `UDP_GRO`. |
293 | | /// |
294 | | /// The option can be enabled by setting the ``ChannelOptions/Types/DatagramReceiveOffload`` channel option. |
295 | | public static let supportsUDPReceiveOffload: Bool = false |
296 | | #endif |
297 | | |
298 | | /// Returns `nil`. |
299 | | @available(*, deprecated, message: "UDP_MAX_SEGMENTS isn't exposed by the Linux kernel") |
300 | 0 | public static var udpMaxSegments: Int? { |
301 | 0 | nil |
302 | 0 | } |
303 | | } |
304 | | |
305 | | #if os(Windows) |
306 | | @usableFromInline |
307 | | package enum Windows { |
308 | | @usableFromInline |
309 | | package static func strerror(_ errnoCode: CInt) -> String? { |
310 | | withUnsafeTemporaryAllocation(of: CChar.self, capacity: 256) { ptr in |
311 | | if strerror_s(ptr.baseAddress, ptr.count, errnoCode) == 0 { |
312 | | return String(cString: UnsafePointer(ptr.baseAddress!)) |
313 | | } |
314 | | return nil |
315 | | } |
316 | | } |
317 | | |
318 | | package static func getenv(_ env: String) -> String? { |
319 | | var count = 0 |
320 | | var ptr: UnsafeMutablePointer<CChar>? = nil |
321 | | withUnsafeMutablePointer(to: &ptr) { buffer in |
322 | | // according to docs only EINVAL and ENOMEM are possible here. |
323 | | _ = _dupenv_s(buffer, &count, env) |
324 | | } |
325 | | defer { if let ptr { free(ptr) } } |
326 | | if count > 0, let ptr { |
327 | | let buffer = UnsafeBufferPointer(start: ptr, count: count) |
328 | | return buffer.withMemoryRebound(to: UInt8.self) { |
329 | | String(decoding: $0, as: Unicode.UTF8.self) |
330 | | } |
331 | | } else { |
332 | | return nil |
333 | | } |
334 | | } |
335 | | } |
336 | | #endif |