XCTest: NSURLSession: Stall on main thread - xcode

When running an XCTest that executes a network operation, i get the following error:
-waitForExpectationsWithTimeout: timed out but was unable to run the timeout handler because the main thread was unresponsive (0.5 seconds is allowed after the wait times out). Conditions that may cause this include processing blocking IO on the main thread, calls to sleep(), deadlocks, and synchronous IPC. Xcode will attempt to relaunch the process and continue with the next test...
I have a test that executes a Network Command method which uses NSURLSession under the hood.
This test is part of a test class with 4 consecutive network request tests.
When run independently from other tests it succeeds every time.
When running the entire Test file (4 tests in total), it succeeds every time.
But when running the entire test suite (50 tests), i get "Stall on main thread" on the first network operation test. The other 3 network operation tests go through fine.
Here is the code for the test:
- (void)test_session_001_ObtainsSessionObjectFromServer
{
XCTestExpectation *expectation = [self expectationWithDescription:[self description]];
id<IPVideoSessionProtocol> provider = [IPVideoSessionFactory getProvider];
[provider createVideoSessionWithUserID:#"SomeUserID"
withCompletionBlock:^(IPVideoSession *session) {
if ([session isKindOfClass:[IPVideoSession class]] &&
[session.sessionID isKindOfClass:[NSString class]] &&
session.sessionID.length > 0) {
// The returned session ID is populated
[expectation fulfill];
}
}];
[self waitForExpectationsWithTimeout:5.0f handler:^(NSError *error) {
if (error)
{
XCTFail(#"IPVideoSessionManagerTests Did not execute in allotted time");
}
}];
}
The code inside createVideoSession...
- (void)createVideoSessionWithUserID:(NSString*)userID
withCompletionBlock:(IPVideoSessionCompletionBlock)block
{
IPCreateVideoSessionCommand* command = [[IPCreateVideoSessionCommand alloc]
initWithUserID:userID];
[command executeWithCompletionBlock:^(IPNetworkError *error, NSString *resultJSON)
{
if (error) {
[IPAlertPresenter displayNetworkError:error];
block(nil);
return;
}
NSDictionary *dict = [resultJSON toJsonDictionary];
block([IPVideoSession getSessionFromDictionary:dict]);
}];
}
The code inside executeWithCompletionBlock:
- (void)executeWithCompletionBlock:(CommandCompletionBlock)block
{
if (!self.isConnected && ![[IPDebugManager sharedInstance] networkBypassActivated]) {
IPNetworkError* error = [[IPNetworkError alloc] initWithType:NetworkErrorTypeNotConnectedToNetwork];
block(error, nil);
return;
}
NSURLSession *session = [self composeSession];
NSURLRequest *request = [self composeRequest];
self.strongSelf = self;
__weak __typeof(self) weakSelf = self;
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
[IPDispatch toMainThread:^{
[weakSelf
handleCommandResponse:response
withData:data
withError:error
withCompletionBlock:block];
weakSelf.strongSelf = nil;
}];
}];
[dataTask resume];
}
NOTE: This error only happens when all tests are run. This test succeeds by itself, and also: if I run all 4 tests inside this Test Class.

Related

ManagedObjectContexts with threads (dispatch queues) gets into a deadlock on iOS7

