imbajin commented on code in PR #3104:
URL: https://github.com/apache/hugegraph/pull/3104#discussion_r3650135863


##########
hugegraph-server/hugegraph-dist/src/assembly/travis/run-riscv64-smoke.sh:
##########
@@ -0,0 +1,542 @@
+#!/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.
+#
+# run-riscv64-smoke.sh — Disposable RISC-V (linux/riscv64) end-to-end smoke 
test.
+#
+# Builds the HugeGraph Server distribution on the contributor's native host
+# (x86-64 / arm64), then runs it inside an emulated linux/riscv64 container to
+# verify the RocksDB backend + Gremlin stack actually work on RISC-V via QEMU.
+#
+# This is a CORRECTNESS gate, not a performance benchmark. QEMU user-mode
+# emulation is several times slower than native; expect the full run (build +
+# init + REST + Gremlin + restart) to take many minutes the first time.
+#
+# It requires Docker with QEMU/binfmt_misc multi-platform support (Docker
+# Desktop ships this; on Linux install it with:
+#   docker run --privileged --rm tonistiigi/binfmt --install all
+# No host RISC-V packages are installed by this script.
+#
+# It removes ONLY what it creates: a uniquely-named container, volume, and
+# locally-built image. The QEMU emulator registration is left untouched
+# (remove it with: docker run --privileged --rm tonistiigi/binfmt --uninstall 
all).
+#
+# Usage:
+#   ./run-riscv64-smoke.sh [path-to-server-dist.tar.gz]
+#   ./run-riscv64-smoke.sh            # builds the dist under hugegraph-server/
+#
+set -euo pipefail
+
+ARCH=riscv64
+PLATFORM="linux/${ARCH}"
+# Ubuntu 24.04 publishes openjdk-11-jre-headless for linux/riscv64 (glibc).
+# TODO: Pin to a SHA256 digest for reproducibility (e.g. ubuntu@sha256:...).
+BASE_IMAGE="ubuntu:24.04"
+
+REPO_ROOT=$(cd "$(dirname "$0")/../../../../.." && pwd)
+
+# Use disk-backed temp: the extracted server (~1GB) blows out a small RAM tmpfs
+# /tmp. Default to a work dir on the same (large) filesystem as the repo.
+: "${TMPDIR:=$REPO_ROOT/.riscv64-smoke-tmp}"
+mkdir -p "$TMPDIR"
+export TMPDIR
+
+# Portable in-place sed: avoids GNU sed -i which breaks on macOS/BSD.
+sedi() {
+    local expr="$1" file="$2"
+    local tmp; tmp=$(mktemp "${TMPDIR:-/tmp}/sedi.XXXXXX")
+    if sed "$expr" "$file" > "$tmp"; then
+        mv "$tmp" "$file"
+    else
+        rm -f "$tmp"
+        return 1
+    fi
+}
+
+# Unique, traceable names so cleanup removes only our own resources.
+# Timestamp + random hex: collision-resistant, still human-readable for 
debugging.
+STAMP=$(date +%Y%m%d%H%M%S)-$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' 
\n')
+TAG="hugegraph-riscv64-smoke:${STAMP}"
+# KEEP: don't auto-cleanup so a failed/finished run stays pokeable.
+VOL="hugegraph-riscv64-data-${STAMP}"
+CONTAINER="hugegraph-riscv64-smoke-${STAMP}"
+
+DIST_ARG="${1:-}"
+DIST_TAR=""
+SERVER_DIR=""
+BUILD_CTX=""
+ROCKS_BUILD=""
+
+GREEN=$'\033[0;32m'; RED=$'\033[0;31m'; NC=$'\033[0m'
+pass() { echo "${GREEN}PASS${NC} $*"; }
+fail() { echo "${RED}FAIL${NC} $*" >&2; }
+
+# Check if the container is still alive (returns 0 if running).
+container_alive() {
+    $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER"
+}
+
+# Run docker exec with container-liveness guard + exit-code logging.
+# Usage: safe_exec exec <container> <cmd> [args...]
+# DOCKER is split into words without eval; handles "docker" or "sudo docker".
+# Returns the command's output on stdout; logs failures to stderr.
+safe_exec() {
+    local rc
+    if ! container_alive; then
+        fail "container '$CONTAINER' is no longer running (before: $*)"
+        dump_health_diagnostics
+        return 1
+    fi
+    set +e
+    local -a docker_parts=($DOCKER)
+    "${docker_parts[@]}" "$@"
+    rc=$?
+    set -e
+    if [[ $rc -ne 0 ]]; then
+        fail "command failed (exit $rc): $*"
+        dump_health_diagnostics
+        return "$rc"
+    fi
+}
+
+# DOCKER: allow callers to override (e.g. "sudo docker"). Defaults to plain 
docker.
+DOCKER=${DOCKER:-docker}
+
+# Dump container diagnostics to stderr. Defined early so the EXIT trap can
+# call it even if the script fails before the main body.
+dump_health_diagnostics() {
+    echo "----- health-check diagnostics ($CONTAINER) -----" >&2
+    $DOCKER inspect --format \
+        'state={{.State.Status}} exit={{.State.ExitCode}} 
error={{.State.Error}}' \
+        "$CONTAINER" >&2 || true
+
+    if $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER"; 
then
+        echo "----- listening processes -----" >&2
+        $DOCKER exec "$CONTAINER" sh -c \
+            'lsof -nP -iTCP:8080 -sTCP:LISTEN || true' >&2 || true
+        for endpoint in versions graphs; do
+            echo "----- GET /$endpoint -----" >&2
+            $DOCKER exec "$CONTAINER" curl -i -sS --connect-timeout 5 
--max-time 15 \
+                "http://127.0.0.1:8080/$endpoint"; >&2 || true
+        done
+    fi
+
+    echo "----- recent container logs -----" >&2
+    $DOCKER logs --tail 100 "$CONTAINER" >&2 || true
+    echo "----- end health-check diagnostics -----" >&2
+}
+
+_exit_handler() {
+    local rc=$?
+    # If the script failed, log the exit code so the root cause isn't lost.
+    if [[ $rc -ne 0 ]]; then
+        echo "" >&2
+        echo "==> Script failed with exit code $rc" >&2
+        if container_alive; then
+            dump_health_diagnostics
+        else
+            echo "==> container '$CONTAINER' is not running (may have been 
OOM-killed or crashed)" >&2
+            # Best-effort: check docker inspect for OOMKilled + dump any 
surviving logs
+            $DOCKER inspect --format \
+                'OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}} 
Status={{.State.Status}}' \
+                "$CONTAINER" >&2 2>/dev/null || true
+            $DOCKER logs --tail 80 "$CONTAINER" >&2 2>/dev/null || true
+        fi
+    fi
+    _cleanup
+    # Propagate cleanup failure: if the main run succeeded but cleanup failed,
+    # exit nonzero so CI records the resource leak.
+    if [[ $rc -eq 0 && $_CLEANUP_OK -ne 0 ]]; then
+        exit 1
+    fi
+    exit "$rc"
+}
+
+_cleanup() {
+    _CLEANUP_OK=0
+    # Dump container logs first so a failure's root cause isn't destroyed by 
cleanup.
+    # This runs even with KEEP=1 so the user gets diagnostics.
+    if $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx 
"$CONTAINER"; then
+        echo "----- container logs ($CONTAINER) -----" >&2
+        $DOCKER logs --tail 60 "$CONTAINER" >&2 || true
+        echo "----- end logs -----" >&2
+        echo "----- hugegraph-server.log -----" >&2
+        $DOCKER cp "$CONTAINER:/server/logs/hugegraph-server.log" - 
2>/dev/null \
+            | tar -xO 2>/dev/null | tail -n 80 >&2 || true
+        echo "----- end hugegraph-server.log -----" >&2
+    fi
+    # KEEP=1: leave everything standing for live poking; skip teardown.
+    if [[ "${KEEP:-}" == "1" ]]; then
+        echo "==> KEEP=1 set — skipping cleanup. Resources left standing:" >&2
+        echo "    container: $CONTAINER" >&2
+        echo "    volume:    $VOL" >&2
+        echo "    image:     $TAG" >&2
+        return 0
+    fi
+    local ok=1
+    $DOCKER rm -f "$CONTAINER" >/dev/null 2>&1 || ok=0
+    # Only remove the volume we created.
+    $DOCKER volume rm "$VOL" >/dev/null 2>&1 || ok=0
+    # Only remove the image we tagged; never prune the host's image store.
+    $DOCKER rmi "$TAG" >/dev/null 2>&1 || ok=0
+    # Remove disk-backed temp work dirs (build context holds the ~1GB extract).
+    if [[ -n "$BUILD_CTX" && -d "$BUILD_CTX" ]]; then
+        rm -rf "$BUILD_CTX" || ok=0
+    fi
+    if [[ -n "$ROCKS_BUILD" && -d "$ROCKS_BUILD" ]]; then
+        rm -rf "$ROCKS_BUILD" || ok=0
+    fi
+    if [[ "$ok" == "1" ]]; then
+        pass "cleanup: container, volume, and image removed"
+    else
+        fail "cleanup: some resources may remain (container=$CONTAINER 
vol=$VOL image=$TAG)"
+        _CLEANUP_OK=1
+    fi
+}
+trap _exit_handler EXIT INT TERM

