47

I am getting EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) on dispatch_semaphore_dispose but don't really know how to track down the root cause of this. My code makes use of dispatch_async, dispatch_group_enter and so on.

UPDATE: The cause of the crash is due to the fact that the webserviceCall (see code below) never calls onCompletion and when the code is run again, I got the error EXC_BAD_INSTRUCTION. I verified this is indeed the case, but not sure why or how to prevent this.

enter image description here

Code:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_group_t group = dispatch_group_create();

     for (...) {
        if (...) {
            dispatch_group_enter(group);
            dispatch_async(queue, ^{

               [self webserviceCall:url onCompletion:^{
                     dispatch_group_leave(group);
               }];
            });
        }
    }

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)));
    dispatch_sync(queue, ^{
        // call completion handler passed in by caller
    });
});
Boon
  • 40,656
  • 60
  • 209
  • 315
  • Not a duplicate. I looked at that, it didn't help me. Notice mine is EXC_I386_INVOP as well. – Boon Jun 21 '14 at 03:07
  • 1
    This is likely an ARC problem. Post anything you are doing with dispatch groups, semaphores, or dispatch_sync. ARC may be attempting to dispose of a semaphore that something already set to NULL. – quellish Jun 21 '14 at 03:56
  • For sake use weakSelf inside the block. – selva Sep 21 '16 at 21:42
  • FYI 1. If you create you reach to a `fatalError`/`assertionFailure` written by **yourself**, you'll get an error `EXC_BAD_INSTRUCTION`. Hence you should see why you've reached your own assertion ie look into its message. That being said, this error is generated by the **compiler**. 2. My point is that the compilers also use `fatalError` and many of the errors we see are because of that – mfaani Aug 17 '18 at 12:59

8 Answers8

54

From your stack trace, EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) occurred because dispatch_group_t was released while it was still locking (waiting for dispatch_group_leave).

According to what you found, this was what happened :

  • dispatch_group_t group was created. group's retain count = 1.
  • -[self webservice:onCompletion:] captured the group. group's retain count = 2.
  • dispatch_async(...., ^{ dispatch_group_wait(group, ...) ... }); captured the group again. group's retain count = 3.
  • Exit the current scope. group was released. group's retain count = 2.
  • dispatch_group_leave was never called.
  • dispatch_group_wait was timeout. The dispatch_async block was completed. group was released. group's retain count = 1.
  • You called this method again. When -[self webservice:onCompletion:] was called again, the old onCompletion block was replaced with the new one. So, the old group was released. group's retain count = 0. group was deallocated. That resulted to EXC_BAD_INSTRUCTION.

To fix this, I suggest you should find out why -[self webservice:onCompletion:] didn't call onCompletion block, and fix it. Then make sure the next call to the method will happen after the previous call did finish.


In case you allow the method to be called many times whether the previous calls did finish or not, you might find someone to hold group for you :

  • You can change the timeout from 2 seconds to DISPATCH_TIME_FOREVER or a reasonable amount of time that all -[self webservice:onCompletion] should call their onCompletion blocks by the time. So that the block in dispatch_async(...) will hold it for you.
    OR
  • You can add group into a collection, such as NSMutableArray.

I think it is the best approach to create a dedicate class for this action. When you want to make calls to webservice, you then create an object of the class, call the method on it with the completion block passing to it that will release the object. In the class, there is an ivar of dispatch_group_t or dispatch_semaphore_t.

3329
  • 1,411
  • 13
  • 17
  • 2
    Wow, amazing analysis. If I add group into a collection, it will never be deallocated though, right? I may not be able to fix the non-returning webservice because this is a framework, and I can't prevent others from creating webservice call that doesn't return (erroneously), I want to make sure my framework doesn't crash in those circumstances. – Boon Jun 23 '14 at 18:50
  • Yes, so you must remove it from the collection when you finish using it. – 3329 Jun 23 '14 at 18:52
  • This answer helped me a lot. Many thanks! In my case, I only called dispatch_group_leave in onCompletion, but forgot to call it in onCancellation. Calling dispatch_group_leave in any condition fixed my problem. – Vince Yuan May 24 '16 at 12:46
  • Helped me a lot , I announced a ivar dispatch group, but didn't enter or leave in compare. – lynulzy May 31 '17 at 12:47
  • For me it was very weird, I had 2 parameters having the same name by mistake and I was getting this error on runtime. – Sam Jan 14 '18 at 20:43
