NSURLSessionDownloadTask issues with Storage almost full disk warnings

I'm having issues with handling "out of space" / "full disk" errors on ios with NSURLSessionDownloadTask


If the disk is full due to downloads done in the app I get a call to


URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?)


with the error having domain NSPOSIXErrorDomain and error code ENOSPC


But this only happens once for a task, not for all running


Ex: If I have 3 tasks running at the time, I only get this for one of them and the other 2 remain in the state Running

They don't receive any bytes, but they don't fail either.

Moreovever, calling cancel on any of those tasks changes their state from Running to Cancelling and they remain like this indefinitely.

My solution was, when I receive this error the first time, to call invalidateAndCancel for the session and handle failure for all running tasks.

This seems to work when the "full disk" is caused by the downloads made by the app.

But if "full disk" error is caused by external downloads (Ex: iTunes file sharing, downloading Podcasts, other apps downloading) I receive no error in the app

All my download tasks remain in Running, or Cancelling (if I try to cancel them)

They don't download anything, they don't fail with any of the callbacks for NSURLSessionDelegate or NSURLSessionDownloadDelegate


Before starting a download, I check the available space available on device

I also take into consideration the currently Running tasks

But I have no control over other downloads on the device that might end up triggering the "full disk" warning


How can I handle these cases?

Are the download tasks expected to remain in Running state though they are not downloading anymore?

Shouldn't I get a didCompleteWithError call with (NSPOSIXErrorDomain, ENOSPC) for each task, or for the session at least?

Or at least shouldn't I be able to successfully cancel them? and get a didCompleteWithError call anyway?


Is there a delegate call I'm missing, one that would let me know it's time to close all running tasks?


I'm using a shared background session for more background download tasks


The download tasks are created with NSURLSession's:

func downloadTaskWithRequest(_

request
: NSURLRequest) -> NSURLSessionDownloadTask


The sesion configuration is created using NSURLSessionConfiguration's:

class func backgroundSessionConfigurationWithIdentifier(_

identifier
: String
) -> NSURLSessionConfiguration


The seesion is created using, NSURLSession's

init(configuration

configuration
: NSURLSessionConfiguration,

delegate

delegate
: NSURLSessionDelegate?,

delegateQueue

queue
: NSOperationQueue?)


I'm using a NSOperationQueue with a maxConcurrentOperationCount of 3

I have implemented my NSURLSessionDelegate and NSURLSessionDownloadDelegate


Tasks seem to run fine in foreground and background.


Thanks

Replies

I’ve moved this thread to Core OS > Networking because other users of NSURLSession are more likely to find it there.

The short answer here is that NSURLSession does not include any smarts for handling the disk full case. The expected behaviour is that any task that’s actually transferring when the disk gets full will write to a file, that write will fail (with

ENOSPC
, as you’ve noticed), and the task will complete with that error. In a foreground session, where you can reliably predict when tasks will run, this is reasonably easy to handle. However, background sessions make this much more complex:
  • A task may be in the

    .Running
    state but not actually active on the network. This can happen for all sorts of reasons (for example, the session may have delayed the task until network conditions improve). If the task is not actively transferring, it won’t write to disk, so it will never see this error.
  • A background session uses the disk to persist its own state. This is what allows, for example, the session to resume work on a download after you’ve restarted the device. If the session can’t persist its own state because the disk is full, things won’t end well.

  • It’s likely that you’ll have no code running at the time that the disk gets full, so you can’t take active steps to avoid the problem.

I don’t know of any good way to resolve all of these issues. That, in itself, means that you should file a bug report about this. Please include a description of the various issues you’re seeing. If you can include a small test project that illustrates the issue, that’d be ideal.

I’d appreciate you posting your bug number, just for the record.

The best you can do right now is to avoid getting into this problem in the first place. That means:

  • checking for disk space before you start a download — You’re already doing this.

  • monitor disk space when your app is downloading — If your app is in the foreground, or your app gets resumed (or relaunched) in the background, actively monitor the disk space available. If it’s below some threshold, suspend your downloads (by calling

    -cancelByProducingResumeData:
    , so you can resume them later on).

Share and Enjoy

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

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

Hi,


I have a similar issue after receiving "disk full" error on a background download. I tried to "recover" from this error by cancelling all download tasks calling the invalidateAndCancelon the session but it doesn't work. Some download tasks cannot be cancelled in any way. I receive them always when I call getTasksWithCompletionHandler and their state is NSURLSessionTaskStateRunning.


Is there any solution to remove those download tasks without removing the app?


EDIT: Note that after rebooting the device I can see in the device console that all tasks are cancelled (but this is not the solution I'm looking for):

Download [<__NSCFBackgroundDownloadTask: 0x12e8e7ae0>{ taskIdentifier: 151 }] completed with error: The operation couldn’t be completed. No such file or directory

After the device was up and running again I started the app in the debugger and ... getTasksWithCompletionHandler returns no download tasks and this is great .. the only little problem remains the requirement to reboot the device.


Thank you,

Mihai

-invalidateAndCancel
should completely **** the session, including all of its tasks. I’ve no idea why it’s not working in your case.

Oh, one thing: are you sure you’re not ‘leaking’ background sessions? I’ve seen folks have problems like this, where they generate the background session identifier at runtime, and thus can tie themselves in knots when the session identifier they think is correct doesn’t match the actual session identifier being used by the system. At that point they have two actual background sessions, which can cause symptoms like this (invalidating session A doesn’t stop the tasks in session B).

Share and Enjoy

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

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

WWDC runs Mon, 13 Jun through to Fri, 17 Jun. During that time all of DTS will be at the conference, helping folks out face-to-face. http://developer.apple.com/wwdc/

Hi,


The background session identifier is hardcoded. I do not generate it at runtime.

Note that I call getTasksWithCompletionHandler on the session (created using the hardcoded identifier) somewhere at app startup and I get those active tasks.


Even if I would create a new background session with a new name (not my case), I think that calling getTasksWithCompletionHandler on the new session cannot give me back the tasks that run on the other session.


Regards,

Mihai