From cf9ef3f40debf447e36d919f6a5ceb8290a0d2b7 Mon Sep 17 00:00:00 2001 From: imbajin Date: Tue, 13 Oct 2026 01:48:56 +0800 Subject: [PATCH 1/4] fix(server): reject unsafe cgroup cache budgets - exclude cache protected by child memory.min - reject finite limits with unknown memory usage - preserve ordinary warm-cache and v1 behavior - cover permission failures and document conservative sizing --- docs/server-runtime.md | 24 ++++ .../src/assembly/static/bin/util.sh | 35 +++++- .../src/assembly/travis/test-cgroup-memory.sh | 119 +++++++++++++++++- 3 files changed, 174 insertions(+), 4 deletions(-) diff --git a/docs/server-runtime.md b/docs/server-runtime.md index d35b6b34ab..eadf24afb0 100644 --- a/docs/server-runtime.md +++ b/docs/server-runtime.md @@ -28,6 +28,30 @@ uses G1 with an occupancy threshold of 45 when `JAVA_OPTIONS` is empty. Do not combine explicit `-g` selection with a different collector in `JAVA_OPTIONS` or `-j`; the JVM rejects conflicting collector selections. +### Launcher memory budget + +The default launcher heap uses half the smaller of host available memory and the +remaining budget under visible cgroup v1/v2 hard limits, with a 512 MiB minimum and a +32 GiB maximum. It refuses startup when the effective budget is below 512 MiB. +If a known hard limit has missing, unreadable or invalid usage, the budget is zero; +the launcher refuses startup instead of using host memory. + +Inactive file cache can contribute to this budget on a warm restart. For a finite +cgroup v2 limit, the launcher checks the immediate cgroup children of that reclaim +target. If any child's `memory.min` is nonzero, missing, invalid or unreadable, it +excludes cache credit at that limit: child cache may be protected from ancestor reclaim. +If the target directory cannot be enumerated, or a child cannot be searched, cache +credit is also excluded because child protection cannot be determined. Ordinary +searchable directories without cgroup controller files are skipped. +Unprotected children retain the existing warm-cache budget, including inherited and +nested hard limits. A cgroup's own `memory.min` does not block cache credit for its +own limit; another ancestor can still constrain it through child protection. +The cgroup v1 cache calculation is unchanged. + +This is a startup estimate, not a guarantee against runtime OOM. Native allocations +and concurrent processes also consume memory. Explicit heap options retain their +existing precedence; see [container heap sizing](../docker/README.md#server-heap-sizing). + ### Serializer configuration Use the Gremlin configuration files shipped with the upgraded Server. Serializer classes diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/util.sh b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/util.sh index 5641a52bdd..d7893f31ae 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/util.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/util.sh @@ -497,15 +497,20 @@ function cgroup_free_memory() { dir="${dir%/}" fi local limit usage inactive available smallest="" + local child child_min local cache_key=inactive_file [[ "$version" != cgroup ]] || cache_key=total_inactive_file while :; do limit="" - usage=0 + usage="" [[ ! -r "$dir/$limit_file" ]] || read -r limit < "$dir/$limit_file" || true - [[ ! -r "$dir/$usage_file" ]] || read -r usage < "$dir/$usage_file" || true if [[ "$limit" =~ ^[0-9]+$ ]]; then - [[ "$usage" =~ ^[0-9]+$ ]] || usage=0 + # Unknown usage under a known limit must not fall back to host memory. + if [[ ! -r "$dir/$usage_file" ]] || ! read -r usage < "$dir/$usage_file" || + [[ ! "$usage" =~ ^[0-9]+$ ]]; then + echo 0 + return 0 + fi # Inactive file cache can be reclaimed on a warm restart. For v1, # use the hierarchical total so ancestor budgets include children. inactive=0 @@ -513,6 +518,30 @@ function cgroup_free_memory() { inactive=$(awk -v key="$cache_key" '$1 == key {print $2; exit}' "$dir/memory.stat") fi [[ "$inactive" =~ ^[0-9]+$ ]] || inactive=0 + if [[ "$version" == cgroup2 && "$inactive" != 0 ]]; then + # A child's protected cache cannot be reclaimed by this limit. + # The target's own memory.min does not protect against its own reclaim. + if [[ ! -r "$dir" || ! -x "$dir" ]]; then + # Known files can be readable while child enumeration is denied. + inactive=0 + else + for child in "$dir"/* "$dir"/.[!.]* "$dir"/..?*; do + [[ -d "$child" ]] || continue + # An unsearchable directory can hide a protected cgroup. + if [[ ! -x "$child" ]]; then + inactive=0 + break + fi + [[ -e "$child/memory.current" && -e "$child/memory.max" ]] || continue + child_min="" + [[ ! -r "$child/memory.min" ]] || read -r child_min < "$child/memory.min" || true + if [[ "$child_min" != 0 ]]; then + inactive=0 + break + fi + done + fi + fi # Inconsistent snapshots receive no cache credit. awk also avoids # shell integer overflow for v1's unlimited sentinel. available=$(awk -v limit="$limit" -v usage="$usage" -v inactive="$inactive" 'BEGIN { diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-cgroup-memory.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-cgroup-memory.sh index 9e570e2537..48d96f20e8 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-cgroup-memory.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-cgroup-memory.sh @@ -22,11 +22,12 @@ UTIL=${1:?Usage: $0 UTIL_SH} # shellcheck disable=SC1090,SC2329 source "$UTIL" FIXTURE=$(mktemp -d) -trap 'rm -rf "$FIXTURE"' EXIT +trap 'chmod -R u+rwX "$FIXTURE"; rm -rf "$FIXTURE"' EXIT ROOT="$FIXTURE/cgroup" MEMBERSHIP="$FIXTURE/membership" MOUNTINFO="$FIXTURE/mountinfo" mkdir -p "$ROOT" +printf '0\n' > "$ROOT/memory.min" assert_memory() { local expected=$1 actual actual=$(cgroup_free_memory "$ROOT" "$MEMBERSHIP" "$MOUNTINFO") @@ -35,6 +36,45 @@ assert_memory() { exit 1 } } +# Verify unknown usage and its zero-budget path through the actual launcher helpers. +assert_unknown_usage() { + local usage_file=$1 valid_usage=$2 + printf 'invalid\n' > "$usage_file" + assert_memory 0 + : > "$usage_file" + assert_memory 0 + rm "$usage_file" + assert_memory 0 + ( + # Only supply host data; the cgroup helper still reads the real fixture. + # shellcheck disable=SC2329 + uname() { echo Linux; } + # shellcheck disable=SC2329 + cat() { + if [[ "$1" == /proc/meminfo ]]; then + printf 'MemFree: 1048576 kB\nBuffers: 0 kB\nCached: 0 kB\n' + else + command cat "$@" + fi + } + # shellcheck disable=SC2329 + cgroup_free_memory() { + bash -c 'source "$1"; cgroup_free_memory "$2" "$3" "$4"' fixture \ + "$UTIL" "$ROOT" "$MEMBERSHIP" "$MOUNTINFO" + } + [[ $(free_memory) == 0 ]] + if (calc_xmx 512 32768); then + echo 'unknown cgroup usage fell back to host memory' >&2 + exit 1 + fi + ) + printf '%s\n' "$valid_usage" > "$usage_file" + chmod 000 "$usage_file" + if [[ ! -r "$usage_file" ]]; then + assert_memory 0 + fi + chmod 600 "$usage_file" +} # No controller keeps the original host fallback. assert_memory "" printf '0::/\n' > "$MEMBERSHIP" @@ -45,6 +85,8 @@ assert_memory "" printf '4294967296\n' > "$ROOT/memory.max" printf '1073741824\n' > "$ROOT/memory.current" assert_memory 3072 +assert_unknown_usage "$ROOT/memory.current" 1073741824 +assert_memory 3072 # Usage at/over limit must not become a negative or wrapped number. printf '4294967296\n' > "$ROOT/memory.current" assert_memory 0 @@ -52,6 +94,8 @@ printf '5368709120\n' > "$ROOT/memory.current" assert_memory 0 # A finite ancestor constrains an unlimited child. mkdir -p "$ROOT/team/service" +printf '0\n' > "$ROOT/team/memory.min" +printf '0\n' > "$ROOT/team/service/memory.min" printf '0::/team/service\n' > "$MEMBERSHIP" printf 'max\n' > "$ROOT/team/service/memory.max" printf '0\n' > "$ROOT/team/service/memory.current" @@ -77,6 +121,8 @@ printf '0\n' > "$ROOT/memory/memory.usage_in_bytes" printf '2147483648\n' > "$ROOT/memory/job/memory.limit_in_bytes" printf '536870912\n' > "$ROOT/memory/job/memory.usage_in_bytes" assert_memory 1536 +assert_unknown_usage "$ROOT/memory/job/memory.usage_in_bytes" 536870912 +assert_memory 1536 # Keep launcher min/max semantics; insufficient effective memory fails. printf '0::/unified\n6:memory:/job\n' > "$MEMBERSHIP" assert_memory 1536 @@ -100,6 +146,7 @@ assert_memory 1536 # Skip unrelated bind mounts before applying namespace fallback. UNRELATED="$FIXTURE/unrelated" mkdir -p "$UNRELATED" +printf '0\n' > "$UNRELATED/memory.min" printf '17179869184\n' > "$UNRELATED/memory.max" printf '0\n' > "$UNRELATED/memory.current" printf '0::/team/service\n' > "$MEMBERSHIP" @@ -146,6 +193,76 @@ assert_memory 1024 printf '6:memory:/job\n' > "$MEMBERSHIP" printf 'inactive_file 0\ntotal_inactive_file 536870912\n' > "$ROOT/memory/job/memory.stat" assert_memory 2048 +# A separate v2 hierarchy preserves ordinary dual/inherited warm limits. +ROOT="$FIXTURE/v2-warm" +mkdir -p "$ROOT/target" "$ROOT/sibling" "$ROOT/notes" +for dir in "$ROOT" "$ROOT/target" "$ROOT/sibling"; do + printf '0\n' > "$dir/memory.min" + printf 'max\n' > "$dir/memory.max" + printf '0\n' > "$dir/memory.current" + printf 'inactive_file 0\n' > "$dir/memory.stat" +done +printf '0::/target\n' > "$MEMBERSHIP" +: > "$MOUNTINFO" +printf '2147483648\n' > "$ROOT/memory.max" +printf '1610612736\n' > "$ROOT/memory.current" +printf 'inactive_file 1610612736\n' > "$ROOT/memory.stat" +printf '2147483648\n' > "$ROOT/target/memory.max" +printf '1610612736\n' > "$ROOT/target/memory.current" +printf 'inactive_file 1610612736\n' > "$ROOT/target/memory.stat" +# A directory without controller files is not a cgroup child. +printf '1610612736\n' > "$ROOT/notes/memory.min" +assert_memory 2048 +printf 'max\n' > "$ROOT/target/memory.max" +assert_memory 2048 +# Protection is relative to the reclaim target: its own min is ignored. +printf 'max\n' > "$ROOT/memory.max" +printf '2147483648\n' > "$ROOT/target/memory.max" +printf '1610612736\n' > "$ROOT/target/memory.min" +assert_memory 2048 +printf '0\n' > "$ROOT/target/memory.min" +printf '2147483648\n' > "$ROOT/memory.max" +# Move the warm cache to a protected sibling; the ancestor must not credit it. +printf '0\n' > "$ROOT/target/memory.current" +printf 'inactive_file 0\n' > "$ROOT/target/memory.stat" +printf '1610612736\n' > "$ROOT/sibling/memory.current" +printf 'inactive_file 1610612736\n' > "$ROOT/sibling/memory.stat" +printf '1610612736\n' > "$ROOT/sibling/memory.min" +assert_memory 512 +printf 'invalid\n' > "$ROOT/sibling/memory.min" +assert_memory 512 +rm "$ROOT/sibling/memory.min" +assert_memory 512 +printf '0\n' > "$ROOT/sibling/memory.min" +chmod 000 "$ROOT/sibling/memory.min" +if [[ ! -r "$ROOT/sibling/memory.min" ]]; then + assert_memory 512 +fi +chmod 600 "$ROOT/sibling/memory.min" +assert_memory 2048 +# Hidden child cgroups (as used by systemd) have the same protection semantics. +mv "$ROOT/sibling" "$ROOT/.sibling" +printf '1610612736\n' > "$ROOT/.sibling/memory.min" +assert_memory 512 +# Actual filesystem permissions must not disguise a cgroup as an ordinary dir. +printf '0\n' > "$ROOT/.sibling/memory.min" +chmod 000 "$ROOT/.sibling" +if [[ ! -x "$ROOT/.sibling" ]]; then + assert_memory 512 +else + # Root with DAC override can still read the actual zero protection value. + assert_memory 2048 +fi +chmod 700 "$ROOT/.sibling" +# Search permission alone permits known files but cannot enumerate children. +chmod 111 "$ROOT" +if [[ ! -r "$ROOT" ]]; then + assert_memory 512 +else + # Root with DAC override can enumerate the unprotected children. + assert_memory 2048 +fi +chmod 700 "$ROOT" # shellcheck disable=SC2329 free_memory() { echo 3072; } [[ $(calc_xmx 512 32768) == 1536 ]] From a2888b55fcd2eb284baaa716b217815d31468e74 Mon Sep 17 00:00:00 2001 From: imbajin Date: Tue, 13 Oct 2026 02:08:45 +0800 Subject: [PATCH 2/4] fix(server): keep v1 unlimited memory fallback - derive the v1 sentinel from native word and page sizes - continue checking finite ancestors of unlimited cgroups - retain host sizing when no finite cgroup limit exists - verify unknown-usage and sentinel boundaries --- docs/server-runtime.md | 4 +- .../src/assembly/static/bin/util.sh | 18 ++++- .../src/assembly/travis/test-cgroup-memory.sh | 76 ++++++++++++++++++- 3 files changed, 95 insertions(+), 3 deletions(-) diff --git a/docs/server-runtime.md b/docs/server-runtime.md index eadf24afb0..cefd2caf19 100644 --- a/docs/server-runtime.md +++ b/docs/server-runtime.md @@ -34,7 +34,9 @@ The default launcher heap uses half the smaller of host available memory and the remaining budget under visible cgroup v1/v2 hard limits, with a 512 MiB minimum and a 32 GiB maximum. It refuses startup when the effective budget is below 512 MiB. If a known hard limit has missing, unreadable or invalid usage, the budget is zero; -the launcher refuses startup instead of using host memory. +the launcher refuses startup instead of using host memory. Cgroup v1 numeric unlimited +levels are skipped so readable finite ancestors still apply; without a finite limit, +host sizing remains available. The sentinel uses the native long and page sizes. Inactive file cache can contribute to this budget on a warm restart. For a finite cgroup v2 limit, the launcher checks the immediate cgroup children of that reclaim diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/util.sh b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/util.sh index d7893f31ae..e329a9f0cb 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/util.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/util.sh @@ -499,11 +499,27 @@ function cgroup_free_memory() { local limit usage inactive available smallest="" local child child_min local cache_key=inactive_file - [[ "$version" != cgroup ]] || cache_key=total_inactive_file + local unlimited="" long_bits="" page_size="" + if [[ "$version" == cgroup ]]; then + cache_key=total_inactive_file + # v1 exposes PAGE_COUNTER_MAX * PAGE_SIZE instead of "max". + long_bits=$(getconf LONG_BIT 2>/dev/null) || true + page_size=$(getconf PAGESIZE 2>/dev/null) || true + if [[ "$page_size" =~ ^[1-9][0-9]*$ ]]; then + case "$long_bits" in + 64) unlimited=$((9223372036854775807 / page_size * page_size)) ;; + 32) unlimited=$(awk -v page="$page_size" 'BEGIN {printf "%.0f", 2147483647 * page}') ;; + esac + fi + fi while :; do limit="" usage="" [[ ! -r "$dir/$limit_file" ]] || read -r limit < "$dir/$limit_file" || true + if [[ -n "$unlimited" && "$limit" =~ ^[0-9]+$ ]] && + awk -v limit="$limit" -v unlimited="$unlimited" 'BEGIN {exit !(limit >= unlimited)}'; then + limit="" + fi if [[ "$limit" =~ ^[0-9]+$ ]]; then # Unknown usage under a known limit must not fall back to host memory. if [[ ! -r "$dir/$usage_file" ]] || ! read -r usage < "$dir/$usage_file" || diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-cgroup-memory.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-cgroup-memory.sh index 48d96f20e8..3c91aa419b 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-cgroup-memory.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-cgroup-memory.sh @@ -114,15 +114,89 @@ assert_memory 3072 printf 'invalid\n' > "$ROOT/memory.max" assert_memory "" # v1 has different names and a numeric unlimited sentinel. +LONG_BITS=$(getconf LONG_BIT) +PAGE_SIZE=$(getconf PAGESIZE) +case "$LONG_BITS" in + 64) V1_UNLIMITED=$((9223372036854775807 / PAGE_SIZE * PAGE_SIZE)) ;; + 32) V1_UNLIMITED=$(awk -v page="$PAGE_SIZE" 'BEGIN {printf "%.0f", 2147483647 * page}') ;; + *) echo 'unsupported native long size for the v1 fixture' >&2; exit 1 ;; +esac mkdir -p "$ROOT/memory/job" printf '5:cpu,cpuacct:/job\n6:memory:/job\n' > "$MEMBERSHIP" -printf '9223372036854771712\n' > "$ROOT/memory/memory.limit_in_bytes" +printf '%s\n' "$V1_UNLIMITED" > "$ROOT/memory/memory.limit_in_bytes" printf '0\n' > "$ROOT/memory/memory.usage_in_bytes" printf '2147483648\n' > "$ROOT/memory/job/memory.limit_in_bytes" printf '536870912\n' > "$ROOT/memory/job/memory.usage_in_bytes" assert_memory 1536 assert_unknown_usage "$ROOT/memory/job/memory.usage_in_bytes" 536870912 assert_memory 1536 +# A v1 unlimited child must not reject unreadable usage or hide a finite ancestor. +printf '%s\n' "$V1_UNLIMITED" > "$ROOT/memory/job/memory.limit_in_bytes" +printf '2147483648\n' > "$ROOT/memory/memory.limit_in_bytes" +printf '536870912\n' > "$ROOT/memory/memory.usage_in_bytes" +printf 'invalid\n' > "$ROOT/memory/job/memory.usage_in_bytes" +assert_memory 1536 +if [[ "$LONG_BITS" == 64 ]]; then + # The existing 64-bit/4-KiB sentinel is also an unlimited boundary. + printf '9223372036854771712\n' > "$ROOT/memory/job/memory.limit_in_bytes" + assert_memory 1536 + printf '%s\n' "$V1_UNLIMITED" > "$ROOT/memory/job/memory.limit_in_bytes" +fi +rm "$ROOT/memory/job/memory.usage_in_bytes" +assert_memory 1536 +printf '536870912\n' > "$ROOT/memory/job/memory.usage_in_bytes" +chmod 000 "$ROOT/memory/job/memory.usage_in_bytes" +assert_memory 1536 +chmod 600 "$ROOT/memory/job/memory.usage_in_bytes" +# Native-query failures must keep unknown numeric-limit usage conservative. +rm "$ROOT/memory/job/memory.usage_in_bytes" +# The page immediately below the sentinel is finite and still needs usage. +printf '%s\n' "$((V1_UNLIMITED - PAGE_SIZE))" > "$ROOT/memory/job/memory.limit_in_bytes" +assert_memory 0 +printf '%s\n' "$V1_UNLIMITED" > "$ROOT/memory/job/memory.limit_in_bytes" +( + # shellcheck disable=SC2329 + getconf() { return 127; } + assert_memory 0 +) +( + # shellcheck disable=SC2329 + getconf() { echo invalid; } + assert_memory 0 +) +( + # shellcheck disable=SC2329 + getconf() { [[ "$1" != LONG_BIT ]] || echo 64; } + assert_memory 0 +) +# With no finite limits, unknown unlimited usage still permits host sizing. +printf '%s\n' "$V1_UNLIMITED" > "$ROOT/memory/memory.limit_in_bytes" +rm "$ROOT/memory/memory.usage_in_bytes" +assert_memory "" +( + # Supply host data; the actual helper reads the unlimited fixture. + # shellcheck disable=SC2329 + uname() { echo Linux; } + # shellcheck disable=SC2329 + cat() { + if [[ "$1" == /proc/meminfo ]]; then + printf 'MemFree: 1048576 kB\nBuffers: 0 kB\nCached: 0 kB\n' + else + command cat "$@" + fi + } + # shellcheck disable=SC2329 + cgroup_free_memory() { + bash -c 'source "$1"; cgroup_free_memory "$2" "$3" "$4"' fixture \ + "$UTIL" "$ROOT" "$MEMBERSHIP" "$MOUNTINFO" + } + [[ $(free_memory) == 1024 ]] + [[ $(calc_xmx 512 32768) == 512 ]] +) +printf '0\n' > "$ROOT/memory/memory.usage_in_bytes" +printf '2147483648\n' > "$ROOT/memory/job/memory.limit_in_bytes" +printf '536870912\n' > "$ROOT/memory/job/memory.usage_in_bytes" +assert_memory 1536 # Keep launcher min/max semantics; insufficient effective memory fails. printf '0::/unified\n6:memory:/job\n' > "$MEMBERSHIP" assert_memory 1536 From e1613fe971ada3ac1494f75b1d715c0be0dbf681 Mon Sep 17 00:00:00 2001 From: imbajin Date: Tue, 13 Oct 2026 10:04:39 +0800 Subject: [PATCH 3/4] fix(server): detect cgroup kernel limit width - infer v1 unlimited limits from known kernel machines - preserve finite limits on unknown or masked machines - cover compatibility metadata and missing usage --- .../src/assembly/static/bin/util.sh | 12 ++-- .../src/assembly/travis/test-cgroup-memory.sh | 66 ++++++++++++++----- 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/util.sh b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/util.sh index e329a9f0cb..65720021eb 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/util.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/util.sh @@ -499,16 +499,18 @@ function cgroup_free_memory() { local limit usage inactive available smallest="" local child child_min local cache_key=inactive_file - local unlimited="" long_bits="" page_size="" + local unlimited="" kernel_machine="" page_size="" if [[ "$version" == cgroup ]]; then cache_key=total_inactive_file # v1 exposes PAGE_COUNTER_MAX * PAGE_SIZE instead of "max". - long_bits=$(getconf LONG_BIT 2>/dev/null) || true + # LONG_BIT describes userspace, not the kernel. Personality can also + # mask uname as 32-bit, so infer unlimited only from known 64-bit names. + kernel_machine=$(uname -m 2>/dev/null) || true page_size=$(getconf PAGESIZE 2>/dev/null) || true if [[ "$page_size" =~ ^[1-9][0-9]*$ ]]; then - case "$long_bits" in - 64) unlimited=$((9223372036854775807 / page_size * page_size)) ;; - 32) unlimited=$(awk -v page="$page_size" 'BEGIN {printf "%.0f", 2147483647 * page}') ;; + case "$kernel_machine" in + x86_64|aarch64|riscv64) + unlimited=$((9223372036854775807 / page_size * page_size)) ;; esac fi fi diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-cgroup-memory.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-cgroup-memory.sh index 3c91aa419b..860bbffe81 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-cgroup-memory.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-cgroup-memory.sh @@ -48,7 +48,7 @@ assert_unknown_usage() { ( # Only supply host data; the cgroup helper still reads the real fixture. # shellcheck disable=SC2329 - uname() { echo Linux; } + uname() { [[ "${1:-}" != -m ]] && echo Linux || echo x86_64; } # shellcheck disable=SC2329 cat() { if [[ "$1" == /proc/meminfo ]]; then @@ -113,14 +113,13 @@ assert_memory 3072 # Malformed limits do not introduce arithmetic errors or an artificial zero. printf 'invalid\n' > "$ROOT/memory.max" assert_memory "" -# v1 has different names and a numeric unlimited sentinel. -LONG_BITS=$(getconf LONG_BIT) +# The v1 fixture represents a known 64-bit Linux kernel, independently of the +# test host's userspace or machine name (the checks also run on macOS). +# shellcheck disable=SC2329 +uname() { [[ "${1:-}" != -m ]] && command uname "$@" || echo x86_64; } +export -f uname PAGE_SIZE=$(getconf PAGESIZE) -case "$LONG_BITS" in - 64) V1_UNLIMITED=$((9223372036854775807 / PAGE_SIZE * PAGE_SIZE)) ;; - 32) V1_UNLIMITED=$(awk -v page="$PAGE_SIZE" 'BEGIN {printf "%.0f", 2147483647 * page}') ;; - *) echo 'unsupported native long size for the v1 fixture' >&2; exit 1 ;; -esac +V1_UNLIMITED=$((9223372036854775807 / PAGE_SIZE * PAGE_SIZE)) mkdir -p "$ROOT/memory/job" printf '5:cpu,cpuacct:/job\n6:memory:/job\n' > "$MEMBERSHIP" printf '%s\n' "$V1_UNLIMITED" > "$ROOT/memory/memory.limit_in_bytes" @@ -136,12 +135,10 @@ printf '2147483648\n' > "$ROOT/memory/memory.limit_in_bytes" printf '536870912\n' > "$ROOT/memory/memory.usage_in_bytes" printf 'invalid\n' > "$ROOT/memory/job/memory.usage_in_bytes" assert_memory 1536 -if [[ "$LONG_BITS" == 64 ]]; then - # The existing 64-bit/4-KiB sentinel is also an unlimited boundary. - printf '9223372036854771712\n' > "$ROOT/memory/job/memory.limit_in_bytes" - assert_memory 1536 - printf '%s\n' "$V1_UNLIMITED" > "$ROOT/memory/job/memory.limit_in_bytes" -fi +# The existing 64-bit/4-KiB sentinel is also an unlimited boundary. +printf '9223372036854771712\n' > "$ROOT/memory/job/memory.limit_in_bytes" +assert_memory 1536 +printf '%s\n' "$V1_UNLIMITED" > "$ROOT/memory/job/memory.limit_in_bytes" rm "$ROOT/memory/job/memory.usage_in_bytes" assert_memory 1536 printf '536870912\n' > "$ROOT/memory/job/memory.usage_in_bytes" @@ -166,9 +163,46 @@ printf '%s\n' "$V1_UNLIMITED" > "$ROOT/memory/job/memory.limit_in_bytes" ) ( # shellcheck disable=SC2329 - getconf() { [[ "$1" != LONG_BIT ]] || echo 64; } + getconf() { [[ "$1" != PAGESIZE ]] || return 0; } assert_memory 0 ) +# A 32-bit userspace cannot turn an 8-TiB finite limit into unlimited. +( + # shellcheck disable=SC2329 + getconf() { [[ "$1" != LONG_BIT ]] && echo 4096 || echo 32; } + printf '8796093022208\n' > "$ROOT/memory/memory.limit_in_bytes" + printf '8795824586752\n' > "$ROOT/memory/memory.usage_in_bytes" + printf '9223372036854771712\n' > "$ROOT/memory/job/memory.limit_in_bytes" + for machine in x86_64 aarch64 riscv64; do + # shellcheck disable=SC2329 + uname() { echo "$machine"; } + assert_memory 256 + done + # Unknown/32-bit-looking names, including personality-masked uname, must + # retain finite limits rather than infer a 32-bit unlimited sentinel. + printf '8796093022208\n' > "$ROOT/memory/job/memory.limit_in_bytes" + printf '8795824586752\n' > "$ROOT/memory/job/memory.usage_in_bytes" + for machine in unknown i686 armv8l; do + assert_memory 256 + rm "$ROOT/memory/job/memory.usage_in_bytes" + assert_memory 0 + printf '9223372036854771712\n' > "$ROOT/memory/job/memory.limit_in_bytes" + assert_memory 0 + printf '8796093022208\n' > "$ROOT/memory/job/memory.limit_in_bytes" + printf '8795824586752\n' > "$ROOT/memory/job/memory.usage_in_bytes" + done + # shellcheck disable=SC2329 + uname() { return 127; } + assert_memory 256 + rm "$ROOT/memory/job/memory.usage_in_bytes" + assert_memory 0 + printf '9223372036854771712\n' > "$ROOT/memory/job/memory.limit_in_bytes" + assert_memory 0 +) +# Restore the original finite-ancestor/unlimited-child fixture. +printf '2147483648\n' > "$ROOT/memory/memory.limit_in_bytes" +printf '536870912\n' > "$ROOT/memory/memory.usage_in_bytes" +printf '%s\n' "$V1_UNLIMITED" > "$ROOT/memory/job/memory.limit_in_bytes" # With no finite limits, unknown unlimited usage still permits host sizing. printf '%s\n' "$V1_UNLIMITED" > "$ROOT/memory/memory.limit_in_bytes" rm "$ROOT/memory/memory.usage_in_bytes" @@ -176,7 +210,7 @@ assert_memory "" ( # Supply host data; the actual helper reads the unlimited fixture. # shellcheck disable=SC2329 - uname() { echo Linux; } + uname() { [[ "${1:-}" != -m ]] && echo Linux || echo x86_64; } # shellcheck disable=SC2329 cat() { if [[ "$1" == /proc/meminfo ]]; then From d71b4d512291cb0b2ad45eb923fc5c88851c85cb Mon Sep 17 00:00:00 2001 From: imbajin Date: Tue, 13 Oct 2026 12:19:44 +0800 Subject: [PATCH 4/4] docs(server): align cgroup sentinel wording - describe kernel architecture and page-size recognition - preserve finite limits for unknown architectures - keep runtime and deployment verification boundaries --- docs/server-runtime.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/server-runtime.md b/docs/server-runtime.md index cefd2caf19..bff7c51783 100644 --- a/docs/server-runtime.md +++ b/docs/server-runtime.md @@ -34,9 +34,13 @@ The default launcher heap uses half the smaller of host available memory and the remaining budget under visible cgroup v1/v2 hard limits, with a 512 MiB minimum and a 32 GiB maximum. It refuses startup when the effective budget is below 512 MiB. If a known hard limit has missing, unreadable or invalid usage, the budget is zero; -the launcher refuses startup instead of using host memory. Cgroup v1 numeric unlimited -levels are skipped so readable finite ancestors still apply; without a finite limit, -host sizing remains available. The sentinel uses the native long and page sizes. +the launcher refuses startup instead of using host memory. For cgroup v1, numeric +unlimited values are recognized only when `uname -m` reports `x86_64`, `aarch64` or +`riscv64` and `getconf PAGESIZE` returns a positive integer. The threshold is the +signed 64-bit maximum rounded down to that page size. Recognized unlimited levels +are skipped so readable finite ancestors still apply; without a finite limit, +host sizing remains available. Unknown or masked machine names, or an unavailable +or invalid page size, keep numeric limits on the finite-limit path. Inactive file cache can contribute to this budget on a warm restart. For a finite cgroup v2 limit, the launcher checks the immediate cgroup children of that reclaim