You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spark.apache.org by gu...@apache.org on 2026/10/20 12:15:56 UTC
(spark) branch master updated: [SPARK-60340][CORE] Avoid System.getenv on every JavaUtils.isTesting call
This is an automated email from the ASF dual-hosted git repository.
HyukjinKwon pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/master by this push:
new 5e2cef87a190 [SPARK-60340][CORE] Avoid System.getenv on every JavaUtils.isTesting call
5e2cef87a190 is described below
commit 5e2cef87a19035f5dc3b840aaab3c78b46399ce8
Author: Sebastian Hillig <se...@databricks.com>
AuthorDate: Tue Oct 20 21:15:41 2026 +0900
[SPARK-60340][CORE] Avoid System.getenv on every JavaUtils.isTesting call
### What changes were proposed in this pull request?
This PR makes `JavaUtils.isTesting()` look up the `SPARK_TESTING` environment variable once, caching it in a `private static final boolean`, instead of calling `System.getenv("SPARK_TESTING")` on every invocation. The `spark.testing` system property is still read on every call, and the evaluation order (env first, then property) is unchanged.
`Utils.isTesting` and `SparkEnvUtils.isTesting` delegate to this method, so they benefit too.
### Why are the changes needed?
On Linux, `System.getenv(String)` goes through `java.lang.ProcessEnvironment`, which wraps the name in a new byte[]-backed `Variable` on every lookup, so each `isTesting()` call allocates (56 bytes per call in the measurement below). `isTesting` is on hot paths. For example, `AnalysisHelper.assertNotAnalysisRule` calls it from the `LogicalPlan` overrides of `transformDownWithPruning` / `transformUpWithPruning` / `transformAllExpressionsWithPruning`, which run once per plan node visite [...]
A JVM's environment cannot change after startup, so caching the env lookup is safe. The system property cannot be cached: tests set and clear it at runtime (`SparkTestSuite.beforeAll`, `BenchmarkBase`, `KryoSerializerBenchmark`). The only code that mutates the process environment reflectively (`SparkConnectClientSuite.withEnvs`, `JavaXmlSuite.setEnv`) touches `SPARK_REMOTE` / `SPARK_LOCAL_IP`, not `SPARK_TESTING`. Code that sets `SPARK_TESTING` for a child process via `ProcessBuilder. [...]
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
Existing tests:
```
build/sbt common-utils-java/test common-utils/test 'network-common/testOnly *JavaUtilsSuite' catalyst/compile
```
All passed (common-utils-java: 25, common-utils: 121, JavaUtilsSuite: 2), and catalyst compiled.
I also ran a manual allocation check (not committed): 1M calls on one thread, measured with `ThreadMXBean.getThreadAllocatedBytes`, `SPARK_TESTING` unset, JDK 17, after warm-up:
| | bytes / 1M calls | time / 1M calls |
|---|---|---|
| before (`System.getenv` + `System.getProperty`) | 56,000,000 (56 B/call) | ~25-43 ms |
| after | 0 | ~2-2.5 ms |
The same check confirmed that setting and clearing `spark.testing` at runtime still flips the result (`false` / `true` / `false`), and that with `SPARK_TESTING=1` the result is `true` as before.
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Isaac (Claude Opus 5.5)
This pull request and its description were written by Isaac.
Closes #59593 from sebastianhillig-db/istesting-cache-getenv.
Authored-by: Sebastian Hillig <se...@databricks.com>
Signed-off-by: Hyukjin Kwon <hy...@databricks.com>
---
.../apache/spark/network/util/JavaUtilsSuite.java | 28 ++++++++++++++++++++++
.../org/apache/spark/network/util/JavaUtils.java | 7 +++++-
2 files changed, 34 insertions(+), 1 deletion(-)
diff --git a/common/network-common/src/test/java/org/apache/spark/network/util/JavaUtilsSuite.java b/common/network-common/src/test/java/org/apache/spark/network/util/JavaUtilsSuite.java
index 64edc0edae6f..a690f3148960 100644
--- a/common/network-common/src/test/java/org/apache/spark/network/util/JavaUtilsSuite.java
+++ b/common/network-common/src/test/java/org/apache/spark/network/util/JavaUtilsSuite.java
@@ -18,12 +18,15 @@ package org.apache.spark.network.util;
import java.io.File;
import java.io.IOException;
+import java.lang.reflect.Field;
import java.nio.file.Files;
+import java.util.Map;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
public class JavaUtilsSuite {
@@ -90,4 +93,29 @@ public class JavaUtilsSuite {
assertEquals(1, JavaUtils.listFiles(symlink).size());
assertEquals(1, JavaUtils.listPaths(symlink).size());
}
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testIsTestingReadsSparkTestingEnvOnce() throws Exception {
+ // sbt and Maven run tests with SPARK_TESTING set.
+ assumeTrue(System.getenv("SPARK_TESTING") != null);
+ assertTrue(JavaUtils.isTesting());
+
+ Map<String, String> env = System.getenv();
+ Field field = env.getClass().getDeclaredField("m");
+ field.setAccessible(true);
+ Map<String, String> writableEnv = (Map<String, String>) field.get(env);
+ String envValue = writableEnv.remove("SPARK_TESTING");
+ String propValue = System.clearProperty("spark.testing");
+ try {
+ assertNull(System.getenv("SPARK_TESTING"));
+ // The env lookup is cached at class initialization, so it is unaffected.
+ assertTrue(JavaUtils.isTesting());
+ } finally {
+ writableEnv.put("SPARK_TESTING", envValue);
+ if (propValue != null) {
+ System.setProperty("spark.testing", propValue);
+ }
+ }
+ }
}
diff --git a/common/utils-java/src/main/java/org/apache/spark/network/util/JavaUtils.java b/common/utils-java/src/main/java/org/apache/spark/network/util/JavaUtils.java
index 2cf4570488ee..225aeab694fc 100644
--- a/common/utils-java/src/main/java/org/apache/spark/network/util/JavaUtils.java
+++ b/common/utils-java/src/main/java/org/apache/spark/network/util/JavaUtils.java
@@ -696,11 +696,16 @@ public class JavaUtils {
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
}
+ // The process environment is immutable, and System.getenv(String) allocates on every lookup,
+ // which shows up on hot paths such as Catalyst's per-transform isTesting check. The
+ // spark.testing system property is still read on each call since tests set and clear it.
+ private static final boolean SPARK_TESTING_ENV_SET = System.getenv("SPARK_TESTING") != null;
+
/**
* Indicates whether Spark is currently running unit tests.
*/
public static boolean isTesting() {
- return System.getenv("SPARK_TESTING") != null || System.getProperty("spark.testing") != null;
+ return SPARK_TESTING_ENV_SET || System.getProperty("spark.testing") != null;
}
/**
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@spark.apache.org
For additional commands, e-mail: commits-help@spark.apache.org