I know there are many threads about NSManagedObjectContexts and threads but my problem seems to be only specific to iOS7. (Or at least not visible in OS6)
I have an app that makes use of dispatch_queue_ and runs multiple threads to fetch data from the server and update the UI. The app was working fine on iOS6 but on iOS7 it seems to get into deadlocks(mutex wait). See below the stack trace -
The "wait" happens in different methods usually when executing a fetch request and saving a (different) context. The commit Method is as follows :
-(void)commit:(BOOL) shouldUndoIfError forMoc:(NSManagedObjectContext*)moc {
#try {
// shouldUndoIfError = NO;
// get the moc for this thread
NSManagedObjectContext *moc = [self safeManagedObjectContext];
NSThread *thread = [NSThread currentThread];
NSLog(#"got login");
if ([thread isMainThread] == NO) {
// only observe notifications other than the main thread
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:moc];
NSLog(#"not main thread");
}
NSError *error;
if (![moc save:&error]) {
// fail
NSLog(#"ERROR: SAVE OPERATION FAILED %#", error);
if(shouldUndoIfError) {
[moc undo];
}
}
if ([thread isMainThread] == NO) {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:moc];
}
}
#catch (NSException *exception) {
NSLog(#"Store commit - %#",exception);
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:#"name",#"store commit",#"exception", exception.description, nil];
[Flurry logEvent:#"MyException" withParameters:dictionary timed:YES];
}
#finally {
NSLog(#"Store saved");
}
}
How I'm creating new contexts for each thread :
-(NSManagedObjectContext *)safeManagedObjectContext {
#try {
if(self.managedObjectContexts == nil){
NSMutableDictionary *_dict = [[NSMutableDictionary alloc]init];
self.managedObjectContexts = _dict;
[_dict release];
_dict = nil;
}
NSManagedObjectContext *moc = self.managedObjectContext;
NSThread *thread = [NSThread currentThread];
if ([thread isMainThread]) {
return moc;
}
// a key to cache the context for the given thread
NSString *threadKey = [NSString stringWithFormat:#"%p", thread];
if ( [self.managedObjectContexts valueForKey:threadKey] == nil) {
// create a context for this thread
NSManagedObjectContext *threadContext = [[[NSManagedObjectContext alloc] init] retain];
[threadContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
[threadContext setPersistentStoreCoordinator:[moc persistentStoreCoordinator]];
[threadContext setUndoManager:nil];
// cache the context for this thread
[self.managedObjectContexts setObject:threadContext forKey:threadKey];
NSLog(#"added a context to dictionary, length is %d",[self.managedObjectContexts count]);
}
return [self.managedObjectContexts objectForKey:threadKey];
}
#catch (NSException *exception) {
//
}
#finally {
//
}
}
What I have so far :
One Persistent Store coordinator.
Each New thread has its own Managed Object Context.
Strange part is that the same code worked fine on OS6 but not on OS7. I am still using the xcode4.6.3 to compile the code. Most of the code works on this principle, I run a thread, fetch data, commit it and then post a notification. Could the freeze/deadlock be because the notification gets posted and my UI elements fetch the data before the save(&merge) are reflected? Anything else that I'm missing ?

cocoa downloading file callbacks not called when downloaded from background thread, works on main thread

I am new to cocoa programming , I am trying to download binary file from a url to disk. Unfortunately the methods are not calledback for some reason. The call to downloadFile is made from a background thread started via [self performSelectorOnBackground] etc. Any ideas what am I doing wrong ?
NOTE
When I make call to downloadFile from main UI thread, it seems to work, what's up with background thread ?
-(BOOL) downloadFile
{
BOOL downloadStarted = NO;
// Create the request.
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:versionLocation]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// Create the connection with the request and start loading the data.
NSURLDownload *theDownload = [[NSURLDownload alloc] initWithRequest:theRequest
delegate:self];
if (theDownload) {
// Set the destination file.
[theDownload setDestination:#"/tmp" allowOverwrite:YES];
downloadStarted = YES;
} else {
// inform the user that the download failed.
downloadStarted = NO;
}
return downloadStarted;
}
- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
{
// Release the connection.
[download release];
// Inform the user.
NSLog(#"Download failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)downloadDidFinish:(NSURLDownload *)download
{
// Release the connection.
[download release];
// Do something with the data.
NSLog(#"%#",#"downloadDidFinish");
}
I think you should start the run loop to which your NSURLDownload object is attached. By default it will use the current thread's run loop, so you probably should do something like this after initialization of NSURLDownload object:
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
while (!self.downloaded && !self.error && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]])
;
Properties self.downloaded and self.error should be set in your callbacks.
In main thread run loop probably started by the NSApplication object.

NSURLConnection sendAsynchronousRequest with retry functionality

I have my main UI thread that calls sendAsynchronousRequest method of NSURLConnection to fetch data.
[NSURLConnection sendAsynchronousRequest:[self request]
queue:[NSOperationQueue alloc] init
completionHandler:
^(NSURLResponse *response, NSData *data, NSError *error)
{
if (error)
{
//error handler
}
else
{
//dispatch_asych to main thread to process data.
}
}];
All this is fine and good.
My question here is, I need to implement retry functionality on error.
Can I do it in this block and call sendSynchronousRequest to retry as this is the background queue.
Or dispatch to main thread and let the main thread handle retry (by calling sendAsynchronousRequest and repeating the same cycle).
You're getting the request by calling [self request]. If request is an atomic #property, or is otherwise thread safe, I can't think of any reason you couldn't kick off a retry from a non-main thread.
Alternately, you could put a copy of the request into a local variable prior to your +sendAsynchronousRequest:queue: call. If you do that, and then reference it in your completion handler, then it will be retained implicitly and [self request] will only be called once.
Generally speaking, this probably isn't a great pattern. If the service is down, absent some other checks, it will just keep trying forever. You might try something like this:
NSURLRequest* req = [self request];
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
__block NSUInteger tries = 0;
typedef void (^CompletionBlock)(NSURLResponse *, NSData *, NSError *);
__block CompletionBlock completionHandler = nil;
// Block to start the request
dispatch_block_t enqueueBlock = ^{
[NSURLConnection sendAsynchronousRequest:req queue:queue completionHandler:completionHandler];
};
completionHandler = ^(NSURLResponse *resp, NSData *data, NSError *error) {
tries++;
if (error)
{
if (tries < 3)
{
enqueueBlock();
}
else
{
// give up
}
}
else
{
//dispatch_asych to main thread to process data.
}
};
// Start the first request
enqueueBlock();

How can I wait for result from geocodeAddressString iPhone

I know its something to do with locks or dispatch groups, but I just cant seem to code it...
I need to know if the address was a valid address before leaving the method. Currently the thread just overruns and returns TRUE. I've tried locks, dispatchers the works but can't seem to get it correct. Any help appreciated:
- (BOOL) checkAddressIsReal
{
__block BOOL result = TRUE;
// Lets Build the address
NSString *location = [NSString stringWithFormat:#" %# %#, %#, %#, %#", streetNumberText.text, streetNameText.text, townNameText.text, cityNameText.text, countryNameText.text];
// Put a pin on it if it is valid
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error) {
result = [placemarks count] != 0;
}];
return result;
}
The documentation says that CLGeocoder calls the completionHandler on the main thread. Since you are probably also calling your method from the main thread it cannot wait for the geocoder's answer without giving it the opportunity to deliver the result.
That would be done by polling the runloop, using some API as -[NSRunLoop runMode:beforeDate:].
The disadvantage is that depending on the mode this will also deliver events and fire timers while waiting for the result.
Just use block as parameter:
- (void) checkAddressIsRealWithComplectionHandler:(void (^)(BOOL result))complectionHandler
{
__block BOOL result = TRUE;
// Lets Build the address
NSString *location = [NSString stringWithFormat:#" %# %#, %#, %#, %#", streetNumberText.text, streetNameText.text, townNameText.text, cityNameText.text, countryNameText.text];
// Put a pin on it if it is valid
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error) {
result = [placemarks count] != 0;
complectionHandler(result);
}];
}

NSOperationQueue waitUntillAllOperationsAreFinished blocks operations

I've created my own NSOperation subclass and now I want some of its instances to run in parallel. As it's a concurrent operation I've overwritten
- (void)start {
[self willChangeValueForKey:#"isExecuting"];
isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
error=NO;
startedSection=NO;
// start the task
[task launch];
}
- (BOOL)isConcurrent {
return YES;
}
- (BOOL)isExecuting {
return isExecuting;
}
- (BOOL)isFinished {
return isFinished;
}
and the following code is executed when the operation finishes:
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
isExecuting = NO;
isFinished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
When I add [myQueue waitUntillAllOperationsAreFinished] after having added the operations to myQueue none of the operations even starts! When I remove the [myQueue waitUntillAllOperationsAreFinished] I get what I want, except for the fact that this could lead to some errors because of the main thread interfering with these operations ...
How is the concurrency of the operations implemented? I suspect you're dispatching things to the main thread and you're also blocking in the main thread in -waitUntillAllOperationsAreFinished. That will, of course, deadlock.

Resources