This is an automated email from the ASF dual-hosted git repository.
imbajin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hugegraph.git
The following commit(s) were added to refs/heads/master by this push:
new 16382ccfb fix(pd,store): fg mode exit code propagation in startup
scripts (#3047)
16382ccfb is described below
commit 16382ccfbd4ea871c8560bc622539060d4661a5e
Author: KAI <[email protected]>
AuthorDate: Thu Jun 4 18:55:42 2026 +0530
fix(pd,store): fg mode exit code propagation in startup scripts (#3047)
In foreground mode (-d false), start-hugegraph-pd.sh had no foreground
branch — the script always backgrounded Java with exec ... &, wrote $!
to the pid file, and exited 0, losing Java's exit code entirely.
Fix: add DAEMON="true" default and -d flag to getopts. In the daemon
branch, keep the existing exec ... & pattern. In the foreground branch,
write $$ to the pid file before exec (exec replaces the shell with Java,
so $$ == Java's PID after exec), then exec java without & so the process
blocks and Java's exit code propagates out directly.
No trap needed in the foreground branch — exec replaces the shell
process with Java, so signals from Docker/systemd go directly to Java
without a wrapper to forward through.
Add test-start-hugegraph-pd.sh with 4 tests (daemon regression,
foreground blocking, exit code propagation on SIGKILL, SIGTERM
forwarding via exec) — 12 assertions, all pass after the fix.
Baseline on unmodified code: 3 passed, 9 failed.
After fix: 12 passed, 0 failed.
Wire test into pd-store-ci.yml for the RocksDB backend.
Related to: #3043
---
.github/workflows/pd-store-ci.yml | 12 +
.../src/assembly/static/bin/start-hugegraph-pd.sh | 43 ++-
.../src/assembly/travis/test-start-hugegraph-pd.sh | 352 +++++++++++++++++++++
.../assembly/travis/test-start-hugegraph-store.sh | 351 ++++++++++++++++++++
.../assembly/static/bin/start-hugegraph-store.sh | 48 ++-
5 files changed, 779 insertions(+), 27 deletions(-)
diff --git a/.github/workflows/pd-store-ci.yml
b/.github/workflows/pd-store-ci.yml
index 5e752d609..29ac9c3ac 100644
--- a/.github/workflows/pd-store-ci.yml
+++ b/.github/workflows/pd-store-ci.yml
@@ -107,6 +107,12 @@ jobs:
run: |
mvn clean package -U -Dmaven.javadoc.skip=true
-Dmaven.test.skip=true -ntp --fail-at-end
+ - name: Run start-hugegraph-pd.sh foreground mode tests
+ run: |
+ VERSION=$(mvn help:evaluate -Dexpression=project.version -q
-DforceStdout)
+ PD_DIR=hugegraph-pd/apache-hugegraph-pd-$VERSION/
+ $TRAVIS_DIR/test-start-hugegraph-pd.sh $PD_DIR
+
- name: Prepare env and service
run: |
$TRAVIS_DIR/start-pd.sh
@@ -163,6 +169,12 @@ jobs:
run: |
mvn clean package -U -Dmaven.javadoc.skip=true
-Dmaven.test.skip=true -ntp --fail-at-end
+ - name: Run start-hugegraph-store.sh foreground mode tests
+ run: |
+ VERSION=$(mvn help:evaluate -Dexpression=project.version -q
-DforceStdout)
+ STORE_DIR=hugegraph-store/apache-hugegraph-store-$VERSION/
+ $TRAVIS_DIR/test-start-hugegraph-store.sh $STORE_DIR
+
- name: Prepare env and service
run: |
$TRAVIS_DIR/start-pd.sh
diff --git
a/hugegraph-pd/hg-pd-dist/src/assembly/static/bin/start-hugegraph-pd.sh
b/hugegraph-pd/hg-pd-dist/src/assembly/static/bin/start-hugegraph-pd.sh
index 3bbdb1cf0..1329df227 100755
--- a/hugegraph-pd/hg-pd-dist/src/assembly/static/bin/start-hugegraph-pd.sh
+++ b/hugegraph-pd/hg-pd-dist/src/assembly/static/bin/start-hugegraph-pd.sh
@@ -26,14 +26,18 @@ fi
if [ -z "$OPEN_TELEMETRY" ];then
OPEN_TELEMETRY="false"
fi
+if [ -z "$DAEMON" ]; then
+ DAEMON="true"
+fi
-while getopts "g:j:y:" arg; do
+while getopts "d:g:j:y:" arg; do
case ${arg} in
g) GC_OPTION="$OPTARG" ;;
j) USER_OPTION="$OPTARG" ;;
# Telemetry is used to collect metrics, traces and logs
+ d) DAEMON="$OPTARG" ;;
y) OPEN_TELEMETRY="$OPTARG" ;;
- ?) echo "USAGE: $0 [-g g1] [-j xxx] [-y true|false]" && exit 1 ;;
+ ?) echo "USAGE: $0 [-d true|false] [-g g1] [-j xxx] [-y true|false]"
&& exit 1 ;;
esac
done
@@ -163,20 +167,33 @@ if [ $(ps -ef|grep -v grep| grep java|grep -cE ${CONF})
-ne 0 ]; then
echo "HugeGraphPDServer is already running..."
exit 0
fi
-echo "Starting HugeGraphPDServer..."
JVM_OPTIONS="-Dlog4j.configurationFile=${CONF}/log4j2.xml
-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager"
# Turn on security check
-if [[ "${STDOUT_MODE:-false}" == "true" ]]; then
- exec ${JAVA} -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} -jar \
- -Dspring.config.location=${CONF}/application.yml
${LIB}/hg-pd-service-*.jar &
+if [[ $DAEMON == "true" ]]; then
+ echo "Starting HugeGraphPDServer in daemon mode..."
+ if [[ "${STDOUT_MODE:-false}" == "true" ]]; then
+ exec ${JAVA} -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} -jar \
+ -Dspring.config.location=${CONF}/application.yml
${LIB}/hg-pd-service-*.jar &
+ else
+ exec ${JAVA} -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} -jar \
+ -Dspring.config.location=${CONF}/application.yml
${LIB}/hg-pd-service-*.jar >> ${OUTPUT} 2>&1 &
+ fi
+ PID="$!"
+ # Write pid to file
+ echo "$PID" > "$PID_FILE"
+ echo "[+pid] $PID"
else
- exec ${JAVA} -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} -jar \
- -Dspring.config.location=${CONF}/application.yml
${LIB}/hg-pd-service-*.jar >> ${OUTPUT} 2>&1 &
+ echo "Starting HugeGraphPDServer in foreground mode..."
+ # Write $$ before exec — exec replaces this shell with Java, so $$ becomes
Java's PID
+ echo "$$" > "$PID_FILE"
+ echo "[+pid] $$"
+ if [[ "${STDOUT_MODE:-false}" == "true" ]]; then
+ exec ${JAVA} -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} -jar \
+ -Dspring.config.location=${CONF}/application.yml
${LIB}/hg-pd-service-*.jar
+ else
+ exec ${JAVA} -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} -jar \
+ -Dspring.config.location=${CONF}/application.yml
${LIB}/hg-pd-service-*.jar >> ${OUTPUT} 2>&1
+ fi
fi
-
-PID="$!"
-# Write pid to file
-echo "$PID" > "$PID_FILE"
-echo "[+pid] $PID"
diff --git
a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-pd.sh
b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-pd.sh
new file mode 100755
index 000000000..db3428b13
--- /dev/null
+++
b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-pd.sh
@@ -0,0 +1,352 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# test-start-hugegraph-pd.sh — Tests for start-hugegraph-pd.sh foreground mode
fix
+#
+# Baseline (unmodified code): Tests 1 PASS — Tests 2, 3, 4 FAIL
+# After chunk 2 fix: All 4 tests PASS
+#
+# Usage: ./test-start-hugegraph-pd.sh [path-to-pd-dist-root]
+# path-to-pd-dist-root: path to extracted PD dist e.g.
+# hugegraph-pd/apache-hugegraph-pd-1.7.0/
+# defaults to current directory if not provided
+
+set -uo pipefail
+
+PD_ROOT="${1:-$(pwd)}"
+BIN="$PD_ROOT/bin"
+START_SCRIPT="$BIN/start-hugegraph-pd.sh"
+PID_FILE="$BIN/pid"
+PD_URL="http://localhost:8620"
+STARTUP_WAIT=60 # seconds to wait for PD HTTP to respond
+SETTLE_WAIT=5 # seconds after kill before checking exit
+WAIT_TIMEOUT=30 # seconds for wait_script_exit timeout
+
+PASS=0
+FAIL=0
+ERRORS=()
+
+GREEN='\033[0;32m'
+RED='\033[0;31m'
+YELLOW='\033[1;33m'
+NC='\033[0m'
+
+pass() {
+ echo -e "${GREEN} PASS${NC} $1"
+ PASS=$((PASS + 1))
+}
+
+fail() {
+ echo -e "${RED} FAIL${NC} $1"
+ ERRORS+=("$1")
+ FAIL=$((FAIL + 1))
+}
+
+info() {
+ echo -e "${YELLOW} ....${NC} $1"
+}
+
+section() {
+ echo ""
+ echo "── $1 ──"
+}
+
+cleanup() {
+ info "Cleaning up..."
+ if [[ -s "$PID_FILE" ]]; then
+ kill "$(cat "$PID_FILE")" 2>/dev/null || true
+ fi
+ rm -f "$PID_FILE"
+ rm -rf "$PD_ROOT/logs/"
+ # kill anything still holding the PD port
+ lsof -ti :8620 | xargs kill -9 2>/dev/null || true
+ lsof -ti :8686 | xargs kill -9 2>/dev/null || true
+ sleep 3
+}
+
+# Wait until PD health endpoint responds or timeout
+wait_for_pd() {
+ local elapsed=0
+ while (( elapsed < STARTUP_WAIT )); do
+ local status
+ status=$(curl -s -o /dev/null -w "%{http_code}" \
+ "$PD_URL/v1/health" 2>/dev/null || echo "000")
+ if [[ "$status" == "200" || "$status" == "401" ]]; then
+ return 0
+ fi
+ sleep 2
+ elapsed=$((elapsed + 2))
+ done
+ return 1
+}
+
+# Wait until bin/pid is non-empty or timeout
+wait_for_pid_file() {
+ local elapsed=0
+ while [[ ! -s "$PID_FILE" ]] && (( elapsed < 30 )); do
+ sleep 1
+ elapsed=$((elapsed + 1))
+ done
+}
+
+# Wait for a background script PID to exit with timeout
+# Must be called from the same shell that spawned script_pid
+wait_script_exit() {
+ local script_pid="$1"
+ ( sleep "$WAIT_TIMEOUT" && kill "$script_pid" 2>/dev/null ) &
+ local killer_pid=$!
+ wait "$script_pid" 2>/dev/null
+ local exit_code=$?
+ kill "$killer_pid" 2>/dev/null || true
+ wait "$killer_pid" 2>/dev/null || true
+ return $exit_code
+}
+
+# ── preflight
─────────────────────────────────────────────────────────────────
+
+echo ""
+echo "start-hugegraph-pd.sh chunk 2 test suite"
+echo "root: $PD_ROOT"
+echo ""
+
+if [[ ! -f "$START_SCRIPT" ]]; then
+ echo -e "${RED}ERROR:${NC} $START_SCRIPT not found."
+ echo " Pass the PD dist root as \$1"
+ exit 1
+fi
+
+for tool in lsof curl java; do
+ if ! command -v "$tool" >/dev/null 2>&1; then
+ echo "SKIP: required tool '$tool' not found — skipping test suite"
+ exit 0
+ fi
+done
+
+cleanup
+
+# ── test 1: daemon mode regression ───────────────────────────────────────────
+
+section "Test 1 — daemon mode regression"
+
+info "Starting in daemon mode (no -d flag, default behavior)..."
+"$START_SCRIPT" >/dev/null 2>&1 &
+SCRIPT_PID=$!
+
+# daemon mode: script backgrounds Java and exits quickly
+sleep 5
+if ! ps -p "$SCRIPT_PID" >/dev/null 2>&1; then
+ pass "script exited after backgrounding Java (daemon mode)"
+else
+ info "script still running after 5s (may still be starting)"
+ wait_script_exit "$SCRIPT_PID" || true
+fi
+
+wait_for_pid_file
+DAEMON_PID=$(cat "$PID_FILE" 2>/dev/null || echo "")
+
+if [[ -n "$DAEMON_PID" ]]; then
+ pass "bin/pid written with PID $DAEMON_PID"
+else
+ fail "bin/pid is empty or missing after daemon start"
+fi
+
+if [[ -n "$DAEMON_PID" ]] && ps -p "$DAEMON_PID" >/dev/null 2>&1; then
+ pass "Java process is running (pid $DAEMON_PID)"
+else
+ fail "Java process not found for pid '$DAEMON_PID'"
+fi
+
+info "Waiting up to ${STARTUP_WAIT}s for PD health endpoint..."
+if wait_for_pd; then
+ pass "PD health endpoint responding at $PD_URL/v1/health"
+else
+ fail "PD health endpoint not responding after ${STARTUP_WAIT}s"
+fi
+
+cleanup
+
+# ── test 2: foreground mode blocks ───────────────────────────────────────────
+
+section "Test 2 — foreground mode blocks until Java exits"
+
+info "Starting in foreground mode (-d false)..."
+"$START_SCRIPT" -d false >/dev/null 2>&1 &
+SCRIPT_PID=$!
+
+info "Waiting up to ${STARTUP_WAIT}s for PD to come up..."
+if wait_for_pd; then
+ info "PD is up"
+else
+ info "PD did not respond — continuing to check blocking behavior"
+fi
+
+# foreground mode: script should still be running (blocked on Java)
+if ps -p "$SCRIPT_PID" >/dev/null 2>&1; then
+ pass "script is still running (blocking correctly)"
+else
+ fail "script exited early — foreground mode is not blocking"
+fi
+
+wait_for_pid_file
+FG_PID=$(cat "$PID_FILE" 2>/dev/null || echo "")
+
+if [[ -n "$FG_PID" ]]; then
+ info "pid file written with PID $FG_PID"
+else
+ info "pid file not written (expected before chunk 2 fix)"
+fi
+
+# cleanup: kill Java or the script
+if [[ -n "$FG_PID" ]]; then
+ kill "$FG_PID" 2>/dev/null || true
+else
+ kill "$SCRIPT_PID" 2>/dev/null || true
+fi
+sleep "$SETTLE_WAIT"
+
+if ! ps -p "$SCRIPT_PID" >/dev/null 2>&1; then
+ info "script exited after Java was killed"
+else
+ kill "$SCRIPT_PID" 2>/dev/null || true
+fi
+
+cleanup
+
+# ── test 3: exit code propagates ─────────────────────────────────────────────
+
+section "Test 3 — exit code propagates from Java"
+
+info "Starting in foreground mode (-d false)..."
+"$START_SCRIPT" -d false >/dev/null 2>&1 &
+SCRIPT_PID=$!
+
+info "Waiting up to ${STARTUP_WAIT}s for PD..."
+if wait_for_pd; then
+ info "PD is up"
+else
+ info "PD did not respond — continuing"
+fi
+
+wait_for_pid_file
+FG_PID=$(cat "$PID_FILE" 2>/dev/null || echo "")
+
+if [[ -n "$FG_PID" ]]; then
+ pass "bin/pid written with PID $FG_PID"
+else
+ fail "bin/pid is empty or missing in foreground mode"
+fi
+
+if [[ -n "$FG_PID" ]] && ps -p "$FG_PID" >/dev/null 2>&1; then
+ pass "Java process running (pid $FG_PID)"
+else
+ fail "Java process not found for pid '$FG_PID'"
+fi
+
+if [[ -z "$FG_PID" ]]; then
+ fail "could not get PID — skipping exit code check"
+ kill "$SCRIPT_PID" 2>/dev/null || true
+else
+ info "Hard-killing Java with SIGKILL (pid $FG_PID)..."
+ kill -9 "$FG_PID" 2>/dev/null || true
+
+ wait_script_exit "$SCRIPT_PID"
+ ACTUAL_EXIT=$?
+
+ if [[ $ACTUAL_EXIT -ne 0 ]]; then
+ pass "script exited non-zero ($ACTUAL_EXIT) after SIGKILL — Docker
restart will fire"
+ else
+ fail "script exited 0 after SIGKILL — Docker would NOT restart (exit
code lost)"
+ fi
+
+ if [[ $ACTUAL_EXIT -eq 137 ]]; then
+ pass "exit code is 137 (128+9 for SIGKILL) — correctly propagated"
+ elif [[ $ACTUAL_EXIT -ne 0 ]]; then
+ info "exit code was $ACTUAL_EXIT (not 137 — may be shell-wrapped,
acceptable if non-zero)"
+ fi
+fi
+
+cleanup
+
+# ── test 4: SIGTERM forwarded to Java ────────────────────────────────────────
+
+section "Test 4 — SIGTERM forwarded to Java in foreground mode"
+
+info "Starting in foreground mode (-d false)..."
+"$START_SCRIPT" -d false >/dev/null 2>&1 &
+SCRIPT_PID=$!
+
+info "Waiting up to ${STARTUP_WAIT}s for PD..."
+if wait_for_pd; then
+ info "PD is up"
+else
+ info "PD did not respond — continuing"
+fi
+
+wait_for_pid_file
+FG_PID=$(cat "$PID_FILE" 2>/dev/null || echo "")
+
+if [[ -z "$FG_PID" ]]; then
+ fail "could not get Java PID — skipping SIGTERM check"
+ kill "$SCRIPT_PID" 2>/dev/null || true
+else
+ info "Sending SIGTERM to process (pid $SCRIPT_PID)..."
+ # In foreground mode with exec, SCRIPT_PID == FG_PID after exec replaces
the shell
+ # Signal goes directly to Java — no wrapper to forward through
+ kill -TERM "$SCRIPT_PID" 2>/dev/null || true
+
+ wait_script_exit "$SCRIPT_PID"
+ ACTUAL_EXIT=$?
+
+ if ! ps -p "$FG_PID" >/dev/null 2>&1; then
+ pass "Java process terminated after SIGTERM"
+ else
+ fail "Java process still running after SIGTERM — signal not delivered"
+ kill "$FG_PID" 2>/dev/null || true
+ fi
+
+ if [[ $ACTUAL_EXIT -ne 0 ]]; then
+ pass "process exited non-zero ($ACTUAL_EXIT) after SIGTERM"
+ else
+ fail "process exited 0 after SIGTERM — unexpected"
+ fi
+
+ if [[ $ACTUAL_EXIT -eq 143 ]]; then
+ pass "exit code is 143 (128+15 for SIGTERM) — correctly propagated"
+ elif [[ $ACTUAL_EXIT -ne 0 ]]; then
+ info "exit code was $ACTUAL_EXIT (not 143 — acceptable if non-zero)"
+ fi
+fi
+
+cleanup
+
+# ── summary
───────────────────────────────────────────────────────────────────
+
+echo ""
+echo "════════════════════════════════"
+echo -e " Results: ${GREEN}$PASS passed${NC} ${RED}$FAIL failed${NC}"
+echo "════════════════════════════════"
+
+if [[ ${#ERRORS[@]} -gt 0 ]]; then
+ echo ""
+ echo "Failed tests:"
+ for err in "${ERRORS[@]}"; do
+ echo -e " ${RED}✗${NC} $err"
+ done
+fi
+
+echo ""
+[[ $FAIL -eq 0 ]] && exit 0 || exit 1
diff --git
a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-store.sh
b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-store.sh
new file mode 100755
index 000000000..7da932376
--- /dev/null
+++
b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-store.sh
@@ -0,0 +1,351 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# test-start-hugegraph-store.sh — Tests for start-hugegraph-store.sh
foreground mode fix
+#
+# Baseline (unmodified code): Test 1 PASS — Tests 2, 3, 4 FAIL
+# After chunk 3 fix: All 4 tests PASS
+#
+# Usage: ./test-start-hugegraph-store.sh [path-to-store-dist-root]
+
+set -uo pipefail
+
+STORE_ROOT="${1:-$(pwd)}"
+BIN="$STORE_ROOT/bin"
+START_SCRIPT="$BIN/start-hugegraph-store.sh"
+STOP_SCRIPT="$BIN/stop-hugegraph-store.sh"
+PID_FILE="$BIN/pid"
+STORE_URL="http://localhost:8520"
+PD_URL="http://localhost:8620"
+STARTUP_WAIT=60 # seconds to wait for Store HTTP to respond
+SETTLE_WAIT=5 # seconds after kill before checking exit
+WAIT_TIMEOUT=30 # seconds for wait_script_exit timeout
+
+PASS=0
+FAIL=0
+ERRORS=()
+
+GREEN='\033[0;32m'
+RED='\033[0;31m'
+YELLOW='\033[1;33m'
+NC='\033[0m'
+
+pass() { echo -e "${GREEN} PASS${NC} $1"; PASS=$((PASS + 1)); }
+fail() { echo -e "${RED} FAIL${NC} $1"; ERRORS+=("$1"); FAIL=$((FAIL + 1)); }
+info() { echo -e "${YELLOW} ....${NC} $1"; }
+section() { echo ""; echo "── $1 ──"; }
+
+cleanup() {
+ info "Cleaning up..."
+ if [[ -s "$PID_FILE" ]]; then
+ kill "$(cat "$PID_FILE")" 2>/dev/null || true
+ fi
+ rm -f "$PID_FILE"
+ rm -rf "$STORE_ROOT/logs/"
+ # kill anything holding Store ports (8520 REST, 8510 raft, 8500 gRPC)
+ lsof -ti :8520 | xargs kill -9 2>/dev/null || true
+ lsof -ti :8510 | xargs kill -9 2>/dev/null || true
+ lsof -ti :8500 | xargs kill -9 2>/dev/null || true
+ sleep 3
+}
+
+wait_for_store() {
+ local elapsed=0
+ while (( elapsed < STARTUP_WAIT )); do
+ local status
+ status=$(curl -s -o /dev/null -w "%{http_code}" \
+ "$STORE_URL/v1/health" 2>/dev/null || echo "000")
+ if [[ "$status" == "200" || "$status" == "401" ]]; then
+ return 0
+ fi
+ sleep 2
+ elapsed=$((elapsed + 2))
+ done
+ return 1
+}
+
+wait_for_pid_file() {
+ local elapsed=0
+ while [[ ! -s "$PID_FILE" ]] && (( elapsed < 30 )); do
+ sleep 1
+ elapsed=$((elapsed + 1))
+ done
+}
+
+wait_script_exit() {
+ local script_pid="$1"
+ ( sleep "$WAIT_TIMEOUT" && kill "$script_pid" 2>/dev/null ) &
+ local killer_pid=$!
+ wait "$script_pid" 2>/dev/null
+ local exit_code=$?
+ kill "$killer_pid" 2>/dev/null || true
+ wait "$killer_pid" 2>/dev/null || true
+ return $exit_code
+}
+
+# ── preflight
─────────────────────────────────────────────────────────────────
+
+echo ""
+echo "start-hugegraph-store.sh chunk 3 test suite"
+echo "root: $STORE_ROOT"
+echo ""
+
+if [[ ! -f "$START_SCRIPT" ]]; then
+ echo -e "${RED}ERROR:${NC} $START_SCRIPT not found."
+ echo " Pass the Store dist root as \$1"
+ exit 1
+fi
+
+for tool in lsof curl java; do
+ if ! command -v "$tool" >/dev/null 2>&1; then
+ echo "SKIP: required tool '$tool' not found — skipping test suite"
+ exit 0
+ fi
+done
+
+# Store ulimit check (safely handling "unlimited" string)
+LIMIT_N=$(ulimit -n)
+if [[ "$LIMIT_N" != "unlimited" ]]; then
+ if (( LIMIT_N < 1024 )); then
+ echo "SKIP: ulimit -n is $LIMIT_N — store requires >= 1024. Run:
ulimit -n 1024"
+ exit 0
+ fi
+fi
+
+# Check if PD is running, warn if not (Store depends on it for HTTP health
check)
+PD_RUNNING=false
+PD_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$PD_URL/v1/health"
2>/dev/null || echo "000")
+if [[ "$PD_STATUS" == "200" || "$PD_STATUS" == "401" ]]; then
+ PD_RUNNING=true
+ info "PD is running — Store should become healthy"
+else
+ info "PD is NOT running at $PD_URL — Store will start but health checks
may timeout (Acceptable for this test)"
+fi
+
+cleanup
+
+# ── test 1: daemon mode regression ───────────────────────────────────────────
+
+section "Test 1 — daemon mode regression"
+
+info "Starting in daemon mode (no -d flag, default behavior)..."
+"$START_SCRIPT" >/dev/null 2>&1 &
+SCRIPT_PID=$!
+
+sleep 5
+if ! ps -p "$SCRIPT_PID" >/dev/null 2>&1; then
+ pass "script exited after backgrounding Java (daemon mode)"
+else
+ info "script still running after 5s (may still be starting)"
+ wait_script_exit "$SCRIPT_PID" || true
+fi
+
+wait_for_pid_file
+DAEMON_PID=$(cat "$PID_FILE" 2>/dev/null || echo "")
+
+if [[ -n "$DAEMON_PID" ]]; then
+ pass "bin/pid written with PID $DAEMON_PID"
+else
+ fail "bin/pid is empty or missing after daemon start"
+fi
+
+if [[ -n "$DAEMON_PID" ]] && ps -p "$DAEMON_PID" >/dev/null 2>&1; then
+ pass "Java process is running (pid $DAEMON_PID)"
+else
+ fail "Java process not found for pid '$DAEMON_PID'"
+fi
+
+info "Waiting up to ${STARTUP_WAIT}s for Store health endpoint..."
+if wait_for_store; then
+ pass "Store health endpoint responding at $STORE_URL/v1/health"
+else
+ if [[ "$PD_RUNNING" == "true" ]]; then
+ fail "Store health endpoint not responding even though PD is up"
+ else
+ info "Store health endpoint not responding (Expected since PD is not
running)"
+ fi
+fi
+
+cleanup
+
+# ── test 2: foreground mode blocks ───────────────────────────────────────────
+
+section "Test 2 — foreground mode blocks until Java exits"
+
+info "Starting in foreground mode (-d false)..."
+"$START_SCRIPT" -d false >/dev/null 2>&1 &
+SCRIPT_PID=$!
+
+info "Waiting up to ${STARTUP_WAIT}s for Store to come up..."
+if wait_for_store; then
+ info "Store is up"
+else
+ info "Store did not respond — continuing to check blocking behavior"
+fi
+
+if ps -p "$SCRIPT_PID" >/dev/null 2>&1; then
+ pass "script is still running (blocking correctly)"
+else
+ fail "script exited early — foreground mode is not blocking"
+fi
+
+wait_for_pid_file
+FG_PID=$(cat "$PID_FILE" 2>/dev/null || echo "")
+
+if [[ -n "$FG_PID" ]]; then
+ info "pid file written with PID $FG_PID"
+else
+ info "pid file not written (expected before chunk 3 fix)"
+fi
+
+if [[ -n "$FG_PID" ]]; then
+ kill "$FG_PID" 2>/dev/null || true
+else
+ kill "$SCRIPT_PID" 2>/dev/null || true
+fi
+sleep "$SETTLE_WAIT"
+
+if ! ps -p "$SCRIPT_PID" >/dev/null 2>&1; then
+ info "script exited after Java was killed"
+else
+ kill "$SCRIPT_PID" 2>/dev/null || true
+fi
+
+cleanup
+
+# ── test 3: exit code propagates ─────────────────────────────────────────────
+
+section "Test 3 — exit code propagates from Java"
+
+info "Starting in foreground mode (-d false)..."
+"$START_SCRIPT" -d false >/dev/null 2>&1 &
+SCRIPT_PID=$!
+
+info "Waiting up to ${STARTUP_WAIT}s for Store..."
+if wait_for_store; then
+ info "Store is up"
+else
+ info "Store did not respond — continuing"
+fi
+
+wait_for_pid_file
+FG_PID=$(cat "$PID_FILE" 2>/dev/null || echo "")
+
+if [[ -n "$FG_PID" ]]; then
+ pass "bin/pid written with PID $FG_PID"
+else
+ fail "bin/pid is empty or missing in foreground mode"
+fi
+
+if [[ -n "$FG_PID" ]] && ps -p "$FG_PID" >/dev/null 2>&1; then
+ pass "Java process running (pid $FG_PID)"
+else
+ fail "Java process not found for pid '$FG_PID'"
+fi
+
+if [[ -z "$FG_PID" ]]; then
+ fail "could not get PID — skipping exit code check"
+ kill "$SCRIPT_PID" 2>/dev/null || true
+else
+ info "Hard-killing Java with SIGKILL (pid $FG_PID)..."
+ kill -9 "$FG_PID" 2>/dev/null || true
+
+ wait_script_exit "$SCRIPT_PID"
+ ACTUAL_EXIT=$?
+
+ if [[ $ACTUAL_EXIT -ne 0 ]]; then
+ pass "script exited non-zero ($ACTUAL_EXIT) after SIGKILL"
+ else
+ fail "script exited 0 after SIGKILL — Docker would NOT restart"
+ fi
+
+ if [[ $ACTUAL_EXIT -eq 137 ]]; then
+ pass "exit code is 137 (128+9 for SIGKILL) — correctly propagated"
+ elif [[ $ACTUAL_EXIT -ne 0 ]]; then
+ info "exit code was $ACTUAL_EXIT (not 137 — acceptable if non-zero)"
+ fi
+fi
+
+cleanup
+
+# ── test 4: SIGTERM forwarded to Java ────────────────────────────────────────
+
+section "Test 4 — SIGTERM forwarded to Java in foreground mode"
+
+info "Starting in foreground mode (-d false)..."
+"$START_SCRIPT" -d false >/dev/null 2>&1 &
+SCRIPT_PID=$!
+
+info "Waiting up to ${STARTUP_WAIT}s for Store..."
+if wait_for_store; then
+ info "Store is up"
+else
+ info "Store did not respond — continuing"
+fi
+
+wait_for_pid_file
+FG_PID=$(cat "$PID_FILE" 2>/dev/null || echo "")
+
+if [[ -z "$FG_PID" ]]; then
+ fail "could not get Java PID — skipping SIGTERM check"
+ kill "$SCRIPT_PID" 2>/dev/null || true
+else
+ info "Sending SIGTERM to process (pid $SCRIPT_PID)..."
+ kill -TERM "$SCRIPT_PID" 2>/dev/null || true
+
+ wait_script_exit "$SCRIPT_PID"
+ ACTUAL_EXIT=$?
+
+ if ! ps -p "$FG_PID" >/dev/null 2>&1; then
+ pass "Java process terminated after SIGTERM"
+ else
+ fail "Java process still running after SIGTERM — signal not delivered"
+ kill "$FG_PID" 2>/dev/null || true
+ fi
+
+ if [[ $ACTUAL_EXIT -ne 0 ]]; then
+ pass "process exited non-zero ($ACTUAL_EXIT) after SIGTERM"
+ else
+ fail "process exited 0 after SIGTERM — unexpected"
+ fi
+
+ if [[ $ACTUAL_EXIT -eq 143 ]]; then
+ pass "exit code is 143 (128+15 for SIGTERM) — correctly propagated"
+ elif [[ $ACTUAL_EXIT -ne 0 ]]; then
+ info "exit code was $ACTUAL_EXIT (not 143 — acceptable if non-zero)"
+ fi
+fi
+
+cleanup
+
+# ── summary
───────────────────────────────────────────────────────────────────
+
+echo ""
+echo "════════════════════════════════"
+echo -e " Results: ${GREEN}$PASS passed${NC} ${RED}$FAIL failed${NC}"
+echo "════════════════════════════════"
+
+if [[ ${#ERRORS[@]} -gt 0 ]]; then
+ echo ""
+ echo "Failed tests:"
+ for err in "${ERRORS[@]}"; do
+ echo -e " ${RED}✗${NC} $err"
+ done
+fi
+
+echo ""
+[[ $FAIL -eq 0 ]] && exit 0 || exit 1
diff --git
a/hugegraph-store/hg-store-dist/src/assembly/static/bin/start-hugegraph-store.sh
b/hugegraph-store/hg-store-dist/src/assembly/static/bin/start-hugegraph-store.sh
index 17245e8b4..5a11eeab3 100755
---
a/hugegraph-store/hg-store-dist/src/assembly/static/bin/start-hugegraph-store.sh
+++
b/hugegraph-store/hg-store-dist/src/assembly/static/bin/start-hugegraph-store.sh
@@ -100,14 +100,18 @@ fi
if [ -z "$OPEN_TELEMETRY" ];then
OPEN_TELEMETRY="false"
fi
+if [ -z "$DAEMON" ]; then
+ DAEMON="true"
+fi
-while getopts "g:j:y:" arg; do
+while getopts "d:g:j:y:" arg; do
case ${arg} in
g) GC_OPTION="$OPTARG" ;;
j) USER_OPTION="$OPTARG" ;;
# Telemetry is used to collect metrics, traces and logs
y) OPEN_TELEMETRY="$OPTARG" ;;
- ?) echo "USAGE: $0 [-g g1] [-j xxx] [-y true|false]" && exit 1 ;;
+ d) DAEMON="$OPTARG" ;;
+ ?) echo "USAGE: $0 [-d true|false] [-g g1] [-j xxx] [-y true|false]"
&& exit 1 ;;
esac
done
@@ -222,17 +226,33 @@ fi
echo "Starting HG-StoreServer..."
# Turn on security check
-if [[ "${STDOUT_MODE:-false}" == "true" ]]; then
- exec ${JAVA} -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS} -jar \
- -Dspring.config.location=${CONF}/application.yml \
- ${LIB}/hg-store-node-*.jar &
+if [[ $DAEMON == "true" ]]; then
+ echo "Starting HugeGraphStoreServer in daemon mode..."
+ if [[ "${STDOUT_MODE:-false}" == "true" ]]; then
+ exec ${JAVA} -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS}
-jar \
+ -Dspring.config.location=${CONF}/application.yml \
+ ${LIB}/hg-store-node-*.jar &
+ else
+ exec ${JAVA} -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS}
-jar \
+ -Dspring.config.location=${CONF}/application.yml \
+ ${LIB}/hg-store-node-*.jar >> ${OUTPUT} 2>&1 &
+ fi
+ PID="$!"
+ # Write pid to file
+ echo "$PID" > "$PID_FILE"
+ echo "[+pid] $PID"
else
- exec ${JAVA} -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS} -jar \
- -Dspring.config.location=${CONF}/application.yml \
- ${LIB}/hg-store-node-*.jar >> ${OUTPUT} 2>&1 &
+ echo "Starting HugeGraphStoreServer in foreground mode..."
+ # Write $$ before exec — exec replaces this shell with Java, so $$ becomes
Java's PID
+ echo "$$" > "$PID_FILE"
+ echo "[+pid] $$"
+ if [[ "${STDOUT_MODE:-false}" == "true" ]]; then
+ exec ${JAVA} -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS}
-jar \
+ -Dspring.config.location=${CONF}/application.yml \
+ ${LIB}/hg-store-node-*.jar
+ else
+ exec ${JAVA} -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS}
-jar \
+ -Dspring.config.location=${CONF}/application.yml \
+ ${LIB}/hg-store-node-*.jar >> ${OUTPUT} 2>&1
+ fi
fi
-
-PID="$!"
-# Write pid to file
-echo "$PID" > "$PID_FILE"
-echo "[+pid] $PID"