NSURLSession’s Resume Rate Limiter

This thread has been locked by a moderator.

NSURLSession’s background session support on iOS includes a resume rate limiter. This limiter exists to prevent apps from abusing the background session support in order to run continuously in the background. It works as follows:

  • nsurlsessiond
    (the daemon that does all the background session work) maintains a delay value for your app.
  • It doubles that delay every time it resumes (or relaunches) your app.

  • It resets that delay to 0 when the user brings your app to the front.

  • It also resets the delay to 0 if the delay period elapses without it having resumed your app.

  • When your app creates a new task while it is in the background, the task does not start until that delay has expired.

To understand the impact of this, consider what happens when you download 10 resources. If you pass them to the background session all at once, you see something like this:

  1. Your app creates tasks 1 through 10 in the background session.

  2. nsurlsessiond
    starts working on the first few tasks.
  3. As tasks complete,

    starts working on subsequent ones.
  4. Eventually all the tasks complete and

    resumes your app.

Now consider what happens if you only schedule one task at a time:

  1. Your app creates task 1.

  2. nsurlsessiond
    starts working on it.
  3. When it completes,

    resumes your app.
  4. Your app creates task 2.

  5. nsurlsessiond
    delays the start of task 2 a little bit.
  6. nsurlsessiond
    starts working on task 2.
  7. When it completes,

    resumes your app.
  8. Your app creates task 3.

  9. nsurlsessiond
    delays the start of task 3 by double the previous amount.
  10. nsurlsessiond
    starts working on task 3.
  11. When it completes,

    resumes your app.
  12. Steps 8 through 11 repeat, and each time the delay doubles. Eventually the delay gets so large that it looks like your app has stopped making progress.

If you have a lot of tasks to run then you can mitigate this problem by starting tasks in batches. That is, rather than start just one task in step 1, you would start 100. This only helps up to a point. If you have thousands of tasks to run, you will eventually start seeing serious delays. In that case it’s much better to change your design to use fewer, larger transfers.

Note All of the above applies to iOS 8 and later. Things worked differently in iOS 7. There’s a post on DevForums that explains the older approach.

Finally, keep in mind that there may be other reasons for your task not starting. Specifically, if the task is flagged as discretionary (because you set the

flag when creating the task’s session or because the task was started while your app was in the background), the task may be delayed for other reasons (low power, lack of Wi-Fi, and so on).

(r. 22323366)

Up vote post of eskimo