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 433f576407 TIKA-4815: trivial follow-ups from the PR #3022 review 
(#3026)
433f576407 is described below

commit 433f57640712648698ff58ed159ad4c0e60b7168
Author: Tim Allison <[email protected]>
AuthorDate: Fri Aug 14 15:40:46 2026 -0400

    TIKA-4815: trivial follow-ups from the PR #3022 review (#3026)
---
 docs/modules/ROOT/pages/using-tika/grpc/index.adoc |   8 ++
 .../tika/pipes/grpc/TikaGrpcConcurrencyTest.java   |  10 +-
 .../tika/pipes/core/PerClientServerManager.java    |   2 +-
 .../tika/pipes/core/PipesClientInterruptTest.java  | 120 ++++++++++-----------
 4 files changed, 76 insertions(+), 64 deletions(-)

diff --git a/docs/modules/ROOT/pages/using-tika/grpc/index.adoc 
b/docs/modules/ROOT/pages/using-tika/grpc/index.adoc
index ab58eec64d..e3b803b506 100644
--- a/docs/modules/ROOT/pages/using-tika/grpc/index.adoc
+++ b/docs/modules/ROOT/pages/using-tika/grpc/index.adoc
@@ -29,6 +29,14 @@ register a fetcher (`SaveFetcher`) and then submit 
`FetchAndParseRequest`
 messages, each of which returns a `FetchAndParseReply` with extracted
 metadata and content.
 
+== Concurrency
+
+`FetchAndParse` and its streaming variants run on a pool of forked worker JVMs
+sized by `pipes.numClients` (default: derived from host cores, at most 4). A
+call that cannot get a worker within `pipes.maxWaitForClientMillis` (default
+60s) returns the in-band reply status `CLIENT_UNAVAILABLE_WITHIN_MS` -- at
+capacity, not failing. `pipes.useSharedServer: true` shares one JVM instead.
+
 == Security
 
 [WARNING]
diff --git 
a/tika-grpc/src/test/java/org/apache/tika/pipes/grpc/TikaGrpcConcurrencyTest.java
 
b/tika-grpc/src/test/java/org/apache/tika/pipes/grpc/TikaGrpcConcurrencyTest.java
index d8064ae833..edbf52f834 100644
--- 
a/tika-grpc/src/test/java/org/apache/tika/pipes/grpc/TikaGrpcConcurrencyTest.java
+++ 
b/tika-grpc/src/test/java/org/apache/tika/pipes/grpc/TikaGrpcConcurrencyTest.java
@@ -80,7 +80,7 @@ public class TikaGrpcConcurrencyTest {
      */
     @Test
     public void concurrentCallsAllParseTheirOwnDocument(Resources resources) 
throws Exception {
-        runConcurrentBurst(resources, writeConfig(null, null, null));
+        runConcurrentBurst(resources, writeConfig(null, null, null), false);
     }
 
     /**
@@ -91,12 +91,16 @@ public class TikaGrpcConcurrencyTest {
      */
     @Test
     public void sharedServerModeParsesConcurrently(Resources resources) throws 
Exception {
-        runConcurrentBurst(resources, writeConfig(null, null, Boolean.TRUE));
+        runConcurrentBurst(resources, writeConfig(null, null, Boolean.TRUE), 
true);
     }
 
-    private void runConcurrentBurst(Resources resources, Path config) throws 
Exception {
+    private void runConcurrentBurst(Resources resources, Path config, boolean 
expectSharedMode)
+            throws Exception {
         int concurrency = 4;
         TikaGrpcServerImpl service = new 
TikaGrpcServerImpl(config.toAbsolutePath().toString());
+        // the burst alone can't tell the modes apart
+        assertEquals(expectSharedMode, service.pipesParser.isSharedMode(),
+                "pipes.useSharedServer did not take effect");
         List<File> testFiles = new ArrayList<>();
         ExecutorService pool = Executors.newFixedThreadPool(concurrency);
         try {
diff --git 
a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/PerClientServerManager.java
 
b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/PerClientServerManager.java
index 024afd292e..69da42d536 100644
--- 
a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/PerClientServerManager.java
+++ 
b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/PerClientServerManager.java
@@ -290,7 +290,7 @@ public class PerClientServerManager implements 
ServerManager {
 
     @Override
     public void connectionAbandoned() {
-        LOG.info("clientId={}: connection abandoned mid-request, recycling the 
worker", clientId);
+        LOG.info("clientId={}: connection abandoned, worker will be recycled 
on next use", clientId);
         pendingRestart = true;
     }
 
diff --git 
a/tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/PipesClientInterruptTest.java
 
b/tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/PipesClientInterruptTest.java
index f6b478fd73..a7a3e1be53 100644
--- 
a/tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/PipesClientInterruptTest.java
+++ 
b/tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/PipesClientInterruptTest.java
@@ -55,7 +55,7 @@ public class PipesClientInterruptTest {
      * open with an abandoned request on it.
      */
     @Test
-    @Timeout(30)
+    @Timeout(45)
     public void interruptClosesTheConnection() throws Exception {
         try (ServerSocket serverSocket = new ServerSocket(0)) {
             CountDownLatch heartbeatStarted = new CountDownLatch(1);
@@ -67,37 +67,36 @@ public class PipesClientInterruptTest {
 
             PipesConfig pipesConfig = new PipesConfig();
             SentinelServerManager manager = new 
SentinelServerManager(serverSocket.getLocalPort());
-            PipesClient client = new PipesClient(pipesConfig, manager);
-
-            AtomicReference<Throwable> fromProcess = new AtomicReference<>();
-            CountDownLatch processReturned = new CountDownLatch(1);
-            Thread worker = new Thread(() -> {
-                try {
-                    client.process(new FetchEmitTuple("interrupt-test",
-                            new FetchKey("fetcher", "key"), new EmitKey(), new 
Metadata(),
-                            new ParseContext(), 
FetchEmitTuple.ON_PARSE_EXCEPTION.SKIP));
-                } catch (Throwable t) {
-                    fromProcess.set(t);
-                } finally {
-                    processReturned.countDown();
-                }
-            });
-            worker.start();
+            try (PipesClient client = new PipesClient(pipesConfig, manager)) {
+                AtomicReference<Throwable> fromProcess = new 
AtomicReference<>();
+                CountDownLatch processReturned = new CountDownLatch(1);
+                Thread worker = new Thread(() -> {
+                    try {
+                        client.process(new FetchEmitTuple("interrupt-test",
+                                new FetchKey("fetcher", "key"), new EmitKey(), 
new Metadata(),
+                                new ParseContext(), 
FetchEmitTuple.ON_PARSE_EXCEPTION.SKIP));
+                    } catch (Throwable t) {
+                        fromProcess.set(t);
+                    } finally {
+                        processReturned.countDown();
+                    }
+                });
+                worker.start();
 
-            assertTrue(heartbeatStarted.await(15, TimeUnit.SECONDS),
-                    "the scripted server never got the request; the test 
proves nothing");
-            worker.interrupt();
+                assertTrue(heartbeatStarted.await(15, TimeUnit.SECONDS),
+                        "the scripted server never got the request; the test 
proves nothing");
+                worker.interrupt();
 
-            assertTrue(processReturned.await(15, TimeUnit.SECONDS),
-                    "process() must return after the interrupt");
-            assertTrue(fromProcess.get() instanceof InterruptedException,
-                    "process() must rethrow the interrupt, got: " + 
fromProcess.get());
-            assertTrue(connectionClosed.await(5, TimeUnit.SECONDS),
-                    "the interrupted client left its connection open with a 
request in flight");
-            assertTrue(manager.abandoned,
-                    "the manager was not told; a per-client worker never dials 
back, so the "
-                            + "next connect() would wait out the accept 
timeout for nothing");
-            client.close();
+                assertTrue(processReturned.await(15, TimeUnit.SECONDS),
+                        "process() must return after the interrupt");
+                assertTrue(fromProcess.get() instanceof InterruptedException,
+                        "process() must rethrow the interrupt, got: " + 
fromProcess.get());
+                assertTrue(connectionClosed.await(5, TimeUnit.SECONDS),
+                        "the interrupted client left its connection open with 
a request in flight");
+                assertTrue(manager.abandoned,
+                        "the manager was not told; a per-client worker never 
dials back, so the "
+                                + "next connect() would wait out the accept 
timeout for nothing");
+            }
         }
     }
 
@@ -109,7 +108,7 @@ public class PipesClientInterruptTest {
      * is delivered.
      */
     @Test
-    @Timeout(30)
+    @Timeout(45)
     public void interruptDuringStartupBackoffAbandonsTheConnection() throws 
Exception {
         try (ServerSocket serverSocket = new ServerSocket(0)) {
             CountDownLatch badHandshakeSent = new CountDownLatch(1);
@@ -120,38 +119,39 @@ public class PipesClientInterruptTest {
             sentinel.start();
 
             PipesConfig pipesConfig = new PipesConfig();
+            // handshake reads aren't interrupt-responsive; a late interrupt 
waits this out
+            pipesConfig.setStartupTimeoutMillis(1000);
             SentinelServerManager manager = new 
SentinelServerManager(serverSocket.getLocalPort());
-            PipesClient client = new PipesClient(pipesConfig, manager);
-
-            AtomicReference<Throwable> fromProcess = new AtomicReference<>();
-            CountDownLatch processReturned = new CountDownLatch(1);
-            Thread worker = new Thread(() -> {
-                try {
-                    client.process(new FetchEmitTuple("interrupt-startup-test",
-                            new FetchKey("fetcher", "key"), new EmitKey(), new 
Metadata(),
-                            new ParseContext(), 
FetchEmitTuple.ON_PARSE_EXCEPTION.SKIP));
-                } catch (Throwable t) {
-                    fromProcess.set(t);
-                } finally {
-                    processReturned.countDown();
-                }
-            });
-            worker.start();
+            try (PipesClient client = new PipesClient(pipesConfig, manager)) {
+                AtomicReference<Throwable> fromProcess = new 
AtomicReference<>();
+                CountDownLatch processReturned = new CountDownLatch(1);
+                Thread worker = new Thread(() -> {
+                    try {
+                        client.process(new 
FetchEmitTuple("interrupt-startup-test",
+                                new FetchKey("fetcher", "key"), new EmitKey(), 
new Metadata(),
+                                new ParseContext(), 
FetchEmitTuple.ON_PARSE_EXCEPTION.SKIP));
+                    } catch (Throwable t) {
+                        fromProcess.set(t);
+                    } finally {
+                        processReturned.countDown();
+                    }
+                });
+                worker.start();
 
-            assertTrue(badHandshakeSent.await(15, TimeUnit.SECONDS),
-                    "the scripted server never got a connection; the test 
proves nothing");
-            worker.interrupt();
+                assertTrue(badHandshakeSent.await(15, TimeUnit.SECONDS),
+                        "the scripted server never got a connection; the test 
proves nothing");
+                worker.interrupt();
 
-            assertTrue(processReturned.await(15, TimeUnit.SECONDS),
-                    "process() must return after the interrupt");
-            assertTrue(fromProcess.get() instanceof InterruptedException,
-                    "process() must rethrow the interrupt, got: " + 
fromProcess.get());
-            assertTrue(connectionClosed.await(5, TimeUnit.SECONDS),
-                    "the interrupted client left its half-established 
connection open");
-            assertTrue(manager.abandoned,
-                    "the manager was not told; an abandoned per-client worker 
never "
-                            + "dials back, mid-handshake or not");
-            client.close();
+                assertTrue(processReturned.await(15, TimeUnit.SECONDS),
+                        "process() must return after the interrupt");
+                assertTrue(fromProcess.get() instanceof InterruptedException,
+                        "process() must rethrow the interrupt, got: " + 
fromProcess.get());
+                assertTrue(connectionClosed.await(5, TimeUnit.SECONDS),
+                        "the interrupted client left its half-established 
connection open");
+                assertTrue(manager.abandoned,
+                        "the manager was not told; an abandoned per-client 
worker never "
+                                + "dials back, mid-handshake or not");
+            }
         }
     }
 

Reply via email to