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 d6e621622 fix(server): fix foreground mode exit code propagation in 
startup scripts (#3044)
d6e621622 is described below

commit d6e62162224234b0ae7ec480bca7159e2a13d1d1
Author: KAI <[email protected]>
AuthorDate: Wed Jun 3 19:12:36 2026 +0530

    fix(server): fix foreground mode exit code propagation in startup scripts 
(#3044)
    
    The previous implementation captured $! after the daemon/foreground
    if/else block. The script blocked at hugegraph-server.sh until Java
    exited, then $! was empty, the pid file got an empty string, and the
    script exited 0, losing Java's exit code entirely.
---
 .github/workflows/server-ci.yml                    |   8 +
 .../src/assembly/static/bin/start-hugegraph.sh     |  52 +--
 .../assembly/static/conf/rest-server.properties    |   2 +-
 .../src/assembly/travis/test-start-hugegraph.sh    | 437 +++++++++++++++++++++
 4 files changed, 474 insertions(+), 25 deletions(-)

diff --git a/.github/workflows/server-ci.yml b/.github/workflows/server-ci.yml
index 266e70feb..20619797c 100644
--- a/.github/workflows/server-ci.yml
+++ b/.github/workflows/server-ci.yml
@@ -70,6 +70,14 @@ jobs:
         run: |
           mvn clean compile -U -Dmaven.javadoc.skip=true -ntp
 
+      - name: Run start-hugegraph.sh foreground mode tests
+        if: ${{ env.BACKEND == 'rocksdb' }}
+        run: |
+          mvn package -Dmaven.test.skip=true -pl 
hugegraph-server/hugegraph-dist -am -ntp
+          VERSION=$(mvn help:evaluate -Dexpression=project.version -q 
-DforceStdout)
+          SERVER_DIR=hugegraph-server/apache-hugegraph-server-$VERSION/
+          $TRAVIS_DIR/test-start-hugegraph.sh $SERVER_DIR
+
       - name: Run unit test
         run: |
           $TRAVIS_DIR/run-unit-test.sh $BACKEND
diff --git 
a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/start-hugegraph.sh 
b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/start-hugegraph.sh
index 297ed47ca..2c9923832 100644
--- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/start-hugegraph.sh
+++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/start-hugegraph.sh
@@ -103,33 +103,37 @@ if [[ $DAEMON == "true" ]]; then
     echo "Starting HugeGraphServer in daemon mode..."
     "${BIN}"/hugegraph-server.sh "${CONF}/${GREMLIN_SERVER_CONF}" 
"${CONF}"/rest-server.properties \
     "${OPEN_SECURITY_CHECK}" "${USER_OPTION}" "${GC_OPTION}" 
"${OPEN_TELEMETRY}" &
-else
-    echo "Starting HugeGraphServer in foreground mode..."
-    "${BIN}"/hugegraph-server.sh "${CONF}/${GREMLIN_SERVER_CONF}" 
"${CONF}"/rest-server.properties \
-    "${OPEN_SECURITY_CHECK}" "${USER_OPTION}" "${GC_OPTION}" 
"${OPEN_TELEMETRY}"
-fi
 
-PID="$!"
-# Write pid to file
-echo "$PID" > "$PID_FILE"
+    PID="$!"
+    # Write pid to file
+    echo "$PID" > "$PID_FILE"
 
-trap 'kill $PID; exit' SIGHUP SIGINT SIGQUIT SIGTERM
+    trap 'kill $PID; exit' SIGHUP SIGINT SIGQUIT SIGTERM
 
-wait_for_startup ${PID} 'HugeGraphServer' "$REST_SERVER_URL/graphs" 
"${SERVER_STARTUP_TIMEOUT_S}" || {
-    if [[ "${STDOUT_MODE:-false}" == "true" ]]; then
-        echo "See 'docker logs' for HugeGraphServer log output." >&2
-    else
-        echo "See $LOGS/hugegraph-server.log for HugeGraphServer log output." 
>&2
-    fi
-    if [[ $DAEMON == "true" ]]; then
+    wait_for_startup ${PID} 'HugeGraphServer' "$REST_SERVER_URL/graphs" 
"${SERVER_STARTUP_TIMEOUT_S}" || {
+        if [[ "${STDOUT_MODE:-false}" == "true" ]]; then
+            echo "See 'docker logs' for HugeGraphServer log output." >&2
+        else
+            echo "See $LOGS/hugegraph-server.log for HugeGraphServer log 
output." >&2
+        fi
         exit 1
+    }
+    disown
+
+    if [ "$OPEN_MONITOR" == "true" ]; then
+        if ! "$BIN"/start-monitor.sh; then
+            echo "Failed to open monitor, please start it manually"
+        fi
+        echo "An HugeGraphServer monitor task has been append to crontab"
     fi
-}
-disown
-
-if [ "$OPEN_MONITOR" == "true" ]; then
-    if ! "$BIN"/start-monitor.sh; then
-        echo "Failed to open monitor, please start it manually"
-    fi
-    echo "An HugeGraphServer monitor task has been append to crontab"
+else
+    echo "Starting HugeGraphServer in foreground mode..."
+    "${BIN}"/hugegraph-server.sh "${CONF}/${GREMLIN_SERVER_CONF}" 
"${CONF}"/rest-server.properties \
+    "${OPEN_SECURITY_CHECK}" "${USER_OPTION}" "${GC_OPTION}" 
"${OPEN_TELEMETRY}" &
+    PID="$!"
+    # Write pid to file
+    echo "$PID" > "$PID_FILE"
+    trap 'kill $PID; wait $PID; exit $?' SIGHUP SIGINT SIGQUIT SIGTERM
+    wait $PID
+    exit $?
 fi
diff --git 
a/hugegraph-server/hugegraph-dist/src/assembly/static/conf/rest-server.properties
 
b/hugegraph-server/hugegraph-dist/src/assembly/static/conf/rest-server.properties
index ad3e2700f..bbb956aaf 100644
--- 
a/hugegraph-server/hugegraph-dist/src/assembly/static/conf/rest-server.properties
+++ 
b/hugegraph-server/hugegraph-dist/src/assembly/static/conf/rest-server.properties
@@ -1,6 +1,6 @@
 # bind url
 # could use '0.0.0.0' or specified (real)IP to expose external network access
-restserver.url=127.0.0.1:8080
+restserver.url=http://127.0.0.1:8080
 #restserver.enable_graphspaces_filter=false
 # gremlin server url, need to be consistent with host and port in 
gremlin-server.yaml
 #gremlinserver.url=127.0.0.1:8182
diff --git 
a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph.sh 
b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph.sh
new file mode 100755
index 000000000..d2dd492fc
--- /dev/null
+++ 
b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph.sh
@@ -0,0 +1,437 @@
+#!/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.sh — Tests for start-hugegraph.sh foreground mode fix
+#
+# Baseline (unmodified code):  Tests 1, 2, 4 PASS — Tests 3, 5 FAIL
+# After chunk 1 fix:           All 5 tests PASS
+#
+# Usage: ./test-start-hugegraph.sh [path-to-hugegraph-root]
+#   path-to-hugegraph-root: path to the extracted HugeGraph server dist 
directory
+#                           defaults to current directory if not provided
+#
+# Note: Tests require crontab access. On macOS Catalina+ this may
+#       require Full Disk Access permission in System Preferences.
+# Note: Assumes rocksdb backend. init-store.sh is run automatically if needed.
+
+set -uo pipefail
+
+HUGEGRAPH_ROOT="${1:-$(pwd)}"
+BIN="$HUGEGRAPH_ROOT/bin"
+START_SCRIPT="$BIN/start-hugegraph.sh"
+STOP_SCRIPT="$BIN/stop-hugegraph.sh"
+PID_FILE="$BIN/pid"
+SERVER_URL="http://127.0.0.1:8080";
+STARTUP_WAIT=60   # seconds to wait for server HTTP to respond
+SETTLE_WAIT=5     # seconds after kill before checking process exit
+WAIT_TIMEOUT=30   # seconds for timeout on wait calls
+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 ──"
+}
+
+# Kill server, clear pid file, kill ports, clear crontab monitor entry
+cleanup() {
+    info "Cleaning up..."
+    "$STOP_SCRIPT" >/dev/null 2>&1 || true
+    if [[ -s "$PID_FILE" ]]; then
+        kill "$(cat "$PID_FILE")" 2>/dev/null || true
+    fi
+    rm -f "$PID_FILE"
+    # kill anything still holding server ports so check_port doesn't fail next 
test
+    lsof -ti :8080 | xargs kill -9 2>/dev/null || true
+    lsof -ti :8182 | xargs kill -9 2>/dev/null || true
+    lsof -ti :8088 | xargs kill -9 2>/dev/null || true
+    sleep 3
+    crontab -l 2>/dev/null | grep -v monitor-hugegraph | crontab - 2>/dev/null 
|| true
+}
+
+# Wait until server HTTP endpoint responds (200 or 401) or timeout
+wait_for_server() {
+    local elapsed=0
+    while (( elapsed < STARTUP_WAIT )); do
+        local status
+        status=$(curl -s -o /dev/null -w "%{http_code}" \
+            "$SERVER_URL/versions" 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
+# Usage: wait_script_exit <script_pid>
+# Returns the real exit code of the process, or kills it after WAIT_TIMEOUT 
seconds
+# NOTE: must be called from the same shell that spawned script_pid so that
+#       bash's built-in wait can track it as a child process
+wait_script_exit() {
+    local script_pid="$1"
+    # background killer fires after timeout if process hasn't exited yet
+    ( sleep "$WAIT_TIMEOUT" && kill "$script_pid" 2>/dev/null ) &
+    local killer_pid=$!
+    wait "$script_pid" 2>/dev/null
+    local exit_code=$?
+    # cancel the killer if process already exited
+    kill "$killer_pid" 2>/dev/null || true
+    wait "$killer_pid" 2>/dev/null || true
+    return $exit_code
+}
+
+# ── preflight 
─────────────────────────────────────────────────────────────────
+
+echo ""
+echo "start-hugegraph.sh chunk 1 test suite"
+echo "root: $HUGEGRAPH_ROOT"
+echo ""
+
+if [[ ! -f "$START_SCRIPT" ]]; then
+    echo -e "${RED}ERROR:${NC} $START_SCRIPT not found."
+    echo "       Pass the HugeGraph dist root as \$1"
+    exit 1
+fi
+
+if [[ ! -f "$STOP_SCRIPT" ]]; then
+    echo -e "${RED}ERROR:${NC} $STOP_SCRIPT not found."
+    exit 1
+fi
+
+for tool in lsof crontab 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
+
+# start-monitor.sh requires JAVA_HOME
+if [[ -z "${JAVA_HOME:-}" ]]; then
+    if command -v /usr/libexec/java_home >/dev/null 2>&1; then
+        export JAVA_HOME="$(/usr/libexec/java_home 2>/dev/null)"
+    fi
+fi
+
+# Warn if JAVA_HOME could not be determined
+if [[ -z "${JAVA_HOME:-}" ]]; then
+    echo -e "${YELLOW}WARN${NC} JAVA_HOME is not set"
+    echo "       Test 4 may fail because start-monitor.sh requires JAVA_HOME"
+    echo "       Export JAVA_HOME before running this script"
+fi
+
+# Run init-store.sh once if RocksDB has not been initialized yet
+if [[ ! -d "$HUGEGRAPH_ROOT/rocksdb-data" ]]; then
+    info "RocksDB not initialized — running init-store.sh..."
+    if "$BIN/init-store.sh" >/dev/null 2>&1; then
+        pass "init-store.sh completed successfully"
+    else
+        echo -e "${RED}ERROR:${NC} init-store.sh failed. Cannot run tests."
+        exit 1
+    fi
+else
+    info "RocksDB already initialized, skipping init-store.sh"
+fi
+
+cleanup
+
+# ── test 1: daemon mode regression ───────────────────────────────────────────
+
+section "Test 1 — daemon mode regression"
+
+info "Starting in daemon mode (waiting up to ${STARTUP_WAIT}s for HTTP)..."
+"$START_SCRIPT" -d true -t "$STARTUP_WAIT" >/dev/null 2>&1
+EXIT_CODE=$?
+
+if [[ $EXIT_CODE -eq 0 ]]; then
+    pass "script returned exit 0 in daemon mode"
+else
+    fail "script returned non-zero exit $EXIT_CODE in daemon mode"
+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
+
+if wait_for_server; then
+    pass "server HTTP endpoint responding at $SERVER_URL"
+else
+    fail "server HTTP endpoint not responding after ${STARTUP_WAIT}s"
+fi
+
+info "Stopping server..."
+"$STOP_SCRIPT" >/dev/null 2>&1
+sleep 3
+
+if [[ -n "$DAEMON_PID" ]] && ! ps -p "$DAEMON_PID" >/dev/null 2>&1; then
+    pass "stop-hugegraph.sh successfully killed the process"
+else
+    fail "process still running after stop-hugegraph.sh"
+fi
+
+cleanup
+
+# ── test 2: foreground mode blocks ───────────────────────────────────────────
+
+section "Test 2 — foreground mode blocks until Java exits"
+
+info "Starting in foreground mode (backgrounded for observation)..."
+"$START_SCRIPT" -d false >/dev/null 2>&1 &
+SCRIPT_PID=$!
+
+info "Waiting up to ${STARTUP_WAIT}s for server to come up..."
+if wait_for_server; then
+    info "Server is up"
+else
+    info "Server did not respond — continuing to check blocking behavior"
+fi
+
+# Primary assertion: script must still be running while Java is alive
+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
+
+# Secondary observations only (the actual bug is tested in Test 3)
+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 in foreground mode (expected before chunk 1 
fix)"
+fi
+
+if [[ -n "$FG_PID" ]] && ps -p "$FG_PID" >/dev/null 2>&1; then
+    info "Java process running (pid $FG_PID)"
+else
+    info "Java PID not available (expected before chunk 1 fix)"
+fi
+
+if [[ -n "$FG_PID" ]]; then
+    info "Killing Java process..."
+    kill "$FG_PID" 2>/dev/null || true
+    sleep "$SETTLE_WAIT"
+
+    if ! ps -p "$SCRIPT_PID" >/dev/null 2>&1; then
+        info "script exited after Java was killed"
+    else
+        info "script still running after Java was killed"
+        kill "$SCRIPT_PID" 2>/dev/null || true
+    fi
+else
+    kill "$SCRIPT_PID" 2>/dev/null || true
+fi
+
+cleanup
+
+# ── test 3: exit code propagates ─────────────────────────────────────────────
+
+section "Test 3 — foreground PID tracking and exit propagation"
+
+info "Starting in foreground mode..."
+"$START_SCRIPT" -d false >/dev/null 2>&1 &
+SCRIPT_PID=$!
+
+info "Waiting up to ${STARTUP_WAIT}s for server..."
+if wait_for_server; then
+    info "Server is up"
+else
+    info "Server 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 from pid file — 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: monitor registers in daemon mode 
──────────────────────────────────
+
+section "Test 4 — -m true registers cron job in daemon mode"
+
+info "Clearing crontab..."
+crontab -l 2>/dev/null | grep -v monitor-hugegraph | crontab - 2>/dev/null || 
true
+
+info "Starting in daemon mode with -m true (waiting up to ${STARTUP_WAIT}s)..."
+"$START_SCRIPT" -d true -m true -t "$STARTUP_WAIT" >/dev/null 2>&1
+EXIT_CODE=$?
+
+if [[ $EXIT_CODE -eq 0 ]]; then
+    pass "daemon started successfully with -m true"
+else
+    fail "daemon start failed with exit $EXIT_CODE — monitor may not have 
registered"
+fi
+
+if crontab -l 2>/dev/null | grep -q "monitor-hugegraph"; then
+    pass "cron job registered for monitor-hugegraph.sh"
+else
+    fail "cron job NOT registered — OPEN_MONITOR block broken in daemon mode"
+fi
+
+info "Stopping server..."
+"$STOP_SCRIPT" -m false >/dev/null 2>&1 || true
+
+cleanup
+
+# ── test 5: SIGTERM forwarded to Java in foreground mode ─────────────────────
+
+section "Test 5 — SIGTERM forwarded to Java in foreground mode"
+
+info "Starting in foreground mode..."
+"$START_SCRIPT" -d false >/dev/null 2>&1 &
+SCRIPT_PID=$!
+
+info "Waiting up to ${STARTUP_WAIT}s for server..."
+if wait_for_server; then
+    info "Server is up"
+else
+    info "Server 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 signal forwarding check"
+    kill "$SCRIPT_PID" 2>/dev/null || true
+else
+    info "Sending SIGTERM to wrapper script (pid $SCRIPT_PID)..."
+    kill -TERM "$SCRIPT_PID" 2>/dev/null || true
+
+    wait_script_exit "$SCRIPT_PID"
+    ACTUAL_EXIT=$?
+
+    # If the trap fired correctly, the wrapper's `wait $PID` already reaped 
Java.
+    # If wait_script_exit timed out (killer fired), Java may still be running 
— also a failure.
+    if ! ps -p "$FG_PID" >/dev/null 2>&1; then
+        pass "Java process terminated after SIGTERM sent to wrapper"
+    else
+        fail "Java process still running after SIGTERM — signal not forwarded"
+        kill "$FG_PID" 2>/dev/null || true
+    fi
+
+    if [[ $ACTUAL_EXIT -ne 0 ]]; then
+        pass "wrapper script exited non-zero ($ACTUAL_EXIT) after SIGTERM"
+    else
+        fail "wrapper script exited 0 after SIGTERM — exit code not propagated"
+    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 — may be shell-wrapped, 
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

Reply via email to