Отменить длительный матч с регулярным выражением? - PullRequest
19 голосов
/ 26 мая 2009

Допустим, у меня есть служба, где пользователи могут отправлять регулярные выражения для поиска по большому количеству данных. Если пользователь отправляет регулярное выражение очень медленно (т.е. Matcher.find () возвращает минуты), я хочу отменить это совпадение. Единственный способ, которым я могу думать об этом, - это иметь другой монитор потока, сколько времени занимает совпадение, и использовать Thread.stop (), чтобы отменить его при необходимости.

Переменные-члены:

long REGEX_TIMEOUT = 30000L;
Object lock = new Object();
boolean finished = false;
Thread matcherThread;

Соответствующая нить:

try {
    matcherThread = Thread.currentThread();

    // imagine code to start monitor thread is here

    try {
        matched = matcher.find();
    } finally {
        synchronized (lock) {
            finished = true;
            lock.notifyAll();
        }
    }
} catch (ThreadDeath td) {
    // send angry message to client
    // handle error without rethrowing td
}

Поток монитора:

synchronized (lock) {
    while (! finished) {
        try {
            lock.wait(REGEX_TIMEOUT);

            if (! finished) {
                matcherThread.stop();
            }
        } catch (InterruptedException ex) {
            // ignore, top level method in dedicated thread, etc..
        }
    }
}

Я прочитал java.sun.com/j2se/1.4.2/docs/guide/misc/threadPrimitiveDeprecation.html и думаю, что это использование безопасно, так как я контролирую, где ThreadDeath выбрасывается через синхронизацию, и обрабатываю его и единственными поврежденными объектами могут быть мои экземпляры Pattern и Matcher, которые все равно будут отброшены. Я думаю, что это нарушает Thread.stop (), потому что я не выкидываю ошибку, но я не хочу, чтобы поток умирал, просто прервите метод find ().

Мне удалось избежать использования этих устаревших компонентов API, но Matcher.find (), похоже, не прерываемый и может занять очень много времени, чтобы вернуться. Есть ли лучший способ сделать это?

Ответы [ 4 ]

41 голосов
/ 26 мая 2009

От Heritrix: ( crawler.archive.org )

/**
 * CharSequence that noticed thread interrupts -- as might be necessary 
 * to recover from a loose regex on unexpected challenging input. 
 * 
 * @author gojomo
 */
public class InterruptibleCharSequence implements CharSequence {
    CharSequence inner;
    // public long counter = 0; 

    public InterruptibleCharSequence(CharSequence inner) {
        super();
        this.inner = inner;
    }

    public char charAt(int index) {
        if (Thread.interrupted()) { // clears flag if set
            throw new RuntimeException(new InterruptedException());
        }
        // counter++;
        return inner.charAt(index);
    }

    public int length() {
        return inner.length();
    }

    public CharSequence subSequence(int start, int end) {
        return new InterruptibleCharSequence(inner.subSequence(start, end));
    }

    @Override
    public String toString() {
        return inner.toString();
    }
}

Оберните ваш CharSequence этим, и прерывания Thread будут работать ...

4 голосов
/ 05 июля 2012

С небольшими вариациями можно избежать использования дополнительных потоков для этого:

public class RegularExpressionUtils {

    // demonstrates behavior for regular expression running into catastrophic backtracking for given input
    public static void main(String[] args) {
        Matcher matcher = createMatcherWithTimeout(
                "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "(x+x+)+y", 2000);
        System.out.println(matcher.matches());
    }

    public static Matcher createMatcherWithTimeout(String stringToMatch, String regularExpression, int timeoutMillis) {
        Pattern pattern = Pattern.compile(regularExpression);
        return createMatcherWithTimeout(stringToMatch, pattern, timeoutMillis);
    }

    public static Matcher createMatcherWithTimeout(String stringToMatch, Pattern regularExpressionPattern, int timeoutMillis) {
        CharSequence charSequence = new TimeoutRegexCharSequence(stringToMatch, timeoutMillis, stringToMatch,
                regularExpressionPattern.pattern());
        return regularExpressionPattern.matcher(charSequence);
    }

    private static class TimeoutRegexCharSequence implements CharSequence {

        private final CharSequence inner;

        private final int timeoutMillis;

        private final long timeoutTime;

        private final String stringToMatch;

        private final String regularExpression;

        public TimeoutRegexCharSequence(CharSequence inner, int timeoutMillis, String stringToMatch, String regularExpression) {
            super();
            this.inner = inner;
            this.timeoutMillis = timeoutMillis;
            this.stringToMatch = stringToMatch;
            this.regularExpression = regularExpression;
            timeoutTime = System.currentTimeMillis() + timeoutMillis;
        }

        public char charAt(int index) {
            if (System.currentTimeMillis() > timeoutTime) {
                throw new RuntimeException("Timeout occurred after " + timeoutMillis + "ms while processing regular expression '"
                                + regularExpression + "' on input '" + stringToMatch + "'!");
            }
            return inner.charAt(index);
        }

        public int length() {
            return inner.length();
        }

        public CharSequence subSequence(int start, int end) {
            return new TimeoutRegexCharSequence(inner.subSequence(start, end), timeoutMillis, stringToMatch, regularExpression);
        }

        @Override
        public String toString() {
            return inner.toString();
        }
    }

}

Большое спасибо Доусу за то, что он указал мне на это решение в ответ на ненужный сложный вопрос !

0 голосов
/ 15 февраля 2016

Может быть, вам нужна новая библиотека, которая реализует алгоритм NFA.

Алгоритм NFA в сотни раз быстрее алгоритма, который используется стандартной библиотекой Java.

И библиотека Java std lib чувствительна к регулярному выражению ввода, что может привести к возникновению вашей проблемы - некоторые входные данные заставляют процессор работать годами.

И время ожидания может быть установлено алгоритмом NFA с помощью шагов, которые он использует. Это эффективнее, чем решение Thread. Поверьте мне, я использую таймаут потока для относительной проблемы, это ужасно для производительности. Я наконец исправляю проблему, изменяя основной цикл моего алгоритма. Я вставляю некоторую контрольную точку в основной цикл, чтобы проверить время.

Подробности можно найти здесь: https://swtch.com/~rsc/regexp/regexp1.html.

0 голосов
/ 26 мая 2009

Другой обходной путь - ограничить область сопоставителя, затем вызвать find(), повторяя до тех пор, пока поток не прервется или не будет найдено совпадение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...