From 5cad9dc8217f0aa8139eb68ac1e32c6ab2af0df6 Mon Sep 17 00:00:00 2001 From: Himanshu Verma Date: Wed, 2 Sep 2026 23:23:20 +0530 Subject: [PATCH 1/2] fix(docker): make the Server startup timeout configurable The entrypoint ran start-hugegraph.sh with a literal -t 120, so a Server that needed longer than 120 seconds to answer on its REST port was terminated by its own container, no matter how much startup budget the orchestrator's probe allowed. Read the timeout from HG_SERVER_STARTUP_TIMEOUT_S instead, keep 120 as the default, and reject values that are not positive whole numbers before init-store runs. Covered by docker-entrypoint-test.sh: default passthrough, an explicit override, and rejection of an invalid value. Documented in the Server docker README. Closes #3186 --- .../hugegraph-dist/docker/README.md | 17 ++++++++++++++ .../docker/docker-entrypoint-test.sh | 22 +++++++++++++++++++ .../docker/docker-entrypoint.sh | 15 ++++++++++++- 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/hugegraph-server/hugegraph-dist/docker/README.md b/hugegraph-server/hugegraph-dist/docker/README.md index 9214aa830e..2cfc101bf0 100644 --- a/hugegraph-server/hugegraph-dist/docker/README.md +++ b/hugegraph-server/hugegraph-dist/docker/README.md @@ -144,3 +144,20 @@ native `HEALTHCHECK` instructions. `docker ps` shows real health status: | `hugegraph/hugegraph-store` | `GET /v1/health` on port 8520 | The entrypoints supervise the Java process directly — when Java exits, the container exits. If started with a restart policy (the provided compose files use `restart: unless-stopped`), Docker will bring it back automatically. The old cron-based monitor (`-m true`) is for VM/bare-metal deployments only and is not used in Docker images. + +## 7. Server Startup Timeout + +The entrypoint gives the Server a fixed budget to answer on its REST port and +ends the container when the budget runs out. It is 120 seconds by default. Set +`HG_SERVER_STARTUP_TIMEOUT_S` to a positive whole number of seconds to change +it: + +```bash +docker run -itd --name=graph -p 8080:8080 -e HG_SERVER_STARTUP_TIMEOUT_S=450 hugegraph/hugegraph:1.7.0 +``` + +Raise it on slow or contended hosts, and wherever an orchestrator already owns +the startup budget through a probe of its own: a startup probe cannot extend a +container that has already ended the JVM it was waiting for. A value that is +not a positive whole number stops the container at startup instead of silently +falling back to the default. diff --git a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh index 6e22885ebe..6a712cc688 100755 --- a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh +++ b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh @@ -35,6 +35,7 @@ backend=rocksdb EOF cat > "${TEST_HOME}/bin/start-hugegraph.sh" <<'EOF' #!/usr/bin/env bash +printf '%s\n' "$*" >> ./docker/start-hugegraph-args exit 0 EOF cat > "${TEST_HOME}/bin/init-store.sh" <<'EOF' @@ -232,4 +233,25 @@ rm -f "${TEST_HOME}/docker/init_complete" ) grep -Fqx -- '-n' "${TEST_HOME}/docker/init-store-password" +last_start_args() { tail -n 1 "${TEST_HOME}/docker/start-hugegraph-args"; } + +[[ "$(last_start_args)" == *"-t 120"* ]] + +( + cd "${TEST_HOME}" + HG_SERVER_STARTUP_TIMEOUT_S=450 bash ./docker-entrypoint.sh +) +[[ "$(last_start_args)" == *"-t 450"* ]] + +start_calls_before_invalid_timeout=$(wc -l < "${TEST_HOME}/docker/start-hugegraph-args") +if ( + cd "${TEST_HOME}" + HG_SERVER_STARTUP_TIMEOUT_S=2m bash ./docker-entrypoint.sh +); then + echo "invalid startup timeout unexpectedly succeeded" >&2 + exit 1 +fi +[[ "$(wc -l < "${TEST_HOME}/docker/start-hugegraph-args")" -eq \ + "${start_calls_before_invalid_timeout}" ]] + echo "PASS: Docker entrypoint configures HStore discovery and authentication" diff --git a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh index fe9974c430..e02cbf5791 100755 --- a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh +++ b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh @@ -98,6 +98,19 @@ if [[ -n "${HG_SERVER_AUTH_TOKEN_SECRET:-}" ]]; then fi fi +# How long the entrypoint lets the server take to answer on its REST port +# before it gives up and ends the container. An orchestrator that already +# owns this budget through a startup probe needs to raise it, otherwise the +# container terminates a JVM that is still starting and the probe never gets +# to decide. Validated here so a typo fails before init-store runs, rather +# than reaching the arithmetic in wait_for_startup. +SERVER_STARTUP_TIMEOUT_S="${HG_SERVER_STARTUP_TIMEOUT_S:-120}" +if [[ ! "${SERVER_STARTUP_TIMEOUT_S}" =~ ^[1-9][0-9]*$ ]]; then + log "ERROR: HG_SERVER_STARTUP_TIMEOUT_S must be a positive whole number" \ + "of seconds, got '${SERVER_STARTUP_TIMEOUT_S}'" + exit 1 +fi + if [[ -n "${PASSWORD:-}" && "${HG_SERVER_REQUIRE_AUTH_TOKEN_SECRET:-false}" == "true" && -z "${HG_SERVER_AUTH_TOKEN_SECRET:-}" ]]; then @@ -217,7 +230,7 @@ else ./bin/init-store.sh fi -./bin/start-hugegraph.sh -j "${JAVA_OPTS:-}" -t 120 +./bin/start-hugegraph.sh -j "${JAVA_OPTS:-}" -t "${SERVER_STARTUP_TIMEOUT_S}" # Post-startup cluster stabilization check (hstore only — rocksdb has no partitions) ACTUAL_BACKEND=$(grep -E '^[[:space:]]*backend[[:space:]]*=' "${GRAPH_CONF}" | head -n 1 | sed 's/.*=//' | tr -d '[:space:]' || true) From 6ca3df360bb0ea81013f0653ddfd493c0c1127b3 Mon Sep 17 00:00:00 2001 From: Himanshu Verma Date: Thu, 3 Sep 2026 18:06:10 +0530 Subject: [PATCH 2/2] fix(docker): bound the startup timeout and reject an empty value Review follow-up on the configurable Server startup timeout. The guard accepted any positive integer, but wait_for_startup computes its deadline as $((now_s + timeout_s)). Near the 64-bit ceiling that sum wraps negative, the wait loop exits before its first probe, and the container reports a startup timeout immediately: the failure the variable exists to prevent. The accepted range is now 1 to 86400 seconds, bounded first by a five-digit pattern so the comparison itself cannot overflow. The default also used ':-', which treats an explicitly empty value as unset and silently restores 120. Compose writes exactly that whenever an interpolated host variable is missing, so a deployment that believed it had set 450 still died at 120, and the README said such a value would stop the container. Plain '-' keeps the default for an unset variable and rejects an empty one. Tests now cover the accepted upper bound, an unset variable, and seven rejected values including empty, whitespace, 86401 and INT64_MAX, and assert that a rejected value runs neither start-hugegraph nor init-store, which pins the ordering the guard's comment claims. The new assertions exit explicitly rather than relying on set -e with [[ ]], which bash 3.2 ignores. The README documents the range and that the container health check keeps its own budget, which does not move with this variable. --- .../hugegraph-dist/docker/README.md | 20 +++++-- .../docker/docker-entrypoint-test.sh | 53 +++++++++++++++---- .../docker/docker-entrypoint.sh | 22 +++++--- 3 files changed, 75 insertions(+), 20 deletions(-) diff --git a/hugegraph-server/hugegraph-dist/docker/README.md b/hugegraph-server/hugegraph-dist/docker/README.md index 2cfc101bf0..07f5c5ae08 100644 --- a/hugegraph-server/hugegraph-dist/docker/README.md +++ b/hugegraph-server/hugegraph-dist/docker/README.md @@ -149,8 +149,8 @@ The entrypoints supervise the Java process directly — when Java exits, the con The entrypoint gives the Server a fixed budget to answer on its REST port and ends the container when the budget runs out. It is 120 seconds by default. Set -`HG_SERVER_STARTUP_TIMEOUT_S` to a positive whole number of seconds to change -it: +`HG_SERVER_STARTUP_TIMEOUT_S` to a whole number of seconds between 1 and 86400 +to change it: ```bash docker run -itd --name=graph -p 8080:8080 -e HG_SERVER_STARTUP_TIMEOUT_S=450 hugegraph/hugegraph:1.7.0 @@ -158,6 +158,16 @@ docker run -itd --name=graph -p 8080:8080 -e HG_SERVER_STARTUP_TIMEOUT_S=450 hug Raise it on slow or contended hosts, and wherever an orchestrator already owns the startup budget through a probe of its own: a startup probe cannot extend a -container that has already ended the JVM it was waiting for. A value that is -not a positive whole number stops the container at startup instead of silently -falling back to the default. +container that has already ended the JVM it was waiting for. Anything outside +the accepted range, an empty value included, stops the container at startup +instead of silently falling back to the default. + +Raising this budget does not move the health check described in section 6, +which runs on a clock of its own. The images set `--interval=15s +--start-period=90s --retries=3`, so a container given a longer startup budget +is reported `unhealthy` around 135 seconds while the entrypoint is still +legitimately waiting; raise it with `--health-start-period` on `docker run`. +The Compose files replace those values with their own (`start_period: 60s`, +`interval: 10s`, `retries: 30`, so roughly 360 seconds), and anything gated on +`depends_on: condition: service_healthy`, Hubble included, waits on that budget +rather than on this variable. Move the two together. diff --git a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh index 6a712cc688..6a38520bb3 100755 --- a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh +++ b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh @@ -243,15 +243,50 @@ last_start_args() { tail -n 1 "${TEST_HOME}/docker/start-hugegraph-args"; } ) [[ "$(last_start_args)" == *"-t 450"* ]] -start_calls_before_invalid_timeout=$(wc -l < "${TEST_HOME}/docker/start-hugegraph-args") -if ( +( cd "${TEST_HOME}" - HG_SERVER_STARTUP_TIMEOUT_S=2m bash ./docker-entrypoint.sh -); then - echo "invalid startup timeout unexpectedly succeeded" >&2 - exit 1 -fi -[[ "$(wc -l < "${TEST_HOME}/docker/start-hugegraph-args")" -eq \ - "${start_calls_before_invalid_timeout}" ]] + HG_SERVER_STARTUP_TIMEOUT_S=86400 bash ./docker-entrypoint.sh +) +[[ "$(last_start_args)" == *"-t 86400"* ]] + +# An empty value is a set value, not an absent one: Compose writes it whenever +# an interpolated host variable is missing. 2m is the shape of a typo, and the +# two large values bracket the point where the deadline arithmetic in +# wait_for_startup would wrap negative and end the wait before its first probe. +for invalid_timeout in "" " " 0 +5 2m 86401 9223372036854775807; do + start_calls_before_invalid=$(wc -l < "${TEST_HOME}/docker/start-hugegraph-args") + init_calls_before_invalid=$(wc -l < "${TEST_HOME}/docker/init-store-calls") + if ( + cd "${TEST_HOME}" + HG_SERVER_STARTUP_TIMEOUT_S="${invalid_timeout}" \ + bash ./docker-entrypoint.sh + ); then + echo "startup timeout '${invalid_timeout}' unexpectedly succeeded" >&2 + exit 1 + fi + # The server must not have started, and the guard must have run ahead of + # init-store, as the comment above it in the entrypoint claims. Spelled + # with an explicit exit rather than a bare [[ ]]: bash 3.2, still the + # /bin/bash of macOS, does not apply set -e to a failing [[ ]], so a bare + # assertion passes silently there while CI catches the regression. + [[ "$(wc -l < "${TEST_HOME}/docker/start-hugegraph-args")" -eq \ + "${start_calls_before_invalid}" ]] || { + echo "startup timeout '${invalid_timeout}' started the server" >&2 + exit 1 + } + [[ "$(wc -l < "${TEST_HOME}/docker/init-store-calls")" -eq \ + "${init_calls_before_invalid}" ]] || { + echo "startup timeout '${invalid_timeout}' was rejected only after" \ + "init-store ran" >&2 + exit 1 + } +done + +# An unset variable still keeps the historical default. +( + cd "${TEST_HOME}" + bash ./docker-entrypoint.sh +) +[[ "$(last_start_args)" == *"-t 120"* ]] echo "PASS: Docker entrypoint configures HStore discovery and authentication" diff --git a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh index e02cbf5791..b5ba2de34f 100755 --- a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh +++ b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh @@ -102,12 +102,22 @@ fi # before it gives up and ends the container. An orchestrator that already # owns this budget through a startup probe needs to raise it, otherwise the # container terminates a JVM that is still starting and the probe never gets -# to decide. Validated here so a typo fails before init-store runs, rather -# than reaching the arithmetic in wait_for_startup. -SERVER_STARTUP_TIMEOUT_S="${HG_SERVER_STARTUP_TIMEOUT_S:-120}" -if [[ ! "${SERVER_STARTUP_TIMEOUT_S}" =~ ^[1-9][0-9]*$ ]]; then - log "ERROR: HG_SERVER_STARTUP_TIMEOUT_S must be a positive whole number" \ - "of seconds, got '${SERVER_STARTUP_TIMEOUT_S}'" +# to decide. Validated here so a bad value fails before init-store runs, +# rather than reaching the arithmetic in wait_for_startup: that deadline is +# $((now_s + timeout_s)), which wraps negative near the 64-bit ceiling and +# makes the wait exit before its first probe, the very failure this variable +# exists to avoid. The five-digit bound keeps this comparison in range too, +# and a day is already far past any real start. Plain '-' rather than ':-', +# so an explicitly empty value is rejected instead of quietly becoming the +# default: Compose interpolation such as ${SOME_VAR:-} yields empty, not +# unset, whenever the host variable is missing. +SERVER_STARTUP_TIMEOUT_MAX_S=86400 +SERVER_STARTUP_TIMEOUT_S="${HG_SERVER_STARTUP_TIMEOUT_S-120}" +if [[ ! "${SERVER_STARTUP_TIMEOUT_S}" =~ ^[1-9][0-9]{0,4}$ ]] || + (( SERVER_STARTUP_TIMEOUT_S > SERVER_STARTUP_TIMEOUT_MAX_S )); then + log "ERROR: HG_SERVER_STARTUP_TIMEOUT_S must be a whole number of" \ + "seconds from 1 to ${SERVER_STARTUP_TIMEOUT_MAX_S}," \ + "got '${SERVER_STARTUP_TIMEOUT_S}'" exit 1 fi