-
Notifications
You must be signed in to change notification settings - Fork 1.2k
[NSRunLoop] Remove _CFRunLoopFinished check #258
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
[NSRunLoop] Remove _CFRunLoopFinished check #258
Conversation
A call to `_CFRunLoopFinished` in `NSRunLoop.runMode()` always returns `false` on OS X, causing unexpected behavior. For example, the following program exits immediately on OS X: ```swift import SwiftFoundation let runLoop = NSRunLoop.currentRunLoop() let date = NSDate(timeIntervalSinceNow: 60) while runLoop.runMode(NSDefaultRunLoopMode, beforeDate: date) {} ``` Whereas it (correctly) takes 60 seconds to exit on Linux: ```swift import Foundation let runLoop = NSRunLoop.currentRunLoop() let date = NSDate(timeIntervalSinceNow: 60) while runLoop.runMode(NSDefaultRunLoopMode, beforeDate: date) {} ``` The problem appears to be `_CFRunLoopFinished`, which incorrectly returns `false` on OS X. Remove the faulty check to get things working on both OS X and Linux.
if _CFRunLoopFinished(_cfRunLoop, modeArg) { | ||
return false | ||
} | ||
|
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.
removing this is contrary to how the darwin version is implemented; this means that there is something seriously incorrect with how the backing for CFRunLoop is functioning if this "fixes the problem".
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.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let rl = NSRunLoop.currentRunLoop()
let mode = "foo mode"
let now = NSDate()
let res = rl.runMode(mode, beforeDate: now.dateByAddingTimeInterval(10.0))
assert(res == false)
let t = NSDate().timeIntervalSinceReferenceDate - now.timeIntervalSinceReferenceDate
assert(t < 10.0)
}
NSRunLoop.currentRunLoop().runUntilDate(NSDate.distantFuture())
or even the case
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let rl = NSRunLoop.currentRunLoop()
let mode = "foo mode"
let now = NSDate()
let port = NSPort()
rl.addPort(port, forMode: mode)
rl.removePort(port, forMode: mode)
let res = rl.runMode(mode, beforeDate: now.dateByAddingTimeInterval(10.0))
assert(res == false)
let t = NSDate().timeIntervalSinceReferenceDate - now.timeIntervalSinceReferenceDate
assert(t < 10.0)
}
NSRunLoop.currentRunLoop().runUntilDate(NSDate.distantFuture())
Neither of these would work if the run loop is not claimed as finished at this point.
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.
_CFRunLoopFinished is returning correctly in the previous case since there is no port or any source scheduled on the run loop. If there is nothing to keep the loop alive, the loop is finished.
Note: those test cases are running against the objc version. Here is a bit more of a detailed explanation #include <CoreFoundation/CoreFoundation.h>
#include <pthread.h>
CF_EXPORT Boolean _CFRunLoopFinished(CFRunLoopRef rl, CFStringRef modeName);
static void *rlTest(void *ctx) {
Boolean finished = _CFRunLoopFinished(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
if (!finished) {
printf("not finished\n");
} else {
printf("finished\n"); // expected result...
}
return NULL;
}
int main(int argc, const char * argv[]) {
Boolean finished = _CFRunLoopFinished(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
if (!finished) {
printf("main loop not finished\n"); // expected result... because there is an implicit port in this run loop ;)
} else {
printf("main loop finished\n");
}
pthread_t t;
pthread_create(&t, NULL, &rlTest, NULL);
while (true) {
sleep(1);
}
return 0;
} What is probably missing is the implicit port in the main run loop on linux. We can probably create a dummy socket for the main CFRunLoop that keeps it alive in a default mode. |
Awesome! Glad you're able to narrow it down. Forgive my ignorance, I actually don't know very much about I opened this pull request at @parkera's request to get a conversation started--if the implementation for the solution is more complicated than my removed lines, it might be best if someone else opened a pull request of their own to fix the problem, or just pushed the fix to |
Thanks for looking at this @phausler . It is correct that run loop exits 'early' if there are no ports (this trips people up all the time). Another option is to put a timer with a fire date of distant future in the run loop. |
I guess we could do this during the setup of Foundation in __CFInitializeSwift and add something along the lines of: let mainSourceEmulation = CFRunLoopTimerCreate(kCFAllocatorSystemDefault, CFAbsoluteTime.max, 1.0, 0, 0, nil, nil)
CFRunLoopAddTimer(CFRunLoopGetMain(), mainSourceEmulation, kCFRunLoopDefaultMode) |
No, it would be up to XCTest to make sure that there is something in the run loop before it runs it. Foundation shouldn't insert something by default into the main run loop. |
This seems reasonable to me. Should XCTest do this for just OS X, or for Linux as well? Why is it that Linux doesn't have the same behavior? Or is this an anomaly that will be corrected? |
It's likely that XCTest for OS X does do this; I haven't looked at those sources to check. |
Sorry, I meant, should swift-corelibs-xctest do this for both platforms, or just conditionally for Darwin? I agree that Apple's Objective-C XCTest probably does this already. |
Ah yes, swift-corelibs-xctest should do this for both platforms. |
OK! I'll attach a change to swiftlang/swift-corelibs-xctest#43 and CC you guys on it. Thanks again! 🙇 |
…ptionalsort-rdar60799439 [sourcekitd] Add an option to completion request to sort results
A call to
_CFRunLoopFinished
inNSRunLoop.runMode()
always returnsfalse
on OS X, causing unexpected behavior.For example, the following program exits immediately on OS X:
Whereas it (correctly) takes 60 seconds to exit on Linux:
The problem appears to be
_CFRunLoopFinished
, which incorrectly returnsfalse
on OS X. Remove the faulty check to get things working on both OS X and Linux.As requested by @parkera in our discussion on swiftlang/swift-corelibs-xctest#43. See that pull request for another example of code that misbehaves on OS X due to this check.
/cc @phausler, who originally added the deleted lines.
/cc @Catfish-Man, since he strikes me as the kind of CoreFoundation guru that would know why this doesn't work on OS X. :)