Skip to content

Commit 46d1858

Browse files
committed
add in flight close test
1 parent 18a5a67 commit 46d1858

1 file changed

Lines changed: 80 additions & 0 deletions

File tree

tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/pipes/core/SharedServerModeTest.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.concurrent.ExecutorService;
2929
import java.util.concurrent.Executors;
3030
import java.util.concurrent.Future;
31+
import java.util.concurrent.TimeUnit;
3132

3233
import org.junit.jupiter.api.Test;
3334
import org.junit.jupiter.api.io.TempDir;
@@ -77,6 +78,13 @@ public class SharedServerModeTest {
7778
"<fakeload millis=\"60000\" cpu=\"1\" mb=\"10\"/>" +
7879
"</mock>";
7980

81+
private static final String MOCK_INFLIGHT = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" +
82+
"<mock>" +
83+
"<metadata action=\"add\" name=\"dc:creator\">In-flight Author</metadata>" +
84+
"<write element=\"p\">In-flight content</write>" +
85+
"<fakeload millis=\"3000\" cpu=\"1\" mb=\"10\"/>" +
86+
"</mock>";
87+
8088
@Test
8189
public void testBasicSharedMode(@TempDir Path tmp) throws Exception {
8290
Path inputDir = setupInputDir(tmp);
@@ -216,6 +224,78 @@ public void testGracefulShutdown(@TempDir Path tmp) throws Exception {
216224
}
217225
}
218226

227+
/**
228+
* Verifies that close() unblocks an in-flight parse and returns promptly when
229+
* the parser is shut down at a randomized point in the parse lifecycle.
230+
* <p>
231+
* After a warmup parse to ensure the shared server is fully started, this picks
232+
* a random sleep in [0, 4000]ms before calling close(). Given the inflight
233+
* fakeload runs ~3000ms, the chosen delay can land in init, mid-parse, near the
234+
* tail of parse, or post-completion. The seed is logged and included in every
235+
* assertion message so a CI failure can be reproduced via
236+
* {@code -DcloseInFlightSeed=<seed>}.
237+
*/
238+
@Test
239+
public void testCloseAtRandomPhase(@TempDir Path tmp) throws Exception {
240+
long seed = Long.getLong("closeInFlightSeed", System.nanoTime());
241+
java.util.Random rng = new java.util.Random(seed);
242+
long sleepBeforeCloseMs = rng.nextInt(4001); // [0, 4000]
243+
String repro = "[seed=" + seed + ", sleep=" + sleepBeforeCloseMs + "ms]";
244+
System.out.println("testCloseAtRandomPhase " + repro);
245+
246+
Path inputDir = setupInputDir(tmp);
247+
Files.writeString(inputDir.resolve("warmup.xml"), MOCK_OK, StandardCharsets.UTF_8);
248+
Files.writeString(inputDir.resolve("inflight.xml"), MOCK_INFLIGHT, StandardCharsets.UTF_8);
249+
250+
Path tikaConfigPath = PluginsTestHelper.getFileSystemFetcherConfig(
251+
"tika-config-shared-server.json", tmp, inputDir, tmp.resolve("output"), false);
252+
TikaJsonConfig tikaJsonConfig = TikaJsonConfig.load(tikaConfigPath);
253+
PipesConfig pipesConfig = PipesConfig.load(tikaJsonConfig);
254+
255+
PipesParser pipesParser = PipesParser.load(tikaJsonConfig, pipesConfig, tikaConfigPath);
256+
ExecutorService executor = Executors.newSingleThreadExecutor();
257+
try {
258+
// Warmup so the shared server is fully started; without this, short
259+
// sleeps would always land in init rather than reaching mid-parse.
260+
PipesResult warmup = pipesParser.parse(new FetchEmitTuple(
261+
"warmup.xml",
262+
new FetchKey(FETCHER_NAME, "warmup.xml"),
263+
new EmitKey(EMITTER_NAME, ""),
264+
new Metadata(),
265+
new ParseContext(),
266+
FetchEmitTuple.ON_PARSE_EXCEPTION.SKIP));
267+
assertTrue(warmup.isSuccess(), "Warmup parse must succeed " + repro);
268+
269+
Future<PipesResult> future = executor.submit(() -> pipesParser.parse(new FetchEmitTuple(
270+
"inflight.xml",
271+
new FetchKey(FETCHER_NAME, "inflight.xml"),
272+
new EmitKey(EMITTER_NAME, ""),
273+
new Metadata(),
274+
new ParseContext(),
275+
FetchEmitTuple.ON_PARSE_EXCEPTION.SKIP)));
276+
277+
if (sleepBeforeCloseMs > 0) {
278+
Thread.sleep(sleepBeforeCloseMs);
279+
}
280+
281+
long closeStart = System.currentTimeMillis();
282+
pipesParser.close();
283+
long closeDuration = System.currentTimeMillis() - closeStart;
284+
285+
assertTrue(closeDuration < 15000,
286+
"close() should return promptly even with in-flight work; took " +
287+
closeDuration + "ms " + repro);
288+
289+
// The in-flight request must return - not deadlock waiting on a closed socket
290+
PipesResult result = future.get(15, TimeUnit.SECONDS);
291+
assertNotNull(result, "In-flight parse should return a result, not hang " + repro);
292+
// Result may be SUCCESS (parse finished before close) or a crash/init-failure
293+
// status (close tore down the socket mid-flight) - both are acceptable.
294+
} finally {
295+
executor.shutdownNow();
296+
}
297+
}
298+
219299
@Test
220300
public void testPerClientModeStillWorks(@TempDir Path tmp) throws Exception {
221301
// Verify default per-client mode still works

0 commit comments

Comments
 (0)