'Session lifecycle management with AlamoFire

My app has been reporting an unreproducible crash due to this error:

Task created in a session that has been invalidated

Other posts with this issue are usually calling invalidateAndCancel() or finishTasksAndInvalidate() on a Session and then reusing that Session. In this case we never explicitly call invalidate and the only place I see invalidation occurring is internally within the AlamoFire framework when the SessionManager is deinitialized.

My use case is a custom background SessionManager with a hardcoded sessionIdentifier to poll an S3 url and render a JSON once the URL returns a valid response.

Relevant code:

class Poller {
    private let jsonRequestClient: JSONRequestClient

    init() {
        self.jsonRequestClient = JSONRequestClient("com.app.poller")
    }

    private func scheduleRequest() {
        scheduledTask = ScheduledTask(timeIntervalSeconds: config.getPollingInterval())
        scheduledTask?.eventHandler = getResponse
        scheduledTask?.resume()
    }

    private lazy var getResponse = {[weak self] in
        guard let strongSelf = self else { return }

        strongSelf.scheduledRequest = strongSelf.jsonRequestClient.requestFromPath(path: strongSelf.urlString, params: [:], success: { response in
            strongSelf.handleSuccess(response: response)
            strongSelf.scheduleRequest()
        }, failure: { error, httpStatusCode in
            strongSelf.scheduleRequest()
        })
    }

}

class JSONRequestClient {
    private let sessionManager: SessionManager

    private init(sessionIdentifier: String) {
        let config = URLSessionConfiguration.background(withIdentifier: sessionIdentifier)
        self.sessionManager = Alamofire.SessionManager(configuration: config)
        self.sessionManager.retrier = retrier
        self.sessionManager.startRequestsImmediately = true
    }

    @discardableResult func requestFromPath(path: String, params: [String: String], success: @escaping (DataResponse<Any>?) -> Void, failure: @escaping (_ error: Error?, _ statusCode: Int?) -> Void) -> DataRequest? {
        var urlRequest = URLRequest(url: url)
        return sessionManager.request(urlRequest)
    }
}

I have noticed that in some cases, multiple of these Pollers can be created which would cause multiple SessionManagers to share the same id. I'm planning to resolving this by creating a singleton for the JSONRequestClient, which is a best practice mentioned in AlamoFire docs. However, from my testing, reusing the same sessionId on multiple Sessions does not cause this crash. Another scenario I have tested is creating multiple Sessions that share a single sessionId, then invalidating one of the sessions and attempting to make a request using the other one, this also did not repro the crash.

Some Questions:

Is this likely being caused from multiple SessionManagers sharing the same id?

Is it possible that after a SessionManager has been deinitialized the invalidated session is still referenced in AlamoFire? My code does not reference the Session and only uses the SessionManager so I would not think it's possible.

Another possibility I considered was a retain cycle in my Poller's eventHandler scheduling tasks causing it to call jsonRequestClient.requestFromPath() after the SessionManager has already been deinitialized. I added some safeguards (guard let strongSelf=self else { return }) to prevent this but maybe it's still happening?

I'm stumped so any ideas are appreciated, thanks!



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source