Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request explicitly cancelled #3795

Closed
MohammadAlBarari opened this issue Nov 15, 2023 · 5 comments
Closed

Request explicitly cancelled #3795

MohammadAlBarari opened this issue Nov 15, 2023 · 5 comments
Assignees
Labels

Comments

@MohammadAlBarari
Copy link

MohammadAlBarari commented Nov 15, 2023

What did you do?

Some of the user devices cant call the api "get request" because "Request explicitly cancelled." , I can't reproduce the bug. I am using combine
And the stack trace in crashlytics show the below

Non-fatal: Error
0  FormFly                        0x3a7520 FIRCLSUserLoggingRecordError + 393 (FIRCLSUserLogging.m:393)
1  FormFly                        0x1244c4 closure #1 in SessionApiRepository.validateOrganization(_:) + 60 (SessionApiRepository.swift:60)
2  Combine                        0x4a70 Publishers.Map.Inner.receive(_:)
3  Combine                        0x4a88 Publishers.Map.Inner.receive(_:)
4  FormFly                        0x436e10 closure #1 in DataResponsePublisher.Inner.request(_:)
5  FormFly                        0x43944c partial apply for closure #1 in DataResponsePublisher.Inner.request(_:)
6  FormFly                        0x4cb20c partial apply for closure #2 in closure #2 in closure #3 in closure #1 in DownloadRequest._response<A>(queue:responseSerializer:completionHandler:)
7  FormFly                        0x45fe78 thunk for @escaping @callee_guaranteed @Sendable () -> () (<compiler-generated>)
8  libdispatch.dylib              0x637a8 _dispatch_call_block_and_release
9  libdispatch.dylib              0x64780 _dispatch_client_callout
10 libdispatch.dylib              0x10bdc _dispatch_main_queue_drain
11 libdispatch.dylib              0x1082c _dispatch_main_queue_callback_4CF$VARIANT$mp
12 CoreFoundation                 0x919ac __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
13 CoreFoundation                 0x75648 __CFRunLoopRun
14 CoreFoundation                 0x79d20 CFRunLoopRunSpecific
15 GraphicsServices               0x1998 GSEventRunModal
16 UIKitCore                      0x37180c -[UIApplication _run]
17 UIKitCore                      0x371484 UIApplicationMain
18 libswiftUIKit.dylib            0x2addc UIApplicationMain(_:_:_:_:)
19 FormFly                        0x97ae4 main (AppDelegate.swift)
20 ???                            0x1b036c344 (Missing)

ℹ Please replace this with what you did.

What did you expect to happen?

ℹ Please replace this with what you expected to happen.

What happened instead?

ℹ Please replace this with of what happened instead.

Alamofire Environment

**Alamofire Version:5.8.0
Dependency Manager:
**Xcode Version:15
Swift Version:
**Platform(s) Running Alamofire: iPadOS 16.7.2
macOS Version Running Xcode:

Demo Project

ℹ Please link to or upload a project we can download that reproduces the issue.

@MohammadAlBarari
Copy link
Author

nserror-domain | Request explicitly cancelled.

@jshier
Copy link
Contributor

jshier commented Nov 15, 2023

Without any of your code I can't be sure, but most likely you're not properly retaining the cancellation token from your Combine stream, and it's being deinited, leading to the cancellation. In fact, some number of cancellations are expected when you scope requests to the lifetimes of things like view controllers or SwiftUI views.

Feel free to investigate further and respond with additional details if you find this cancellation is happening without the stream being cancelled or another cancel() call somewhere.

@jshier jshier closed this as completed Nov 15, 2023
@jshier jshier self-assigned this Nov 15, 2023
@jshier jshier added the support label Nov 15, 2023
@MohammadAlBarari
Copy link
Author

Also, I got Domain: URL is not valid error
although the domain was valid.

@jshier
Copy link
Contributor

jshier commented Nov 15, 2023

In that case you likely need to properly escape your URL, or use URLComponents to build it and have the escaping and validation done for you.

@MohammadAlBarari
Copy link
Author

MohammadAlBarari commented Nov 16, 2023

I believe the issue is that the request was not explicitly cancelled it something else.

What did you do?

class NetworkManager {
    static let shared = NetworkManager()
    private let session = Alamofire.Session(interceptor: interceptor)

    private static let credential = OAuthCredential()

    // Create the interceptor
    private static let authenticator = OAuthAuthenticator(endpoint: RefreshTokenEndpoint.refreshToken)
    private static let interceptor = AuthenticationInterceptor(authenticator: authenticator,
                                                               credential: credential)

    private init() {}

