diff --git a/Benchmarks/Thresholds/6.0/Benchmarks.HTTPFields.init(dictionaryLiteral).p90.json b/Benchmarks/Thresholds/6.0/Benchmarks.HTTPFields.init(dictionaryLiteral).p90.json index bc880db..d30466d 100644 --- a/Benchmarks/Thresholds/6.0/Benchmarks.HTTPFields.init(dictionaryLiteral).p90.json +++ b/Benchmarks/Thresholds/6.0/Benchmarks.HTTPFields.init(dictionaryLiteral).p90.json @@ -1,3 +1,3 @@ { - "mallocCountTotal" : 14 + "mallocCountTotal" : 13 } \ No newline at end of file diff --git a/Benchmarks/Thresholds/nightly-6.0/Benchmarks.HTTPFields.init(dictionaryLiteral).p90.json b/Benchmarks/Thresholds/nightly-6.0/Benchmarks.HTTPFields.init(dictionaryLiteral).p90.json index bc880db..d30466d 100644 --- a/Benchmarks/Thresholds/nightly-6.0/Benchmarks.HTTPFields.init(dictionaryLiteral).p90.json +++ b/Benchmarks/Thresholds/nightly-6.0/Benchmarks.HTTPFields.init(dictionaryLiteral).p90.json @@ -1,3 +1,3 @@ { - "mallocCountTotal" : 14 + "mallocCountTotal" : 13 } \ No newline at end of file diff --git a/Benchmarks/Thresholds/nightly-main/Benchmarks.HTTPFields.init(dictionaryLiteral).p90.json b/Benchmarks/Thresholds/nightly-main/Benchmarks.HTTPFields.init(dictionaryLiteral).p90.json index bc880db..d30466d 100644 --- a/Benchmarks/Thresholds/nightly-main/Benchmarks.HTTPFields.init(dictionaryLiteral).p90.json +++ b/Benchmarks/Thresholds/nightly-main/Benchmarks.HTTPFields.init(dictionaryLiteral).p90.json @@ -1,3 +1,3 @@ { - "mallocCountTotal" : 14 + "mallocCountTotal" : 13 } \ No newline at end of file diff --git a/Sources/HTTPTypes/HTTPFields.swift b/Sources/HTTPTypes/HTTPFields.swift index c5319b4..5b63be5 100644 --- a/Sources/HTTPTypes/HTTPFields.swift +++ b/Sources/HTTPTypes/HTTPFields.swift @@ -12,6 +12,10 @@ // //===----------------------------------------------------------------------===// +#if compiler(>=6.0) +import Synchronization +#endif // compiler(>=6.0) + /// A collection of HTTP fields. It is used in `HTTPRequest` and `HTTPResponse`, and can also be /// used as HTTP trailer fields. /// @@ -21,13 +25,19 @@ /// `HTTPFields` adheres to modern HTTP semantics. In particular, the "Cookie" request header field /// is split into separate header fields by default. public struct HTTPFields: Sendable, Hashable { - private final class _Storage: @unchecked Sendable, Hashable { + private class _Storage: @unchecked Sendable, Hashable { var fields: [(field: HTTPField, next: UInt16)] = [] var index: [String: (first: UInt16, last: UInt16)]? = [:] - let lock = LockStorage.create(value: ()) + + required init() { + } + + func withLock(_ body: () throws -> Result) rethrows -> Result { + fatalError() + } var ensureIndex: [String: (first: UInt16, last: UInt16)] { - self.lock.withLockedValue { _ in + self.withLock { if let index = self.index { return index } @@ -45,10 +55,10 @@ public struct HTTPFields: Sendable, Hashable { } } - func copy() -> _Storage { - let newStorage = _Storage() + func copy() -> Self { + let newStorage = Self() newStorage.fields = self.fields - self.lock.withLockedValue { _ in + self.withLock { newStorage.index = self.index } return newStorage @@ -99,7 +109,40 @@ public struct HTTPFields: Sendable, Hashable { } } - private var _storage = _Storage() + #if compiler(>=6.0) + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) + private final class _StorageWithMutex: _Storage, @unchecked Sendable { + let mutex = Mutex(()) + + override func withLock(_ body: () throws -> Result) rethrows -> Result { + try self.mutex.withLock { _ in + try body() + } + } + } + #endif // compiler(>=6.0) + + private final class _StorageWithNIOLock: _Storage, @unchecked Sendable { + let lock = LockStorage.create(value: ()) + + override func withLock(_ body: () throws -> Result) rethrows -> Result { + try self.lock.withLockedValue { _ in + try body() + } + } + } + + private var _storage = { + #if compiler(>=6.0) + if #available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) { + _StorageWithMutex() + } else { + _StorageWithNIOLock() + } + #else // compiler(>=6.0) + _StorageWithNIOLock() + #endif // compiler(>=6.0) + }() /// Create an empty list of HTTP fields public init() {}