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

Create infrastructure for running interactive local process #8495

Merged
merged 11 commits into from
Oct 31, 2019

Conversation

gshuflin
Copy link
Contributor

Draft of what an interactive process runner might look like, along with the stub of a run @console_rule. This isn't complete yet but I wanted to see what other people thought of this design.

Copy link
Contributor

@jsirois jsirois left a comment

Choose a reason for hiding this comment

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

This LGTM. If the Workspace solution made sense to everyone, this is just the same pattern for another side-effecting io operation. Whatever we were / are going to do to robustify / improve / evolve Workspace surely applies here too.

}

// deliberately do-nothing SIGINT handler
ctrlc::set_handler(|| {}).map_err(|e| e.to_string())?;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this will need to use tokio-net's ctrl-c: Detegr/rust-ctrlc#30

Copy link
Contributor

@illicitonion illicitonion left a comment

Choose a reason for hiding this comment

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

The shape looks reasonable to me, with one comment

}

// deliberately do-nothing SIGINT handler
ctrlc::set_handler(|| {}).map_err(|e| e.to_string())?;
Copy link
Contributor

Choose a reason for hiding this comment

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

We'll want to remove this signal handler (and reset it to whatever it was set to in python) when this function finishes...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I'm not happy with this solution either, but I'm not sure what the right way is to save the existing SIGINT handler and restore it after the subprocess execution is done (although I'll check out @jsirois 's link in the above comment).

@gshuflin gshuflin changed the title Starting work on interactive local subprocesses Create infrastructure for running interactive local process Oct 22, 2019
@gshuflin gshuflin marked this pull request as ready for review October 22, 2019 19:30
@gshuflin
Copy link
Contributor Author

Problem

pants run and pants repl (and perhaps other goals) have as their "output" the actual execution of a program, locally and interactively, which the current ExecuteProcessRequest infrastructure isn't designed to handle well.

Solution

This commit introduces a new type InteractiveRunner, which works like Console and Workspace, in that it can be requested only by a @console_rule and offers a blocking method to kick off an interactive subprocess, that takes control of the terminal.

This commit also happens to include the stub of a v2 run @console_rule (called, currently, v2-run), which is a stub that just runs /usr/bin/python and was only included to test the rule - eventually this will become a full-featured v2 run goal.

@gshuflin gshuflin force-pushed the subprocess-runner branch 3 times, most recently from c71be9c to d7f50e5 Compare October 23, 2019 19:48
Copy link
Contributor

@illicitonion illicitonion left a comment

Choose a reason for hiding this comment

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

Generally looks nice - thanks!

else:
state = Return(self._from_value(raw_root.handle))
roots.append(state)
finally:
self._native.lib.nodes_destroy(raw_roots)

if remaining_runtime_exceptions_to_capture:
raise ExecutionError('Internal logic error in scheduler: expected elements in '
Copy link
Contributor

Choose a reason for hiding this comment

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

The error message here appears to be the negation of the if condition?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is coming from one of the commits @cosmicexplorer had in his currently-open-PR that I rebased off of becuase the bug he fixed was affecting me. I'm not sure if makes sense to try to get that merged and then merge this PR after that, or just merge this one with those commits.

Some(TempDir::new().map_err(|err| format!("Error creating tempdir: {}", err))?)
};

maybe_tempdir
Copy link
Contributor

Choose a reason for hiding this comment

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

Personal style thing: I find:

if let Some(tempdir) = maybe_tempdir {
  command.current_dir(tempdir.path())
}

more straightforward to read than a side-effecting map of an Option :)

value: &Value,
field: &str,
) -> Result<BTreeMap<String, String>, String> {
let mut map: BTreeMap<String, String> = BTreeMap::new();
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be implemented kind of cutely as:

Ok(parts.into_iter().tuples<(_, _)>().collect())

@gshuflin gshuflin force-pushed the subprocess-runner branch 6 times, most recently from 33caec7 to f96d141 Compare October 30, 2019 00:54
cosmicexplorer and others added 10 commits October 30, 2019 12:30
Draft of what an interactive process runner might look like
Instead of establishing a rust signal handler, use the ExceptionSink
infrastructure that already exists to selectively ignore SIGINT. In the
V2 engine, a SIGINT raised when execution is in rust does not get
handled by the python signal handler until sometime after the engine is
finished executing a goal, which is why the existing `ignoring_sigint`
context in `ExceptionSink` does not work (it successfuly increments and
then decrements _threads_ignoring_sigint; but the signal is only handled
*after* that happens).
Use a bool flag instead to make it easier to pass across FFI.
@gshuflin gshuflin merged commit 2bdb223 into pantsbuild:master Oct 31, 2019
@gshuflin gshuflin deleted the subprocess-runner branch October 31, 2019 00:25
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`).
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.

4 participants