    func request<T: Decodable>(_ endpoint: Endpoint) -> AnyPublisher<Result<T, NetworkError>, Never> {
        let requestModifier: Session.RequestModifier = { request in
            if let dicToStream = endpoint.bodyToStream {
                let jsonData = try? JSONSerialization.data(withJSONObject: dicToStream, options: [])
                if let jsonData = jsonData {
                    let inputStream = InputStream(data: jsonData)
                    request.httpBodyStream = inputStream
                }
            }
        }
        NetworkManager.authenticator.endPoint = endpoint

        return session.request(endpoint.url,
                               method: endpoint.httpMethod,
                               parameters: endpoint.parameters,
                               encoding: endpoint.httpMethod == .get ? URLEncoding.default : JSONEncoding.default,
                               headers: HTTPHeaders(endpoint.headers ?? [:]),
                               requestModifier: requestModifier)
            .customValidate(url: endpoint.url)
            .cURLDescription { description in
                self.logRequest(endpoint, description)
            }
            .publishResponse(using: CustomResponseSerializable<T>(endpoint: endpoint))
            .map { result in
                result.value!
            }
            .eraseToAnyPublisher()
    }
}
struct OAuthCredential: AuthenticationCredential {
    var accessToken: Token? {
        SessionManager.shared.getCurrentUser()?.accessToken
    }

    var refreshToken: Token? {
        SessionManager.shared.getCurrentUser()?.refreshToken
    }

    // Require refresh if within 5 minutes of expiration
    var requiresRefresh: Bool {
        guard let refreshToken else { return false }

        if let accessToken {
            let expiryDate = Date(timeIntervalSince1970: Double(accessToken.expiry / 1000))
            return Date(timeIntervalSinceNow: 60 * 5) > expiryDate
        }
        return false
    }
}

class OAuthAuthenticator: Authenticator {
    var endPoint: Endpoint
    var subscriptions = Set<AnyCancellable>()
    var refreshTokenIfNeededUseCase = RefreshTokenIfNeededUseCase()

    init(endpoint: Endpoint) {
        self.endPoint = endpoint
    }

    func apply(_ credential: OAuthCredential, to urlRequest: inout URLRequest) {
        guard let refreshTokenEndPoint = (endPoint as? RefreshTokenProtocol)?.refreshTokenEndPoint
        else {
            /// If the request does not require authentication, we can directly return it as unmodified.
            return
        }

        urlRequest.setValue("" + (credential.accessToken?.value ?? ""), forHTTPHeaderField: "interceptor-token")
    }

    func refresh(_ credential: OAuthCredential, for session: Session, completion: @escaping (Result<OAuthCredential, Error>) -> Void) {
        guard let refreshTokenEndPoint = ((endPoint as? RefreshTokenProtocol)?.refreshTokenEndPoint as? RefreshTokenEndpoint)
        else {
            /// If the request does not require authentication, we can directly return it as unmodified.
            return
        }

        refreshTokenIfNeededUseCase.execute(value: .init(forceRefresh: true)).sink { result in
            switch result {
            case .success:
                completion(.success(OAuthCredential()))
            case .failure(let error):
                completion(.failure(error))
            }
        }.store(in: &subscriptions)
    }

    func didRequest(_ urlRequest: URLRequest, with response: HTTPURLResponse, failDueToAuthenticationError error: Error) -> Bool {
        return response.statusCode == 401
    }

    func isRequest(_ urlRequest: URLRequest, authenticatedWith credential: OAuthCredential) -> Bool {
        let token = credential.accessToken?.value ?? ""
        return urlRequest.headers["interceptor-token"] == token
    }
}

What did you expect to happen?

All the request should be sent successfully
And if it needs to refresh the token and add the authorization token, it should be added. if not, it should not be there

What happened instead?

All the expected work is fine but we have five users with the below issue
The users stuck on api call that was a get request that didn't need to add tokens using the interceptor. "It returned from the apply method without changing the request."
We sent that request to alamofire using the request method in our NetworkManager and it has not returned to success or failure block
But when we removed the authentication interceptor from the session property for this get request for those users, it worked, and when we put it back, it happened again.
So the problem happend if I use the interceptor in session proberty
private let session = Alamofire.Session(interceptor: interceptor)
But if I removed the interceptor as below it worked

    static var session : Session = {
        let configuration = URLSessionConfiguration.default
        configuration.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
        configuration.urlCache = nil // Disable URLCache
        
        return Alamofire.Session(configuration: configuration)
    }()

Users devices

Model: iPad 7th Generation (Cellular)
iOS Version: 17.1.1

Model: iPad 9th generation (Cellular)
iOS Version: 17.1.1

Model: iPad 9th generation (Cellular)
iOS Version: 17.1.1

iPad 9th generation (Cellular)
iPad12,2
iOS Version: 17.0.3

Alamofire Environment

**Alamofire Version:5.8.0
Dependency Manager:
**Xcode Version:**15.0.1 (15A507)
Swift Version:
Platform(s) Running Alamofire:
macOS Version Running Xcode:

Demo Project

ℹ Please link to or upload a project we can download that reproduces the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants