Compare commits

...

8 Commits

Author SHA1 Message Date
Eric Freese
3391962a15 Clear async global variables on successful response
If _ZSH_AUTOSUGGEST_ASYNC_FD is not cleared, something else may open the
file descriptor pointed to by it. And then the next call into
_zsh_autosuggest_async_request will close it causing trouble.

It seems like good practice to clean up _ZSH_AUTOSUGGEST_CHILD_PID as
well, though it's not directly causing any known problems at the moment.

I was able to produce errors with ZSH_AUTOSUGGEST_MANUAL_REBIND active
and sourcing a file async-widget-setup.zsh after the first precmd with
the plugin active. If manual rebind is not active or if the widgets are
created before the first precmd, then zsh-autosuggestions wraps the
widgets and for some reason we don't seem to get any fd conflicts.

The .zshrc:

```
ZSH_AUTOSUGGEST_MANUAL_REBIND=true
source zsh-autosuggestions.zsh
```

and async-widget-setup.zsh:

```
function async-widget-fork() {
	exec {fd}< <(echo foo)
	zle -M "opened: $fd, autosuggest fd: $_ZSH_AUTOSUGGEST_ASYNC_FD"
}

function async-widget-read() {
	zle -M "reading from $fd: $(cat <&$fd)"
}

zle -N async-widget-fork
zle -N async-widget-read

bindkey ^A async-widget-fork
bindkey ^B async-widget-read
```

Then run `ZDOTDIR=$PWD zsh` and run `source async-widget-setup.zsh`. At
the next prompt, type one character e.g. "a" to trigger an async
request/response cycle. This leaves _ZSH_AUTOSUGGEST_ASYNC_FD set to the
stale file descriptor number. Then press ^A to activate the fork. This
will set the fd parameter to the same number as
_ZSH_AUTOSUGGEST_ASYNC_FD. Then type another character e.g. "a" to
trigger an async request. This will print a "No handler installed" error
and close the file descriptor pointed to by both
_ZSH_AUTOSUGGEST_ASYNC_FD and fd. Pressing ^B at this point will fail to
read with a "bad file descriptor" error.
2023-08-25 22:05:57 -06:00
Eric Freese
61257de667 Avoid accessing unset param when zsh/system is not available 2023-08-25 16:29:02 -06:00
Eric Freese
84c7cfaa89 Prevent parameter value being interpreted as kill flag 2023-08-25 16:29:02 -06:00
Eric Freese
6259d46d36 Move check for MONITOR directly after fork
This should properly handle the edge case where the MONITOR option is
changed between forking and killing.
2023-08-25 16:28:18 -06:00
Eric Freese
b350b900ce Prevent suggestion being treated as an option for echo 2023-08-25 16:27:29 -06:00
Eric Freese
e386de0247 cleanup: Remove unnecessary/inconsistent quoting from fd arg 2023-08-25 16:27:29 -06:00
Eric Freese
2b730c9634 cleanup: More precisely check number of args instead of second arg value 2023-08-25 16:27:29 -06:00
Eric Freese
35ed23efe8 cleanup: Use simpler arithmetic expression 2023-08-25 16:27:28 -06:00
2 changed files with 38 additions and 36 deletions

View File

