Skip to content

Introduce Swift Subprocess Proposal #397

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 4 commits into from
Feb 28, 2024

Conversation

iCharlesHu
Copy link
Contributor

This PR introduces a proposal for Subprocess.

@iCharlesHu iCharlesHu added the proposal This PR is for a proposal label Feb 2, 2024
@iCharlesHu iCharlesHu mentioned this pull request Feb 2, 2024
@iCharlesHu iCharlesHu force-pushed the charles/subprocess-proposal branch from 0d6d94f to 233af5e Compare February 15, 2024 19:29
@iCharlesHu
Copy link
Contributor Author

Updated based on community feedbacks. Thanks all!

Copy link
Contributor

@weissi weissi left a comment

Choose a reason for hiding this comment

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

This looks like a great basis for an API. Requesting changes because two crucial lifecycle things are not specified (from what I can tell):

  1. What happens under Swift Concurrency cancellation?
  2. Who manages the lifetime (i.e. who closes) file descriptors inherited into sub processes

@available(tvOS, unavailable)
@available(watchOS, unavailable)
extension Subprocess {
public static func run(
Copy link
Contributor

Choose a reason for hiding this comment

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

@iCharlesHu This needs to specify the cancellation behaviour. For Swift Concurrency to work it's crucial that under task cancellation (taskGroup.cancelAll()) the async methods return quickly. For sub processes that will mean that it needs to get rid of the process which requires sending a signal -- likely SIGKILL. But this needs to be clearly specified.

Copy link
Member

Choose a reason for hiding this comment

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

I mentioned this in the pitch as well and Charles said that he is going to change this to send a SIGKILL

@available(watchOS, unavailable)
public struct InputMethod: Sendable, Hashable {
public static var noInput: Self
public static func readFrom(_ fd: FileDescriptor, closeWhenDone: Bool) -> Self
Copy link
Contributor

Choose a reason for hiding this comment

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

It's crucial here to specify who closes this file descriptor in the parent and precisely when and how. Please note that these file descriptors must usually be closed in the parent right after fork/posix_spawn. It is not acceptable to wait with the closing until after the process exits because that may lead to a deadlock. For example, the parent commonly inherits a pipe into the child. But the child can only see EOF on the pipe if there are no more writers left. Also: It's crucial to keep the number of open file descriptors low (macOS default limit is 256).

I would assume that Subprocess either always closes or has the ability to close file descriptors that it inherits into a child at the earliest possible point. But there doesn't seem to be API or docs around this (unless I missed it).

Copy link
Contributor

Choose a reason for hiding this comment

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

(same for writeTo).


In addition to supporting the direct passing of `Sequence<UInt8>` and `AsyncSequence<UInt8>` as the standard input to the child process, `Subprocess` also provides a `Subprocess.InputMethod` type that includes two additional input options:
- `.noInput`: Specifies that the subprocess does not require any standard input. This is the default value.
- `.readFrom`: Specifies that the subprocess should read its standard input from a file descriptor provided by the developer. Subprocess will automatically close the file descriptor after the process exits if `closeWhenDone` is set to `true`.
Copy link
Contributor

Choose a reason for hiding this comment

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

After the process is done is too late, this should happen as soon as possible.


## Automatic `Subprocess` Cancellation

Currently, `Subprocess` does not terminate the spawned process, even if the `Subprocess` instance goes out of scope. It would be beneficial to investigate whether it is more sensible to attempt terminating the spawned process when the `Subprocess` itself goes out of scope or when the parent task is canceled.
Copy link
Contributor

Choose a reason for hiding this comment

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

It really ought to terminate the process. Or else you'll deadlock super easily. Imagine a task group which reads chunks from the sub process's output. Under cancellation that AsyncSequence will stop being iterated. That however means that the sub process won't be able to write a lot because the pipe will run full (as the parent is no longer reading). And that means the sub process will never exit --> deadlock.

@iCharlesHu
Copy link
Contributor Author

Updated for v3

Copy link
Contributor

@itingliu itingliu left a comment

Choose a reason for hiding this comment

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

General comment, but non blocking for review: We should document the expected behaviors and their arguments next to each function since these behaviors might not be super straightforward

Copy link
Contributor

@itingliu itingliu left a comment

Choose a reason for hiding this comment

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

It looks to me that pending questions were answered in V3. I'd like to move this to formal review. @iCharlesHu will keep taking questions and improve the proposal as needed during review period.


* Proposal: [SF-NNNN](NNNN-swift-subprocess.md)
* Authors: [Charles Hu](https://github.com/iCharlesHu)
* Review Manager: TBD
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* Review Manager: TBD
* Review Manager: [Tina Liu](https://github.com/itingliu)

@@ -0,0 +1,878 @@
# Introducing Swift Subprocess

* Proposal: [SF-NNNN](NNNN-swift-subprocess.md)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* Proposal: [SF-NNNN](NNNN-swift-subprocess.md)
* Proposal: [SF-0007](0007-swift-subprocess.md)

* Proposal: [SF-NNNN](NNNN-swift-subprocess.md)
* Authors: [Charles Hu](https://github.com/iCharlesHu)
* Review Manager: TBD
* Status: **Draft**
Copy link
Contributor

@itingliu itingliu Feb 27, 2024

Choose a reason for hiding this comment

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

Suggested change
* Status: **Draft**
* Status: **Active Review: Feb 28, 2024...Mar 06, 2024**

Copy link
Contributor

@weissi weissi left a comment

Choose a reason for hiding this comment

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

Approving to allow merging which will unlock the actual discussion

@itingliu itingliu merged commit 1e54bb7 into swiftlang:main Feb 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal This PR is for a proposal
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants