EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) on dispatch_semaphore_dispose
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 thegroup
.group
's retain count = 2. -
dispatch_async(...., ^{ dispatch_group_wait(group, ...) ... });
captured thegroup
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. Thedispatch_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 oldonCompletion
block was replaced with the new one. So, the oldgroup
was released.group
's retain count = 0.group
was deallocated. That resulted toEXC_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 theironCompletion
blocks by the time. So that the block indispatch_async(...)
will hold it for you.
OR - You can add
group
into a collection, such asNSMutableArray
.
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
.
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.