summaryrefslogtreecommitdiff
path: root/docs/error-handling/subshell
diff options
context:
space:
mode:
Diffstat (limited to 'docs/error-handling/subshell')
-rw-r--r--docs/error-handling/subshell/repeated-printing.md84
1 files changed, 84 insertions, 0 deletions
diff --git a/docs/error-handling/subshell/repeated-printing.md b/docs/error-handling/subshell/repeated-printing.md
new file mode 100644
index 0000000..fa94a1b
--- /dev/null
+++ b/docs/error-handling/subshell/repeated-printing.md
@@ -0,0 +1,84 @@
+# Problem: Repeated printing of (parts of) the stack trace
+
+## Gist
+
+* With subshells, several stack traces may be printed.
+* All but the first stack trace can be ignored.
+
+
+## Problematic behaviour
+
+* For each subshell in the current stack of subshells (including the root
+ shell), we get a stack trace.
+ * The `ERR` trap is caught for each subshell.
+ * Reason: The trap on `ERR` returns non-zero, and so does the subshell.
+ * If it returned zero, the error would be ignored on the upper level,
+ and program execution continue, which is undesired.
+* More precisely, each subshell gives a stack trace on the stack from itself
+ up to the root shell.
+ * Reason: Subshells inherit the knowledge of its ancestors, but do not
+ know of its descendant subshells.
+ * Thus, the closer to the root of the stack, the more often we get the
+ (same) information printed; while the information on where the error
+ originally occurred is printed only once.
+
+
+## Desired behaviour
+
+* The whole stack trace is printed once, and nothing more.
+ * This would be achieved if only the subshell where the error originally
+ occurred were to print a stack trace.
+
+
+## Considerations on fixes / improvements
+
+### Only print the stack trace for the root shell.
+
+* This is easy, just check for `[[ $BASH_SUBSHELL -eq 0 ]]`.
+* This would mean that on error within a subshell, we do not get
+ information below where the first subshell was invoked.
+* This may be deemed acceptable if subshells are rarely used and/or only
+ with short local code within.
+ * Short local code would be, e.g., `$(head -n 1 FILE)`.
+ * Short local code would not be, e.g., `$(local_nontrivial_function)`.
+
+
+### Reserve a special exit code.
+
+* Let `$SPECIAL_EXIT_CODE` be some exit code distinct from `0` and `1`.
+* Change `ERR` trap to `trap 'basic::on_error 0' ERR`, and define `on_error()`
+ as follows:
+ ```
+ function basic::on_error()
+ {
+ [[ $? -eq $SPECIAL_EXIT_CODE ]] && exit 1
+ local -ri offset="$1"
+ basic::print_stacktrace $((offset + 1))
+ exit $SPECIAL_EXIT_CODE
+ }
+ ```
+* The idea is that `on_error()` only prints the stack trace in the lowest
+ subshell, which has the full stack trace.
+* Naturally, this does not work properly if the original actual error had
+ `$SPECIAL_EXIT_CODE` as exit code.
+* That is, we'd need an exit code that cannot occur anywhere else.
+ * This should be impossible in the general case.
+
+
+### Use a temporary file
+
+* Use a temporary file to indicate whether `print_stacktrace()` was already
+ called in a subshell.
+* This feels somewhat evil.
+
+
+### Inspect `$BASH_COMMAND`
+
+* The `$BASH_COMMAND` variable contains the command executed that caused the
+ trap.
+* We'd have to identify whether the command spawned a subshell (and the error
+ came from there).
+* Note that `(` is a valid command name---but `$BASH_COMMAND` maintains any
+ necessary quoting.
+* It might suffice to check for `(.*` and `${varname_regex}=\$(.*`, given the
+ [other self-imposed restrictions](../../error-handling.md#Subshell).