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");
+    }
 }

Reply via email to