Skip to content
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

Optionally execute processes exclusively in the foreground #8974

Closed

Conversation

stuhood
Copy link
Member

@stuhood stuhood commented Jan 15, 2020

Problem

As #8923 explains: streaming, foreground access to processes run by Pants is a useful tool for implementing debugging facilities. But it is currently only possible inside of a @goal_rule via InteractiveProcessRequest (rather than ExecuteProcessRequest), and this makes it more challenging to implement toggling of debug facilities without parallel codepaths that deal with the independent types and APIs.

Solution

Add support for a foreground flag on ExecuteProcessRequest that forces the process to run:

  1. locally
  2. with streaming stdout/stderr (NB: and stdin: worth discussing)

Result

./pants test --debug is implemented using this facility.

@stuhood
Copy link
Member Author

stuhood commented Jan 15, 2020

This is a working draft for discussion, with a few TODOs:
1. Node::cacheable (and ExecuteProcessRequest persistent caching) should be disabled for foreground processes (...that consume stdin: see the next item)
2. It's possible that we should turn foreground into an enum or ternary that will disable stdin and allow for streaming output while still allowing a process to be cached (because it will not have any additional inputs to the process)
3. We might want to block landing this until #6598 is implemented, to allow the uncacheable process to act as expected when it is deeper in the graph.

Commits are useful to review independently.

@gshuflin
Copy link
Contributor

I'm concerned that this PR is too closely tied to what would've been useful for making specifically #8827 easier to implement, especially now that that change is done with and merged. I do recognize that we will probably want to add --debug flags for non-Python tests in the future though.

with streaming stdout/stderr (NB: and stdin: worth discussing)

We definitely need streaming stdin to work for at least two subprocess usecases - ./pants run, which is currently using InteractiveProcessRunner (InteractiveProcessRunner was basically built in order to implement ./pants run), and ./pants repl, which no one has gotten to porting to v2 yet, but which I had imagined would also use InteractiveProcessRunner. So even if we merge this PR, it doesn't let us get rid of InteractiveProcessRunner.

The test --debug usecase also needs it, since we expect that debugging tests is very often going to involve firing up some kind of debugger REPL within the test and typing commands into it real-time. So if streaming stdin doesn't currently work, I don't think this PR solves the problem we need it to solve either.

3. We might want to block landing this until #6598 is implemented, to allow the uncacheable process to act as expected when it is deeper in the graph.

I think this PR only makes sense if #6598 is implemented first. Without it, we can't guarantee that the engine will actually run an EPR with the foreground flag rather than pulling its cached value or the cached value of one of the rule graph nodes that includes it, and this needs to happen in an interactive context.

I basically see this proposal + #6598 as a way of getting rid of the need for side-effecting types like InteractiveProcessRunner. The engine can allow side-effecting operation safely in one of two ways - it can statically enforce that types that perform side-effecting operations get pushed to the edge of the graph, which is the current system, and the reason we implemented #8922 . Or, the engine can allow side-effecting operations anywhere, provided #6598 is implemented and the engine can automatically invalidate every cached node between a top-level goal rule and an EPR with a foreground flag.

I don't think it makes sense to have two separate ways of solving the problem of running a subprocess, so we should aim either to make IPR the right way to run a foregrounded subprocess, or implement this change to EPR and remove IPR. I'm concerned that right now this PR doesn't let us replicate everything IPR does with modifications to EPR; and also that even if it did, this would necessarily involve making large chunks of the rule graph selectively uncached by flipping a boolean in an EPR data structure somewhere.

It might desirable to force rule-writers to structure their types such that they can only do side-effecting things in a goal_rule, but that desideratum is in conflict with the goal of this PR to reduce parallel code paths. However, I do think that it's possible in principle to design rule code paths in such a way that the branch between the interactive and non-interactive states happens only at the very last moment (cf. this commit: 37eca64 , which pulled InteractiveProcessRunner out of a non-goal rule and put it in a goal rule).

@stuhood
Copy link
Member Author

stuhood commented Jan 16, 2020

We definitely need streaming stdin to work for at least two subprocess usecases

To be clear: it already works here. I'm suggesting that in some cases folks might want streaming stdout/stderr without stdin, and so we might not want foreground to be boolean.

@stuhood
Copy link
Member Author

stuhood commented Jan 23, 2020

