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