This is an automated email from the ASF dual-hosted git repository.
tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git
The following commit(s) were added to refs/heads/main by this push:
new 968e89eae9 TIKA-4886: Do not start subprocess, if granted timeout is
<= 0 (#3149)
968e89eae9 is described below
commit 968e89eae9999f286bb203615c6b77213cac8f6f
Author: Tim Grein <[email protected]>
AuthorDate: Wed Sep 9 17:54:01 2026 +0200
TIKA-4886: Do not start subprocess, if granted timeout is <= 0 (#3149)
---
.../java/org/apache/tika/utils/ProcessUtils.java | 36 +++++++++++++++
.../org/apache/tika/utils/ProcessUtilsTest.java | 52 ++++++++++++++++++++++
2 files changed, 88 insertions(+)
diff --git a/tika-core/src/main/java/org/apache/tika/utils/ProcessUtils.java
b/tika-core/src/main/java/org/apache/tika/utils/ProcessUtils.java
index 3d63d989dc..e1916b43cc 100644
--- a/tika-core/src/main/java/org/apache/tika/utils/ProcessUtils.java
+++ b/tika-core/src/main/java/org/apache/tika/utils/ProcessUtils.java
@@ -20,6 +20,7 @@ package org.apache.tika.utils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@@ -146,6 +147,12 @@ public class ProcessUtils {
long grantedTimeoutMillis = context == null
? requestedTimeoutMillis
:
ParseTimeout.getOrCreate(context).budgetFor(requestedTimeoutMillis);
+
+ Optional<FileProcessResult> potentiallyFailedResult =
failFastIfNoGrantedTimeout(requestedTimeoutMillis, grantedTimeoutMillis);
+ if (potentiallyFailedResult.isPresent()) {
+ return potentiallyFailedResult.get();
+ }
+
Process p = null;
String id = null;
try {
@@ -261,7 +268,14 @@ public class ProcessUtils {
long grantedTimeoutMillis = context == null
? requestedTimeoutMillis
:
ParseTimeout.getOrCreate(context).budgetFor(requestedTimeoutMillis);
+
+ Optional<FileProcessResult> potentiallyFailedResult =
failFastIfNoGrantedTimeout(requestedTimeoutMillis, grantedTimeoutMillis);
+ if (potentiallyFailedResult.isPresent()) {
+ return potentiallyFailedResult.get();
+ }
+
pb.redirectOutput(stdoutRedirect.toFile());
+
Process p = null;
String id = null;
try {
@@ -442,4 +456,26 @@ public class ProcessUtils {
}
}
+ /**
+ * Guards against both 0 and negative grants.
+ *
+ * @param requestedTimeoutMillis
+ * @param grantedTimeoutMillis
+ * @return result, if the process shouldn't be started, otherwise empty
optional
+ */
+ private static Optional<FileProcessResult> failFastIfNoGrantedTimeout(long
requestedTimeoutMillis, long grantedTimeoutMillis) {
+ if (grantedTimeoutMillis <= 0) {
+ FileProcessResult result = new FileProcessResult();
+ result.isTimeout = true;
+ result.requestedTimeoutMillis = requestedTimeoutMillis;
+ result.grantedTimeoutMillis = grantedTimeoutMillis;
+ result.processTimeMillis = 0;
+ result.stdoutLength = 0;
+ result.stderrLength = 0;
+
+ return Optional.of(result);
+ }
+
+ return Optional.empty();
+ }
}
diff --git
a/tika-core/src/test/java/org/apache/tika/utils/ProcessUtilsTest.java
b/tika-core/src/test/java/org/apache/tika/utils/ProcessUtilsTest.java
index 3c973cf78b..9b920a4aa7 100644
--- a/tika-core/src/test/java/org/apache/tika/utils/ProcessUtilsTest.java
+++ b/tika-core/src/test/java/org/apache/tika/utils/ProcessUtilsTest.java
@@ -16,10 +16,16 @@
*/
package org.apache.tika.utils;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.jspecify.annotations.NonNull;
import org.junit.jupiter.api.Test;
import org.apache.tika.config.ParseTimeout;
@@ -133,4 +139,50 @@ public class ProcessUtilsTest {
assertTrue(elapsed < 4_000,
"checkCommandWithTimeout must honor its own timeout, not the
default; took " + elapsed + "ms");
}
+
+ @Test
+ public void testExecuteFailsFastIfTimeoutIsZero() throws Exception {
+ assumeFalse(SystemUtils.IS_OS_WINDOWS);
+
+ ProcessBuilder pb = new
ProcessBuilder("non-existing-command-throwing-exception-if-executed");
+ ParseContext context = new ParseContext();
+ context.set(TimeoutLimits.class, new TimeoutLimits(0, 0));
+
+ long start = System.nanoTime();
+ FileProcessResult result = ProcessUtils.execute(pb, context, 5_000L,
1000, 1000);
+ long elapsed = System.nanoTime() - start;
+
+ assertSubprocessDidNotStart(result, elapsed);
+ }
+
+ @Test
+ public void testExecuteWithRedirectFailsFastIfTimeoutIsZero() throws
Exception {
+ ParseContext context = new ParseContext();
+ context.set(TimeoutLimits.class, new TimeoutLimits(0, 0));
+ ProcessBuilder pb = new
ProcessBuilder("non-existing-command-throwing-exception-if-executed");
+ Path stdoutRedirect = createTmpRedirect();
+
+ long start = System.nanoTime();
+ FileProcessResult result = ProcessUtils.execute(pb, context, 5_000L,
stdoutRedirect, 1000);
+ long elapsed = System.nanoTime() - start;
+
+ assertSubprocessDidNotStart(result, elapsed);
+ }
+
+ private static @NonNull Path createTmpRedirect() throws IOException {
+ Path tmpDir = Path.of(System.getProperty("java.io.tmpdir"));
+ Files.createDirectories(tmpDir);
+ Path stdoutRedirect = Files.createTempFile(tmpDir, "tika-test-",
".out");
+ stdoutRedirect.toFile().deleteOnExit();
+ return stdoutRedirect;
+ }
+
+ private static void assertSubprocessDidNotStart(FileProcessResult result,
long elapsed) {
+ assertTrue(result.isTimeout(), "a process with a 0 timeout should
timeout immediately without starting");
+ assertEquals(0, result.getGrantedTimeoutMillis(), "the process should
not have been granted any timeout; got " + result.getGrantedTimeoutMillis() +
"ms");
+ assertEquals(0, result.getProcessTimeMillis(), "processTimeMillis
should be 0 on fast path");
+ assertEquals(0, result.getStdoutLength(), "stdoutLength should be 0 on
fast path");
+ assertEquals(0, result.getStderrLength(), "stderrLength should be 0 on
fast path");
+ assertTrue(elapsed < 4_000_000_000L, "fast path should return without
spawning; took " + elapsed + "ms");
+ }
}