Coverage Report

Created: 2026-07-29 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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