Reputation: 2322
I have a function that constructs an NSMutableDictionary
using bk_apply
, a method provided by the third-party block utility library BlocksKit. The function's test suite usually passes just fine, but once every couple runs it crashes.
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
[inputSet bk_apply:^(NSString *property) {
NSString *localValueName = propertyToLocalName[property];
NSObject *localValue = [self valueForKey:localValueName];
result[property] = localValue ?: defaults[property]; // Crash
// Convert all dates in result to ISO 8601 strings
if ([result[property] isKindOfClass:[NSDate class]]) { // Crash
result[property] = ((NSDate *)result[property]).ISODateString; // Crash
}
}];
The crash always happens on a line where result
is referenced, but it's not the same line every time.
Examining the contents of result
in the debugger, I've seen very strange values like
po result
{
val1 = "Some reasonable value";
val2 = "Also reasonable value";
(null) = (null);
}
It's impossible for an NSDictionary
to have null
keys or values, so clearly some invariant is being violated.
What is causing this crash and how do I fix it?
Upvotes: 1
Views: 66
Reputation: 2322
From the BlocksKit documentation for bk_apply
:
Enumeration will occur on appropriate background queues. This will have a noticeable speed increase, especially on dual-core devices, but you must be aware of the thread safety of the objects you message from within the block.
The code above is highly unsafe with respect to threading, because it reads from and writes to a mutable variable on multiple threads.
The intermittent nature of the crash comes from the fact that the thread scheduler is non-deterministic. The crash won't happen when several threads accessing shared memory happen to have their execution scheduled in sequence rather than in parallel. It is therefore possible to "get lucky" some or even most of the time, but the code is still wrong.
The debugger printout is a good example of the danger. The thread that's paused is most likely reading from result
while another thread performs an insertion.
NSMutableDictionary
insertions are likely not atomic; example steps might be,
If you read the dictionary from another thread between steps 1 and 2, you will see an entry for which memory has been allocated, but the memory contains no values.
The simplest fix is to switch to bk_each
. bk_each
does the same thing as bk_apply
but it's implemented in a way that guarantees sequential execution.
Upvotes: 2