I'm concerned that this PR is too closely tied to what would've been useful for making specifically #8827 easier to implement, especially now that that change is done with and merged. I do recognize that we will probably want to add --debug flags for non-Python tests in the future though.

Yes: all of them. Which means that all test runners will have this duplication.

But additionally, there has been a desire to (conditionally) stream output from processes: you could imagine the test goal automatically pushing down a signal for foreground=True if there was exactly one test to run, for example.

I don't think it makes sense to have two separate ways of solving the problem of running a subprocess, so we should aim either to make IPR the right way to run a foregrounded subprocess, or implement this change to EPR and remove IPR. I'm concerned that right now this PR doesn't let us replicate everything IPR does with modifications to EPR; and also that even if it did, this would necessarily involve making large chunks of the rule graph selectively uncached by flipping a boolean in an EPR data structure somewhere.

I agree that it would be unfortunate to need two ways to do things, but I think I agree that having sideeffecting operations (and to be clear: the sideeffecting part here is not stdin: rather, the fact that the process runs in the buildroot, rather than in a chroot) deeper in the graph is a no-go. Is it possible that there is a middle ground where IPR can be made significantly simpler, and essentially targeted at only repl/run?

stuhood added a commit that referenced this pull request Feb 15, 2020
### Problem

The rust level `Node::cacheable` flag is currently only used to mark `@goal_rule`s as uncacheable (because they are allowed to operate on `@sideeffecting` types, such as the `Console` and the `Workspace`). But since the implementation of `cacheable` did not allow it to operate deeply in the Graph, we additionally needed to mark their parent `Select` nodes uncacheable, and could not use the flag in more positions.

Via #7350, #8495, #8347, and #8974, it has become clear that we would like to safely allow nodes deeper in the graph to be uncacheable, as this allows for the re-execution of non-deterministic processes, or re-consumption of un-trackable state, such as:
1. a process receiving stdin from a user
2. an intrinsic rule that pokes an un-watched file on the filesystem
3. interacting with a stateful process like git

Note that these would all be intrinsic Nodes: it's not clear that we want to expose this facility to `@rule`s directly.

### Solution

Finish adding support for uncacheable nodes. Fixes #6598.

When an uncacheable node completes, it will now keep the value it completed with (in order to correctly compute a `Generation` value), but it will re-compute the value once per `Session`. The accurate `Generation` value for the uncacheable node allows its dependents to "clean" themselves and not re-run unless the uncacheable node produced a different value than it had before. 

### Result

The `Node::cacheable` flag may be safely used deeper in the graph, with the semantics that requests for any of an uncacheable node's dependents will cause it to re-run once per `Session`. The dependents will not re-run unless the value of the uncacheable node changes (regardless of the `Session`).
@stuhood stuhood force-pushed the stuhood/process-execute-foreground branch from 0e03fc1 to 10720dc Compare February 16, 2020 04:33
@stuhood
Copy link
Member Author

stuhood commented Feb 16, 2020

Still a working draft, but now based on #9015, so it has proper cache behavior.

@@ -188,25 +187,15 @@ def get_packages_to_cover(
description=f'Run Pytest for {target_with_origin.adaptor.address.reference()}',
timeout_seconds=test_setup.timeout_seconds if test_setup.timeout_seconds is not None else 9999,
env=env,
foreground=test_options.values.debug,
Copy link
Member Author

Choose a reason for hiding this comment

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

This flag replaces the debug-specific test runner code.

@Eric-Arellano
Copy link
Contributor

Is this stale? I think you mentioned wanting to land this last week in a thread on test output.

@stuhood
Copy link
Member Author

stuhood commented Nov 18, 2020

This is stale, but the idea isn't dead. Can close it if it would clean things up to do so.

I'd still like to unify the InteractiveProcess and Process but one complicating factor that we noticed today is that because pdb requires a TTY to implement its repl, just copying stdout/stderr through like we do here won't just work. The foreground boolean value could potentially be an enum of {background, foreground, tty}, where background and foreground could capture stdio, but tty could not.

@Eric-Arellano
Copy link
Contributor

Can close it if it would clean things up to do so.

It'd be great to close, if you don't mind. I think it's helpful for the # of PRs to remain low and only be work that we have an intent to merge in the near future. Helps us to stay on track with things like users submitting PRs.

@stuhood
Copy link
Member Author

stuhood commented Nov 18, 2020

Have updated #8923 with the new information. Closing for now.

@stuhood stuhood closed this Nov 18, 2020
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