Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Race condition in Apple's sample code for Advanced NSOperations

TL:DR

When is the .Completed state set on NSURLSessionTask and how does it depend/affect the completionHandler of the same task?

Is there a way to make sure that the .Completed state is only set after the completionHandler is finished executing?

Question

Following another question on here... Chaining multiple async functions in Swift

I was pointed in the direction of the Advanced NSOperations WWDC Talk and Sample Code.

After replicating part of the code into my own project I've found that I seem to encounter a race condition that will sometimes work and sometimes fail depending on the way the race condition plays out.

The operation I have created is pretty much a copy of the DownloadEarthquakesOperation in the sample code.

It is a subclass of GroupOperation and contains a URLSessionTaskOperation. The NSURLSessionTask is created with a completionHandler which processes the downloaded data.

The class URLSessionTaskOperation works by observing the state property of its task and then finishing the operation when it is changed to .Completed.

The problem I am encountering is that it seems the state of the task is changed to .Completed before the completionHandler has finished processing.

The completion handler I have is like this...

// this is a direct copy of the sample code just using data task
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
    data, response, error in    
    self.downloadFinished(data, response: response as? NSHTTPURLResponse, error: error)
}

The function it calls (in pseudo code) is something like this... (I don't have access to the exact code at the moment).

func downloadFinished(data: NSData?, response: NSHTTPURLResponse?, error, NSError?) {
    // if the error is not nil then finish operation with error

    // if the response status code is not correct then finish with error

    // try to convert the data to a JSON object using NSJSONSerialization

    // finish with error if conversion failed

    // get a single Int value out of the JSON object

    // store the single Int value in an instance variable
}

There is no asynchronous code here.

After this has completed the part operation of this (equivalent to the GetEarthquakesOperation in the sample code) gets the value from the instance variable and passes it into the next operation.

The problem is that sometimes this value is there and sometimes it is nil.

By logging out various lines in the different classes I can see that the network operation is set as completed at some point along the execution of the completion handler. Sometimes before the value is set and sometimes after.

What's baffling is that I have tried forcing this to happen in the sample app but I couldn't. I have tried sleeping the completion handler and I've tried putting in a long execution time loop but neither of them seem to work.

Can anyone help me in trying to fix this race condition.

like image 712
Fogmeister Avatar asked Jul 17 '16 15:07

Fogmeister


1 Answers

OK, this is bizarre.

http://www.oliverfoggin.com/advanced-nsoperations-nsurlsessiondatatask-vs-nsurlsessiondownloadtask/

NSURLSessionDataTask and NSURLSessionDownloadTask both run their completion handlers and set their completed state in different ways.

The download task only sets itself to completed after the completion handler has finished executing.

The data task sets itself to completed after the completion handler has started executing.

This is causing the race condition in my own project. I think I'll switch to using the download task for now.

I'll be filing a radar also.

like image 83
Fogmeister Avatar answered Oct 31 '22 16:10

Fogmeister