Skip to content

fix(env): read environment via System.getenv instead of forking a subprocess (#117)#118

Merged
tkuhn merged 2 commits into
mainfrom
fix/issue-117-getenvstring-subprocess
Jun 1, 2026
Merged

fix(env): read environment via System.getenv instead of forking a subprocess (#117)#118
tkuhn merged 2 commits into
mainfrom
fix/issue-117-getenvstring-subprocess

Conversation

@tkuhn
Copy link
Copy Markdown
Contributor

@tkuhn tkuhn commented Jun 1, 2026

Summary

Fixes #117 — the full repo silently falling behind meta, undetectable by the monitor.

Utils.getEnvString read the environment via Apache Commons Exec's EnvironmentUtils.getProcEnvironment(), which forks a subprocess (env/sh) and parses its stdout on every call. Since the per-repo feature flags (FeatureFlags.fullRepoEnabled() and friends) are evaluated for every nanopub, from the 4-thread loading pool, this brittle, non-thread-safe read could intermittently return a mangled value. When the result wasn't a clean "true", fullRepoEnabled() evaluated to false and the full-repo write was skipped for that one nanopub — while pubkey/type/meta proceeded.

The lock-step ordering in executeLoading only guarantees full ≥ meta while the full task is submitted; the flag gate sits before submission, outside the wait-for-all guard. So a single mis-read leaves a permanent, invisible hole: the monitor and the Nanopub-Query-Loaded-Nanopub-Count/-Checksum headers both source from meta.

This matches the observed signature exactly: sparse, intermittent, recent, full-only misses where the missing nanopubs are present in meta and their per-pubkey repos (meta=true, pubkey=true, full=false, zero content in full — never written, not deleted).

Changes

Remediation note

This stops the gap from growing, but does not backfill nanopubs already missing from full — those must be rebuilt via FORCE_RESYNC/FORCE_RELOAD on affected instances (already done on nanodash and knowledgepixels; petapico still pending at time of writing).

Testing

./mvnw -o compile passes. The change is a behavior-preserving swap of the env-read mechanism (subprocess fork → in-memory lookup) with identical fallback semantics.

🤖 Generated with Claude Code

…process

Utils.getEnvString read the environment via Apache Commons Exec's
EnvironmentUtils.getProcEnvironment(), which forks a subprocess (env/sh)
and parses its stdout on every call. This method is on the per-nanopub
hot path — FeatureFlags.fullRepoEnabled() and the other repo flags are
evaluated for every nanopub, from the 4-thread loading pool. The
subprocess read is neither thread-safe nor robust under load, so an
intermittently mangled result made fullRepoEnabled() evaluate to false
and silently skip the full-repo write for individual nanopubs. Because
the meta task is deferred until the (submitted) per-repo tasks succeed,
but the full task is gated by the flag *before* submission, this leaves
the full repo behind meta with no error — and undetectably, since the
monitor and the Nanopub-Query-Loaded-Nanopub-Count/-Checksum headers
source from meta (issue #117).

Switch getEnvString to System.getenv, which reads the JVM's in-memory
environment block: thread-safe, allocation-free, deterministic, and it
never forks or throws.

Also retire the same getProcEnvironment() mechanism in the TripleStore
constructor. It is startup-only (not part of the #117 trigger), but a
fork failure there throws IOException and leaves the singleton
uninitialised; System.getenv removes that failure mode and the last use
of the fragile API. ENDPOINT_BASE/ENDPOINT_TYPE are container env vars
set at launch, so the read is semantically identical.

Remediation note: this stops the gap from growing but does not backfill
nanopubs already missing from full — those must be rebuilt via
FORCE_RESYNC/FORCE_RELOAD on affected instances.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
UtilsTest.getEnvString/getEnvInt mocked EnvironmentUtils.getProcEnvironment(),
which no longer governs getEnvString. System.getenv can't be mocked directly
(Mockito refuses to mock java.lang.System), so extract a package-private
getRawEnv(name) seam around System.getenv and stub that via the existing
mockStatic(Utils.class, CALLS_REAL_METHODS) pattern. Dropped the obsolete
IOException-throw case (System.getenv doesn't throw).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@tkuhn tkuhn merged commit e4cd421 into main Jun 1, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

full repo can fall behind meta repo, undetected by the monitor (meta-vs-full load gap)

1 participant