/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) |