Skip to content

Use exec to ensure JVM receives SIGTERM for graceful shutdown#4600

Closed
JeethJJ wants to merge 1 commit into
wso2:4.12.xfrom
JeethJJ:4.12.x
Closed

Use exec to ensure JVM receives SIGTERM for graceful shutdown#4600
JeethJJ wants to merge 1 commit into
wso2:4.12.xfrom
JeethJJ:4.12.x

Conversation

@JeethJJ

@JeethJJ JeethJJ commented Jun 3, 2026

Copy link
Copy Markdown

Problem

When deploying WSO2 Identity Server in a Docker container, the JVM does not receive SIGTERM on container stop, resulting in a forceful kill after the grace period with no graceful shutdown.

The root cause is in wso2server.sh — the Java process is launched as a child of the shell:

$JAVACMD \
    -Xbootclasspath/a: ...
    org.wso2.carbon.bootstrap.Bootstrap $*

This makes the shell PID 1 inside the container, and since the shell does not forward signals to its children, SIGTERM sent by Docker is never received by the JVM.

Fix

Add exec before $JAVACMD of wso2server.sh:

exec $JAVACMD \
    -Xbootclasspath/a: ...
    org.wso2.carbon.bootstrap.Bootstrap $*

exec replaces the shell process with the JVM, making Java PID 1 directly. SIGTERM is now received by the JVM and graceful shutdown is triggered.

Verification

  1. Apply the fix and run the container.
  2. Run ps -ef inside the container — Java is now PID 1.
  3. Run docker stop — graceful shutdown logs are now visible confirming the JVM received SIGTERM.

Public issue : wso2/product-is#27711
Related PR: wso2/docker-is#507

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Chores
    • Updated server startup to optionally replace the startup shell with the Java process when running as PID 1 in container environments, changing how the Java process is launched during server boot. This affects server startup behavior in containerized deployments.

Walkthrough

The startup script now initializes JAVA_LAUNCH_PREFIX and conditionally sets it to exec when running as PID 1 in a container-like environment; the Java launch uses $JAVA_LAUNCH_PREFIX $JAVACMD ... so Java may replace the shell process in those cases.

Changes

Java Process Startup Optimization

Layer / File(s) Summary
Conditional exec launch and prefix
distribution/kernel/carbon-home/bin/wso2server.sh
Adds JAVA_LAUNCH_PREFIX and sets it to exec only when PID 1 and container indicators (/.dockerenv, /run/.containerenv, or container cgroups) are present; the startup loop uses $JAVA_LAUNCH_PREFIX $JAVACMD ....

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The PR description comprehensively covers the problem, fix, and verification steps, but the required template sections (Goals, Approach, Release notes, etc.) are not formally addressed. Provide explicit sections following the repository template: Goals, Release note, Documentation, Security checks, and Test environment details for clarity.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: using exec to ensure the JVM receives SIGTERM for graceful shutdown in Docker containers.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
distribution/kernel/carbon-home/bin/wso2server.sh (1)

300-345: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Unconditional exec breaks the script’s restart loop semantics.

At Line 302, exec replaces the shell, so Line 344 (status=$?) never runs and the while loop cannot handle START_EXIT_STATUS anymore. This can regress script-driven restart flow (see SystemRestarter.restart() using System.exit(EXIT_CODE) for wso2server.sh launches).

Suggested adjustment (use exec only when this script is PID 1)
 while [ "$status" = "$START_EXIT_STATUS" ]
 do
-    exec $JAVACMD \
+    if [ "$$" -eq 1 ]; then
+      exec "$JAVACMD" \
+    else
+      "$JAVACMD" \
+    fi \
     -Xbootclasspath/a:"$CARBON_XBOOTCLASSPATH" \
     $JVM_MEM_OPTS \
@@
     -Dcarbon.new.config.dir.path="$CARBON_HOME/repository/resources/conf" \
     org.wso2.carbon.bootstrap.Bootstrap $*
     status=$?
 done
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@distribution/kernel/carbon-home/bin/wso2server.sh` around lines 300 - 345,
The loop uses an unconditional exec which replaces the shell so the exit code
assignment (status=$?) and restart logic never run; modify the block in
wso2server.sh around the while [ "$status" = "$START_EXIT_STATUS" ] loop to
either (A) remove exec and invoke the JVM command normally so the script retains
control and status=$? executes, or (B) guard the exec so it only runs when the
script is PID 1 (check $$ -eq 1) and otherwise run the JVM without exec; update
the invocation of org.wso2.carbon.bootstrap.Bootstrap $* accordingly so
START_EXIT_STATUS logic and SystemRestarter-driven restarts work.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@distribution/kernel/carbon-home/bin/wso2server.sh`:
- Around line 300-345: The loop uses an unconditional exec which replaces the
shell so the exit code assignment (status=$?) and restart logic never run;
modify the block in wso2server.sh around the while [ "$status" =
"$START_EXIT_STATUS" ] loop to either (A) remove exec and invoke the JVM command
normally so the script retains control and status=$? executes, or (B) guard the
exec so it only runs when the script is PID 1 (check $$ -eq 1) and otherwise run
the JVM without exec; update the invocation of
org.wso2.carbon.bootstrap.Bootstrap $* accordingly so START_EXIT_STATUS logic
and SystemRestarter-driven restarts work.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 69bede93-297f-413e-af64-a3fecd98fbea

