-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RxAtomic causes Thread Sanitiser race conditions #1853
Comments
For what it's worth, I am in no way suggesting that AtomicInt is in itself not thread safe as I can see it's wrapping stdatomic's functions, however the fact that the swift functions AtomicInt.increment() and AtomicInt.decrement() are declared as mutable is tripping the Thread Sanitiser up. As observed events are triggered by multiple events in my source project and often observed on alternate threads (i.e. the main one) the new resource tracing checks are constantly tripping the sanitiser. I have had to resort to disabling all sanitiser checks for the whole RxSwift framework in order to continue allowing checking of my own projects. |
Is this basically a false positive of Thread Sanitizer? |
As I have added above, it is, but to shut it up I have to stop all sanitisation checks for the Reactive framework. Unfortunately it's a 'feature' of swift, unless there's a way to have increment() and decrement() call sites that aren't 'mutable'. |
Ok, should we ditch RxAtomic, introduce We will probably be slightly slower, but I don't think we should be more then a few percentages slower. |
Did we ever benchmark how bad it would be? I imagine it shouldn't be too noticeable on >95% of the systems |
I guess the difference depends on the concrete chain being tested so it's hard to quantify the exact impact. The smaller the Swift overhead, the bigger the impact is. We can rerun the couple of performance tests we have and assess the difference there. |
Hey @kzaher |
There are some interesting thoughts here: http://www.cocoawithlove.com/blog/2016/06/02/threads-and-mutexes.html |
Interesting article @freak4pc :)) |
I've tried running performance tests with the following implementation of I got 10-17% worse performance on https://github.com/ReactiveX/RxSwift/tree/mutex_atomic Feel free to try to improve this. Anyway, if there is no other alternative, I'll merge this, but it seems a bit wasteful :( final class AtomicInt {
private var mutex: pthread_mutex_t
private var value: Int32
init(_ initialValue: Int32) {
self.value = initialValue
var attr = pthread_mutexattr_t()
pthread_mutexattr_init(&attr); defer { pthread_mutexattr_destroy(&attr) }
self.mutex = pthread_mutex_t()
guard pthread_mutex_init(&self.mutex, &attr) == 0 else { fatalError() }
}
@discardableResult
func increment() -> Int32 {
guard pthread_mutex_lock(&self.mutex) == 0 else { fatalError() }
let oldValue = self.value
self.value += 1
guard pthread_mutex_unlock(&self.mutex) == 0 else { fatalError() }
return oldValue
}
@discardableResult
func decrement() -> Int32 {
guard pthread_mutex_lock(&self.mutex) == 0 else { fatalError() }
let oldValue = self.value
self.value -= 1
guard pthread_mutex_unlock(&self.mutex) == 0 else { fatalError() }
return oldValue
}
func isFlagSet(_ mask: Int32) -> Bool {
guard pthread_mutex_lock(&self.mutex) == 0 else { fatalError() }
let oldValue = self.value
guard pthread_mutex_unlock(&self.mutex) == 0 else { fatalError() }
return oldValue & mask != 0
}
@discardableResult
func fetchOr(_ mask: Int32) -> Int32 {
guard pthread_mutex_lock(&self.mutex) == 0 else { fatalError() }
let oldValue = self.value
self.value |= mask
guard pthread_mutex_unlock(&self.mutex) == 0 else { fatalError() }
return oldValue
}
func load() -> Int32 {
guard pthread_mutex_lock(&self.mutex) == 0 else { fatalError() }
let oldValue = self.value
guard pthread_mutex_unlock(&self.mutex) == 0 else { fatalError() }
return oldValue
}
} |
The performance hit is quite large - I didn't expect it to be this bad. Also, did you make an attempt on NSRecursiveLock? Was it even slower? |
@freak4pc Feel free to play with it. But I'm not sure I know how to do better than this. I'm not sure is there a way to silence the thread sanitizer? Somehow I doubt it. |
Yeah I understand the silencing part - but is it really worth 10% performance downgrade just for a warning? I guess I mainly wonder if it would be at all noticeable. I'll see if I can actually play with it tomorrow. What did you use for the benchmark? |
There is a benchmark target inside or the Rx project. I think it's called |
Hey @kzaher, @freak4pc
Also, notice that the Benchmarks target was with Debug config on the Test action which I think will compile -Onone and not give us a fair benchmark, so changed to Release-Test to compile -O. The modifications are here https://github.com/LucianoPAlmeida/RxSwift/tree/mutex_atomic Question, I'm not familiar with the usage of AtomicInt on the project, so maybe it is not required, but it wouldn't be good to add PTHREAD_MUTEX_RECURSIVE attr on the mutex to avoid deadlock, just for safety? |
@freak4pc All the benchmarks I've seen on Darwin with Apple Foundation it shows that it is slower. And on Linux with Corelibs Foundation, the NSRecusiveLock is implemented as a wrapper on top of pthread (see here), so there's no difference I think :)) |
I somehow doubt that we'll come close to the speed of the current version of |
The real question is whether we should replace AtomicInt just because of a thread sanitizer false positive bug 🤔 |
Does anybody know how does the ThreadSanitizer actually work, is there some way to plug into it and mark the memory as properly synchronized? |
Actually, reading more about it maybe SanitizerSpecialCaseList will be the one to use here :) |
I had some free time so, did #1860 maybe it can be useful :)) |
@andyj-at-aspin I've merged fixes for TSan, please check the Thnx a lot @LucianoPAlmeida . |
The issue no longer occurs with pulls from the master branch and I have verified that RxSwift is very definitely making use of the restructured AtomicInt objects. Thanks so much, guys. I can't wait for this to be included in a formal release. |
Short description of the issue:
The newly added AtomicInt.swift causes data race reports from the Xcode Thread Sanitiser when running in DEBUG on iOS Simulator
Expected outcome:
No reports of data races.
What actually happens:
Any subroutines wrapped in TRACE_RESOURCES involved with incrementing or decrementing counters can trigger Thread Sanitiser data race reports.
Self contained code example that reproduces the issue:
RxSwift/RxCocoa/RxBlocking/RxTest version/commit
4.4.0
Platform/Environment
How easy is to reproduce? (chances of successful reproduce after running the self contained code)
Xcode version:
Installation method:
I have multiple versions of Xcode installed:
(so we can know if this is a potential cause of your issue)
Level of RxSwift knowledge:
(this is so we can understand your level of knowledge
and formulate the response in an appropriate manner)
The text was updated successfully, but these errors were encountered: