Xcode Incorrectly Reporting Swift Access Race Condition

Xcode Incorrectly Reporting Swift Access Race Condition

The thread sanitizer reports a Swift access race to the

var syncDict = SynchronizedDictionary<String, String>()

structure, because there is a mutating access (via the subscript setter) at

syncDict["\(i)"] = "\(i)"

from one thread, and a read-only access to the same structure (via the subscript getter) at

_ = syncDict["\(i)"]

from a different thread, without synchronization.

This has nothing to do with conflicting access to the private var dictionary property, or with what happens inside the subscript methods at all. You'll get the same “Swift access race” if you simplify the structure to

public struct SynchronizedDictionary<K: Hashable, V> {
private let dummy = 1

public subscript(key: String) -> String {
get {
return key
}
set {
}
}
}

So this is a correct report from the thread sanitizer, not a bug.

A possible solution would be to define a class instead:

public class SynchronizedDictionary<K: Hashable, V> { ... }

That is a reference type and the subscript setter no longer mutates the syncDict variable (which is now a “pointer” into the actual object storage). With that change, your code runs without errors.

How to avoid data race with GCD DispatchWorkItem.notify?

EDIT (2019-01-07): As mentioned by @Rob in a comment on the question, this can't be reproduced anymore with recent versions of Xcode/Foundation (I don't have Xcode installed anymore, I won't guess a version number). There is no workaround required.


Well looks like I found out. Using a DispatchGroup.notify to get notified when the group's dispatched items have completed, instead of DispatchWorkItem.notify, avoids the data race. Here's the same-ish snippet without the data race:

  private func incrementAsync() {
let queue = DispatchQueue.global(qos: .background)

let item = DispatchWorkItem { [weak self] in
guard let strongSelf = self else { return }
strongSelf.x += 1
}

let group = DispatchGroup()
group.notify(queue: .main) { [weak self] in
guard let strongSelf = self else { return }
print("> \(strongSelf.x)")
}
queue.async(group: group, execute: item)
}

So DispatchGroup introduces a happens-before relationship and notify is safely called after the threads (in this case, a single async work item) finished execution, while DispatchWorkItem.notify doesn't offer this guarantee.

Finding the cause of EXC_BAD_ACCESS Code=1 on startup of Swift iOS App