Review Comment:
   ‼️ The same handler is installed for `EXIT`, `INT`, and `TERM`, while the 
handler itself calls `exit`. A focused Bash reproduction runs the handler twice 
after `kill -INT $$`/`kill -TERM $$` and exits 0; with `KEEP=1`, an interrupted 
correctness run is therefore reported as successful, while normal mode can 
attempt teardown twice. Please keep cleanup on `EXIT` only and make the signal 
traps exit with 130/143 so cleanup runs once and cancellation remains a failure.



##########
hugegraph-server/hugegraph-dist/src/assembly/travis/run-riscv64-smoke.sh:
##########
@@ -0,0 +1,542 @@
+#!/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.
+#
+# run-riscv64-smoke.sh — Disposable RISC-V (linux/riscv64) end-to-end smoke 
test.
+#
+# Builds the HugeGraph Server distribution on the contributor's native host
+# (x86-64 / arm64), then runs it inside an emulated linux/riscv64 container to
+# verify the RocksDB backend + Gremlin stack actually work on RISC-V via QEMU.
+#
+# This is a CORRECTNESS gate, not a performance benchmark. QEMU user-mode
+# emulation is several times slower than native; expect the full run (build +
+# init + REST + Gremlin + restart) to take many minutes the first time.
+#
+# It requires Docker with QEMU/binfmt_misc multi-platform support (Docker
+# Desktop ships this; on Linux install it with:
+#   docker run --privileged --rm tonistiigi/binfmt --install all
+# No host RISC-V packages are installed by this script.
+#
+# It removes ONLY what it creates: a uniquely-named container, volume, and
+# locally-built image. The QEMU emulator registration is left untouched
+# (remove it with: docker run --privileged --rm tonistiigi/binfmt --uninstall 
all).
+#
+# Usage:
+#   ./run-riscv64-smoke.sh [path-to-server-dist.tar.gz]
+#   ./run-riscv64-smoke.sh            # builds the dist under hugegraph-server/
+#
+set -euo pipefail
+
+ARCH=riscv64
+PLATFORM="linux/${ARCH}"
+# Ubuntu 24.04 publishes openjdk-11-jre-headless for linux/riscv64 (glibc).
+# TODO: Pin to a SHA256 digest for reproducibility (e.g. ubuntu@sha256:...).
+BASE_IMAGE="ubuntu:24.04"
+
+REPO_ROOT=$(cd "$(dirname "$0")/../../../../.." && pwd)
+
+# Use disk-backed temp: the extracted server (~1GB) blows out a small RAM tmpfs
+# /tmp. Default to a work dir on the same (large) filesystem as the repo.
+: "${TMPDIR:=$REPO_ROOT/.riscv64-smoke-tmp}"
+mkdir -p "$TMPDIR"
+export TMPDIR
+
+# Portable in-place sed: avoids GNU sed -i which breaks on macOS/BSD.
+sedi() {
+    local expr="$1" file="$2"
+    local tmp; tmp=$(mktemp "${TMPDIR:-/tmp}/sedi.XXXXXX")
+    if sed "$expr" "$file" > "$tmp"; then
+        mv "$tmp" "$file"
+    else
+        rm -f "$tmp"
+        return 1
+    fi
+}
+
+# Unique, traceable names so cleanup removes only our own resources.
+# Timestamp + random hex: collision-resistant, still human-readable for 
debugging.
+STAMP=$(date +%Y%m%d%H%M%S)-$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' 
\n')
+TAG="hugegraph-riscv64-smoke:${STAMP}"
+# KEEP: don't auto-cleanup so a failed/finished run stays pokeable.
+VOL="hugegraph-riscv64-data-${STAMP}"
+CONTAINER="hugegraph-riscv64-smoke-${STAMP}"
+
+DIST_ARG="${1:-}"
+DIST_TAR=""
+SERVER_DIR=""
+BUILD_CTX=""
+ROCKS_BUILD=""
+
+GREEN=$'\033[0;32m'; RED=$'\033[0;31m'; NC=$'\033[0m'
+pass() { echo "${GREEN}PASS${NC} $*"; }
+fail() { echo "${RED}FAIL${NC} $*" >&2; }
+
+# Check if the container is still alive (returns 0 if running).
+container_alive() {
+    $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER"
+}
+
+# Run docker exec with container-liveness guard + exit-code logging.
+# Usage: safe_exec exec <container> <cmd> [args...]
+# DOCKER is split into words without eval; handles "docker" or "sudo docker".
+# Returns the command's output on stdout; logs failures to stderr.
+safe_exec() {
+    local rc
+    if ! container_alive; then
+        fail "container '$CONTAINER' is no longer running (before: $*)"
+        dump_health_diagnostics
+        return 1
+    fi
+    set +e
+    local -a docker_parts=($DOCKER)
+    "${docker_parts[@]}" "$@"
+    rc=$?
+    set -e
+    if [[ $rc -ne 0 ]]; then
+        fail "command failed (exit $rc): $*"
+        dump_health_diagnostics
+        return "$rc"
+    fi
+}
+
+# DOCKER: allow callers to override (e.g. "sudo docker"). Defaults to plain 
docker.
+DOCKER=${DOCKER:-docker}
+
+# Dump container diagnostics to stderr. Defined early so the EXIT trap can
+# call it even if the script fails before the main body.
+dump_health_diagnostics() {
+    echo "----- health-check diagnostics ($CONTAINER) -----" >&2
+    $DOCKER inspect --format \
+        'state={{.State.Status}} exit={{.State.ExitCode}} 
error={{.State.Error}}' \
+        "$CONTAINER" >&2 || true
+
+    if $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER"; 
then
+        echo "----- listening processes -----" >&2
+        $DOCKER exec "$CONTAINER" sh -c \
+            'lsof -nP -iTCP:8080 -sTCP:LISTEN || true' >&2 || true
+        for endpoint in versions graphs; do
+            echo "----- GET /$endpoint -----" >&2
+            $DOCKER exec "$CONTAINER" curl -i -sS --connect-timeout 5 
--max-time 15 \
+                "http://127.0.0.1:8080/$endpoint"; >&2 || true
+        done
+    fi
+
+    echo "----- recent container logs -----" >&2
+    $DOCKER logs --tail 100 "$CONTAINER" >&2 || true
+    echo "----- end health-check diagnostics -----" >&2
+}
+
+_exit_handler() {
+    local rc=$?
+    # If the script failed, log the exit code so the root cause isn't lost.
+    if [[ $rc -ne 0 ]]; then
+        echo "" >&2
+        echo "==> Script failed with exit code $rc" >&2
+        if container_alive; then
+            dump_health_diagnostics
+        else
+            echo "==> container '$CONTAINER' is not running (may have been 
OOM-killed or crashed)" >&2
+            # Best-effort: check docker inspect for OOMKilled + dump any 
surviving logs
+            $DOCKER inspect --format \
+                'OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}} 
Status={{.State.Status}}' \
+                "$CONTAINER" >&2 2>/dev/null || true
+            $DOCKER logs --tail 80 "$CONTAINER" >&2 2>/dev/null || true
+        fi
+    fi
+    _cleanup
+    # Propagate cleanup failure: if the main run succeeded but cleanup failed,
+    # exit nonzero so CI records the resource leak.
+    if [[ $rc -eq 0 && $_CLEANUP_OK -ne 0 ]]; then
+        exit 1
+    fi
+    exit "$rc"
+}
+
+_cleanup() {
+    _CLEANUP_OK=0
+    # Dump container logs first so a failure's root cause isn't destroyed by 
cleanup.
+    # This runs even with KEEP=1 so the user gets diagnostics.
+    if $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx 
"$CONTAINER"; then
+        echo "----- container logs ($CONTAINER) -----" >&2
+        $DOCKER logs --tail 60 "$CONTAINER" >&2 || true
+        echo "----- end logs -----" >&2
+        echo "----- hugegraph-server.log -----" >&2
+        $DOCKER cp "$CONTAINER:/server/logs/hugegraph-server.log" - 
2>/dev/null \
+            | tar -xO 2>/dev/null | tail -n 80 >&2 || true
+        echo "----- end hugegraph-server.log -----" >&2
+    fi
+    # KEEP=1: leave everything standing for live poking; skip teardown.
+    if [[ "${KEEP:-}" == "1" ]]; then
+        echo "==> KEEP=1 set — skipping cleanup. Resources left standing:" >&2
+        echo "    container: $CONTAINER" >&2
+        echo "    volume:    $VOL" >&2
+        echo "    image:     $TAG" >&2
+        return 0
+    fi
+    local ok=1
+    $DOCKER rm -f "$CONTAINER" >/dev/null 2>&1 || ok=0
+    # Only remove the volume we created.
+    $DOCKER volume rm "$VOL" >/dev/null 2>&1 || ok=0
+    # Only remove the image we tagged; never prune the host's image store.
+    $DOCKER rmi "$TAG" >/dev/null 2>&1 || ok=0
+    # Remove disk-backed temp work dirs (build context holds the ~1GB extract).
+    if [[ -n "$BUILD_CTX" && -d "$BUILD_CTX" ]]; then
+        rm -rf "$BUILD_CTX" || ok=0
+    fi
+    if [[ -n "$ROCKS_BUILD" && -d "$ROCKS_BUILD" ]]; then
+        rm -rf "$ROCKS_BUILD" || ok=0
+    fi
+    if [[ "$ok" == "1" ]]; then
+        pass "cleanup: container, volume, and image removed"
+    else
+        fail "cleanup: some resources may remain (container=$CONTAINER 
vol=$VOL image=$TAG)"
+        _CLEANUP_OK=1
+    fi
+}
+trap _exit_handler EXIT INT TERM
+
+need() { command -v "$1" >/dev/null 2>&1 || { fail "missing required command: 
$1"; exit 1; }; }
+
+# Preflight host tools before any privileged operations.
+need docker
+$DOCKER buildx version >/dev/null 2>&1 || { fail "docker buildx is required 
(pacman -S docker-buildx)"; exit 1; }
+command -v mvn >/dev/null 2>&1 || { fail "mvn is required to build the 
distribution"; exit 1; }

Review Comment:
   ⚠️ This unconditional Maven preflight runs before `DIST_ARG` is checked, 
although Maven is only invoked in the no-argument build branch. As a result, 
the documented `./run-riscv64-smoke.sh path/to/server-dist.tar.gz` mode fails 
on an otherwise valid runtime host without Maven even though it is supposed to 
skip the build. Please move the Maven requirement into the branch that builds 
the distribution.



##########
hugegraph-server/hugegraph-dist/src/assembly/travis/run-riscv64-smoke.sh:
##########
@@ -0,0 +1,542 @@
+#!/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.
+#
+# run-riscv64-smoke.sh — Disposable RISC-V (linux/riscv64) end-to-end smoke 
test.
+#
+# Builds the HugeGraph Server distribution on the contributor's native host
+# (x86-64 / arm64), then runs it inside an emulated linux/riscv64 container to
+# verify the RocksDB backend + Gremlin stack actually work on RISC-V via QEMU.
+#
+# This is a CORRECTNESS gate, not a performance benchmark. QEMU user-mode
+# emulation is several times slower than native; expect the full run (build +
+# init + REST + Gremlin + restart) to take many minutes the first time.
+#
+# It requires Docker with QEMU/binfmt_misc multi-platform support (Docker
+# Desktop ships this; on Linux install it with:
+#   docker run --privileged --rm tonistiigi/binfmt --install all
+# No host RISC-V packages are installed by this script.
+#
+# It removes ONLY what it creates: a uniquely-named container, volume, and
+# locally-built image. The QEMU emulator registration is left untouched
+# (remove it with: docker run --privileged --rm tonistiigi/binfmt --uninstall 
all).
+#
+# Usage:
+#   ./run-riscv64-smoke.sh [path-to-server-dist.tar.gz]
+#   ./run-riscv64-smoke.sh            # builds the dist under hugegraph-server/
+#
+set -euo pipefail
+
+ARCH=riscv64
+PLATFORM="linux/${ARCH}"
+# Ubuntu 24.04 publishes openjdk-11-jre-headless for linux/riscv64 (glibc).
+# TODO: Pin to a SHA256 digest for reproducibility (e.g. ubuntu@sha256:...).
+BASE_IMAGE="ubuntu:24.04"
+
+REPO_ROOT=$(cd "$(dirname "$0")/../../../../.." && pwd)
+
+# Use disk-backed temp: the extracted server (~1GB) blows out a small RAM tmpfs
+# /tmp. Default to a work dir on the same (large) filesystem as the repo.
+: "${TMPDIR:=$REPO_ROOT/.riscv64-smoke-tmp}"
+mkdir -p "$TMPDIR"
+export TMPDIR
+
+# Portable in-place sed: avoids GNU sed -i which breaks on macOS/BSD.
+sedi() {
+    local expr="$1" file="$2"
+    local tmp; tmp=$(mktemp "${TMPDIR:-/tmp}/sedi.XXXXXX")
+    if sed "$expr" "$file" > "$tmp"; then
+        mv "$tmp" "$file"
+    else
+        rm -f "$tmp"
+        return 1
+    fi
+}
+
+# Unique, traceable names so cleanup removes only our own resources.
+# Timestamp + random hex: collision-resistant, still human-readable for 
debugging.
+STAMP=$(date +%Y%m%d%H%M%S)-$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' 
\n')
+TAG="hugegraph-riscv64-smoke:${STAMP}"
+# KEEP: don't auto-cleanup so a failed/finished run stays pokeable.
+VOL="hugegraph-riscv64-data-${STAMP}"
+CONTAINER="hugegraph-riscv64-smoke-${STAMP}"
+
+DIST_ARG="${1:-}"
+DIST_TAR=""
+SERVER_DIR=""
+BUILD_CTX=""
+ROCKS_BUILD=""
+
+GREEN=$'\033[0;32m'; RED=$'\033[0;31m'; NC=$'\033[0m'
+pass() { echo "${GREEN}PASS${NC} $*"; }
+fail() { echo "${RED}FAIL${NC} $*" >&2; }
+
+# Check if the container is still alive (returns 0 if running).
+container_alive() {
+    $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER"
+}
+
+# Run docker exec with container-liveness guard + exit-code logging.
+# Usage: safe_exec exec <container> <cmd> [args...]
+# DOCKER is split into words without eval; handles "docker" or "sudo docker".
+# Returns the command's output on stdout; logs failures to stderr.
+safe_exec() {
+    local rc
+    if ! container_alive; then
+        fail "container '$CONTAINER' is no longer running (before: $*)"
+        dump_health_diagnostics
+        return 1
+    fi
+    set +e
+    local -a docker_parts=($DOCKER)
+    "${docker_parts[@]}" "$@"
+    rc=$?
+    set -e
+    if [[ $rc -ne 0 ]]; then
+        fail "command failed (exit $rc): $*"
+        dump_health_diagnostics
+        return "$rc"
+    fi
+}
+
+# DOCKER: allow callers to override (e.g. "sudo docker"). Defaults to plain 
docker.
+DOCKER=${DOCKER:-docker}
+
+# Dump container diagnostics to stderr. Defined early so the EXIT trap can
+# call it even if the script fails before the main body.
+dump_health_diagnostics() {
+    echo "----- health-check diagnostics ($CONTAINER) -----" >&2
+    $DOCKER inspect --format \
+        'state={{.State.Status}} exit={{.State.ExitCode}} 
error={{.State.Error}}' \
+        "$CONTAINER" >&2 || true
+
+    if $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER"; 
then
+        echo "----- listening processes -----" >&2
+        $DOCKER exec "$CONTAINER" sh -c \
+            'lsof -nP -iTCP:8080 -sTCP:LISTEN || true' >&2 || true
+        for endpoint in versions graphs; do
+            echo "----- GET /$endpoint -----" >&2
+            $DOCKER exec "$CONTAINER" curl -i -sS --connect-timeout 5 
--max-time 15 \
+                "http://127.0.0.1:8080/$endpoint"; >&2 || true
+        done
+    fi
+
+    echo "----- recent container logs -----" >&2
+    $DOCKER logs --tail 100 "$CONTAINER" >&2 || true
+    echo "----- end health-check diagnostics -----" >&2
+}
+
+_exit_handler() {
+    local rc=$?
+    # If the script failed, log the exit code so the root cause isn't lost.
+    if [[ $rc -ne 0 ]]; then
+        echo "" >&2
+        echo "==> Script failed with exit code $rc" >&2
+        if container_alive; then
+            dump_health_diagnostics
+        else
+            echo "==> container '$CONTAINER' is not running (may have been 
OOM-killed or crashed)" >&2
+            # Best-effort: check docker inspect for OOMKilled + dump any 
surviving logs
+            $DOCKER inspect --format \
+                'OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}} 
Status={{.State.Status}}' \
+                "$CONTAINER" >&2 2>/dev/null || true
+            $DOCKER logs --tail 80 "$CONTAINER" >&2 2>/dev/null || true
+        fi
+    fi
+    _cleanup
+    # Propagate cleanup failure: if the main run succeeded but cleanup failed,
+    # exit nonzero so CI records the resource leak.
+    if [[ $rc -eq 0 && $_CLEANUP_OK -ne 0 ]]; then
+        exit 1
+    fi
+    exit "$rc"
+}
+
+_cleanup() {
+    _CLEANUP_OK=0
+    # Dump container logs first so a failure's root cause isn't destroyed by 
cleanup.
+    # This runs even with KEEP=1 so the user gets diagnostics.
+    if $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx 
"$CONTAINER"; then
+        echo "----- container logs ($CONTAINER) -----" >&2
+        $DOCKER logs --tail 60 "$CONTAINER" >&2 || true
+        echo "----- end logs -----" >&2
+        echo "----- hugegraph-server.log -----" >&2
+        $DOCKER cp "$CONTAINER:/server/logs/hugegraph-server.log" - 
2>/dev/null \
+            | tar -xO 2>/dev/null | tail -n 80 >&2 || true
+        echo "----- end hugegraph-server.log -----" >&2
+    fi
+    # KEEP=1: leave everything standing for live poking; skip teardown.
+    if [[ "${KEEP:-}" == "1" ]]; then
+        echo "==> KEEP=1 set — skipping cleanup. Resources left standing:" >&2
+        echo "    container: $CONTAINER" >&2
+        echo "    volume:    $VOL" >&2
+        echo "    image:     $TAG" >&2
+        return 0
+    fi
+    local ok=1
+    $DOCKER rm -f "$CONTAINER" >/dev/null 2>&1 || ok=0
+    # Only remove the volume we created.
+    $DOCKER volume rm "$VOL" >/dev/null 2>&1 || ok=0
+    # Only remove the image we tagged; never prune the host's image store.
+    $DOCKER rmi "$TAG" >/dev/null 2>&1 || ok=0
+    # Remove disk-backed temp work dirs (build context holds the ~1GB extract).
+    if [[ -n "$BUILD_CTX" && -d "$BUILD_CTX" ]]; then
+        rm -rf "$BUILD_CTX" || ok=0
+    fi
+    if [[ -n "$ROCKS_BUILD" && -d "$ROCKS_BUILD" ]]; then
+        rm -rf "$ROCKS_BUILD" || ok=0
+    fi
+    if [[ "$ok" == "1" ]]; then
+        pass "cleanup: container, volume, and image removed"
+    else
+        fail "cleanup: some resources may remain (container=$CONTAINER 
vol=$VOL image=$TAG)"
+        _CLEANUP_OK=1
+    fi
+}
+trap _exit_handler EXIT INT TERM
+
+need() { command -v "$1" >/dev/null 2>&1 || { fail "missing required command: 
$1"; exit 1; }; }
+
+# Preflight host tools before any privileged operations.
+need docker
+$DOCKER buildx version >/dev/null 2>&1 || { fail "docker buildx is required 
(pacman -S docker-buildx)"; exit 1; }
+command -v mvn >/dev/null 2>&1 || { fail "mvn is required to build the 
distribution"; exit 1; }
+command -v javac >/dev/null 2>&1 || { fail "javac is required to compile the 
RocksDB smoke test"; exit 1; }
+
+# Register QEMU user-mode emulation for riscv64 if the Docker daemon can't
+# already run it (otherwise the build/run fails with 'exec format error').
+# binfmt handlers do NOT survive reboots, so a fresh checkout needs this —
+# makes the smoke test self-contained on any x86 host.
+# TODO: Pin to a SHA256 digest for reproducibility (e.g. 
tonistiigi/binfmt@sha256:...).
+#
+# Probe through the Docker daemon, not the host /proc: Docker Desktop keeps
+# binfmt inside its Linux VM; a remote context can have the opposite state.
+if ! $DOCKER run --rm --platform "$PLATFORM" "$BASE_IMAGE" uname -m 
2>/dev/null \
+        | grep -q riscv64; then
+    echo "==> Registering QEMU riscv64 emulation (binfmt)"
+    $DOCKER run --privileged --rm tonistiigi/binfmt:qemu-v8.1.5 --install 
riscv64 >/dev/null
+fi
+
+# 1. Resolve the Server distribution tarball (build on host if not supplied).
+#    Build from the ROOT pom (not hugegraph-server/): the root parent pom
+#    `hugegraph` and the root sibling `hugegraph-struct` must be installed
+#    into ~/.m2 first, or module resolution fails on the ${revision} parent.
+if [[ -n "$DIST_ARG" ]]; then
+    DIST_TAR="$DIST_ARG"
+else
+    echo "==> Building HugeGraph Server distribution on host (native platform)"
+    mvn -f "$REPO_ROOT/pom.xml" package -e -B -ntp \
+        -Dmaven.test.skip=true -Dmaven.javadoc.skip=true -Drat.skip=true \
+        -pl hugegraph-server/hugegraph-dist -am
+    DIST_TAR=$(ls -t 
"$REPO_ROOT"/hugegraph-server/apache-hugegraph-server-*.tar.gz 2>/dev/null | 
head -n1 || true)
+fi
+[[ -f "$DIST_TAR" ]] || { fail "server distribution tarball not found"; exit 
1; }
+echo "==> Using distribution: $DIST_TAR"
+
+# Extract straight into the build context's server/ dir — no second copy
+# (the extracted tree is ~1GB; copying it twice doubled the temp footprint).
+BUILD_CTX=$(mktemp -d "${TMPDIR:-/tmp}/hugegraph-riscv64-ctx.XXXXXX")
+SERVER_DIR="$BUILD_CTX/server"
+mkdir -p "$SERVER_DIR"
+tar -xzf "$DIST_TAR" -C "$SERVER_DIR" --strip-components=1
+
+# Force the RocksDB backend for the smoke run.
+CONF="$SERVER_DIR/conf/graphs/hugegraph.properties"
+grep -qE '^[[:space:]]*backend[[:space:]]*=' "$CONF" \
+    && sedi 's/^[[:space:]]*backend[[:space:]]*=.*/backend=rocksdb/' "$CONF" \
+    || echo "backend=rocksdb" >> "$CONF"
+grep -qE '^[[:space:]]*serializer[[:space:]]*=' "$CONF" \
+    && sedi 's/^[[:space:]]*serializer[[:space:]]*=.*/serializer=binary/' 
"$CONF" \
+    || echo "serializer=binary" >> "$CONF"
+
+# 2. Build a tiny RISC-V runtime image on top of the glibc base.
+#    libatomic1 is installed and exposed via LD_LIBRARY_PATH (+ a /usr/lib
+#    symlink) so RocksDB's __atomic_compare_exchange_1 symbol resolves
+#    automatically — no manual step for the user. If the JNI still lacks the
+#    symbol, the RocksDB step below fails loudly instead of being treated as
+#    the goal.
+# Reuse the repo's own container entrypoint: it runs init-store.sh (create the
+# RocksDB store) BEFORE start-hugegraph.sh. Hand-rolling the start command
+# skips init and the server aborts with an empty log (the failure we hit).
+cp "$REPO_ROOT/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh" \
+    "$BUILD_CTX/server/docker-entrypoint.sh"
+# The entrypoint hardcodes 'start-hugegraph.sh ... -t 120'. Under QEMU 
emulation
+# the analyzer dictionary loads twice (~3.5 min each) plus Gremlin/Groovy 
warmup,
+# so the server needs well over 600s to bind 8080. Raise the startup timeout.
+sedi 's/-t 120/-t 1800/' "$BUILD_CTX/server/docker-entrypoint.sh"
+cat > "$BUILD_CTX/Dockerfile" <<EOF
+FROM ${BASE_IMAGE}
+ENV DEBIAN_FRONTEND=noninteractive
+RUN apt-get -q update && apt-get -q install -y --no-install-recommends 
--no-install-suggests openjdk-11-jre-headless libatomic1 curl jq dumb-init lsof 
procps && apt-get clean && rm -rf /var/lib/apt/lists/*
+# Force libatomic to load before the JNI: rocksdbjni:8.10.2's RISC-V
+# .so has an undefined __atomic_compare_exchange_1 that only libatomic
+# satisfies. LD_LIBRARY_PATH alone is NOT enough (verified: symbol stays
+# unresolved); LD_PRELOAD guarantees libatomic loads first into every
+# Java process (the RocksDB smoke test AND the server).
+# Path is the constant riscv64 multiarch dir confirmed inside the image;
+# dpkg-architecture is unreliable under QEMU emulation, so hardcode it.
+RUN ln -sf /usr/lib/riscv64-linux-gnu/libatomic.so.1 /usr/lib/libatomic.so.1
+WORKDIR /server
+COPY server/ /server/
+RUN chmod 755 /server/docker-entrypoint.sh && sed -i 
"s#^restserver.url.*#restserver.url=http://127.0.0.1:8080#"; 
/server/conf/rest-server.properties
+# The Ubuntu openjdk-11 RISC-V build lacks -XX:G1RSetUpdatingPauseTimePercent,
+# which start-hugegraph.sh always appends for the default G1GC. It is a tuning
+# hint, not required for correctness, so strip it to let the JVM launch.
+RUN sed -i "s/-XX:G1RSetUpdatingPauseTimePercent=5//" 
/server/bin/hugegraph-server.sh
+# JAVA_OPTS mirrors the official image (auth proxy needs --add-exports on JDK 
11).
+# STDOUT_MODE=true routes the server log to stdout so 'docker logs' shows 
failures.
+ENV JAVA_OPTS="-XX:+UnlockExperimentalVMOptions -XX:+UseContainerSupport 
-XX:MaxRAMPercentage=50 
--add-exports=java.base/jdk.internal.reflect=ALL-UNNAMED" 
LD_PRELOAD=/usr/lib/libatomic.so.1 HUGEGRAPH_HOME=server STDOUT_MODE=true
+EXPOSE 8080
+ENTRYPOINT ["/usr/bin/dumb-init", "--"]
+CMD ["/server/docker-entrypoint.sh"]
+EOF
+
+echo "==> Building RISC-V runtime image $TAG"
+$DOCKER buildx build --platform "$PLATFORM" -t "$TAG" "$BUILD_CTX" --load
+
+# 3. Start the server in the background; trap handles cleanup.
+echo "==> Starting server container on ${PLATFORM}"
+$DOCKER run -d --name "$CONTAINER" --platform "$PLATFORM" \
+    --memory=4g --cap-drop ALL --security-opt no-new-privileges \
+    -v "$VOL:/server" "$TAG"
+
+# Wait until /graphs answers 200 (server auto-inits + starts via entrypoint).
+# This is the same readiness endpoint used by start-hugegraph.sh. /versions is
+# asserted separately after the server has completed its startup sequence.
+wait_health() {
+    # This probe starts while the entrypoint is still running init-store.sh.
+    # Under QEMU the analyzer dictionary loads once during initialization and
+    # again during server startup, so the full end-to-end wait needs to exceed
+    # the entrypoint's 1800s startup timeout as well as initialization time.
+    local limit=3600
+    local start=$SECONDS
+    while (( SECONDS - start < limit )); do
+        # Bail fast if the container died instead of polling a corpse for 10 
min.
+        if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx 
"$CONTAINER"; then
+            echo "==> container '$CONTAINER' is no longer running" >&2
+            dump_health_diagnostics
+            return 1
+        fi
+        local code
+        code=$($DOCKER exec "$CONTAINER" \
+            curl -s --connect-timeout 10 --max-time 60 \
+            -o /dev/null -w "%{http_code}" http://127.0.0.1:8080/graphs 
2>/dev/null || true)
+        code=${code:-000}
+        [[ "$code" == "200" ]] && return 0
+        sleep 5
+    done
+    echo "==> health check timed out after ${limit}s (last /graphs HTTP 
status: $code)" >&2
+    dump_health_diagnostics
+    return 1
+}
+
+# 4. Architecture assertion.
+echo "==> Verifying architecture"
+container_alive || { fail "container died before architecture check"; exit 1; }
+ARCH_OK=$($DOCKER exec "$CONTAINER" uname -m)
+[[ "$ARCH_OK" == "riscv64" ]] || { fail "architecture=$ARCH_OK (expected 
riscv64)"; exit 1; }
+pass "architecture: riscv64"
+
+# 5. RocksDB open/put/get/close via a real Java program against the JNI.
+echo "==> RocksDB open/put/get/close test"
+# If the JNI is not linked to libatomic this step's java process dies with
+# "undefined symbol: __atomic_compare_exchange_1" — that is the signal to fix
+# the linkage, NOT the expected final result.
+ROCKS_TEST='import org.rocksdb.*;
+public class RocksSmoke {
+    public static void main(String[] a) throws Exception {
+        RocksDB.loadLibrary();
+        try (Options o = new Options().setCreateIfMissing(true);
+             RocksDB db = RocksDB.open(o, "/tmp/rocksdb-smoke")) {
+            byte[] k = "key".getBytes(java.nio.charset.StandardCharsets.UTF_8);
+            byte[] v = 
"value".getBytes(java.nio.charset.StandardCharsets.UTF_8);
+            db.put(k, v);
+            byte[] got = db.get(k);
+            if (!java.util.Arrays.equals(v, got))
+                throw new AssertionError("rocksdb get mismatch: " + new 
String(got));
+            db.close();

Review Comment:
   ‼️ The standalone RocksDB probe closes its only database instance here and 
immediately reports success without reopening it. The linked HUGEGRAPH-3099 
acceptance criteria require RocksDB `open, put/get, close, and reopen`; the 
later HugeGraph restart uses a different database and cannot verify this JNI 
path. Please open `/tmp/rocksdb-smoke` again after this close and assert that 
the original value is still readable before printing `ROCKS_OK`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to