10

I had a different issue that brought me to this question, which will probably be more common than the overrelease issue in the accepted answer.

Root cause was our completion block being called twice due to bad if/else fallthrough in the network handler, leading to two calls of dispatch_group_leave for every one call to dispatch_group_enter.

Completion block called multiple times:

dispatch_group_enter(group);
[self badMethodThatCallsMULTIPLECompletions:^(NSString *completion) {

    // this block is called multiple times
    // one `enter` but multiple `leave`

    dispatch_group_leave(group);
}];

Debug via the dispatch_group's count

Upon the EXC_BAD_INSTRUCTION, you should still have access to your dispatch_group in the debugger. DispatchGroup: check how many "entered"

Print out the dispatch_group and you'll see:

<OS_dispatch_group: group[0x60800008bf40] = { xrefcnt = 0x2, refcnt = 0x1, port = 0x0, count = -1, waiters = 0 }>

When you see count = -1 it indicates that you've over-left the dispatch_group. Be sure to dispatch_enter and dispatch_leave the group in matched pairs.

pkamb
  • 33,281
  • 23
  • 160
  • 191
  • 1
    The tip about `count = -1` was very helpful - thanks! – Bill Apr 20 '18 at 19:05
  • 1
    I had a similar situation - although using a group=DispatchGroup() with group.enter() and the corresponding group.leave() in a completion block (that was being called multiple times). In the debugger, the count for the group was 1073741823 -- which as a signed 32-bit int is also -1 – TomH Jun 25 '20 at 17:59
8

My problem was took IBOutlet but didn't connect with interface builder and using in swift file.

Mrugesh Tank
  • 3,495
  • 2
  • 29
  • 59
  • I have the same problem. Can we find out from the error message as to which IBOutlet could be unlinked? – AceN Jul 12 '16 at 12:20
  • If you have only one 1 InterfaceBuilder for that Class then you can check every `IBOutlet` connected or disconnected symbol in your .swift or .m / .h file. – Mrugesh Tank Jul 12 '16 at 13:56
  • Had the same issue, was referencing a action from a button without adding the button itself as a IBOutlet. – andromedainiative Mar 13 '17 at 15:08
5

Sometimes all it takes to get a EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) is a missing return statement.

It certainly was my case.

Gabriel
  • 2,841
  • 4
  • 33
  • 43
4

My issue was that I was creating objects that I wanted to be stored in a NSMutableDictionary but I never initialized the dictionary. Therefore the objects were getting deleted by garbage collection and breaking later. Check that you have at least one strong reference to the objects youre interacting with.

mihai
  • 4,184
  • 3
  • 26
  • 27
1

In my case:

PHImageRequestOptions *requestOptions = [PHImageRequestOptions new];
requestOptions.synchronous            = NO;

Was trying to do this with dispatch_group

Zaporozhchenko Oleksandr
  • 4,660
  • 3
  • 26
  • 48
1

I landed here because of an XCTestCase, in which I'd disabled most of the tests by prefixing them with 'no_' as in no_testBackgroundAdding. Once I noticed that most of the answers had something to do with locks and threading, I realized the test contained a few instances of XCTestExpectation with corresponding waitForExpectations. They were all in the disabled tests, but apparently Xcode was still evaluating them at some level.

In the end I found an XCTestExpectation that was defined as @property but lacked the @synthesize. Once I added the synthesize directive, the EXC_BAD_INSTRUCTION disappeared.

Elise van Looij
  • 4,162
  • 3
  • 29
  • 52
0

My issue was that it was in my init(). Probably the "weak self" killed him while the init wasn't finished. I moved it from the init and it solved my issue.