Coverage Report

Created: 2026-09-14 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/swift-nio/Sources/NIOPosix/Pool.swift
Line
Count
Source
1
//===----------------------------------------------------------------------===//
2
//
3
// This source file is part of the SwiftNIO open source project
4
//
5
// Copyright (c) 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
15
#if !os(WASI)
16
17
protocol PoolElement {
18
    init()
19
    func evictedFromPool()
20
}
21
22
class Pool<Element: PoolElement> {
23
    private let maxSize: Int
24
    private var elements: [Element]
25
26
0
    init(maxSize: Int) {
27
0
        self.maxSize = maxSize
28
0
        self.elements = [Element]()
29
0
    }
30
31
0
    deinit {
32
0
        for e in elements {
33
0
            e.evictedFromPool()
34
0
        }
35
0
    }
36
37
0
    func get() -> Element {
38
0
        if elements.isEmpty {
39
0
            return Element()
40
0
        } else {
41
0
            return elements.removeLast()
42
0
        }
43
0
    }
44
45
0
    func put(_ e: Element) {
46
0
        if elements.count == maxSize {
47
0
            e.evictedFromPool()
48
0
        } else {
49
0
            elements.append(e)
50
0
        }
51
0
    }
52
}
53
54
/// A ``PooledBuffer`` is used to track an allocation of memory required
55
/// by a `Channel` or `EventLoopGroup`.
56
///
57
/// ``PooledBuffer`` is a reference type with inline storage. It is intended to
58
/// be bound to a single thread, and ensures that the allocation it stores does not
59
/// get freed before the buffer is out of use.
60
struct PooledBuffer: PoolElement {
61
    private static let sentinelValue = MemorySentinel(0xdead_beef)
62
63
    private let storage: BackingStorage
64
65
0
    init() {
66
0
        self.storage = .create(iovectorCount: Socket.writevLimitIOVectors)
67
0
        self.configureSentinel()
68
0
    }
69
70
0
    func evictedFromPool() {
71
0
        self.validateSentinel()
72
0
    }
73
74
    func withUnsafePointers<ReturnValue>(
75
        _ body: (UnsafeMutableBufferPointer<IOVector>, UnsafeMutableBufferPointer<Unmanaged<AnyObject>>) throws ->
76
            ReturnValue
77
0
    ) rethrows -> ReturnValue {
78
0
        defer {
79
0
            self.validateSentinel()
80
0
        }
81
0
        return try self.storage.withUnsafeMutableTypedPointers { iovecPointer, ownerPointer, _ in
82
0
            try body(iovecPointer, ownerPointer)
83
0
        }
84
0
    }
85
86
    /// Yields buffer pointers containing this ``PooledBuffer``'s readable bytes. You may hold a pointer to those bytes
87
    /// even after the closure has returned iff you model the lifetime of those bytes correctly using the `Unmanaged`
88
    /// instance. If you don't require the pointer after the closure returns, use ``withUnsafePointers``.
89
    ///
90
    /// If you escape the pointer from the closure, you _must_ call `storageManagement.retain()` to get ownership to
91
    /// the bytes and you also must call `storageManagement.release()` if you no longer require those bytes. Calls to
92
    /// `retain` and `release` must be balanced.
93
    ///
94
    /// - Parameters:
95
    ///   - body: The closure that will accept the yielded pointers and the `storageManagement`.
96
    /// - Returns: The value returned by `body`.
97
    func withUnsafePointersWithStorageManagement<ReturnValue>(
98
        _ body: (
99
            UnsafeMutableBufferPointer<IOVector>, UnsafeMutableBufferPointer<Unmanaged<AnyObject>>, Unmanaged<AnyObject>
100
        ) throws -> ReturnValue
101
0
    ) rethrows -> ReturnValue {
102
0
        let storageRef: Unmanaged<AnyObject> = Unmanaged.passUnretained(self.storage)
103
0
        return try self.storage.withUnsafeMutableTypedPointers { iovecPointer, ownerPointer, _ in
104
0
            try body(iovecPointer, ownerPointer, storageRef)
105
0
        }
106
0
    }
107
108
0
    private func configureSentinel() {
109
0
        self.storage.withUnsafeMutableTypedPointers { _, _, sentinelPointer in
110
0
            sentinelPointer.pointee = Self.sentinelValue
111
0
        }
112
0
    }
113
114
0
    private func validateSentinel() {
115
0
        self.storage.withUnsafeMutableTypedPointers { _, _, sentinelPointer in
116
0
            precondition(sentinelPointer.pointee == Self.sentinelValue, "Detected memory handling error!")
117
0
        }
118
0
    }
119
}
120
121
extension PooledBuffer {
122
    fileprivate typealias MemorySentinel = UInt32
123
124
    fileprivate struct PooledBufferHead {
125
        let iovectorCount: Int
126
127
        let spaceForIOVectors: Int
128
129
        let spaceForBufferOwners: Int
130
131
0
        init(iovectorCount: Int) {
132
0
            var spaceForIOVectors = MemoryLayout<IOVector>.stride * iovectorCount
133
0
            spaceForIOVectors.roundUpToAlignment(for: Unmanaged<AnyObject>.self)
134
0
135
0
            var spaceForBufferOwners = MemoryLayout<Unmanaged<AnyObject>>.stride * iovectorCount
136
0
            spaceForBufferOwners.roundUpToAlignment(for: MemorySentinel.self)
137
0
138
0
            self.iovectorCount = iovectorCount
139
0
            self.spaceForIOVectors = spaceForIOVectors
140
0
            self.spaceForBufferOwners = spaceForBufferOwners
141
0
        }
142
143
0
        var totalByteCount: Int {
144
0
            self.spaceForIOVectors + self.spaceForBufferOwners + MemoryLayout<MemorySentinel>.size
145
0
        }
146
147
0
        var iovectorOffset: Int {
148
0
            0
149
0
        }
150
151
0
        var bufferOwnersOffset: Int {
152
0
            self.spaceForIOVectors
153
0
        }
154
155
0
        var memorySentinelOffset: Int {
156
0
            self.spaceForIOVectors + self.spaceForBufferOwners
157
0
        }
158
    }
159
160
    fileprivate final class BackingStorage: ManagedBuffer<PooledBufferHead, UInt8> {
161
0
        static func create(iovectorCount: Int) -> Self {
162
0
            let head = PooledBufferHead(iovectorCount: iovectorCount)
163
0
164
0
            let baseStorage = Self.create(minimumCapacity: head.totalByteCount) { _ in
165
0
                head
166
0
            }
167
0
168
0
            // Here we set up our memory bindings.
169
0
170
0
            // Intentionally using a force cast here to avoid a miss compiliation in 5.10.
171
0
            // This is as fast as an unsafeDownCast since ManagedBuffer is inlined and the optimizer
172
0
            // can eliminate the upcast/downcast pair
173
0
            let storage = baseStorage as! Self
174
0
            storage.withUnsafeMutablePointers { headPointer, tailPointer in
175
0
                UnsafeRawPointer(tailPointer + headPointer.pointee.iovectorOffset).bindMemory(
176
0
                    to: IOVector.self,
177
0
                    capacity: iovectorCount
178
0
                )
179
0
                UnsafeRawPointer(tailPointer + headPointer.pointee.bufferOwnersOffset).bindMemory(
180
0
                    to: Unmanaged<AnyObject>.self,
181
0
                    capacity: iovectorCount
182
0
                )
183
0
                UnsafeRawPointer(tailPointer + headPointer.pointee.memorySentinelOffset).bindMemory(
184
0
                    to: MemorySentinel.self,
185
0
                    capacity: 1
186
0
                )
187
0
            }
188
0
189
0
            return storage
190
0
        }
191
192
        func withUnsafeMutableTypedPointers<ReturnType>(
193
            _ body: (
194
                UnsafeMutableBufferPointer<IOVector>, UnsafeMutableBufferPointer<Unmanaged<AnyObject>>,
195
                UnsafeMutablePointer<MemorySentinel>
196
            ) throws -> ReturnType
197
0
        ) rethrows -> ReturnType {
198
0
            try self.withUnsafeMutablePointers { headPointer, tailPointer in
199
0
                let iovecPointer = UnsafeMutableRawPointer(tailPointer + headPointer.pointee.iovectorOffset)
200
0
                    .assumingMemoryBound(to: IOVector.self)
201
0
                let ownersPointer = UnsafeMutableRawPointer(tailPointer + headPointer.pointee.bufferOwnersOffset)
202
0
                    .assumingMemoryBound(to: Unmanaged<AnyObject>.self)
203
0
                let sentinelPointer = UnsafeMutableRawPointer(tailPointer + headPointer.pointee.memorySentinelOffset)
204
0
                    .assumingMemoryBound(to: MemorySentinel.self)
205
0
206
0
                let iovecBufferPointer = UnsafeMutableBufferPointer(
207
0
                    start: iovecPointer,
208
0
                    count: headPointer.pointee.iovectorCount
209
0
                )
210
0
                let ownersBufferPointer = UnsafeMutableBufferPointer(
211
0
                    start: ownersPointer,
212
0
                    count: headPointer.pointee.iovectorCount
213
0
                )
214
0
                return try body(iovecBufferPointer, ownersBufferPointer, sentinelPointer)
215
0
            }
216
0
        }
217
    }
218
}
219
220
extension Int {
221
0
    fileprivate mutating func roundUpToAlignment<Type>(for: Type.Type) {
222
0
        // Alignment is always positive, we can use unchecked subtraction here.
223
0
        let alignmentGuide = MemoryLayout<Type>.alignment &- 1
224
0
225
0
        // But we can't use unchecked addition.
226
0
        self = (self + alignmentGuide) & (~alignmentGuide)
227
0
    }
228
}
229
230
struct PooledMsgBuffer: PoolElement {
231
232
    private typealias MemorySentinel = UInt32
233
    private static let sentinelValue = MemorySentinel(0xdead_beef)
234
235
    private struct PooledMsgBufferHead {
236
        let count: Int
237
        let spaceForMsgHdrs: Int
238
        let spaceForAddresses: Int
239
        let spaceForControlData: Int
240
241
0
        init(count: Int) {
242
0
            var spaceForMsgHdrs = MemoryLayout<MMsgHdr>.stride * count
243
0
            spaceForMsgHdrs.roundUpToAlignment(for: sockaddr_storage.self)
244
0
245
0
            var spaceForAddress = MemoryLayout<sockaddr_storage>.stride * count
246
0
            spaceForAddress.roundUpToAlignment(for: MemorySentinel.self)
247
0
248
0
            var spaceForControlData = (UnsafeControlMessageStorage.bytesPerMessage * count)
249
0
            spaceForControlData.roundUpToAlignment(for: cmsghdr.self)
250
0
251
0
            self.count = count
252
0
            self.spaceForMsgHdrs = spaceForMsgHdrs
253
0
            self.spaceForAddresses = spaceForAddress
254
0
            self.spaceForControlData = spaceForControlData
255
0
        }
256
257
0
        var totalByteCount: Int {
258
0
            self.spaceForMsgHdrs + self.spaceForAddresses + self.spaceForControlData + MemoryLayout<MemorySentinel>.size
259
0
        }
260
261
0
        var msgHdrsOffset: Int {
262
0
            0
263
0
        }
264
265
0
        var addressesOffset: Int {
266
0
            self.spaceForMsgHdrs
267
0
        }
268
269
0
        var controlDataOffset: Int {
270
0
            self.spaceForMsgHdrs + self.spaceForAddresses
271
0
        }
272
273
0
        var memorySentinelOffset: Int {
274
0
            self.spaceForMsgHdrs + self.spaceForAddresses + self.spaceForControlData
275
0
        }
276
    }
277
278
    private class BackingStorage: ManagedBuffer<PooledMsgBufferHead, UInt8> {
279
0
        static func create(count: Int) -> Self {
280
0
            let head = PooledMsgBufferHead(count: count)
281
0
282
0
            let baseStorage = Self.create(minimumCapacity: head.totalByteCount) { _ in
283
0
                head
284
0
            }
285
0
286
0
            // Intentionally using a force cast here to avoid a miss compiliation in 5.10.
287
0
            // This is as fast as an unsafeDownCast since ManagedBuffer is inlined and the optimizer
288
0
            // can eliminate the upcast/downcast pair
289
0
            let storage = baseStorage as! Self
290
0
            storage.withUnsafeMutablePointers { headPointer, tailPointer in
291
0
                UnsafeRawPointer(tailPointer + headPointer.pointee.msgHdrsOffset).bindMemory(
292
0
                    to: MMsgHdr.self,
293
0
                    capacity: count
294
0
                )
295
0
                UnsafeRawPointer(tailPointer + headPointer.pointee.addressesOffset).bindMemory(
296
0
                    to: sockaddr_storage.self,
297
0
                    capacity: count
298
0
                )
299
0
                // space for control message data not needed to be bound
300
0
                UnsafeRawPointer(tailPointer + headPointer.pointee.memorySentinelOffset).bindMemory(
301
0
                    to: MemorySentinel.self,
302
0
                    capacity: 1
303
0
                )
304
0
            }
305
0
306
0
            return storage
307
0
        }
308
309
        func withUnsafeMutableTypedPointers<ReturnType>(
310
            _ body: (
311
                UnsafeMutableBufferPointer<MMsgHdr>, UnsafeMutableBufferPointer<sockaddr_storage>,
312
                UnsafeControlMessageStorage, UnsafeMutablePointer<MemorySentinel>
313
            ) throws -> ReturnType
314
0
        ) rethrows -> ReturnType {
315
0
            try self.withUnsafeMutablePointers { headPointer, tailPointer in
316
0
                let msgHdrsPointer = UnsafeMutableRawPointer(tailPointer + headPointer.pointee.msgHdrsOffset)
317
0
                    .assumingMemoryBound(to: MMsgHdr.self)
318
0
                let addressesPointer = UnsafeMutableRawPointer(tailPointer + headPointer.pointee.addressesOffset)
319
0
                    .assumingMemoryBound(to: sockaddr_storage.self)
320
0
                let controlDataPointer = UnsafeMutableRawBufferPointer(
321
0
                    start: tailPointer + headPointer.pointee.controlDataOffset,
322
0
                    count: headPointer.pointee.spaceForControlData
323
0
                )
324
0
                let sentinelPointer = UnsafeMutableRawPointer(tailPointer + headPointer.pointee.memorySentinelOffset)
325
0
                    .assumingMemoryBound(to: MemorySentinel.self)
326
0
327
0
                let msgHdrsBufferPointer = UnsafeMutableBufferPointer(
328
0
                    start: msgHdrsPointer,
329
0
                    count: headPointer.pointee.count
330
0
                )
331
0
                let addressesBufferPointer = UnsafeMutableBufferPointer(
332
0
                    start: addressesPointer,
333
0
                    count: headPointer.pointee.count
334
0
                )
335
0
                let controlMessageStorage = UnsafeControlMessageStorage.makeNotOwning(
336
0
                    bytesPerMessage: UnsafeControlMessageStorage.bytesPerMessage,
337
0
                    buffer: controlDataPointer
338
0
                )
339
0
                return try body(msgHdrsBufferPointer, addressesBufferPointer, controlMessageStorage, sentinelPointer)
340
0
            }
341
0
        }
342
    }
343
344
0
    private func validateSentinel() {
345
0
        self.storage.withUnsafeMutableTypedPointers { _, _, _, sentinelPointer in
346
0
            precondition(sentinelPointer.pointee == Self.sentinelValue, "Detected memory handling error!")
347
0
        }
348
0
    }
349
350
    private var storage: BackingStorage
351
352
0
    init() {
353
0
        self.storage = .create(count: Socket.writevLimitIOVectors)
354
0
        self.storage.withUnsafeMutableTypedPointers { _, _, _, sentinelPointer in
355
0
            sentinelPointer.pointee = Self.sentinelValue
356
0
        }
357
0
    }
358
359
0
    func evictedFromPool() {
360
0
        self.validateSentinel()
361
0
    }
362
363
    func withUnsafePointers<ReturnValue>(
364
        _ body: (
365
            UnsafeMutableBufferPointer<MMsgHdr>, UnsafeMutableBufferPointer<sockaddr_storage>,
366
            UnsafeControlMessageStorage
367
        ) throws -> ReturnValue
368
0
    ) rethrows -> ReturnValue {
369
0
        defer {
370
0
            self.validateSentinel()
371
0
        }
372
0
        return try self.storage.withUnsafeMutableTypedPointers { msgs, addresses, controlMessageStorage, _ in
373
0
            try body(msgs, addresses, controlMessageStorage)
374
0
        }
375
0
    }
376
377
    func withUnsafePointersWithStorageManagement<ReturnValue>(
378
        _ body: (
379
            UnsafeMutableBufferPointer<MMsgHdr>, UnsafeMutableBufferPointer<sockaddr_storage>,
380
            UnsafeControlMessageStorage, Unmanaged<AnyObject>
381
        ) throws -> ReturnValue
382
0
    ) rethrows -> ReturnValue {
383
0
        let storageRef: Unmanaged<AnyObject> = Unmanaged.passUnretained(self.storage)
384
0
        return try self.storage.withUnsafeMutableTypedPointers { msgs, addresses, controlMessageStorage, _ in
385
0
            try body(msgs, addresses, controlMessageStorage, storageRef)
386
0
        }
387
0
    }
388
}
389
#endif  // !os(WASI)