skip to Main Content

When i make post API call using URLSession and a network disconnection happens while the API is in progress, i receive a network error callback from URLSession –

Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={_kCFStreamErrorCodeKey=53, NSUnderlyingError=0x2822cc9f0 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={_kCFStreamErrorCodeKey=53, _kCFStreamErrorDomainKey=1}}

Then if the network is immediately reconnected, the URLSession silently retries the API call and i don’t receive any acknowledgement of the API call made.

As we received network error, we believe that the call has failed and attempt to retry the API. At this point 2 API calls were made, one from my retry mechanism and another from URL session auto retry mechanism.

Is there any way i can tell URL session not to auto retry? It is important for me to avoid making duplicate API calls to the backend.

This is my URLSession configuration –

let config = URLSessionConfiguration.default
config.waitsForConnectivity = true
config.timeoutIntervalForRequest = 1
config.timeoutIntervalForResource = 1
URLSession.shared.dataTask(with: request.urlRequest!) {data, response, error in
    // Do something
}.resume()

2

Answers


  1. In order to prevent URLSession from automatically retrying requests, you can set the urlCache property of your URLSessionConfiguration to nil. By default, URLSession uses an internal caching mechanism, and if a request fails, it might attempt to use a cached response when connectivity is restored.

    Here’s how you can modify your URLSession configuration to disable caching:

    let config = URLSessionConfiguration.default
    config.waitsForConnectivity = true
    config.timeoutIntervalForRequest = 1
    config.timeoutIntervalForResource = 1
    
    // Disable caching
    config.urlCache = nil
    config.requestCachePolicy = .reloadIgnoringLocalCacheData
    
    let session = URLSession(configuration: config)
    
    session.dataTask(with: request.urlRequest!) { data, response, error in
         if let error = error as NSError?,
                   error.code == NSURLErrorNetworkConnectionLost {
            session.invalidateAndCancel()
          }
    }.resume()
    
    Login or Signup to reply.
  2. To confirm that two connections are actually made from the one data task you would need to examine your server logs; you would actually see three connections:

    • The original connection
    • The "retried" connection
    • The second connection your code makes after getting the error.

    However, I highly doubt that this is what is occurring. You will see two connections; your original and the second connection your code makes after the error.

    It seems more likely that your server has committed the transaction and the network has failed before it can return the result.

    A network connection failure can happen at essentially three points;

    • The connection directly at the client device can fail (ie the mobile service or wifi network is lost)
    • The connection can fail directly at the server (eg the network switch to which the server is connected loses power) – this is highly unlikely in modern, fault tolerant virtualised data centres.
    • The connection can fail at some other point between the client and the server.

    We now need to consider what each endpoint "sees" in each of these three cases and to understand that TCP connections are quite resilient and will take quite some time to report a connection failure due to packet loss.

    In the first case the client network stack sees that the network connection is physically lost and can quickly report errors on all network connections (this what you see in your app). On the server side, however, the TCP retry process means that it will be some time until the connection is seen as lost (the client device can’t send a TCP packet closing the connection because it has no network connection…)

    In the second instance the situation would be reversed; the server would know straight away that the network had failed (but can’t tell the client) while the client would eventually get a timeout.

    In the third case, both ends would experience an eventual timeout.

    The first scenario is the most likely (and indeed you state that it is the client that has lost the connection).

    When the client connects, the server starts the transaction and presumably completes it in a short period of time, possibly a fraction of a second or a few seconds, but certainly less time than it takes for the server to receive a TCP timeout from the failed client connection.

    You are now left with the situation described by Rob in his comments. The server has completed the work but the client has received a network failure message before receiving the server reply.

    You can fix this on the server. For example, it can delay committing the transaction until the response is successfully delivered to the client. If the server receives a network error sending the response to the client, it can roll back the transaction. Of course there is still the possibility of a server failure before the transaction is committed but after the response has been delivered (but this is typically unlikely); truly idempotent processing is complicated.

    Alternatively you could implement a process for the client to check the state of the server after a network error to determine if the transaction completed or must be retried.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search