implementing URLSession:dataTask:didReceiveResponse:completionHandler: with a custom NSURLProtocol leads to continuous stopLoading/startLoading calls

Originator:nolan.d.obrien
Number:rdar://19494690 Date Originated:Jan 15th, 2015
Status:Open Resolved:
Product:iOS SDK Product Version:iOS 8.0+
Classification:Bug/Regression Reproducible:100%
 
Summary:
We rely on a custom NSURLProtocol for our SPDY (precursor to HTTP/2) communication to give us in depth control over our SPDY configuration.  However, as of iOS 8, there seems to be a significant bug with the URLSession:dataTask:didReceiveResponse:completionHandler: of the NSURLSessionDataTaskDelegate.  Whenever the callback completes, the custom NSURLProtocol will be called by the NSURL stack with stopLoading and then startLoading.  Since stopLoading is used to cancel the instance of the NSURLProtocol (not suspend it), the startLoading will result in continuing the process almost indefinitely (until a timeout happens or the entire payload can be downloaded fast enough).

Steps to Reproduce:
0) With iOS 8 (simulator or device)
1) Have an NSURLSession configured to use a custom NSURLProtocol (CocoaSPDY from Twitter is a good example https://github.com/twitter/CocoaSPDY - use the NSURLConnectionProtocol)
2) Implement an NSURLSessionDataTaskDelegate, specifically the URLSession:dataTask:didReceiveResponse:completionHandler: callback
3) Create an NSURLSession with an NSURLSessionConfiguration that has the custom NSURLProtocol in its "protocols" property, uses the aforementioned delegate and uses an NSOperationQueue with maxConcurrentOperations set to 1
4) Create an NSURLSessionDataTask that will utilize the custom protocol (for SPDYConnectionURLProtocol, you can do an HTTP GET request to https://twitter.com/ <--- must be exact)
5) Add logging to stopLoading & startLoading in the custom protocol and/or logging to the delegate callback


Expected Results:
the didReceiveResponse: callback is hit only once
startLoading is hit only once (per instance)
stopLoading is hit only once (per instance)

Actual Results:
every time the didReceiveResponse: method finishes its callback, stopLoading is called followed by startLoading which restarts the request operation.  This occurs over and over.  Eventually the attempt will timeout or the payload will download fast enough to complete the attempt.

Version:
iOS SDK 8 or higher

Notes:


Configuration:
Anything running iOS 8 (simulator repros just fine)

Sample:
I get 100% repro with this:

- (void)test
{
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://www.twitter.com/"]];
    NSURLSessionConfiguration *nsConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
    nsConfig.protocolClasses = @[[TestURLProtocol class]]; // <-- any proper NSURLProtocol custom implementation
    NSURLSession *session = [NSURLSession sessionWithConfiguration:nsConfig delegate:self delegateQueue:nil];
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
    [task resume];
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
    completionHandler(NSURLSessionResponseAllow);
}

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!