mirror of
https://github.com/apple/swift-nio-extras.git
synced 2025-05-14 00:42:41 +08:00
Strict concurrency for NIOHTTPCompression (#257)
This commit is contained in:
parent
20c59b2d64
commit
0447b0359e
@ -50,7 +50,8 @@ var targets: [PackageDescription.Target] = [
|
||||
.product(name: "NIO", package: "swift-nio"),
|
||||
.product(name: "NIOCore", package: "swift-nio"),
|
||||
.product(name: "NIOHTTP1", package: "swift-nio"),
|
||||
]
|
||||
],
|
||||
swiftSettings: strictConcurrencySettings
|
||||
),
|
||||
.executableTarget(
|
||||
name: "HTTPServerWithQuiescingDemo",
|
||||
@ -132,7 +133,8 @@ var targets: [PackageDescription.Target] = [
|
||||
.product(name: "NIOEmbedded", package: "swift-nio"),
|
||||
.product(name: "NIOHTTP1", package: "swift-nio"),
|
||||
.product(name: "NIOConcurrencyHelpers", package: "swift-nio"),
|
||||
]
|
||||
],
|
||||
swiftSettings: strictConcurrencySettings
|
||||
),
|
||||
.testTarget(
|
||||
name: "NIOSOCKSTests",
|
||||
|
@ -16,7 +16,7 @@ import CNIOExtrasZlib
|
||||
import NIOCore
|
||||
|
||||
/// Namespace for compression code.
|
||||
public enum NIOCompression {
|
||||
public enum NIOCompression: Sendable {
|
||||
|
||||
/// Which algorithm should be used for compression.
|
||||
public struct Algorithm: CustomStringConvertible, Equatable, Sendable {
|
||||
|
@ -16,7 +16,7 @@ import CNIOExtrasZlib
|
||||
import NIOCore
|
||||
|
||||
/// Namespace for decompression code.
|
||||
public enum NIOHTTPDecompression {
|
||||
public enum NIOHTTPDecompression: Sendable {
|
||||
/// Specifies how to limit decompression inflation.
|
||||
public struct DecompressionLimit: Sendable {
|
||||
private enum Limit {
|
||||
|
@ -40,7 +40,7 @@ private class PromiseOrderer {
|
||||
let thisPromiseIndex = promiseArray.count
|
||||
promiseArray.append(promise)
|
||||
|
||||
promise.futureResult.whenComplete { (_: Result<Void, Error>) in
|
||||
promise.futureResult.hop(to: self.eventLoop).assumeIsolated().whenComplete { (_: Result<Void, Error>) in
|
||||
let priorFutures = self.promiseArray[0..<thisPromiseIndex]
|
||||
let subsequentFutures = self.promiseArray[(thisPromiseIndex + 1)...]
|
||||
let allPriorFuturesFired = priorFutures.map { $0.futureResult.isFulfilled }.allSatisfy { $0 }
|
||||
@ -772,9 +772,11 @@ class HTTPResponseCompressorTest: XCTestCase {
|
||||
}
|
||||
|
||||
func testConditionalCompressionEnabled() throws {
|
||||
let predicateWasCalled = expectation(description: "Predicate was called")
|
||||
let loop = EmbeddedEventLoop()
|
||||
defer { try! loop.syncShutdownGracefully() }
|
||||
let predicateWasCalled = loop.makePromise(of: Void.self)
|
||||
let compressor = HTTPResponseCompressor { responseHeaders, isCompressionSupported in
|
||||
defer { predicateWasCalled.fulfill() }
|
||||
defer { predicateWasCalled.succeed() }
|
||||
XCTAssertEqual(responseHeaders.headers, ["Content-Type": "json"])
|
||||
XCTAssertEqual(isCompressionSupported, true)
|
||||
return .compressIfPossible
|
||||
@ -796,13 +798,15 @@ class HTTPResponseCompressorTest: XCTestCase {
|
||||
]
|
||||
)
|
||||
|
||||
waitForExpectations(timeout: 0)
|
||||
try predicateWasCalled.futureResult.wait()
|
||||
}
|
||||
|
||||
func testUnsupportedRequestConditionalCompressionEnabled() throws {
|
||||
let predicateWasCalled = expectation(description: "Predicate was called")
|
||||
let loop = EmbeddedEventLoop()
|
||||
defer { try! loop.syncShutdownGracefully() }
|
||||
let predicateWasCalled = loop.makePromise(of: Void.self)
|
||||
let compressor = HTTPResponseCompressor { responseHeaders, isCompressionSupported in
|
||||
defer { predicateWasCalled.fulfill() }
|
||||
defer { predicateWasCalled.succeed() }
|
||||
XCTAssertEqual(responseHeaders.headers, ["Content-Type": "json"])
|
||||
XCTAssertEqual(isCompressionSupported, false)
|
||||
return .compressIfPossible
|
||||
@ -823,13 +827,15 @@ class HTTPResponseCompressorTest: XCTestCase {
|
||||
]
|
||||
)
|
||||
|
||||
waitForExpectations(timeout: 0)
|
||||
try predicateWasCalled.futureResult.wait()
|
||||
}
|
||||
|
||||
func testUnsupportedStatusConditionalCompressionEnabled() throws {
|
||||
let predicateWasCalled = expectation(description: "Predicate was called")
|
||||
let loop = EmbeddedEventLoop()
|
||||
defer { try! loop.syncShutdownGracefully() }
|
||||
let predicateWasCalled = loop.makePromise(of: Void.self)
|
||||
let compressor = HTTPResponseCompressor { responseHeaders, isCompressionSupported in
|
||||
defer { predicateWasCalled.fulfill() }
|
||||
defer { predicateWasCalled.succeed() }
|
||||
XCTAssertEqual(responseHeaders.status, .notModified)
|
||||
XCTAssertEqual(responseHeaders.headers, ["Content-Type": "json"])
|
||||
XCTAssertEqual(isCompressionSupported, false)
|
||||
@ -862,13 +868,15 @@ class HTTPResponseCompressorTest: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
waitForExpectations(timeout: 0)
|
||||
try predicateWasCalled.futureResult.wait()
|
||||
}
|
||||
|
||||
func testConditionalCompressionDisabled() throws {
|
||||
let predicateWasCalled = expectation(description: "Predicate was called")
|
||||
let loop = EmbeddedEventLoop()
|
||||
defer { try! loop.syncShutdownGracefully() }
|
||||
let predicateWasCalled = loop.makePromise(of: Void.self)
|
||||
let compressor = HTTPResponseCompressor { responseHeaders, isCompressionSupported in
|
||||
defer { predicateWasCalled.fulfill() }
|
||||
defer { predicateWasCalled.succeed() }
|
||||
XCTAssertEqual(responseHeaders.headers, ["Content-Type": "json"])
|
||||
XCTAssertEqual(isCompressionSupported, true)
|
||||
return .doNotCompress
|
||||
@ -889,13 +897,15 @@ class HTTPResponseCompressorTest: XCTestCase {
|
||||
]
|
||||
)
|
||||
|
||||
waitForExpectations(timeout: 0)
|
||||
try predicateWasCalled.futureResult.wait()
|
||||
}
|
||||
|
||||
func testUnsupportedRequestConditionalCompressionDisabled() throws {
|
||||
let predicateWasCalled = expectation(description: "Predicate was called")
|
||||
let loop = EmbeddedEventLoop()
|
||||
defer { try! loop.syncShutdownGracefully() }
|
||||
let predicateWasCalled = loop.makePromise(of: Void.self)
|
||||
let compressor = HTTPResponseCompressor { responseHeaders, isCompressionSupported in
|
||||
defer { predicateWasCalled.fulfill() }
|
||||
defer { predicateWasCalled.succeed() }
|
||||
XCTAssertEqual(responseHeaders.headers, ["Content-Type": "json"])
|
||||
XCTAssertEqual(isCompressionSupported, false)
|
||||
return .doNotCompress
|
||||
@ -916,13 +926,15 @@ class HTTPResponseCompressorTest: XCTestCase {
|
||||
]
|
||||
)
|
||||
|
||||
waitForExpectations(timeout: 0)
|
||||
try predicateWasCalled.futureResult.wait()
|
||||
}
|
||||
|
||||
func testUnsupportedStatusConditionalCompressionDisabled() throws {
|
||||
let predicateWasCalled = expectation(description: "Predicate was called")
|
||||
let loop = EmbeddedEventLoop()
|
||||
defer { try! loop.syncShutdownGracefully() }
|
||||
let predicateWasCalled = loop.makePromise(of: Void.self)
|
||||
let compressor = HTTPResponseCompressor { responseHeaders, isCompressionSupported in
|
||||
defer { predicateWasCalled.fulfill() }
|
||||
defer { predicateWasCalled.succeed() }
|
||||
XCTAssertEqual(responseHeaders.status, .notModified)
|
||||
XCTAssertEqual(responseHeaders.headers, ["Content-Type": "json"])
|
||||
XCTAssertEqual(isCompressionSupported, false)
|
||||
@ -955,14 +967,13 @@ class HTTPResponseCompressorTest: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
waitForExpectations(timeout: 0)
|
||||
try predicateWasCalled.futureResult.wait()
|
||||
}
|
||||
|
||||
func testConditionalCompressionModifiedHeaders() throws {
|
||||
let predicateWasCalled = expectation(description: "Predicate was called")
|
||||
predicateWasCalled.expectedFulfillmentCount = 2
|
||||
let counter = NIOLockedValueBox(0)
|
||||
let compressor = HTTPResponseCompressor { responseHeaders, isCompressionSupported in
|
||||
defer { predicateWasCalled.fulfill() }
|
||||
defer { counter.withLockedValue { $0 += 1 } }
|
||||
let isEnabled = responseHeaders.headers[canonicalForm: "x-compression"].first == "enable"
|
||||
XCTAssertEqual(
|
||||
responseHeaders.headers,
|
||||
@ -999,7 +1010,7 @@ class HTTPResponseCompressorTest: XCTestCase {
|
||||
]
|
||||
)
|
||||
|
||||
waitForExpectations(timeout: 0)
|
||||
XCTAssertEqual(counter.withLockedValue { $0 }, 2)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1009,25 +1020,22 @@ extension EventLoopFuture {
|
||||
// Easy, we're on the EventLoop. Let's just use our knowledge that we run completed future callbacks
|
||||
// immediately.
|
||||
var fulfilled = false
|
||||
self.whenComplete { _ in
|
||||
self.assumeIsolated().whenComplete { _ in
|
||||
fulfilled = true
|
||||
}
|
||||
return fulfilled
|
||||
} else {
|
||||
let lock = NIOLock()
|
||||
let group = DispatchGroup()
|
||||
var fulfilled = false // protected by lock
|
||||
let fulfilled = NIOLockedValueBox(false)
|
||||
|
||||
group.enter()
|
||||
self.eventLoop.execute {
|
||||
let isFulfilled = self.isFulfilled // This will now enter the above branch.
|
||||
lock.withLock {
|
||||
fulfilled = isFulfilled
|
||||
}
|
||||
fulfilled.withLockedValue { $0 = isFulfilled }
|
||||
group.leave()
|
||||
}
|
||||
group.wait() // this is very nasty but this is for tests only, so...
|
||||
return lock.withLock { fulfilled }
|
||||
return fulfilled.withLockedValue { $0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user