Skip to content

waitUntilExit: ignore RunLoop.run()'s return value #4740

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

Merged
merged 2 commits into from
May 19, 2023
Merged

waitUntilExit: ignore RunLoop.run()'s return value #4740

merged 2 commits into from
May 19, 2023

Conversation

fischman-bcny
Copy link
Contributor

@fischman-bcny fischman-bcny commented May 5, 2023

We've been seeing frequent (but not reproducible in isolation) failures in our builds where waitUntilExit returns but terminationStatus fails its precondition on hasFinished because isRunning is still true. This is a speculative fix, since I haven't been able to reproduce the failure in a self-contained test.

This should be safe since Process.waitUntilExit isn't spec'd to relate to RunLoop. In particular there is no guarantee that "stopping" a RunLoop should cause waitUntilExit to return before the monitored process has exited.


Unrelated to the above, the second commit in this PR is required to unbreak XCTest's tests, which are run as part of this repo's CI (and so is required to be able to get a green run for this PR).
Absent that commit, XCTest's tests exit -5 on my M1 Pro and -4 on CI (with no output). On my machine, Console reveals:

BUG IN CLIENT OF LIBPTHREAD: workgroup functions already installed
Abort Cause 8313026928

and running under lldb blames CFRuntime.c:1185's call to libdispatch_init().
Breaking on libdispatch_init in lldb confirms libdispatch_init() is called twice, first by swift-corelibs-libdispatch/src/init.c in an __attribute__((constructor)) and the second in the site being disabled in this commit.

We've been seeing frequent (but not reproducible in isolation) failures
in our builds where waitUntilExit returns but terminationStatus fails
its precondition on hasFinished because isRunning is still true.
This is a speculative fix, since I haven't been able to reproduce the
failure in a self-contained test.

This should be safe since Process.waitUntilExit isn't spec'd to relate
to RunLoop. In particular there is no guarantee that "stopping" a
RunLoop should cause waitUntilExit to return before the monitored
process has exited.
@compnerd
Copy link
Member

compnerd commented May 5, 2023

@swift-ci please test

@fischman-bcny
Copy link
Contributor Author

The reason I believe this PR might help the observed issue is that it's possible to make RunLoop.run(.default) return false with sufficiently esoteric setup, such as:

import Foundation

let waiter = DispatchGroup()
waiter.enter()
let t = Thread {
    defer { waiter.leave() }
    let runLoop = RunLoop.current
    let customMode = RunLoop.Mode("yo")
    runLoop.add(Timer(timeInterval: 0.1, repeats: false) { _ in
        let innerRunResult = runLoop.run(mode: .default, before: Date(timeIntervalSinceNow: 0.1))
        print("inner runLoop.run(.default) returned \(innerRunResult)")
    }, forMode: customMode)

    let outerRunResult = runLoop.run(mode: customMode, before: Date(timeIntervalSinceNow: 1))
    print("outer runLoop.run(custom) returned: \(outerRunResult)")
}
t.start()
waiter.wait()

which emits:

inner runLoop.run(.default) returned false
outer runLoop.run(custom) returned: true

That inner runLoop.run(.default) returned false is what I suspect is similar to what's happening inside Process.waitUntilExit's call to .run(mode: .default, ...) and triggering the too-early return.

@compnerd
Copy link
Member

compnerd commented May 8, 2023

CC: @parkera @iCharlesHu @pcbeard

@compnerd
Copy link
Member

@swift-ci please test macOS platform

@compnerd
Copy link
Member

@swift-ci please test

@compnerd
Copy link
Member

@swift-ci please test Windows platform

@@ -1179,7 +1179,7 @@ void __CFInitialize(void) {
if (!__CFInitialized && !__CFInitializing) {
__CFInitializing = 1;

#if __HAS_DISPATCH__
#if __HAS_DISPATCH__ && !TARGET_OS_MAC
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we would use the deployment version - but this is still forwards/backwards incompatible. @parkera - any thoughts? I'm tempted to say we merge this even with that limitation as this is blocking PRs for other platforms.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is designed to go with the corresponding libdispatch, so if we are making a change to both (or a change to dispatch has already been made), they would go together into a Swift release.

@parkera parkera merged commit 818de48 into swiftlang:main May 19, 2023
@parkera
Copy link
Contributor

parkera commented May 19, 2023

LGTM, thanks @fischman-bcny and @compnerd

MaxDesiatov pushed a commit that referenced this pull request Sep 7, 2023
* waitUntilExit: ignore RunLoop.run()'s return value

We've been seeing frequent (but not reproducible in isolation) failures
in our builds where waitUntilExit returns but terminationStatus fails
its precondition on hasFinished because isRunning is still true.
This is a speculative fix, since I haven't been able to reproduce the
failure in a self-contained test.

This should be safe since Process.waitUntilExit isn't spec'd to relate
to RunLoop. In particular there is no guarantee that "stopping" a
RunLoop should cause waitUntilExit to return before the monitored
process has exited.

* Exclude libdispatch_init() on TARGET_OS_MAC because it duplicates the call from the static constructor https://github.com/apple/swift-corelibs-libdispatch/blob/7fb9d5ceea562d60fe34ec55b6b165ae5aca38eb/src/init.c#L56

(cherry picked from commit 818de48)
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