-
-
Notifications
You must be signed in to change notification settings - Fork 363
fix: Race condition in SentryApplication and Swift implementation #5900
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
base: main
Are you sure you want to change the base?
Conversation
721aa8c
to
fa65d1b
Compare
❌ 1 Tests Failed:
View the top 1 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
a115144
to
00a642f
Compare
Performance metrics 🚀
|
Revision | Plain | With Sentry | Diff |
---|---|---|---|
ebc72be | 1221.24 ms | 1249.66 ms | 28.42 ms |
fc0757d | 1231.83 ms | 1248.98 ms | 17.15 ms |
f97a070 | 1218.88 ms | 1253.12 ms | 34.24 ms |
f5d202b | 1237.90 ms | 1259.49 ms | 21.59 ms |
1a34ddc | 1218.94 ms | 1251.86 ms | 32.92 ms |
ea5a59b | 1222.87 ms | 1253.47 ms | 30.60 ms |
83bb978 | 1238.33 ms | 1260.04 ms | 21.71 ms |
fdea6f5 | 1216.08 ms | 1241.82 ms | 25.73 ms |
ccf1278 | 1226.84 ms | 1248.51 ms | 21.67 ms |
9e0030e | 1222.78 ms | 1242.23 ms | 19.45 ms |
App size
Revision | Plain | With Sentry | Diff |
---|---|---|---|
ebc72be | 23.75 KiB | 908.22 KiB | 884.47 KiB |
fc0757d | 23.75 KiB | 850.73 KiB | 826.98 KiB |
f97a070 | 23.75 KiB | 858.68 KiB | 834.93 KiB |
f5d202b | 23.75 KiB | 904.53 KiB | 880.78 KiB |
1a34ddc | 23.75 KiB | 919.88 KiB | 896.13 KiB |
ea5a59b | 23.75 KiB | 874.46 KiB | 850.71 KiB |
83bb978 | 23.75 KiB | 920.64 KiB | 896.89 KiB |
fdea6f5 | 23.75 KiB | 867.15 KiB | 843.40 KiB |
ccf1278 | 23.75 KiB | 877.15 KiB | 853.40 KiB |
9e0030e | 23.75 KiB | 893.72 KiB | 869.97 KiB |
Previous results on branch: updateApplication
Startup times
Revision | Plain | With Sentry | Diff |
---|---|---|---|
ee17551 | 1217.88 ms | 1245.02 ms | 27.14 ms |
621c433 | 1219.33 ms | 1246.92 ms | 27.59 ms |
8ce26a2 | 1225.54 ms | 1242.65 ms | 17.10 ms |
289850d | 1216.71 ms | 1239.00 ms | 22.29 ms |
App size
Revision | Plain | With Sentry | Diff |
---|---|---|---|
ee17551 | 23.75 KiB | 928.53 KiB | 904.78 KiB |
621c433 | 23.75 KiB | 928.56 KiB | 904.81 KiB |
8ce26a2 | 23.75 KiB | 928.00 KiB | 904.25 KiB |
289850d | 23.75 KiB | 928.00 KiB | 904.25 KiB |
0a54d3f
to
fc48fe4
Compare
fc48fe4
to
1cc7e90
Compare
🔥 |
1cc7e90
to
8304f48
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good progress, let some comments
@@ -185,7 +185,8 @@ - (void)enrichScope:(SentryScope *)scope | |||
// The UIWindowScene is unavailable on visionOS | |||
#if SENTRY_TARGET_REPLAY_SUPPORTED | |||
|
|||
NSArray<UIWindow *> *appWindows = SentryDependencyContainer.sharedInstance.application.windows; | |||
NSArray<UIWindow *> *appWindows = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m
: Is there a good reason why we shouldn't use the thread-safe version here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getWindows
is only defined on the non-thread safe version. AFAIK it's never safe to have a UIWindow instance off the main thread
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or in Swift terms, UIWindow is not Sendable
// This can only be accessed on the main thread | ||
var mainThread_isActive: Bool { get } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m
: Is there a good reason for us to not fully synchronize the application onto the main thread? The app life cycle will always live of main
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure what you mean, this is conformed to by NSApplication and NSApplication can only be used on the main thread. We could write a helper/extension that is a function designed to be called from any context that then dispatches sync to the main thread and check "isActive" but there is no use case in the codebase that requires that right now so I would be hesitant to add extra code, particularly code that uses "dispatch_sync" since that's easy to cause deadlock with and I try to avoid it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I see what you mean, like by doing something similar to what we do with UIApplication. The reason is that before this PR we were not doing anything to try to make this accessible to a background thread, and it was only called from the main thread. Still only called from the main thread and I wanted to avoid any extra changes here other than cleaning up the code to make it more clear what each piece does and fixing the one race condition bug
#if !os(macOS) && !os(watchOS) && !SENTRY_NO_UIKIT | ||
import UIKit | ||
|
||
@objc @_spi(Private) public final class SentryThreadsafeApplication: NSObject { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m
: We should replace the application
in the dependency container with this wrapper, so we can not use the wrong one by accident.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dependency container has both, that's the idea behind this PR because previously some of SentryApplication was designed to be called form anywhere and some was not. The one that can only be used form the main thread is still useful to have in the dependency container IMO since it allows us to mock those calls in tests
|
||
#endif /* SentryUIApplication_Private_h */ | ||
#endif | ||
// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
l
: If we don't need it, remove it please.
8304f48
to
4d434eb
Compare
4d434eb
to
b117726
Compare
🚨 Detected changes in high risk code 🚨High-risk code can easily blow up and is hard to test. We had severe bugs in the past. Be extra careful when changing these files, and have an extra careful look at these:
|
SentryApplication was doing 2 things. It was wrapping UI/NSApplication methods for unit testing purposes, and it was attempting to provide background thread safe access to
isActive
. I say attempting because it used Notification Center to listen for foreground/background and change a state var, but didn't have any kind of locking. So it was prone to a data race where the main thread would write to it through the notification at the same time a background thread read from it.The other issue was the AppKit version direct called in to NSApplication and made no attempt at being thread safe, so this object had different amounts of safety depending on the platform.
This PR separates these two behaviors, so
SentryApplication
is just the system API wrapping (with some extra post-processing on system APIs) andThreadsafeApplication
is a UIKit only thread safe API. ThisThreadsafeApplication
now uses locks to ensure there isn't a data race. Luckily our code was not relying on the macOS version being thread safe, and now that is just more clear at compile time.#skip-changelog