NSURLSessionDownloadTask bug/feature on requests with Range header

I wrote an app which uses NSURLSessionDownloadTask in a background NSURLSession to download a portion of a big file, for example 100MB out of a 1GB file, by setting HTTP Range header in NSMutableURLRequest.

The task is created correctly and countOfBytesExpectedToReceive is correct and represents the requested range and the task would be completed usually but sometimes in an unknown circumstances (I guess occasionally when a task is moved to a new session with same background identifier), from a point onward the Range header will be discarded and task would begin to download entire file, albeit corrupted, and countOfBytesExpectedToReceive would be reset to the size of entire file.

I don't know whether it's my fault or it's a problem within framework itself, but I hope anybody here can help me to solve this problem.

Replies

An NSURLSession background session can automatically resume failed downloads. It does this using ETags and Range headers as described in QA1761 Resumable Downloads. This is a neat-o feature but, alas, it has a bug (r. 22555933). Specifically, the resume code does not take into account any Range header that you might have applied to the original request. The end result is that it’s not safe to use a Range header in an NSURLSession background session download task.

This makes me sad )-:

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thank you for information, please help me on this questions I have:

1. Is there any notification we can take into account to get resume data of downloaded part before it destorys downloaded data? or call to delegate methods?

2. is there a key to change this behaviour of resuming failed task?

3. Is there a plan to fix this issue in next release of iOS or in current release?

  1. Is there any notification we can take into account to get resume

data of downloaded part before it destorys downloaded data? or call to
delegate methods?
No. The problem is that your app might be suspended (or even terminated) when the resume happens, so a notification isn’t possible.

2. is there a key to change this behaviour of resuming failed task?

Not directly.

One option would be to tweak your request so that it’s not resumable. I posted on the old DevForums See the post below as to the criteria that NSURLSession background sessions apply when deciding whether to allow a resume. The drawback with this is … well … that you don’t get resume, which is kinda important.

If you have control over the server the best way to fix this problem would be to have the server publish the resource’s segments as separate resources. So, if the resource is <http://example.com/HugeFile.db, you could have the server allow you to access segments as <http://example.com/HugeFile.db/seg1, <http://example.com/HugeFile.db/seg2, and so on.

3\. Is there a plan to fix this issue in next release of iOS or in
current release?

I can’t comment on The Future™.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
In a post upthread I referenced a post on old DevForums. That link no longer works, alas (for an explanation as to why, see this post). Someone asked me about the info in the old post so I’ve included it below.

IMPORTANT I’ve edited this for appearance only. I read through the content to make sure that it’s not completely bogus but I made no attempt to bring it up to date. When you read the following, imagine that you’re reading something from Feb 2014, which was when it was written.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"



It’s also possible that the task has been paused due to throughput issues. The NSURLSession background session infrastructure imposes a high-level timeout on tasks with low throughput. For example, if the connection is transferring about 10 KB per second, it gets paused after 30 minutes.

Note The exact throughput limits are not documented, and probably never will be, simply because they are likely to change as the system evolves.

At that point NSURLSession waits for network connectivity to change before continuing the task, the theory here being that the new network might be faster. In my experience, turning Airplane Mode on and then off again will cause such a paused task to be continued shortly thereafter.

If the server does not support resuming a download this can result in pathological behaviour. Instead of resuming where it left off, the download will resume at the beginning. If the throughput limit isn’t an artefact of the local network but is, instead, a problem at the server end, the new transfer will run at the same speed of the old transfer and thus get paused at some point. This process continues forever.

IMPORTANT Below you’ll find information about what makes a download resumable.

I should stress that this issue only crops up for discretionary tasks, where the system will work away in the background trying to complete the task. For standard tasks the first failure will cause the system to resume your app to tell you about the failure.

Keep in mind, however, that tasks started while your app is in the background are always treated as discretionary.

Obviously this stuff needs better documentation. I’ve filed a bug requesting that (r. 15565034).

It would also be helpful if there was visibility into this issue, both programmatically and for debugging; we already have bugs on file about this (r. 14783761, r. 15090548, respectively) but feel free to file your own bugs with your own suggestions.



The following criteria must be met for a discretionary download to be resumed automatically by a background session:
  • The scheme of the requested URL must be http or https.

  • The request’s method must be GET.

  • The response must be an NSHTTPURLResponse (which should always be the case given the first point).

  • The response must include either the ETag or Last-Modified headers (or both).

  • The file to which the task is downloading must be present. This file is managed internally by NSURLSession, so you shouldn’t trip over this case (unless there’s been a cache folder cleanup in the interim).