Skip to content

Commit 46c0e2f

Browse files
committed
HBASE-27315 Add timeout to JavaRegexEngine
1 parent 3d66866 commit 46c0e2f

File tree

10 files changed

+371
-1
lines changed

10 files changed

+371
-1
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase;
19+
20+
import org.apache.yetus.audience.InterfaceAudience;
21+
22+
/**
23+
* if exception is not meant to be retried
24+
*/
25+
@InterfaceAudience.Private
26+
public class DoNotRetryUncheckedIOException extends RuntimeException {
27+
private static final long serialVersionUID = 5334859039077080405L;
28+
29+
/**
30+
* @param message the message
31+
*/
32+
public DoNotRetryUncheckedIOException(String message) {
33+
super(message);
34+
}
35+
36+
/**
37+
* Returns a {@link DoNotRetryIOException}.
38+
* @return the {@link DoNotRetryIOException}
39+
*/
40+
public DoNotRetryIOException toDoNotRetryIOException() {
41+
return new DoNotRetryIOException(getMessage());
42+
}
43+
}

hbase-client/src/main/java/org/apache/hadoop/hbase/filter/RegexStringComparator.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
import java.nio.charset.IllegalCharsetNameException;
2222
import java.util.Arrays;
2323
import java.util.regex.Pattern;
24+
import org.apache.hadoop.hbase.conf.ConfigurationHolder;
2425
import org.apache.hadoop.hbase.exceptions.DeserializationException;
2526
import org.apache.hadoop.hbase.util.Bytes;
27+
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
2628
import org.apache.yetus.audience.InterfaceAudience;
2729
import org.jcodings.Encoding;
2830
import org.jcodings.EncodingDB;
@@ -257,9 +259,17 @@ static interface Engine {
257259
static class JavaRegexEngine implements Engine {
258260
private Charset charset = Charset.forName("UTF-8");
259261
private Pattern pattern;
262+
private final long timeoutMillis;
260263

261264
public JavaRegexEngine(String regex, int flags) {
262265
this.pattern = Pattern.compile(regex, flags);
266+
this.timeoutMillis = ConfigurationHolder.getInstance().getConf()
267+
.getLong("hbase.filter.regex.java.timeout", -1);
268+
}
269+
270+
JavaRegexEngine(String regex, int flags, long timeoutMillis) {
271+
this.pattern = Pattern.compile(regex, flags);
272+
this.timeoutMillis = timeoutMillis;
263273
}
264274

265275
@Override
@@ -294,7 +304,14 @@ public int compareTo(byte[] value, int offset, int length) {
294304
} else {
295305
tmp = new String(value, offset, length, charset);
296306
}
297-
return pattern.matcher(tmp).find() ? 0 : 1;
307+
308+
if (timeoutMillis == -1) {
309+
return pattern.matcher(tmp).find() ? 0 : 1;
310+
} else {
311+
final TimeoutCharSequence chars =
312+
new TimeoutCharSequence(tmp, EnvironmentEdgeManager.currentTime(), timeoutMillis);
313+
return pattern.matcher(chars).find() ? 0 : 1;
314+
}
298315
}
299316

300317
@Override
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.filter;
19+
20+
import org.apache.hadoop.hbase.DoNotRetryUncheckedIOException;
21+
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
22+
import org.apache.hadoop.util.StringUtils;
23+
24+
/**
25+
* It checks whether the timeout has been exceeded whenever the charAt method is called.
26+
*/
27+
class TimeoutCharSequence implements CharSequence {
28+
static final int DEFAULT_CHECK_POINT = 10_000;
29+
30+
private final CharSequence value;
31+
private final long startMillis;
32+
private final long timeoutMillis;
33+
private final int checkPoint;
34+
private int numberOfCalls;
35+
36+
/**
37+
* Initialize a TimeoutCharSequence.
38+
* @param value the original data
39+
* @param startMillis time the operation started (ms)
40+
* @param timeoutMillis the timeout (ms)
41+
*/
42+
TimeoutCharSequence(CharSequence value, long startMillis, long timeoutMillis) {
43+
this.value = value;
44+
this.startMillis = startMillis;
45+
this.timeoutMillis = timeoutMillis;
46+
this.checkPoint = DEFAULT_CHECK_POINT;
47+
this.numberOfCalls = 0;
48+
}
49+
50+
/**
51+
* Initialize a TimeoutCharSequence.
52+
* @param value the original data
53+
* @param startMillis time the operation started (ms)
54+
* @param checkPoint the check point
55+
* @param timeoutMillis the timeout (ms)
56+
*/
57+
TimeoutCharSequence(CharSequence value, long startMillis, long timeoutMillis, int checkPoint) {
58+
this.value = value;
59+
this.startMillis = startMillis;
60+
this.timeoutMillis = timeoutMillis;
61+
this.checkPoint = checkPoint;
62+
this.numberOfCalls = 0;
63+
}
64+
65+
@Override
66+
public int length() {
67+
return value.length();
68+
}
69+
70+
@Override
71+
public char charAt(int index) {
72+
numberOfCalls++;
73+
if (numberOfCalls % checkPoint == 0) {
74+
final long diff = EnvironmentEdgeManager.currentTime() - startMillis;
75+
if (diff > timeoutMillis) {
76+
throw new DoNotRetryUncheckedIOException(
77+
String.format("Operation timed out after %s.", StringUtils.formatTime(diff)));
78+
}
79+
numberOfCalls = 0;
80+
}
81+
return value.charAt(index);
82+
}
83+
84+
@Override
85+
public CharSequence subSequence(int start, int end) {
86+
return new TimeoutCharSequence(value.subSequence(start, end), startMillis, timeoutMillis);
87+
}
88+
89+
@Override
90+
public String toString() {
91+
return value.toString();
92+
}
93+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.filter;
19+
20+
import java.nio.charset.StandardCharsets;
21+
import java.util.regex.Pattern;
22+
import org.apache.hadoop.hbase.DoNotRetryUncheckedIOException;
23+
import org.apache.hadoop.hbase.HBaseClassTestRule;
24+
import org.apache.hadoop.hbase.testclassification.SmallTests;
25+
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
26+
import org.apache.hadoop.hbase.util.IncrementingEnvironmentEdge;
27+
import org.junit.ClassRule;
28+
import org.junit.Test;
29+
import org.junit.experimental.categories.Category;
30+
31+
@Category(SmallTests.class)
32+
public class RegexStringComparatorTest {
33+
@ClassRule
34+
public static final HBaseClassTestRule CLASS_RULE =
35+
HBaseClassTestRule.forClass(RegexStringComparatorTest.class);
36+
37+
@Test(expected = DoNotRetryUncheckedIOException.class)
38+
public void testCompareToTimeout() {
39+
final RegexStringComparator.JavaRegexEngine regex =
40+
new RegexStringComparator.JavaRegexEngine("(0*)*A", Pattern.DOTALL, 0);
41+
42+
EnvironmentEdgeManager.injectEdge(new IncrementingEnvironmentEdge());
43+
final byte[] input = "00000000000000000000000000000".getBytes(StandardCharsets.UTF_8);
44+
// It actually takes a few seconds
45+
regex.compareTo(input, 0, input.length);
46+
}
47+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.filter;
19+
20+
import static org.apache.hadoop.hbase.filter.TimeoutCharSequence.DEFAULT_CHECK_POINT;
21+
22+
import org.apache.hadoop.hbase.DoNotRetryUncheckedIOException;
23+
import org.apache.hadoop.hbase.HBaseClassTestRule;
24+
import org.apache.hadoop.hbase.testclassification.SmallTests;
25+
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
26+
import org.apache.hadoop.hbase.util.IncrementingEnvironmentEdge;
27+
import org.junit.Assert;
28+
import org.junit.ClassRule;
29+
import org.junit.Test;
30+
import org.junit.experimental.categories.Category;
31+
32+
@Category(SmallTests.class)
33+
public class TimeoutCharSequenceTest {
34+
@ClassRule
35+
public static final HBaseClassTestRule CLASS_RULE =
36+
HBaseClassTestRule.forClass(TimeoutCharSequenceTest.class);
37+
38+
@Test(expected = DoNotRetryUncheckedIOException.class)
39+
public void testCharAtTimeout() {
40+
final IncrementingEnvironmentEdge environmentEdge = new IncrementingEnvironmentEdge(7);
41+
EnvironmentEdgeManager.injectEdge(environmentEdge);
42+
43+
final TimeoutCharSequence chars = new TimeoutCharSequence("test", 0, 8, 1);
44+
for (int i = 0; i < 3; i++) {
45+
chars.charAt(3);
46+
}
47+
}
48+
49+
@Test
50+
public void testCheckPoint() {
51+
final long initialAmount = 10;
52+
final IncrementingEnvironmentEdge edge = new IncrementingEnvironmentEdge(initialAmount);
53+
EnvironmentEdgeManager.injectEdge(edge);
54+
55+
final TimeoutCharSequence chars = new TimeoutCharSequence("test", 0, 8);
56+
57+
boolean thrownException = false;
58+
for (int i = 0; i < DEFAULT_CHECK_POINT; i++) {
59+
try {
60+
chars.charAt(3);
61+
} catch (DoNotRetryUncheckedIOException e) {
62+
thrownException = true;
63+
}
64+
}
65+
66+
Assert.assertTrue(thrownException);
67+
Assert.assertEquals(edge.currentTime(), initialAmount + 1);
68+
}
69+
70+
@Test
71+
public void testCharAt() {
72+
final IncrementingEnvironmentEdge environmentEdge = new IncrementingEnvironmentEdge(1);
73+
EnvironmentEdgeManager.injectEdge(environmentEdge); // next currentTime: 1
74+
75+
final TimeoutCharSequence chars = new TimeoutCharSequence("test", 0, 10);
76+
Assert.assertEquals('t', chars.charAt(0));
77+
Assert.assertEquals('e', chars.charAt(1));
78+
}
79+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.conf;
19+
20+
import java.util.concurrent.locks.ReentrantLock;
21+
import org.apache.hadoop.conf.Configuration;
22+
import org.apache.hadoop.hbase.HBaseConfiguration;
23+
24+
/**
25+
* ConfigurationHolder class that holds Configuration so that they can be easily accessed anywhere.
26+
*/
27+
public class ConfigurationHolder implements ConfigurationObserver {
28+
private final ReentrantLock confLock = new ReentrantLock();
29+
private Configuration conf;
30+
31+
private ConfigurationHolder() {
32+
}
33+
34+
private static class SingletonHelper {
35+
private static final ConfigurationHolder INSTANCE = new ConfigurationHolder();
36+
}
37+
38+
/**
39+
* Returns a ConfigurationHolder
40+
* @return Configuration
41+
*/
42+
public static ConfigurationHolder getInstance() {
43+
return SingletonHelper.INSTANCE;
44+
}
45+
46+
/**
47+
* Update Configuration
48+
* @param conf Configuration
49+
*/
50+
public void setConf(Configuration conf) {
51+
confLock.lock();
52+
try {
53+
this.conf = conf;
54+
} finally {
55+
confLock.unlock();
56+
}
57+
}
58+
59+
/**
60+
* Check if a Configuration exists.
61+
* If it does, return it. If not, create a new Configuration and return it.
62+
* @return Configuration
63+
*/
64+
public Configuration getConf() {
65+
confLock.lock();
66+
try {
67+
if (conf == null) {
68+
setConf(HBaseConfiguration.create());
69+
}
70+
71+
return conf;
72+
} finally {
73+
confLock.unlock();
74+
}
75+
}
76+
77+
@Override
78+
public void onConfigurationChange(Configuration conf) {
79+
setConf(conf);
80+
}
81+
}

0 commit comments

Comments
 (0)