Skip to content

Commit 507c993

Browse files
authored
TIKA-4497 -- allow per file timeouts in tika-pipes (#2343)
1 parent 807279b commit 507c993

6 files changed

Lines changed: 92 additions & 12 deletions

File tree

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Release 4.0.0-BETA1 - ???
1515

1616
Release 3.3.0 - ???
1717

18+
* Allow per file timeouts in tika-pipes (TIKA-4497).
19+
1820
* Add matroska detector (TIKA-1180).
1921

2022
* Allow multiple values for many Dublin Core keys (TIKA-4466).

tika-core/src/main/java/org/apache/tika/config/TikaTaskTimeout.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
*/
1717
package org.apache.tika.config;
1818

19+
import java.io.Serializable;
20+
1921
import org.apache.tika.parser.ParseContext;
2022

21-
public class TikaTaskTimeout {
23+
public class TikaTaskTimeout implements Serializable {
2224

2325
private final long timeoutMillis;
2426

@@ -31,6 +33,9 @@ public long getTimeoutMillis() {
3133
}
3234

3335
public static long getTimeoutMillis(ParseContext context, long defaultTimeoutMillis) {
36+
if (context == null) {
37+
return defaultTimeoutMillis;
38+
}
3439
TikaTaskTimeout tikaTaskTimeout = context.get(TikaTaskTimeout.class);
3540
if (tikaTaskTimeout == null) {
3641
return defaultTimeoutMillis;

tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/PipesClient.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.slf4j.Logger;
4848
import org.slf4j.LoggerFactory;
4949

50+
import org.apache.tika.config.TikaTaskTimeout;
5051
import org.apache.tika.metadata.Metadata;
5152
import org.apache.tika.metadata.TikaCoreProperties;
5253
import org.apache.tika.parser.ParseContext;
@@ -200,7 +201,8 @@ private PipesResult actuallyProcess(FetchEmitTuple t) throws InterruptedExceptio
200201
": PipesClient closed");
201202
}
202203
executorService.execute(futureTask);
203-
return futureTask.get(pipesConfig.getTimeoutMillis(), TimeUnit.MILLISECONDS);
204+
long timeout = TikaTaskTimeout.getTimeoutMillis(t.getParseContext(), pipesConfig.getTimeoutMillis());
205+
return futureTask.get(timeout, TimeUnit.MILLISECONDS);
204206
} catch (InterruptedException e) {
205207
destroyForcibly();
206208
throw e;

tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/PipesServer.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.xml.sax.SAXException;
4242

4343
import org.apache.tika.config.TikaConfig;
44+
import org.apache.tika.config.TikaTaskTimeout;
4445
import org.apache.tika.detect.Detector;
4546
import org.apache.tika.exception.EncryptedDocumentException;
4647
import org.apache.tika.exception.TikaConfigException;
@@ -135,8 +136,9 @@ public static STATUS lookup(int val) {
135136
//if it is smaller than this value, write it back to the
136137
//PipesClient so that it can cache the extracts and then batch emit.
137138
private final long maxForEmitBatchBytes;
138-
private final long serverParseTimeoutMillis;
139+
private final long defaultServerParseTimeoutMillis;
139140
private final long serverWaitTimeoutMillis;
141+
private volatile long serverParseTimeoutMillis;
140142
private Parser autoDetectParser;
141143
private Parser rMetaParser;
142144
private TikaConfig tikaConfig;
@@ -154,6 +156,7 @@ public PipesServer(Path tikaConfigPath, InputStream in, PrintStream out,
154156
this.input = new DataInputStream(in);
155157
this.output = new DataOutputStream(out);
156158
this.maxForEmitBatchBytes = maxForEmitBatchBytes;
159+
this.defaultServerParseTimeoutMillis = serverParseTimeoutMillis;
157160
this.serverParseTimeoutMillis = serverParseTimeoutMillis;
158161
this.serverWaitTimeoutMillis = serverWaitTimeoutMillis;
159162
this.parsing = false;
@@ -189,9 +192,9 @@ public void run() {
189192
while (true) {
190193
synchronized (lock) {
191194
long elapsed = System.currentTimeMillis() - since;
192-
if (parsing && elapsed > serverParseTimeoutMillis) {
193-
LOG.warn("timeout server; elapsed {} with {}", elapsed,
194-
serverParseTimeoutMillis);
195+
long timeout = serverParseTimeoutMillis;
196+
if (parsing && elapsed > timeout) {
197+
LOG.warn("timeout server; elapsed {}ms with timeout={}", elapsed, timeout);
195198
exit(TIMEOUT_EXIT_CODE);
196199
} else if (!parsing && serverWaitTimeoutMillis > 0 &&
197200
elapsed > serverWaitTimeoutMillis) {
@@ -341,6 +344,7 @@ private void parseOne() {
341344
System.currentTimeMillis() - start);
342345
}
343346
start = System.currentTimeMillis();
347+
updateTimeout(t);
344348
actuallyParse(t);
345349
if (LOG.isTraceEnabled()) {
346350
LOG.trace("timer -- actually parsed: {} ms", System.currentTimeMillis() - start);
@@ -351,10 +355,32 @@ private void parseOne() {
351355
synchronized (lock) {
352356
parsing = false;
353357
since = System.currentTimeMillis();
358+
serverParseTimeoutMillis = defaultServerParseTimeoutMillis;
354359
}
355360
}
356361
}
357362

363+
private void updateTimeout(FetchEmitTuple t) {
364+
if (t == null) {
365+
return;
366+
}
367+
368+
ParseContext parseContext = t.getParseContext();
369+
if (parseContext == null) {
370+
return;
371+
}
372+
373+
TikaTaskTimeout tikaTaskTimeout = parseContext.get(TikaTaskTimeout.class);
374+
if (tikaTaskTimeout == null) {
375+
return;
376+
}
377+
378+
synchronized (lock) {
379+
serverParseTimeoutMillis = tikaTaskTimeout.getTimeoutMillis();
380+
LOG.debug("setting per file timeout {}ms", serverParseTimeoutMillis);
381+
}
382+
}
383+
358384
private void actuallyParse(FetchEmitTuple t) {
359385

360386
long start = System.currentTimeMillis();

tika-pipes/tika-pipes-core/src/test/java/org/apache/tika/pipes/core/PipesClientTest.java

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717
package org.apache.tika.pipes.core;
1818

19+
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
1921
import java.io.IOException;
2022
import java.nio.file.Path;
2123
import java.nio.file.Paths;
@@ -27,6 +29,7 @@
2729
import org.junit.jupiter.api.Test;
2830
import org.xml.sax.SAXException;
2931

32+
import org.apache.tika.config.TikaTaskTimeout;
3033
import org.apache.tika.exception.TikaConfigException;
3134
import org.apache.tika.metadata.Metadata;
3235
import org.apache.tika.metadata.TikaCoreProperties;
@@ -62,9 +65,9 @@ public void testBasic() throws IOException, InterruptedException {
6265
new FetchEmitTuple(testPdfFile, new FetchKey(fetcherName, testPdfFile),
6366
new EmitKey(), new Metadata(), new ParseContext(), FetchEmitTuple.ON_PARSE_EXCEPTION.SKIP));
6467
Assertions.assertNotNull(pipesResult.getEmitData().getMetadataList());
65-
Assertions.assertEquals(1, pipesResult.getEmitData().getMetadataList().size());
68+
assertEquals(1, pipesResult.getEmitData().getMetadataList().size());
6669
Metadata metadata = pipesResult.getEmitData().getMetadataList().get(0);
67-
Assertions.assertEquals("testOverlappingText.pdf", metadata.get(TikaCoreProperties.RESOURCE_NAME_KEY));
70+
assertEquals("testOverlappingText.pdf", metadata.get(TikaCoreProperties.RESOURCE_NAME_KEY));
6871
}
6972

7073
@Test
@@ -76,9 +79,9 @@ public void testMetadataFilter() throws IOException, InterruptedException {
7679
new FetchEmitTuple(testPdfFile, new FetchKey(fetcherName, testPdfFile),
7780
new EmitKey(), new Metadata(), parseContext, FetchEmitTuple.ON_PARSE_EXCEPTION.SKIP));
7881
Assertions.assertNotNull(pipesResult.getEmitData().getMetadataList());
79-
Assertions.assertEquals(1, pipesResult.getEmitData().getMetadataList().size());
82+
assertEquals(1, pipesResult.getEmitData().getMetadataList().size());
8083
Metadata metadata = pipesResult.getEmitData().getMetadataList().get(0);
81-
Assertions.assertEquals("TESTOVERLAPPINGTEXT.PDF", metadata.get(TikaCoreProperties.RESOURCE_NAME_KEY));
84+
assertEquals("TESTOVERLAPPINGTEXT.PDF", metadata.get(TikaCoreProperties.RESOURCE_NAME_KEY));
8285
}
8386

8487
@Test
@@ -90,8 +93,23 @@ public void testMetadataListFilter() throws IOException, InterruptedException {
9093
new FetchEmitTuple("mock/embedded.xml", new FetchKey(fetcherName, "mock/embedded.xml"),
9194
new EmitKey(), new Metadata(), parseContext, FetchEmitTuple.ON_PARSE_EXCEPTION.SKIP));
9295
Assertions.assertNotNull(pipesResult.getEmitData().getMetadataList());
93-
Assertions.assertEquals(5, pipesResult.getEmitData().getMetadataList().size());
96+
assertEquals(5, pipesResult.getEmitData().getMetadataList().size());
9497
Metadata metadata = pipesResult.getEmitData().getMetadataList().get(0);
95-
Assertions.assertEquals(4, Integer.parseInt(metadata.get("X-TIKA:attachment_count")));
98+
assertEquals(4, Integer.parseInt(metadata.get("X-TIKA:attachment_count")));
99+
}
100+
101+
@Test
102+
public void testTimeout() throws IOException, InterruptedException {
103+
//TODO -- add unit test for timeout > default
104+
//TODO -- figure out how to test pipes server timeout alone
105+
//I did both manually during development, but unit tests are better. :D
106+
ParseContext parseContext = new ParseContext();
107+
parseContext.set(TikaTaskTimeout.class, new TikaTaskTimeout(1000));
108+
MetadataListFilter metadataFilter = new CompositeMetadataListFilter(List.of(new AttachmentCountingListFilter()));
109+
parseContext.set(MetadataListFilter.class, metadataFilter);
110+
PipesResult pipesResult = pipesClient.process(
111+
new FetchEmitTuple("mock/timeout-10s.xml", new FetchKey(fetcherName, "mock/timeout-10s.xml"),
112+
new EmitKey(), new Metadata(), parseContext, FetchEmitTuple.ON_PARSE_EXCEPTION.SKIP));
113+
assertEquals(PipesResult.TIMEOUT.getStatus(), pipesResult.getStatus());
96114
}
97115
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
3+
<!--
4+
Licensed to the Apache Software Foundation (ASF) under one
5+
or more contributor license agreements. See the NOTICE file
6+
distributed with this work for additional information
7+
regarding copyright ownership. The ASF licenses this file
8+
to you under the Apache License, Version 2.0 (the
9+
"License"); you may not use this file except in compliance
10+
with the License. You may obtain a copy of the License at
11+
12+
http://www.apache.org/licenses/LICENSE-2.0
13+
14+
Unless required by applicable law or agreed to in writing,
15+
software distributed under the License is distributed on an
16+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
KIND, either express or implied. See the License for the
18+
specific language governing permissions and limitations
19+
under the License.
20+
-->
21+
22+
<mock>
23+
24+
<metadata action="add" name="dc:creator">Nikolai Lobachevsky</metadata>
25+
<write element="p" times="30"> hello </write>
26+
<fakeload millis="10000" cpu="10" mb="10"/>
27+
</mock>

0 commit comments

Comments
 (0)