📥 Commits

Reviewing files that changed from the base of the PR and between 1e87b3c and 65b0ee9.

📒 Files selected for processing (1)
  • distribution/kernel/carbon-home/bin/wso2server.sh

shashimalcse
shashimalcse previously approved these changes Jun 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@distribution/kernel/carbon-home/bin/wso2server.sh`:
- Around line 299-304: The restart loop fails because JAVA_LAUNCH_PREFIX is set
to "exec", causing the JVM launch line to replace the shell and preventing the
while [ "$status" = "$START_EXIT_STATUS" ] loop from observing $?. Fix by only
using exec for one-shot launches and keep the restart-loop path non-exec: detect
whether the script will enter the START_EXIT_STATUS restart loop (the while [
"$status" = "$START_EXIT_STATUS" ] block) and ensure JAVA_LAUNCH_PREFIX is empty
for that case; reserve JAVA_LAUNCH_PREFIX="exec" only for the PID1/one-shot code
path (or refactor to a separate variable/mode for exec vs wrapper-loop) so
status=$? after the JVM exit is always captured.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 9bdf721e-b409-4726-83a8-b1c3af81d56a

📥 Commits

Reviewing files that changed from the base of the PR and between 65b0ee9 and a7d89fa.

📒 Files selected for processing (1)
  • distribution/kernel/carbon-home/bin/wso2server.sh

Comment on lines +299 to +304
JAVA_LAUNCH_PREFIX=""
if [ $$ -eq 1 ]; then
if [ -f "/.dockerenv" ] || [ -f "/run/.containerenv" ] || grep -qE '/(docker|kubepods|containerd|crio)/' /proc/1/cgroup 2>/dev/null; then
JAVA_LAUNCH_PREFIX="exec"
fi
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify that `exec` is used in the same loop that depends on `$?` for restart.
set -euo pipefail

FILE="distribution/kernel/carbon-home/bin/wso2server.sh"

echo "== Restart loop contract =="
sed -n '278,352p' "$FILE"

echo
echo "== Key markers =="
rg -n 'START_EXIT_STATUS|while \[ "\$status" = "\$START_EXIT_STATUS" \]|JAVA_LAUNCH_PREFIX|^\s*\$JAVA_LAUNCH_PREFIX \$JAVACMD|status=\$\?' "$FILE"

Repository: wso2/carbon-kernel

Length of output: 3783


Fix PID1 launch: don’t exec inside the START_EXIT_STATUS=121 restart loop

In distribution/kernel/carbon-home/bin/wso2server.sh, JAVA_LAUNCH_PREFIX="exec" (299-304) causes the JVM invocation inside while [ "$status" = "$START_EXIT_STATUS" ] (~307-351) to replace the shell at the launch line (~309). As a result, status=$? (~351) and the loop re-entry never execute, so the script can’t restart based on exit code 121.

Keep the restart-loop path non-exec (so $? is captured), and only use exec in a separate one-shot mode where restarting is not required (or refactor signal-forwarding without replacing the wrapper loop).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@distribution/kernel/carbon-home/bin/wso2server.sh` around lines 299 - 304,
The restart loop fails because JAVA_LAUNCH_PREFIX is set to "exec", causing the
JVM launch line to replace the shell and preventing the while [ "$status" =
"$START_EXIT_STATUS" ] loop from observing $?. Fix by only using exec for
one-shot launches and keep the restart-loop path non-exec: detect whether the
script will enter the START_EXIT_STATUS restart loop (the while [ "$status" =
"$START_EXIT_STATUS" ] block) and ensure JAVA_LAUNCH_PREFIX is empty for that
case; reserve JAVA_LAUNCH_PREFIX="exec" only for the PID1/one-shot code path (or
refactor to a separate variable/mode for exec vs wrapper-loop) so status=$?
after the JVM exit is always captured.

@JeethJJ JeethJJ closed this Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants