Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HBASE-27315 Add timeout to JavaRegexEngine #4720

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase;

import org.apache.yetus.audience.InterfaceAudience;

/**
* if exception is not meant to be retried
*/
@InterfaceAudience.Private
public class DoNotRetryUncheckedIOException extends RuntimeException {
private static final long serialVersionUID = 5334859039077080405L;

/**
* @param message the message
*/
public DoNotRetryUncheckedIOException(String message) {
super(message);
}

/**
* Returns a {@link DoNotRetryIOException}.
* @return the {@link DoNotRetryIOException}
*/
public DoNotRetryIOException toDoNotRetryIOException() {
return new DoNotRetryIOException(getMessage());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
import java.nio.charset.IllegalCharsetNameException;
import java.util.Arrays;
import java.util.regex.Pattern;
import org.apache.hadoop.hbase.conf.ConfigurationHolder;
import org.apache.hadoop.hbase.exceptions.DeserializationException;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.apache.yetus.audience.InterfaceAudience;
import org.jcodings.Encoding;
import org.jcodings.EncodingDB;
Expand Down Expand Up @@ -257,9 +259,17 @@ static interface Engine {
static class JavaRegexEngine implements Engine {
private Charset charset = Charset.forName("UTF-8");
private Pattern pattern;
private final long timeoutMillis;

public JavaRegexEngine(String regex, int flags) {
this.pattern = Pattern.compile(regex, flags);
this.timeoutMillis = ConfigurationHolder.getInstance().getConf()
.getLong("hbase.filter.regex.java.timeout", -1);
}

JavaRegexEngine(String regex, int flags, long timeoutMillis) {
this.pattern = Pattern.compile(regex, flags);
this.timeoutMillis = timeoutMillis;
}

@Override
Expand Down Expand Up @@ -294,7 +304,14 @@ public int compareTo(byte[] value, int offset, int length) {
} else {
tmp = new String(value, offset, length, charset);
}
return pattern.matcher(tmp).find() ? 0 : 1;

if (timeoutMillis == -1) {
return pattern.matcher(tmp).find() ? 0 : 1;
} else {
final TimeoutCharSequence chars =
new TimeoutCharSequence(tmp, EnvironmentEdgeManager.currentTime(), timeoutMillis);
return pattern.matcher(chars).find() ? 0 : 1;
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.filter;

import org.apache.hadoop.hbase.DoNotRetryUncheckedIOException;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.apache.hadoop.util.StringUtils;

/**
* It checks whether the timeout has been exceeded whenever the charAt method is called.
*/
class TimeoutCharSequence implements CharSequence {
static final int DEFAULT_CHECK_POINT = 10_000;

private final CharSequence value;
private final long startMillis;
private final long timeoutMillis;
private final int checkPoint;
private int numberOfCalls;

/**
* Initialize a TimeoutCharSequence.
* @param value the original data
* @param startMillis time the operation started (ms)
* @param timeoutMillis the timeout (ms)
*/
TimeoutCharSequence(CharSequence value, long startMillis, long timeoutMillis) {
this.value = value;
this.startMillis = startMillis;
this.timeoutMillis = timeoutMillis;
this.checkPoint = DEFAULT_CHECK_POINT;
this.numberOfCalls = 0;
}

/**
* Initialize a TimeoutCharSequence.
* @param value the original data
* @param startMillis time the operation started (ms)
* @param checkPoint the check point
* @param timeoutMillis the timeout (ms)
*/
TimeoutCharSequence(CharSequence value, long startMillis, long timeoutMillis, int checkPoint) {
this.value = value;
this.startMillis = startMillis;
this.timeoutMillis = timeoutMillis;
this.checkPoint = checkPoint;
this.numberOfCalls = 0;
}

@Override
public int length() {
return value.length();
}

@Override
public char charAt(int index) {
numberOfCalls++;
if (numberOfCalls % checkPoint == 0) {
final long diff = EnvironmentEdgeManager.currentTime() - startMillis;
if (diff > timeoutMillis) {
throw new DoNotRetryUncheckedIOException(
String.format("Operation timed out after %s.", StringUtils.formatTime(diff)));
}
numberOfCalls = 0;
}
return value.charAt(index);
}

@Override
public CharSequence subSequence(int start, int end) {
return new TimeoutCharSequence(value.subSequence(start, end), startMillis, timeoutMillis);
}

@Override
public String toString() {
return value.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.filter;

import java.nio.charset.StandardCharsets;
import java.util.regex.Pattern;
import org.apache.hadoop.hbase.DoNotRetryUncheckedIOException;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.testclassification.SmallTests;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.apache.hadoop.hbase.util.IncrementingEnvironmentEdge;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.experimental.categories.Category;

@Category(SmallTests.class)
public class RegexStringComparatorTest {
@ClassRule
public static final HBaseClassTestRule CLASS_RULE =
HBaseClassTestRule.forClass(RegexStringComparatorTest.class);

@Test(expected = DoNotRetryUncheckedIOException.class)
public void testCompareToTimeout() {
final RegexStringComparator.JavaRegexEngine regex =
new RegexStringComparator.JavaRegexEngine("(0*)*A", Pattern.DOTALL, 0);

EnvironmentEdgeManager.injectEdge(new IncrementingEnvironmentEdge());
final byte[] input = "00000000000000000000000000000".getBytes(StandardCharsets.UTF_8);
// It actually takes a few seconds
regex.compareTo(input, 0, input.length);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.filter;

import static org.apache.hadoop.hbase.filter.TimeoutCharSequence.DEFAULT_CHECK_POINT;

import org.apache.hadoop.hbase.DoNotRetryUncheckedIOException;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.testclassification.SmallTests;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.apache.hadoop.hbase.util.IncrementingEnvironmentEdge;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.experimental.categories.Category;

@Category(SmallTests.class)
public class TimeoutCharSequenceTest {
@ClassRule
public static final HBaseClassTestRule CLASS_RULE =
HBaseClassTestRule.forClass(TimeoutCharSequenceTest.class);

@Test(expected = DoNotRetryUncheckedIOException.class)
public void testCharAtTimeout() {
final IncrementingEnvironmentEdge environmentEdge = new IncrementingEnvironmentEdge(7);
EnvironmentEdgeManager.injectEdge(environmentEdge);

final TimeoutCharSequence chars = new TimeoutCharSequence("test", 0, 8, 1);
for (int i = 0; i < 3; i++) {
chars.charAt(3);
}
}

@Test
public void testCheckPoint() {
final long initialAmount = 10;
final IncrementingEnvironmentEdge edge = new IncrementingEnvironmentEdge(initialAmount);
EnvironmentEdgeManager.injectEdge(edge);

final TimeoutCharSequence chars = new TimeoutCharSequence("test", 0, 8);

boolean thrownException = false;
for (int i = 0; i < DEFAULT_CHECK_POINT; i++) {
try {
chars.charAt(3);
} catch (DoNotRetryUncheckedIOException e) {
thrownException = true;
}
}

Assert.assertTrue(thrownException);
Assert.assertEquals(edge.currentTime(), initialAmount + 1);
}

@Test
public void testCharAt() {
final IncrementingEnvironmentEdge environmentEdge = new IncrementingEnvironmentEdge(1);
EnvironmentEdgeManager.injectEdge(environmentEdge); // next currentTime: 1

final TimeoutCharSequence chars = new TimeoutCharSequence("test", 0, 10);
Assert.assertEquals('t', chars.charAt(0));
Assert.assertEquals('e', chars.charAt(1));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.conf;

import java.util.concurrent.locks.ReentrantLock;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.yetus.audience.InterfaceAudience;
import org.apache.yetus.audience.InterfaceStability;

/**
* ConfigurationHolder class that holds Configuration so that they can be easily accessed anywhere.
*/
@InterfaceAudience.Private
@InterfaceStability.Unstable
public class ConfigurationHolder implements ConfigurationObserver {
private final ReentrantLock confLock = new ReentrantLock();
private Configuration conf;

private ConfigurationHolder() {
}

private static class SingletonHelper {
private static final ConfigurationHolder INSTANCE = new ConfigurationHolder();
}

/**
* Returns a ConfigurationHolder
* @return Configuration
*/
public static ConfigurationHolder getInstance() {
return SingletonHelper.INSTANCE;
}

/**
* Update Configuration
* @param conf Configuration
*/
public void setConf(Configuration conf) {
confLock.lock();
try {
this.conf = conf;
} finally {
confLock.unlock();
}
}

/**
* Check if a Configuration exists. If it does, return it. If not, create a new Configuration and
* return it.
* @return Configuration
*/
public Configuration getConf() {
confLock.lock();
try {
if (conf == null) {
setConf(HBaseConfiguration.create());
}

return conf;
} finally {
confLock.unlock();
}
}

@Override
public void onConfigurationChange(Configuration conf) {
setConf(conf);
}
}
Loading