Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions Sources/Conduit/Auth/OAuth2RequestPipelineMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ public struct OAuth2RequestPipelineMiddleware: RequestPipelineMiddleware {
/// against lengthy session locks when terminating mid-flight. This will allow the host process to quickly pick
/// up where the other process left off, if it needs to. Defaults to 30 seconds.
public var tokenRefreshLockRelinquishInterval: TimeInterval = 30
/// Kill switch for atomic in-process refresh serialization; static because instances are per-client value copies. Defaults to true.
public static var refreshClaimCoordinationEnabled = true
let clientConfiguration: OAuth2ClientConfiguration
let authorization: OAuth2Authorization
let tokenStorage: OAuth2TokenStore
Expand Down Expand Up @@ -315,3 +313,23 @@ public struct OAuth2RequestPipelineMiddleware: RequestPipelineMiddleware {
}

}

private let refreshClaimCoordinationFlagLock = NSLock()
private var storedRefreshClaimCoordinationEnabled = true

extension OAuth2RequestPipelineMiddleware {
/// Kill switch for atomic in-process refresh serialization; static because instances are per-client value copies. Defaults to true.
/// Lock-backed so it can be toggled at runtime while request pipelines read it.
public static var refreshClaimCoordinationEnabled: Bool {
get {
refreshClaimCoordinationFlagLock.lock()
defer { refreshClaimCoordinationFlagLock.unlock() }
return storedRefreshClaimCoordinationEnabled
}
set {
refreshClaimCoordinationFlagLock.lock()
defer { refreshClaimCoordinationFlagLock.unlock() }
storedRefreshClaimCoordinationEnabled = newValue
}
}
}
4 changes: 4 additions & 0 deletions Sources/Conduit/Networking/URLSessionClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ public struct URLSessionClient: URLSessionClientType {
throw URLSessionClientError.requestTimeout
}
if let error = result.error {
// The session's own request timeout races the semaphore above; report both as requestTimeout.
if (error as? URLError)?.code == .timedOut {
throw URLSessionClientError.requestTimeout
}
throw error
}
guard let response = result.response else {
Expand Down
13 changes: 11 additions & 2 deletions Tests/ConduitTests/Networking/URLSessionClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ class URLSessionClientTests: XCTestCase {

/// Verify sesson client throws error for timeout
func testBlockingTimeout() throws {
let configuration = URLSessionConfiguration.default
let configuration = URLSessionConfiguration.ephemeral
configuration.timeoutIntervalForRequest = 0.5
configuration.protocolClasses = [StalledURLProtocol.self]
let client = URLSessionClient(sessionConfiguration: configuration, delegateQueue: OperationQueue())
let request = try URLRequest(url: URL(absoluteString: "https://httpbingo.org/delay/1"))
let request = try URLRequest(url: URL(absoluteString: "https://stalled.invalid/delay"))
XCTAssertThrowsError(try client.begin(request: request), "Request did not timeout") { error in
XCTAssertEqual(error as? URLSessionClientError, .requestTimeout)
}
Expand Down Expand Up @@ -358,3 +359,11 @@ private class TransformingResponseMiddleware: ResponsePipelineMiddleware {
}

}

/// Never completes a request, forcing the timeout path deterministically.
private class StalledURLProtocol: URLProtocol {
override class func canInit(with request: URLRequest) -> Bool { true }
override class func canonicalRequest(for request: URLRequest) -> URLRequest { request }
override func startLoading() {}
override func stopLoading() {}
}
Loading