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

feat(job): add parameter to close stdin #14812

Merged
merged 4 commits into from
Jul 13, 2021
Merged

Conversation

gpanders
Copy link
Member

@gpanders gpanders commented Jun 14, 2021

Some programs behave differently when they detect that stdin is being
piped. This can be problematic when these programs are used with the job
control API where stdin is attached, but not typically used. It is
possible to run the job using a PTY which circumvents this problem, but
that includes a lot of overhead when simply closing the stdin pipe would
suffice.

To enable this behavior, add a new parameter to the jobstart options
dict called "stdin" with two valid values: "pipe" (the default)
implements the existing behavior of opening a channel for stdin and
"null" which disconnects stdin (or, if you prefer, connects it to
/dev/null). This is extensible so that other modes can be added in the
future.

Context

This was inspired by a recent change in ripgrep where the behavior of ripgrep changes if it detects that stdin is open. In the linked issue, the ripgrep maintainer rightfully asks the question, "Why is it attaching a socket to stdin in the first place?" (see also vim/vim#4529).

By default Neovim always attachesstdin and only attaches stdout and stderr if the appropriate callbacks are specified. Presently, the only way to disable automatically opening a stdin channel is to run the command in a pty using the 'pty' option; however, this adds a lot of overhead to simply avoid opening a stdin channel. This PR modifies that functionality by allowing the stdin channel to be closed (or rather, never be opened) if the "stdin" option is set to "null" in the options dict passed to jobstart.

@BurntSushi
Copy link

@gpanders Out of curiosity, have you confirmed that closing stdin here would cause ripgrep to do the right thing?

@gpanders
Copy link
Member Author

@gpanders Out of curiosity, have you confirmed that closing stdin here would cause ripgrep to do the right thing?

I have and it does.

@bfredl
Copy link
Member

bfredl commented Jun 15, 2021

should we call it stdin="closed" in case we want to support more modes later (like direct read from file, like vim8)?

@gpanders
Copy link
Member Author

should we call it stdin="closed" in case we want to support more modes later (like direct read from file, like vim8)?

Sure, I like that. What do you want to call the current/default behavior? I'm thinking either 'open', 'attached', or 'pipe' (to match Vim).

@gpanders
Copy link
Member Author

@bfredl I changed stdin_closed to a string parameter stdin that takes one of two values "pipe" or "null". Using "null" disconnects stdin (the same as setting stdin_closed to true) and using "pipe" (the default) implements the existing behavior. "pipe" and "null" mirror the names used in Vim.

@bfredl
Copy link
Member

bfredl commented Jun 16, 2021

sure, if the name choices picked by vim8 is reasonable, we could just as well reuse them :)

@gpanders
Copy link
Member Author

The CI checks seem to be failing for something unrelated to this PR

Some programs behave differently when they detect that stdin is being
piped. This can be problematic when these programs are used with the job
control API where stdin is attached, but not typically used. It is
possible to run the job using a PTY which circumvents this problem, but
that includes a lot of overhead when simply closing the stdin pipe would
suffice.

To enable this behavior, add a new parameter to the jobstart options
dict called "stdin" with two valid values: "pipe" (the default)
implements the existing behavior of opening a channel for stdin and
"null" which disconnects stdin (or, if you prefer, connects it to
/dev/null). This is extensible so that other modes can be added in the
future.
src/nvim/channel.c Outdated Show resolved Hide resolved
@bfredl
Copy link
Member

bfredl commented Jul 12, 2021

A simple test would be good. doesn't need to be complicated, could just be jobstart() and then chansend() failing because channel already closed.

@gpanders
Copy link
Member Author

I modified the it("disallows jobsend on a job that closed stdin") spec to add a test case with stdin = 'null'.

@gpanders gpanders requested a review from bfredl July 13, 2021 18:21
@bfredl bfredl merged commit 02bf251 into neovim:master Jul 13, 2021
ddeville added a commit to ddeville/vim-grepper that referenced this pull request Aug 19, 2021
Starting with version 13, ripgrep always stats stdin and if it's not a
TTY it uses it to read data. Unfortunately, Neovim always attaches a
pipe to stdin by default and that leads to ripgrep reading nothing and
it essentially breaks vim-grepper when targeting a recent version of
ripgrep.

(see neovim/neovim#14812 for more info)

This was fixed in nvim by adding an option to jobstart to not pipe stdin
(see neovim/neovim#14812). So we use this here
which I verified fixes search through rg.

Note that I'm not 100% sure how to gate this. It was technically added
as a commit to neovim after 0.5 shipped so I imagine it will technically
be released in 0.5.1. But it should also be harmless to only gate this
to `nvim` since the options are a dictionary and old versions of neovim
will just ignore that `stdin` key.
lbonn pushed a commit to mhinz/vim-grepper that referenced this pull request Aug 20, 2021
Starting with version 13, ripgrep always stats stdin and if it's not a
TTY it uses it to read data. Unfortunately, Neovim always attaches a
pipe to stdin by default and that leads to ripgrep reading nothing and
it essentially breaks vim-grepper when targeting a recent version of
ripgrep.

(see neovim/neovim#14812 for more info)

This was fixed in nvim by adding an option to jobstart to not pipe stdin
(see neovim/neovim#14812). So we use this here
which I verified fixes search through rg.

Note that I'm not 100% sure how to gate this. It was technically added
as a commit to neovim after 0.5 shipped so I imagine it will technically
be released in 0.5.1. But it should also be harmless to only gate this
to `nvim` since the options are a dictionary and old versions of neovim
will just ignore that `stdin` key.
typedef enum {
kChannelStdinPipe,
kChannelStdinNull,
} ChannelStdinMode;
Copy link
Member

Choose a reason for hiding this comment

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

would it make sense to name this ChannelMode, and add kChannelBuffered, then use this internally to model the stderr_buffered/stdout_buffered flags? And potentially we would add stderr, stdout string options to jobstart(), and deprecate the stderr_buffered/stdout_buffered booleans.

Copy link
Member Author

@gpanders gpanders Aug 27, 2021

Choose a reason for hiding this comment

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

I'm not sure if that would make sense, as that introduces more edge cases we'd need to check for.

For example, what happens when "stdout" is set to "null" but an "on_stdout" callback is provided? What does it mean when "stdin" is set to "buffered"?

Copy link
Member

Choose a reason for hiding this comment

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

For example, what happens when "stdout" is set to "null" but an "on_stdout" callback is provided? What does it mean when "stdin" is set to "buffered"?

At the input layer we would of course validate valid values, just as "foo" is not a valid value for the stdin mode parameter.

Copy link
Member Author

Choose a reason for hiding this comment

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

I understand that, but the point I was trying to make was that I don't know if the additional validation required results in a net reduction in complexity.

Copy link
Member

Choose a reason for hiding this comment

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

It would only make sense if we actually add more flags to the jobstart() interface (e.g stdout="file").

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess we could put most of the validation responsibility on the user. We just return an error if "stdout" is anything other than "null" and "on_stdout" is missing and vice versa.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think this makes sense for the existing feature set, but perhaps it does in a PR adding "buffer" and "file" modes (so there actually are shared modes between instreams and outsteams).

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.

None yet

5 participants