@ -15,31 +15,20 @@ _zsh_autosuggest_async_request() {
zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD
# We won't know the pid unless the user has zsh/system module installed
if [[ -n "$_ZSH_AUTOSUGGEST_CHILD_PID" ]]; then
# Zsh will make a new process group for the child process only if job
# control is enabled (MONITOR option)
if [[ -o MONITOR ]]; then
# Send the signal to the process group to kill any processes that may
# have been forked by the suggestion strategy
kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
else
# Kill just the child process since it wasn't placed in a new process
# group. If the suggestion strategy forked any child processes they may
# be orphaned and left behind.
kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
fi
if (( _ZSH_AUTOSUGGEST_CHILD_PID )); then
kill -TERM -- $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
fi
fi
# Fork a process to fetch a suggestion and open a pipe to read from it
exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <(
# Tell parent process our pid
echo $sysparams[pid]
# Tell parent process our pid if we can
echo ${sysparams[pid]:-}
# Fetch and print the suggestion
local suggestion
_zsh_autosuggest_fetch_suggestion "$1"
echo -nE "$suggestion"
echo -nE - "$suggestion"
)
# There's a weird bug here where ^C stops working unless we force a fork
@ -50,6 +39,15 @@ _zsh_autosuggest_async_request() {
# Read the pid from the child process
read _ZSH_AUTOSUGGEST_CHILD_PID <&$_ZSH_AUTOSUGGEST_ASYNC_FD
# Zsh will make a new process group for the child process only if job
# control is enabled (MONITOR option)
if [[ -o MONITOR ]]; then
# If we need to kill the background process in the future, we'll send
# SIGTERM to the process group to kill any processes that may have been
# forked by the suggestion strategy
_ZSH_AUTOSUGGEST_CHILD_PID=${_ZSH_AUTOSUGGEST_CHILD_PID:+-$_ZSH_AUTOSUGGEST_CHILD_PID}
fi
# When the fd is readable, call the response handler
zle -F "$_ZSH_AUTOSUGGEST_ASYNC_FD" _zsh_autosuggest_async_response
}
@ -60,17 +58,20 @@ _zsh_autosuggest_async_request() {
_zsh_autosuggest_async_response() {
emulate -L zsh
typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID
local suggestion
if [[ -z "$2" || "$2" == "hup" ]]; then
if [[ $# == 1 || "$2" == "hup" ]]; then
# Read everything from the fd and give it as a suggestion
IFS='' read -rd '' -u $1 suggestion
zle autosuggest-suggest -- "$suggestion"
# Close the fd
exec {1}<&-
_ZSH_AUTOSUGGEST_ASYNC_FD=
_ZSH_AUTOSUGGEST_ASYNC_PID=
fi
# Always remove the handler
zle -F "$1"
zle -F $1
}

View File

@ -770,31 +770,20 @@ _zsh_autosuggest_async_request() {
zle -F $_ZSH_AUTOSUGGEST_ASYNC_FD
# We won't know the pid unless the user has zsh/system module installed
if [[ -n "$_ZSH_AUTOSUGGEST_CHILD_PID" ]]; then
# Zsh will make a new process group for the child process only if job
# control is enabled (MONITOR option)
if [[ -o MONITOR ]]; then
# Send the signal to the process group to kill any processes that may
# have been forked by the suggestion strategy
kill -TERM -$_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
else
# Kill just the child process since it wasn't placed in a new process
# group. If the suggestion strategy forked any child processes they may
# be orphaned and left behind.
kill -TERM $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
fi
if (( _ZSH_AUTOSUGGEST_CHILD_PID )); then
kill -TERM -- $_ZSH_AUTOSUGGEST_CHILD_PID 2>/dev/null
fi
fi
# Fork a process to fetch a suggestion and open a pipe to read from it
exec {_ZSH_AUTOSUGGEST_ASYNC_FD}< <(
# Tell parent process our pid
echo $sysparams[pid]
# Tell parent process our pid if we can
echo ${sysparams[pid]:-}
# Fetch and print the suggestion
local suggestion
_zsh_autosuggest_fetch_suggestion "$1"
echo -nE "$suggestion"
echo -nE - "$suggestion"
)
# There's a weird bug here where ^C stops working unless we force a fork
@ -805,6 +794,15 @@ _zsh_autosuggest_async_request() {
# Read the pid from the child process
read _ZSH_AUTOSUGGEST_CHILD_PID <&$_ZSH_AUTOSUGGEST_ASYNC_FD
# Zsh will make a new process group for the child process only if job
# control is enabled (MONITOR option)
if [[ -o MONITOR ]]; then
# If we need to kill the background process in the future, we'll send
# SIGTERM to the process group to kill any processes that may have been
# forked by the suggestion strategy
_ZSH_AUTOSUGGEST_CHILD_PID=${_ZSH_AUTOSUGGEST_CHILD_PID:+-$_ZSH_AUTOSUGGEST_CHILD_PID}
fi
# When the fd is readable, call the response handler
zle -F "$_ZSH_AUTOSUGGEST_ASYNC_FD" _zsh_autosuggest_async_response
}
@ -815,19 +813,22 @@ _zsh_autosuggest_async_request() {
_zsh_autosuggest_async_response() {
emulate -L zsh
typeset -g _ZSH_AUTOSUGGEST_ASYNC_FD _ZSH_AUTOSUGGEST_CHILD_PID
local suggestion
if [[ -z "$2" || "$2" == "hup" ]]; then
if [[ $# == 1 || "$2" == "hup" ]]; then
# Read everything from the fd and give it as a suggestion
IFS='' read -rd '' -u $1 suggestion
zle autosuggest-suggest -- "$suggestion"
# Close the fd
exec {1}<&-
_ZSH_AUTOSUGGEST_ASYNC_FD=
_ZSH_AUTOSUGGEST_ASYNC_PID=
fi
# Always remove the handler
zle -F "$1"
zle -F $1
}
#--------------------------------------------------------------------#