Thanks for being a part of WWDC25!

How did we do? We’d love to know your thoughts on this year’s conference. Take the survey here

URLSession download looping indefinitely until it times out

Hi,

I’m trying to download a remote file in the background, but I keep getting a strange behaviour where URLSession download my file indefinitely during a few minutes, without calling urlSession(_:downloadTask:didFinishDownloadingTo:) until the download eventually times out.

To find out that it’s looping, I’ve observed the total bytes written on disk by implementing urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:).

Note that I can't know the size of the file. The server is not able to calculate the size.

Below is my implementation.

I create an instance of URLSession like this:

  private lazy var session: URLSession = {
    let configuration = URLSessionConfiguration.background(withIdentifier: backgroundIdentifier)

    configuration.isDiscretionary = false
    configuration.sessionSendsLaunchEvents = true

    return URLSession(configuration: configuration,
                      delegate: self,
                      delegateQueue: nil)
  }()

My service is using async/await so I have implemented an AsyncThrowingStream :

  private var downloadTask: URLSessionDownloadTask?
  private var continuation: AsyncThrowingStream<(URL, URLResponse), Error>.Continuation?

  private var stream: AsyncThrowingStream<(URL, URLResponse), Error> {
    AsyncThrowingStream<(URL, URLResponse), Error> { continuation in
      self.continuation = continuation

      self.continuation?.onTermination = { @Sendable [weak self] data in
        self?.downloadTask?.cancel()
      }
      downloadTask?.resume()
    }
  }

Then to start the download, I do :

  private func download(with request: URLRequest) async throws -> (URL, URLResponse) {
    do {
      downloadTask = session.downloadTask(with: request)

      for try await (url, response) in stream {
        return (url, response)
      }
      throw NetworkingError.couldNotBuildRequest
    } catch {
      throw error
    }
  }

Then in the delegate :

  public func urlSession(_ session: URLSession,
                         downloadTask: URLSessionDownloadTask,
                         didFinishDownloadingTo location: URL) {
    guard let response = downloadTask.response,
          downloadTask.error == nil,
          (response as? HTTPURLResponse)?.statusCode == 200 else {
      continuation?.finish(throwing: downloadTask.error)

      return
    }

    do {
      let documentsURL = try FileManager.default.url(for: .documentDirectory,
                                                     in: .userDomainMask,
                                                     appropriateFor: nil,
                                                     create: false)
      let savedURL = documentsURL.appendingPathComponent(location.lastPathComponent)
      
      try FileManager.default.moveItem(at: location, to: savedURL)

      continuation?.yield((savedURL, response))
      continuation?.finish()
    } catch {
      continuation?.finish(throwing: error)
    }
  }

I also tried to replace let configuration = URLSessionConfiguration.background(withIdentifier: backgroundIdentifier) by let configuration = URLSessionConfiguration.default and this time I get a different error at the end of the download:

Task <0457F755-9C52-4CFB-BDB2-F378D0C94912>.<1> failed strict content length check - expected: 0, received: 530692, received (uncompressed): 0

Task <0457F755-9C52-4CFB-BDB2-F378D0C94912>.<1> finished with error [-1005] Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSLocalizedDescription=The network connection was lost., NSErrorFailingURLStringKey=https:/<host>:8190/proxy?Func=downloadVideoByUrl&SessionId=slufzwrMadvyJad8Lkmi9RUNAeqeq, NSErrorFailingURLKey=https://<host>:8190/proxy?Func=downloadVideoByUrl&SessionId=slufzwrMadvyJad8Lkmi9RUNAeqeq, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDownloadTask <0457F755-9C52-4CFB-BDB2-F378D0C94912>.<1>"
), _NSURLErrorFailingURLSessionTaskErrorKey=LocalDownloadTask <0457F755-9C52-4CFB-BDB2-F378D0C94912>.<1>, NSUnderlyingError=0x300d9a7c0 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={NSErrorPeerAddressKey=<CFData 0x302139db0 [0x1fcb1f598]>{length = 16, capacity = 16, bytes = 0x10021ffe91e227500000000000000000}}}}

The log "failed strict content length check” made me look into the response header, which has the following:

content-length: 0
Content-Type: application/force-download
Transfer-encoding: chunked
Connection: KEEP-ALIVE
Content-Transfer-Encoding: binary

So it should be fine the way I setup my URLSession.

The download works fine in Chrome/Safari/Chrome or Postman. My code used to work a couple of weeks before, so I expect something has changed on the server side, but I can’t find what, and I don’t get much help from the guys on the server side.

Has anyone an idea of what’s going on?

Answered by Frameworks Engineer in 839999022

Having both Content-Length: 0 and Transfer-Encoding: chunked is invalid. Please remove the Content-Length header field from the response.

Accepted Answer

Having both Content-Length: 0 and Transfer-Encoding: chunked is invalid. Please remove the Content-Length header field from the response.

URLSession download looping indefinitely until it times out
 
 
Q