diff --git a/CHANGELOG.md b/CHANGELOG.md index 961f1b29c..b55d65177 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,16 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Fixed + +- spark: Propagate the entrypoint's exit code so failed applications are no longer reported as successful ([#1595]). + ### Removed - omid: remove 1.1.2 ([#1593]). [#1593]: https://github.com/stackabletech/docker-images/pull/1593 +[#1595]: https://github.com/stackabletech/docker-images/pull/1595 ## [26.7.0] - 2026-07-21 diff --git a/spark-k8s/stackable/run-spark.sh b/spark-k8s/stackable/run-spark.sh index 8871d3f36..e63db49db 100755 --- a/spark-k8s/stackable/run-spark.sh +++ b/spark-k8s/stackable/run-spark.sh @@ -1,24 +1,52 @@ #!/bin/bash -eval "$_STACKABLE_PRE_HOOK" - -# Forward SIGTERM to the Spark entrypoint to support spark JVM's gracefully shutdown. -/stackable/spark/kubernetes/dockerfiles/spark/entrypoint.sh "$@" & -child_pid=$! +prepare_signal_handlers() { + unset term_child_pid + unset term_kill_needed + trap handle_term_signal TERM INT +} +# Only ever invoked via the trap installed above. # shellcheck disable=SC2329 -_handle_term() { - kill -TERM "$child_pid" 2>/dev/null || true +handle_term_signal() { + if [[ -v term_child_pid ]]; then + kill -TERM "${term_child_pid}" 2>/dev/null || true + else + term_kill_needed="yes" + fi +} + +# Waits for the given child and returns *its* exit status. +# A trapped signal makes `wait` return immediately with a status > 128 while the child keeps +# running its shutdown sequence, so in that case wait again instead of taking the interrupted +# status. +wait_for_termination() { + term_child_pid=$1 + if [[ -v term_kill_needed ]]; then + kill -TERM "${term_child_pid}" 2>/dev/null || true + fi + while true; do + wait "${term_child_pid}" + term_child_status=$? + if [[ "${term_child_status}" -gt 128 ]] && kill -0 "${term_child_pid}" 2>/dev/null; then + continue + fi + return "${term_child_status}" + done } -trap _handle_term TERM INT -# `wait` returns immediately when the trap fires; loop until the child is actually gone. -wait "$child_pid" -while kill -0 "$child_pid" 2>/dev/null; do - wait "$child_pid" 2>/dev/null || true -done -result=$? +eval "$_STACKABLE_PRE_HOOK" + +# The signal handlers are installed before the child is started, so that a signal arriving in +# between is not lost. SIGTERM is forwarded to the Spark entrypoint to let the Spark JVM shut +# down gracefully. +prepare_signal_handlers + +/stackable/spark/kubernetes/dockerfiles/spark/entrypoint.sh "$@" & + +result=0 +wait_for_termination $! || result=$? eval "$_STACKABLE_POST_HOOK" -exit $result +exit "${result}"