The NSFaultingMutableSet in the stack trace pointed to an issue with accessing data from my Core Data store (e.g., Event objects like self.events). My notification manager was operating on a separate thread and creating an unstable situation where Core Data (which I hadn't set up properly for multi-thread access) was being read and modified on the main and secondary threads simultaneously.

I was able to resolve the problem by wrapping the Notification Manager code that accesses Core Data objects in a DispatchQueue.main.async {...} block. There are other ways to set up Core Data objects for access from multiple threads (e.g., Coredata - Multithreading best way), but this was the simplest solution given multi-thread access isn't a priority for what I'm trying to do.

Understanding Swift thread safety

One way to access it asynchronously:

typealias Dict = [Double : [Date:[String:Any]]]
var myDict = Dict()
func getMyDict(f: @escaping (Dict) -> ()) {
queue.async {
DispatchQueue.main.async {
f(myDict)
}
}
}

getMyDict { dict in
assert(Thread.isMainThread)
}

Making the assumption, that queue possibly schedules long lasting closures.

How it works?

You can only access myDict from within queue. In the above function, myDict will be accessed on this queue, and a copy of it gets imported to the main queue. While you are showing the copy of myDict in a UI, you can simultaneously mutate the original myDict. "Copy on write" semantics on Dictionary ensures that copies are cheap.

You can call getMyDict from any thread and it will always call the closure on the main thread (in this implementation).

Caveat:

getMyDict is an async function. Which shouldn't be a caveat at all nowadays, but I just want to emphasise this ;)

Alternatives:

  • Swift Combine. Make myDict a published Value from some Publisher which implements your logic.

  • later, you may also consider to use async & await when it is available.

a race condition or not? delegates & multiple threads

I don't think there's a lock on dealloc versus retain/release. The following example has a dealloc method with a sleep() in it (does anyone know if sleep() breaks locks? I don't think it does, but you never know). A better example might be to repeatedly instantiate/destroy instances of A and B until you get a situation like the one mentioned here, without the sleep().

View controller, in my case, but could be anything:

-(void)septhreadRetainDel
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(@"[thread#2] sleep(1.f);");
sleep(1.f);
NSLog(@"[thread#2] [b retainDelegate];");
[b retainDelegate];
NSLog(@"[thread#2] sleep(2.f);");
sleep(2.f);
NSLog(@"[thread#2] [b release];");
[b release];
[pool release];
}

- (void)viewDidLoad {
NSLog(@"-(void)viewDidLoad:");
[super viewDidLoad];
NSLog(@"a = [[A alloc] init];");
a = [[A alloc] init];
NSLog(@"[a autorelease];");
[a autorelease];
NSLog(@"b = [[B alloc] init];");
b = [[B alloc] init];
NSLog(@"b.delegate = a;");
b.delegate = a;
NSLog(@"[NSThread detachNewThreadSelector:@selector(septhreadRetainDel) toTarget:self withObject:nil];");
[NSThread detachNewThreadSelector:@selector(septhreadRetainDel) toTarget:self withObject:nil];
}

A:

#import "A.h"

@implementation A

-(void)dealloc
{
NSLog(@"A: dealloc; zzz for 2s");
sleep(2.f);
NSLog(@"A: dealloc; waking up in time for my demise!");
[super dealloc];
}
-(id)retain
{
NSLog(@"A retain (%d++>%d)", self.retainCount, self.retainCount+1);
return [super retain];
}
-(void)release
{
NSLog(@"A release (%d-->%d)", self.retainCount, self.retainCount-1);
[super release];
}

@end

B (.h):

#import "A.h"

@interface B : NSObject {
A *delegate;
}

-(void) retainDelegate;

@property (nonatomic, assign) A *delegate;

@end

B (.m):

#import "B.h"

@implementation B

@synthesize delegate;

-(void)retainDelegate
{
NSLog(@"B:: -(void)retainDelegate (delegate currently has %d retain count):", delegate.retainCount);
NSLog(@"B:: [delegate retain];");
[delegate retain];
}
-(void)releaseDelegate
{
NSLog(@"B releases delegate");
[delegate release];
delegate = nil;
}

-(void)dealloc
{
NSLog(@"B dealloc; closing shop");
[self releaseDelegate];
[super dealloc];
}

-(id)retain
{
NSLog(@"B retain (%d++>%d)", self.retainCount, self.retainCount+1);
return [super retain];
}
-(void)release
{
NSLog(@"B release (%d-->%d)", self.retainCount, self.retainCount-1);
[super release];
}

@end

The program ends up crashing with EXC_BAD_ACCESS at B's releaseDelegate method. The following is the output from the NSLogs:

2010-07-10 11:49:27.044 race[832:207] -(void)viewDidLoad:
2010-07-10 11:49:27.050 race[832:207] a = [[A alloc] init];
2010-07-10 11:49:27.053 race[832:207] [a autorelease];
2010-07-10 11:49:27.056 race[832:207] b = [[B alloc] init];
2010-07-10 11:49:27.058 race[832:207] b.delegate = a;
2010-07-10 11:49:27.061 race[832:207] [NSThread detachNewThreadSelector:@selector(septhreadRetainDel) toTarget:self withObject:nil];
2010-07-10 11:49:27.064 race[832:4703] [thread#2] sleep(1.f);
2010-07-10 11:49:27.082 race[832:207] A release (1-->0)
2010-07-10 11:49:27.089 race[832:207] A: dealloc; zzz for 2s
2010-07-10 11:49:28.066 race[832:4703] [thread#2] [b retainDelegate];
2010-07-10 11:49:28.072 race[832:4703] B:: -(void)retainDelegate (delegate currently has 1 retain count):
2010-07-10 11:49:28.076 race[832:4703] B:: [delegate retain];
2010-07-10 11:49:28.079 race[832:4703] A retain (1++>2)
2010-07-10 11:49:28.081 race[832:4703] [thread#2] sleep(2.f);
2010-07-10 11:49:29.092 race[832:207] A: dealloc; waking up in time for my demise!
2010-07-10 11:49:30.084 race[832:4703] [thread#2] [b release];
2010-07-10 11:49:30.089 race[832:4703] B release (1-->0)
2010-07-10 11:49:30.094 race[832:4703] B dealloc; closing shop
2010-07-10 11:49:30.097 race[832:4703] B releases delegate
Program received signal: “EXC_BAD_ACCESS”.

Once -dealloc is called, retain counts are no longer of import. The object will be destroyed (this is probably obvious, though I wonder what would happen if you checked self's retainCount and DID NOT call [super dealloc] if the object had retains... insane idea). Now if we modify the -dealloc for A to set B's delegate to nil first, the program works but only because we're nil'ing delegate in B in releaseDelegate.

I don't know if that answers your question, really, but presuming sleep()'s are not somehow breaking thread locks, the exact same behavior should happen when dealloc is called right before a retain.

iOS (Xcode 4.3) postNotificatioName:object:userInfo: fails with EXC_BAD_ACCESS

The problem turned out to be a race condition between the creation of the Heatmap and the populating of the data model it shares. The populating was happening, occasionally, before the Heatmap was finished being created (both are event-driven).

I ended up re-working the code so that the class holding the Heatmap makes a synchronous call to populate the data after the Heatmap is cleaner.

Thanks for the input, it saved me a headache. :)

Chris



Related Topics



Leave a reply



Submit