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
Fbsdkapplicationdelegate Application Openurl:Sourceapplication:Annotation Deprecated
Testing an Executable with Swift
Why Does Swift Return an Unexpected Pointer When Converting an Optional String into an Unsafepointer
Swiftui: Navigation Bar Title in Reusable Cross-Platform (iOS & MACos) View
Break on Any Occurrence of "Fatal Error: Unexpectedly Found Nil While Unwrapping an Optional Value"
Swift String VS. String! VS. String
Where Is the .Camera Anchorentity Located
Select All Text in Textfield Upon Click Swiftui
Animate the Fractioncomplete of Uiviewpropertyanimator for Blurring the Background
How to Cast an _Nsmallocblock_ to Its Underlying Type in Swift 3
Subclass of Gkgraphnode Costtonode Method Never Getting Called
In Swift, How to Remove a Uiview from Memory Completely
How to Get Unmanaged Object from Realm Query in Swift
How to Filter on an Array of Objects in Swift
Using Swift Library in Xamarin
Detail View Is Not Updated When the Model Is Updated (Using List) Swiftui
Define MACos Window Size Using Swiftui
In Swift, Why Does Assigning to a Static Variable Also Invoke Its Getter