7777 local shard_index="${11}"
7878 local shard_count="${12}"
7979 local volume_mount_source="${13}"
80+ local logs_pid=""
81+ local wait_pid=""
82+ local docker_wait_file="/tmp/.docker_exit_${container_name}"
83+
84+ # Kill the container immediately if the runner is cancelled.
85+ # The GitHub Actions runner can deliver HUP, INT, or TERM on cancellation
86+ # (see actions/runner#1309). Trap all three so the cleanup always fires.
87+ # shellcheck disable=SC2064
88+ trap "echo '::warning::Cancellation signal - killing container ${container_name}'; \
89+ docker kill '${container_name}' 2>/dev/null || true; \
90+ docker rm -f '${container_name}' 2>/dev/null || true; \
91+ rm -f '${docker_wait_file}'; \
92+ if [ -n \"\$logs_pid\" ]; then kill \"\$logs_pid\" 2>/dev/null || true; fi; \
93+ if [ -n \"\$wait_pid\" ]; then kill \"\$wait_pid\" 2>/dev/null || true; fi; \
94+ exit 130" HUP INT TERM
8095
8196 echo "Running tests in: $test_path"
8297 if [ -n "$pytest_options" ]; then
@@ -110,6 +125,7 @@ runs:
110125 -e ISAAC_SIM_LOW_MEMORY=1 \
111126 -e PYTHONUNBUFFERED=1 \
112127 -e PYTHONIOENCODING=utf-8 \
128+ -e GITHUB_ACTIONS=${GITHUB_ACTIONS:-} \
113129 -e TEST_RESULT_FILE=$result_file"
114130
115131 if [ "$curobo_only" = "true" ]; then
@@ -189,16 +205,30 @@ runs:
189205 ./isaaclab.sh -p -m pytest --ignore=tools/conftest.py --ignore=source/isaaclab/test/install_ci $test_path $pytest_options -v --junitxml=tests/$result_file
190206 "
191207
192- # Stream container logs to CI output (this is the killable foreground process).
193- docker logs -f $container_name &
194- local logs_pid=$!
208+ # Stream container logs in background.
209+ docker logs -f "$container_name" &
210+ logs_pid=$!
211+
212+ # Background `docker wait` + bash `wait` (interruptible by signals).
213+ docker wait "$container_name" > "$docker_wait_file" &
214+ wait_pid=$!
215+ wait $wait_pid 2>/dev/null
216+ local wait_status=$?
217+ wait_pid=""
195218
196- # Wait for the container to exit and capture its exit code.
197- DOCKER_EXIT=$(docker wait $container_name) || DOCKER_EXIT=1
219+ # If interrupted by signal, trap already handled cleanup.
220+ if [ $wait_status -gt 128 ]; then
221+ kill $logs_pid 2>/dev/null || true
222+ exit 130
223+ fi
224+
225+ DOCKER_EXIT=$(cat "$docker_wait_file" 2>/dev/null) || DOCKER_EXIT=1
226+ rm -f "$docker_wait_file"
198227
199228 # Stop following logs.
200229 kill $logs_pid 2>/dev/null || true
201230 wait $logs_pid 2>/dev/null || true
231+ logs_pid=""
202232
203233 if [ $DOCKER_EXIT -eq 0 ]; then
204234 echo "🟢 Docker container completed successfully"
@@ -249,7 +279,7 @@ runs:
249279 fi
250280 fi
251281
252- # Copy comparison images (saved by test_rendering_correctness .py).
282+ # Copy comparison images (saved by source/isaaclab_tasks/test/test_rendering_* .py).
253283 local img_dir="$reports_dir/comparison-images"
254284 if [ -n "$volume_mount_source" ] && [ -d "${volume_mount_source}/tests/comparison-images" ]; then
255285 cp -r "${volume_mount_source}/tests/comparison-images" "$img_dir"
@@ -268,6 +298,14 @@ runs:
268298 # Call the function with provided parameters
269299 run_tests "${{ inputs.test-path }}" "${{ inputs.result-file }}" "${{ inputs.container-name }}" "${{ inputs.image-tag }}" "${{ inputs.reports-dir }}" "${{ inputs.pytest-options }}" "${{ inputs.filter-pattern }}" "${{ inputs.curobo-only }}" "${{ inputs.include-files }}" "${{ inputs.quarantined-only }}" "${{ inputs.shard-index }}" "${{ inputs.shard-count }}" "${{ inputs.volume-mount-source }}"
270300
301+ - name : Kill container on cancellation
302+ if : cancelled()
303+ shell : bash
304+ run : |
305+ echo "::warning::Job cancelled - force-killing container"
306+ docker kill "${{ inputs.container-name }}" 2>/dev/null || true
307+ docker rm -f "${{ inputs.container-name }}" 2>/dev/null || true
308+
271309 - name : Write job summary
272310 if : always()
273311 shell : bash
@@ -295,7 +333,7 @@ runs:
295333 retention-days : 7
296334
297335 - name : Clean up Docker container
298- if : always()
336+ if : always() && !cancelled()
299337 shell : bash
300338 run : |
301339 echo "🔵 Cleaning up Docker container..."
0 commit comments