Skip to content

Commit

Permalink
edit (ble/builtin/exit): defer exit in trap handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
akinomyoga committed Feb 20, 2022
1 parent 5015cb5 commit f62fc04
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 45 deletions.
1 change: 1 addition & 0 deletions docs/ChangeLog.md
Expand Up @@ -146,6 +146,7 @@
- util (`ble/util/buffer`): hide cursor in rendering `#D1758` e332dc5
- complete (`action:file`): always suffix `/` to complete symlinked directory names (reported by SuperSandro2000) `#D1759` 397ac1f
- edit (command-help): show source files for functions `#D1779` 0000000
- edit (`ble/builtin/exit`): defer exit in trap handlers (motivated by SuperSandro2000) `#D1782` 0000000

## Fixes

Expand Down
78 changes: 77 additions & 1 deletion note.txt
Expand Up @@ -1806,6 +1806,22 @@ bash_tips
ToDo
-------------------------------------------------------------------------------

2022-02-20

* trap (lastarg): 一応 heredoc 等を使えば eval の中から複数行の lastarg を設
定する事ができるのではないか。他に複数行で、eval されても余分な実行が起こら
ない様な方法はあるだろうか。不完全な引用符の場合には結局エラーが出力されて
しまう。

* exec: builtin sleep に対して C-c が効かない

% 現在の実装だと bash-5.0 以下で C-c でループ中に走っている外部コマンドを止
% めた時に応答がなくなってしまう。bash-5.1 以上では一旦実行が停止するものの
% 一応応答はする。

と思ったらこの問題は外部コマンドではなくて builtin sleep を呼び出している時
特有の問題だった。trap INT を設定しているのは問題なのだろうか。

2022-02-14

* 全ての ble.sh session にコマンドを送る機能があれば screen に再 attach した
Expand Down Expand Up @@ -6104,6 +6120,66 @@ bash_tips
Done (実装ログ)
-------------------------------------------------------------------------------

2022-02-20

