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
[SDK] Fix multiple issues with Swift KVO #20103
Conversation
@swift-ci please smoke test macos |
One test is failing:
|
Fun fact: That test is actually broken. As in, the test was confirming that the previous behavior of this code was broken. The implementation of In other words, there were 3 lines that looked like // CHECK-NEXT: swiftValue 13, objcValue four but only 1 was supposed to be sent. That said, from the test failure it sounds like that line is now printed zero times instead of the expected 1 time, so I guess it's still broken (and the implication being that |
I've updated the PR to fix that test failure. The test itself is still broken, of course, but the current behavior now matches the previous one (as far as that test is concerned). |
Hi Lily, thank you for your contribution. Do you know any workaround for the thread safety of KVO swizzling crash? My app is randomly crash at launch with Fatal error: Should never be reached and it's really disturbing. I'm considering change all of my Swift KVO related implementation to Objective-C. |
@JunyuKuang There are only two workarounds for now:
|
I've added what tests I can reasonably write. I can't test the This PR is ready to move forward. |
@millenomi Is there anything left for me to do on this PR? |
@swift-ci please test |
Build failed |
I'm still in the throes of apple/swift-corelibs-foundation#1755 and won't have time to look at other stuff until it lands T_T |
Build failed |
This is blocked by https://bugs.swift.org/browse/SR-9228. |
Should we replace the initialize-once pattern below:
with
I think we don't need to return an optional void value here, and |
Good point. It was written the way it is because it was adapted from a previous |
@millenomi SR-9228 was merged 21 days ago. Is this still blocked? |
It isn't; I still was v.v' |
@swift-ci please test |
Ah, dang. @kballard — this looks fine to me, but it needs conflict resolution. |
super.init() | ||
objc_setAssociatedObject(object, associationKey(), self, .OBJC_ASSOCIATION_RETAIN) | ||
__KVOKeyPathBridgeMachinery._withBridgeableKeyPath(from: path, to: keyPath) { | ||
object.addObserver(self, forKeyPath: path, options: options, context: nil) |
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.
Do we want to have a private context for added safety?
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 Helper
isn't exposed to anyone else and this is the only observation set up for it. The only way I can think of to get a second observation is if someone swizzles e.g. -[NSObject init]
and has it set up an observation on every single object, which would be pretty nuts. If you think it's worth defending against that sort of thing we could, but I'm not particularly worried (any such swizzle would break a significant amount of KVO code).
Build failed |
Build failed |
I can fix the conflicts, but the reported build failure is compilation failure in completely unrelated code. |
Yeah; please ignore them — they probably just checked out your PR over whatever commit it was pushed on, erasing unrelated progress. (I didn't see the conflict marker until I asked the bot to start.) Fixing the conflict should address that too. |
Rebasing my PR locally actually didn't produce any conflicts at all. I'm wondering if GitHub is reporting a conflict just because the number of commits between the branch and Anyway I'm just running a local build to sanity-check before updating the branch with the rebase. |
In fact, the "conflicts" are just that the |
Given that, I just updated the branch now instead of waiting for my local build. |
there's a radar open for that - rdar://problem/47708852 |
Looking at it. |
It sounds like the associated object trick for ensuring the observer is deregistered isn't working. Does the Obj-C 32-bit runtime check for leaked KVO observers happen before clearing associated objects? I think the quick fix here is to just update the relevant test to ensure the |
I haven't tested it but I'd guess the following will work: diff --git a/test/stdlib/KVOKeyPaths.swift b/test/stdlib/KVOKeyPaths.swift
index b3b302a367..32af1a4eac 100644
--- a/test/stdlib/KVOKeyPaths.swift
+++ b/test/stdlib/KVOKeyPaths.swift
@@ -127,13 +127,17 @@ class Target2 : NSObject, NSKeyValueObservingCustomization {
// with the ability to look up the key path using the other.
static func keyPathsAffectingValue(for key: AnyKeyPath) -> Set<AnyKeyPath> {
print("keyPathsAffectingValue: key == \\.name:", key == \Target2.name)
- _ = Dummy().observe(\.name) { (_, _) in }
+ withExtendedLifetime(Dummy()) { (dummy) in
+ _ = dummy.observe(\.name) { (_, _) in }
+ }
return []
}
static func automaticallyNotifiesObservers(for key: AnyKeyPath) -> Bool {
print("automaticallyNotifiesObservers: key == \\.name:", key == \Target2.name)
- _ = Dummy().observe(\.name) { (_, _) in }
+ withExtendedLifetime(Dummy()) { (dummy) in
+ _ = dummy.observe(\.name) { (_, _) in }
+ }
return true
}
} |
I need to check if automatic deregistration works on 32-bit. It really should, tho. |
Simulator build fix — (temporarily) revert "Merge pull request #20103 from lilyball/fix_kvo"
@lilyball your suggestion works, and I will repropose your patch with the fix. |
This reproposes @lilyball’s fixes in apple#20103 while adding her fix to ensure observations are removed before observed objects in 32-bit testing.
Will be fixed in Swift by apple/swift#20103 but we could use this workaround (as mentioned on the PR) in the meantime
Just an FYI for others: this fix still isn't released as of iOS 12.2 / Xcode 10.2.1, making Swift KVO unusable on background threads for now. Even switching to ObjC (or using The only safe work around is to create a E.g.
import UIKit
import Foundation
private class CrashWorkaround: NSObject {
@objc dynamic var test: String = ""
/// More info: https://github.com/apple/swift/pull/20103
fileprivate static func workaroundSwiftKVOCrash() {
assert(Thread.isMainThread)
let obj = CrashWorkaround()
let observer = obj.observe(\.test, options: [.new]) { (_, _) in
fatalError("Shouldn't be called, value doesn't change")
}
observer.invalidate()
}
}
/// This needs to execute before anything can register KVO in the background
/// so the best way to do this is before the application ever runs
CrashWorkaround.workaroundSwiftKVOCrash()
UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, nil, NSStringFromClass(AppDelegate.self)) This will get called immediately after |
Has there been any update on this recently? What's the status as of iOS 13, Xcode 11 and Swift 5.1? |
@CLoutas: git branch -r --contains 7c51419 shows this was merged into the branch "swift-5.1-branch". Meaning it was included in Swift 5.1. Since the fixes affect the runtime libraries rather than the compiler, this means that it is fixed on iOS 13, macOS 10.15 but still requires the workaround on earlier OS versions. |
@bobergj that's unfortunate, but at least we have a workaround. Thank you very much for clarifying that. |
As seen in swift crashers while building apple/swift#20103
This reproposes @lilyball’s fixes in apple/swift#20103 while adding her fix to ensure observations are removed before observed objects in 32-bit testing.
This PR fixes a number of issues with Swift KVO. It fixes a bad thread safety issue, a problem where
NSKeyValueObservation
was incorrectly replacingNSObject.observeValue(forKeyPath:of:change:context:)
, a potential race withNSKeyValueObservation
handling a KVO notice duringdeinit
, an issue withNSSortDescriptor.keyPath
returning the wrong value, and a problem where theNSKeyValueObservingCustomization
methods could be called with the wrongKeyPath
ifKeyPath
-related work is happening concurrently on another thread.Resolves SR-6795, SR-9074, SR-9075, SR-9076, and SR-9077.