Say I\'m running a service where users can submit a regex to search through lots of data. If the user submits a regex that is very slow (ie. takes minutes for Matcher.find()
A long-running pattern matching process can be stopped using the below method.
StateFulCharSequence
class which manages the state of pattern matching. When that state is changed, an exception is thrown on the next call to charAt
method.ScheduledExecutorService
with a required timeout.Here pattern matching is happening in the main thread and there is no need to check the thread interrupt state every time.
public class TimedPatternMatcher {
public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
Pattern pattern = Pattern.compile("some regex pattern");
StateFulCharSequence stateFulCharSequence = new StateFulCharSequence("some character sequence");
Matcher matcher = pattern.matcher(stateFulCharSequence);
executorService.schedule(stateFulCharSequence, 10, TimeUnit.MILLISECONDS);
try {
boolean isMatched = matcher.find();
}catch (Exception e) {
e.printStackTrace();
}
}
/*
When this runnable is executed, it will set timeOut to true and pattern matching is stopped by throwing exception.
*/
public static class StateFulCharSequence implements CharSequence, Runnable{
private CharSequence inner;
private boolean isTimedOut = false;
public StateFulCharSequence(CharSequence inner) {
super();
this.inner = inner;
}
public char charAt(int index) {
if (isTimedOut) {
throw new RuntimeException(new TimeoutException("Pattern matching timeout occurs"));
}
return inner.charAt(index);
}
@Override
public int length() {
return inner.length();
}
@Override
public CharSequence subSequence(int start, int end) {
return new StateFulCharSequence(inner.subSequence(start, end));
}
@Override
public String toString() {
return inner.toString();
}
public void setTimedOut() {
this.isTimedOut = true;
}
@Override
public void run() {
this.isTimedOut = true;
}
}}