* edit: work around exit called from EXIT trap (reported by SuperSandro2000) [#D1782]
https://github.com/akinomyoga/ble.sh/issues/179#issuecomment-1046122787

nix-shell の内部で exit を実行すると2回 [ble: exit] が表示される。これは
nix-shell が設定する EXIT trap の中で再度 exit が呼び出される為である。
ble.sh は独自に trap を処理しているし、また blehook EXIT も実行したいので、
勝手に EXIT の中から本当に exit されると困る。なので、trap handler 実行中に
ble/builtin/exit が呼び出された時には DEBUG trap を設定して、trap の呼び出
し元まで制御を戻して続きの処理を実行することにする。呼び出し元まで戻った時
に exit が trap handler の中で呼び出されたという事を記録しておいて、trap
handler の処理の終わりに元々呼ばれた exit を呼び出し直す。

* exit を trap handler の中で実行した時にはそのまま終了するのではなくて、
trap の処理が完了するまで待ってから終了するべきなのではないか → その様に
実装した。既存の TRAPDEBUG の枠組みにコードを追加して実現した。

x fixed: exit 1 2 の様に余分な引数を指定した時、exit の準備をして [ble:
exit] まで表示した後に exit: too many arguments のエラーが発生して exit
がキャンセルされる。事前に引数の問題はチェックするべきである。

取り敢えず実装して 3.0, 3.2, 5.0 で動作確認した。期待通りに動いている。以下
のコマンドなどを使ってちゃんと全ての EXIT handler が実行される事を確認した。

$ trap 'exit 5' EXIT; exit
$ trap 'echo trap EXIT' EXIT; f1() { echo "$FUNCNAME $*"; exit "$1"; }; blehook EXIT+='f1 2 hello' EXIT+='f1 3 world';exit

? 関係ないが jobs がある時にユーザーが exit をキャンセルした時、後続の処理
が実行されるのは問題ではないか? これに対しても何らかの対処が必要になるの
ではないか?

? もし現在の TRAPDEBUG の枠組みが信用できるのであれば try/catch の枠組みを
整備しても良いのかもしれないという気がする。その方が見通しも良いのでは。
然し同じ commit でやるのは大げさな気がする。

上記の exit キャンセルの際の振る舞いに関してはその枠組が整ってから対応す
るのが良い気がする。

→上の2つは別項目で処理する事にした。

x bash-5.2 以降で EXIT trap の stdout が /dev/null になっている。これはどう
したら良いのだろうか。うーん。EXIT trap の時だけ stdout/stdin を繋ぎ直す
のが良いだろうか。

調べてみた所 bash-5.2 以降では exit も EXIT handler も現在の関数の内部で
行われる様だ。これは bash-5.1 までの top-level で実行されていたときと振る
舞いが異なる。と思ったが、bash-4.4..5.1 の振る舞いが変だっただけの様だ。

うーん。builtin exit を呼び出す時の redirection を削除するか? と思ったが、
それだとやはり余分なエラーメッセージが出るのが心配である。という事を考え
ると、元々の stdout/stdin を何処かに記録しておくべきなのだろうという気が
する。

* edit: 現在の TRAPDEBUG の枠組みを拡張して try/catch/finally を実装できるのでは
Ref #D1783

もしその枠組がちゃんとできるのであれば edit.sh から util.sh に移動する。


2022-02-19

* 2022-01-20 \C-x\C-v で bash-completion や terminal ID 等の情報も出力する [#D1781]
Expand Down Expand Up @@ -6145,7 +6221,7 @@ bash_tips
* done: starship
* done: bash-it
* done: sbp
* gitstatus
* git: gitstatus

取り敢えず思いつく物は全て登録したのでOK。

Expand Down
150 changes: 107 additions & 43 deletions src/edit.sh
Expand Up @@ -5620,6 +5620,7 @@ function ble-edit/exec/print-PS0 {
fi
}

_ble_builtin_exit_processing=
function ble/builtin/exit/.read-arguments {
[[ ! $_ble_attached || $_ble_edit_exec_inside_userspace ]] &&
ble/base/adjust-BASH_REMATCH
Expand All @@ -5634,18 +5635,19 @@ function ble/builtin/exit/.read-arguments {
opt_flags=${opt_flags}E
fi
done
if ((${#opt_args[@]}>=2)); then
ble/util/print "exit: too many arguments" >&2
opt_flags=${opt_flags}E
fi
[[ ! $_ble_attached || $_ble_edit_exec_inside_userspace ]] &&
ble/base/restore-BASH_REMATCH
}
function ble/builtin/exit {
local ext=$?
if ble/util/is-running-in-subshell || [[ $_ble_decode_bind_state == none ]]; then
if (($#)); then
builtin exit "$@"
else
builtin exit "$ext"
fi
return 1
if [[ ! $_ble_builtin_trap_processing ]] && { ble/util/is-running-in-subshell || [[ $_ble_decode_bind_state == none ]]; }; then
(($#)) || set -- "$ext"
builtin exit "$@"
return $? # オプションの指定間違いなどで失敗する可能性がある。
fi

local set shopt; ble/base/.adjust-bash-options set shopt
Expand All @@ -5659,30 +5661,45 @@ function ble/builtin/exit {
fi
((${#opt_args[@]})) || ble/array#push opt_args "$ext"

local joblist
ble/util/joblist
if ((${#joblist[@]})); then
local ret
while
local cancel_reason=
if ble/util/assign ret 'compgen -A stopped -- ""' 2>/dev/null; [[ $ret ]]; then
cancel_reason='stopped jobs'
elif [[ :$opts: == *:checkjobs:* ]]; then
if ble/util/assign ret 'compgen -A running -- ""' 2>/dev/null; [[ $ret ]]; then
cancel_reason='running jobs'
if [[ $_ble_builtin_trap_processing ]]; then
# Note #D1782: trap の中で処理している時は exit は trap の側で処理する。な
# ので exit は延期して一旦元の呼び出し元まで戻る。これによって細かな動作の
# 違いが問題になる可能性はある。例えば trap の中で time で時間計測中だった
# 場合、時間計測が中止されず結果が出力される。
_ble_edit_exec_TRAPDEBUG_EXIT=$opt_args
ble-edit/exec:gexec/.TRAPDEBUG/trap
return 0
fi

if [[ ! $_ble_builtin_exit_processing ]]; then
# 終了確認と [ble: exit] の出力

local joblist
ble/util/joblist
if ((${#joblist[@]})); then
local ret
while
local cancel_reason=
if ble/util/assign ret 'compgen -A stopped -- ""' 2>/dev/null; [[ $ret ]]; then
cancel_reason='stopped jobs'
elif [[ :$opts: == *:checkjobs:* ]]; then
if ble/util/assign ret 'compgen -A running -- ""' 2>/dev/null; [[ $ret ]]; then
cancel_reason='running jobs'
fi
fi
fi
[[ $cancel_reason ]]
do
jobs
ble/builtin/read -ep "\e[38;5;12m[ble: There are $cancel_reason]\e[m Leave the shell anyway? [yes/No] " ret
case $ret in
([yY]|[yY][eE][sS]) break ;;
([nN]|[nN][oO]|'')
ble/base/.restore-bash-options set shopt
return 0 ;;
esac
done
[[ $cancel_reason ]]
do
jobs
ble/builtin/read -ep "\e[38;5;12m[ble: There are $cancel_reason]\e[m Leave the shell anyway? [yes/No] " ret
case $ret in
([yY]|[yY][eE][sS]) break ;;
([nN]|[nN][oO]|'')
ble/base/.restore-bash-options set shopt
return 0 ;;
esac
done
fi
ble/util/print "${_ble_term_setaf[12]}[ble: exit]$_ble_term_sgr0" >&2
fi

# Note #D1765: Bash 4.4..5.1 では "{ time { exit 2>/dev/tty; } } 2>/dev/null"
Expand Down Expand Up @@ -5710,12 +5727,17 @@ function ble/builtin/exit {
TIMEFORMAT=
fi

ble/util/print "${_ble_term_setaf[12]}[ble: exit]$_ble_term_sgr0" >&2
ble/base/.restore-bash-options set shopt
_ble_builtin_exit_processing=1
ble/fd#alloc _ble_builtin_exit_stdout '>&1' # EXIT trap で stdin/stdout を復元する
ble/fd#alloc _ble_builtin_exit_stderr '>&2'
builtin exit "${opt_args[@]}" &>/dev/null
builtin exit "${opt_args[@]}" &>/dev/null

# exit に失敗した時はできるだけ元の状態に戻す
_ble_builtin_exit_processing=
ble/fd#close _ble_builtin_exit_stdout
ble/fd#close _ble_builtin_exit_stderr
if ((40400<=_ble_bash&&_ble_bash<50200)); then
builtin eval -- "$global_TIMEFORMAT"
ble/variable#copy-state local_TIMEFORMAT TIMEFORMAT
Expand Down Expand Up @@ -5958,6 +5980,8 @@ _ble_edit_exec_TRAPDEBUG_enabled=
_ble_edit_exec_TRAPDEBUG_lastexit=
_ble_edit_exec_TRAPDEBUG_lastarg=
_ble_edit_exec_TRAPDEBUG_postproc=
_ble_edit_exec_TRAPDEBUG_INT=
_ble_edit_exec_TRAPDEBUG_EXIT=
_ble_edit_exec_inside_begin=
_ble_edit_exec_inside_prologue=
_ble_edit_exec_inside_userspace=
Expand All @@ -5982,7 +6006,7 @@ function ble-edit/exec:gexec/.TRAPDEBUG/trap {
builtin trap -- 'ble-edit/exec:gexec/.TRAPDEBUG "$*"; builtin eval -- "$_ble_edit_exec_TRAPDEBUG_postproc"' DEBUG

# Note: 以下は条件付きで user trap を直接 trap するコード。
# if [[ $_ble_attached && _ble_edit_exec_INT -ne 0 || :$1: == *:filter:* ]]; then
# if [[ $_ble_attached && _ble_edit_exec_TRAPDEBUG_INT || :$1: == *:filter:* ]]; then
# builtin trap -- 'ble-edit/exec:gexec/.TRAPDEBUG "$*"; builtin eval -- "$_ble_edit_exec_TRAPDEBUG_postproc"' DEBUG
# else
# local user_trap=${_ble_builtin_trap_handlers[_ble_builtin_trap_DEBUG]}
Expand Down Expand Up @@ -6014,17 +6038,56 @@ function ble-edit/exec:gexec/.TRAPDEBUG {
[[ ! ( ${FUNCNAME[1]-} == _ble_prompt_update__eval_prompt_command_1 && ( $bash_command == 'ble-edit/exec/.setexit '* || $bash_command == 'BASH_COMMAND='*' builtin eval -- '* ) ) ]] || return 0
[[ ! ${_ble_builtin_trap_inside-} ]] || return 0

if [[ $_ble_attached ]] && ((_ble_edit_exec_INT!=0)); then
local __set __shopt; ble/base/.adjust-bash-options __set __shopt
# Handle EXIT
if [[ $_ble_edit_exec_TRAPDEBUG_EXIT ]]; then
# 或る特定のレベルまでは素通りする (そもそも exit なのでユーザーの DEBUG
# trap も処理しなくて良い)。
local flag_clear= flag_exit=
if [[ ! $_ble_builtin_trap_processing ]] || ((${#FUNCNAME[*]}<=1)); then
# 本来は此処に来る事はない筈
flag_clear=1
flag_exit=$_ble_edit_exec_TRAPDEBUG_EXIT
else
case "${FUNCNAME[1]-} ${FUNCNAME[2]-}" in
('ble/builtin/trap/invoke.sandbox ble/builtin/trap/invoke')
_ble_trap_done=exit
_ble_trap_lastarg=$_ble_edit_exec_TRAPDEBUG_EXIT
_ble_edit_exec_TRAPDEBUG_postproc='return 0'
flag_clear=1 ;;
('blehook/invoke ble/builtin/trap/.handler')
_ble_builtin_trap_processing=exit:$_ble_edit_exec_TRAPDEBUG_EXIT
flag_clear=1 ;;
('ble/builtin/trap/invoke '* | 'ble/builtin/trap/.handler '* | 'ble-edit/exec:gexec/.TRAPDEBUG '*)
# 此処にも本来は来ない筈
flag_clear=1 ;;
(*)
# trap handler 内部の処理は全てスキップして呼び出し元に戻る。
_ble_edit_exec_TRAPDEBUG_postproc="{ return 130 || break; } &>/dev/null" ;;
esac
fi

if [[ $flag_clear ]]; then
if [[ ! ${_ble_builtin_trap_handlers[_ble_builtin_trap_DEBUG]+set} ]]; then
_ble_edit_exec_TRAPDEBUG_postproc="builtin trap - DEBUG${_ble_edit_exec_TRAPDEBUG_postproc:+;$_ble_edit_exec_TRAPDEBUG_postproc}"
fi
_ble_edit_exec_TRAPDEBUG_EXIT=
if [[ $flag_exit ]]; then
builtin exit "$flag_exit"
fi
fi

elif [[ $_ble_edit_exec_TRAPDEBUG_INT ]]; then
local IFS=$_ble_term_IFS __set __shopt
ble/base/.adjust-bash-options __set __shopt

# Run user DEBUG trap in the sandbox
local _ble_builtin_trap_processing=$_ble_builtin_trap_DEBUG
local _ble_builtin_trap_postproc
ble/util/setexit "$__lastexit" "$__lastarg"
ble/builtin/trap/invoke DEBUG
_ble_edit_exec_TRAPDEBUG_postproc=$_ble_builtin_trap_postproc # unused

# Handle INT
local IFS=$_ble_term_IFS
local depth=${#FUNCNAME[*]}
if ((depth>=2)) && ! ble/string#match "${FUNCNAME[*]:1}" '^ble-edit/exec:gexec/\.|(^| )ble/builtin/trap/\.handler'; then
# 関数内にいるが、ble-edit/exec:gexec/. の中ではない時
Expand All @@ -6033,13 +6096,13 @@ function ble-edit/exec:gexec/.TRAPDEBUG {
local lineno=${_ble_term_setaf[2]}${BASH_LINENO[0]}
local func=${_ble_term_setaf[6]}' ('${_ble_term_setaf[4]}${FUNCNAME[1]}${1:+ $1}${_ble_term_setaf[6]}')'
ble/util/print "${_ble_term_setaf[9]}[SIGINT]$_ble_term_sgr0 $source$sep$lineno$func$_ble_term_sgr0" >/dev/tty
_ble_edit_exec_TRAPDEBUG_postproc="{ return $_ble_edit_exec_INT || break; } &>/dev/null"
_ble_edit_exec_TRAPDEBUG_postproc="{ return $_ble_edit_exec_TRAPDEBUG_INT || break; } &>/dev/null"
elif ((depth==1)) && ! ble/string#match "$bash_command" '^ble-edit/exec:gexec/\.'; then
# 一番外側で、ble-edit/exec:gexec/. 関数ではない時
local source=${_ble_term_setaf[5]}global
local sep=${_ble_term_setaf[6]}:
ble/util/print "${_ble_term_setaf[9]}[SIGINT]$_ble_term_sgr0 $source$sep$_ble_term_sgr0 $bash_command" >/dev/tty
_ble_edit_exec_TRAPDEBUG_postproc="{ return $_ble_edit_exec_INT || break; } &>/dev/null"
_ble_edit_exec_TRAPDEBUG_postproc="break &>/dev/null"
fi

ble/base/.restore-bash-options __set __shopt
Expand Down Expand Up @@ -6143,7 +6206,7 @@ function ble-edit/exec:gexec/.TRAPINT {
((_ble_bash>=40300)) || sig=128 # bash-4.2 以下は 128
if [[ $_ble_attached ]]; then
ble/util/print "$_ble_term_bold^C$_ble_term_sgr0" >&2
_ble_edit_exec_INT=$sig
_ble_edit_exec_TRAPDEBUG_INT=$sig
ble-edit/exec:gexec/.TRAPDEBUG/trap
else
_ble_builtin_trap_postproc="{ return $sig || break; } &>/dev/tty"
Expand Down Expand Up @@ -6243,14 +6306,13 @@ function ble-edit/exec:gexec/.end {
function ble-edit/exec:gexec/.prologue {
_ble_edit_exec_inside_prologue=1
local IFS=$_ble_term_IFS
BASH_COMMAND=$1
_ble_edit_exec_BASH_COMMAND=$1
ble-edit/restore-PS1
ble-edit/restore-READLINE
ble-edit/restore-IGNOREEOF
builtin unset -v HISTCMD; ble/history/get-count -v HISTCMD

_ble_edit_exec_INT=0
_ble_edit_exec_TRAPDEBUG_INT=
ble/util/joblist.clear
ble-edit/exec:gexec/invoke-hook-with-setexit PREEXEC "$_ble_edit_exec_BASH_COMMAND"
ble-edit/exec/print-PS0 >&$_ble_util_fd_stdout 2>&$_ble_util_fd_stderr
Expand Down Expand Up @@ -6302,10 +6364,12 @@ function ble-edit/exec:gexec/.epilogue {
# Note: 他の関数呼び出しよりも先
builtin eval -- "$_ble_bash_FUNCNEST_adjust"
ble/base/adjust-builtin-wrappers-1
if ((_ble_edit_exec_lastexit==0)); then
_ble_edit_exec_lastexit=$_ble_edit_exec_INT
if [[ $_ble_edit_exec_TRAPDEBUG_INT ]]; then
if ((_ble_edit_exec_lastexit==0)); then
_ble_edit_exec_lastexit=$_ble_edit_exec_TRAPDEBUG_INT
fi
_ble_edit_exec_TRAPDEBUG_INT=
fi
_ble_edit_exec_INT=0

local IFS=$_ble_term_IFS
# Note: builtin trap -- - DEBUG は此処では何故か効かない
Expand Down

0 comments on commit f62fc04

Please sign in to comment.