Regrettably there is no way to specify a timeout when using a regular expression on a String in Java. So if you have no strict control over what patterns get applied to which input, you might end up having threads that consume a lot of CPU while endlessly trying to match (not so well designed) patterns to (malicious?) input.
I'm aware of the reasons why Thread#stop() is deprecated (see http://download.oracle.com/javase/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html). They are centered around objects that might get damaged in case of ThreadDeath exceptions, and which then pollute your running JVM environment and can lead to subtle errors.
My question to anyone who has deeper insight than me into the workings of the JVM is this: If the thread that needs to be stopped does not hold any (obvious) monitors on or references to objects that are used by the rest of the program, can it then be acceptable to use Thread#stop() nevertheless?
I created a rather defensive solution to be able to process regular expression matching with a timeout. I would be glad for any comment or remark, especially on problems that this approach can cause despite my efforts to avoid them.
Thanks!
import java.util.concurrent.Callable;
public class SafeRegularExpressionMatcher {
// demonstrates behavior for regular expression running into catastrophic backtracking for given input
public static void main(String[] args) {
SafeRegularExpressionMatcher matcher = new SafeRegularExpressionMatcher(
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "(x+x+)+y", 2000);
System.out.println(matcher.matches());
}
final String stringToMatch;
final String regularExpression;
final int timeoutMillis;
public SafeRegularExpressionMatcher(String stringToMatch, String regularExpression, int timeoutMillis) {
this.stringToMatch = stringToMatch;
this.regularExpression = regularExpression;
this.timeoutMillis = timeoutMillis;
}
public Boolean matches() {
CallableThread<Boolean> thread = createSafeRegularExpressionMatchingThread();
Boolean result = tryToGetResultFromThreadWithTimeout(thread);
return result;
}
private CallableThread<Boolean> createSafeRegularExpressionMatchingThread() {
final String stringToMatchForUseInThread = new String(stringToMatch);
final String regularExpressionForUseInThread = new String(regularExpression);
Callable<Boolean> callable = createRegularExpressionMatchingCallable(stringToMatchForUseInThread,
regularExpressionForUseInThread);
CallableThread<Boolean> thread = new CallableThread<Boolean>(callable);
return thread;
}
private Callable<Boolean> createRegularExpressionMatchingCallable(final String stringToMatchForUseInThread,
final String regularExpressionForUseInThread) {
Callable<Boolean> callable = new Callable<Boolean>() {
public Boolean call() throws Exception {
return Boolean.valueOf(stringToMatchForUseInThread.matches(regularExpressionForUseInThread));
}
};
return callable;
}
private Boolean tryToGetResultFromThreadWithTimeout(CallableThread<Boolean> thread) {
startThreadAndApplyTimeout(thread);
Boolean result = processThreadResult(thread);
return result;
}
private void startThreadAndApplyTimeout(CallableThread<Boolean> thread) {
thread.start();
try {
thread.join(timeoutMillis);
} catch (InterruptedException e) {
throwRuntimeException("Interrupt", e);
}
}
private Boolean processThreadResult(CallableThread<Boolean> thread) {
Boolean result = null;
if (thread.isAlive()) {
killThread(thread); // do not use anything from the thread anymore, objects may be damaged!
throwRuntimeException("Timeout", null);
} else {
Exception exceptionOccurredInThread = thread.getException();
if (exceptionOccurredInThread != null) {
throwRuntimeException("Exception", exceptionOccurredInThread);
} else {
result = thread.getResult();
}
}
return result;
}
private void throwRuntimeException(String situation, Exception e) {
throw new RuntimeException(situation + " occured while applying pattern /" + regularExpression + "/ to input '"
+ stringToMatch + " after " + timeoutMillis + "ms!", e);
}
/**
* This method uses {@link Thread#stop()} to kill a thread that is running wild. Although it is acknowledged that
* {@link Thread#stop()} is inherently unsafe, the assumption is that the thread to kill does not hold any monitors on or
* even references to objects referenced by the rest of the JVM, so it is acceptable to do this.
*
* After calling this method nothing from the thread should be used anymore!
*
* @param thread Thread to stop
*/
@SuppressWarnings("deprecation")
private static void killThread(CallableThread<Boolean> thread) {
thread.stop();
}
private static class CallableThread<V> extends Thread {
private final Callable<V> callable;
private V result = null;
private Exception exception = null;
public CallableThread(Callable<V> callable) {
this.callable = callable;
}
@Override
public void run() {
try {
V result = compute();
setResult(result);
} catch (Exception e) {
exception = e;
} catch (ThreadDeath e) {
cleanup();
}
}
private V compute() throws Exception {
return callable.call();
}
private synchronized void cleanup() {
result = null;
}
private synchronized void setResult(V result) {
this.result = result;
}
public synchronized V getResult() {
return result;
}
public synchronized Exception getException() {
return exception;
}
}
}
EDIT:
Thanks to dawce who pointed me to this solution I have been able to solve my original problem without the need for additional threads. I have posted the code there. Thanks to all who have responded.