Skip to content

[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

Closed

Conversation

modocache
Copy link
Contributor

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:

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:

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.


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. :)

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
}

Copy link
Contributor

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".

Copy link
Contributor

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.

Copy link
Contributor

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.

@phausler
Copy link
Contributor

phausler commented Feb 9, 2016

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.

@modocache
Copy link
Contributor Author

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 CFRunLoop on any platform--just enough to get NSRunLoop to work in swiftlang/swift-corelibs-xctest#43. 😅

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 master. Feel free to close this pull request if/when that happens.

@parkera
Copy link
Contributor

parkera commented Feb 9, 2016

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.

@phausler
Copy link
Contributor

phausler commented Feb 9, 2016

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)

@parkera
Copy link
Contributor

parkera commented Feb 10, 2016

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.

@modocache
Copy link
Contributor Author

No, it would be up to XCTest to make sure that there is something in the run loop before it runs it.

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?

@parkera
Copy link
Contributor

parkera commented Feb 10, 2016

It's likely that XCTest for OS X does do this; I haven't looked at those sources to check.

@modocache
Copy link
Contributor Author

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.

@parkera
Copy link
Contributor

parkera commented Feb 10, 2016

Ah yes, swift-corelibs-xctest should do this for both platforms.

@modocache
Copy link
Contributor Author

OK! I'll attach a change to swiftlang/swift-corelibs-xctest#43 and CC you guys on it. Thanks again! 🙇

@modocache modocache closed this Feb 10, 2016
atrick pushed a commit to atrick/swift-corelibs-foundation that referenced this pull request Jan 12, 2021
…ptionalsort-rdar60799439

[sourcekitd] Add an option to completion request to sort results
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants