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

Implement ability to timeout execution of a regexp #78

Merged
merged 2 commits into from
May 16, 2024
Merged
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
8 changes: 7 additions & 1 deletion src/org/joni/ByteCodeMachine.java
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ private final int execute(final boolean checkThreadInterrupt) throws Interrupted
int interruptCheckCounter = 0;
while (true) {
if (interruptCheckCounter++ >= interruptCheckEvery) {
if (timeout != -1) handleTimeout();
handleInterrupted(checkThreadInterrupt);
interruptCheckCounter = 0;
}
Expand Down Expand Up @@ -306,11 +307,16 @@ private final int execute(final boolean checkThreadInterrupt) throws Interrupted
} // main while
}

private void handleTimeout() throws InterruptedException {
if (System.nanoTime() - startTime > timeout) throw TIMEOUT_EXCEPTION;
}

private final int executeSb(final boolean checkThreadInterrupt) throws InterruptedException {
final int[] code = this.code;
int interruptCheckCounter = 0;
while (true) {
if (interruptCheckCounter++ >= interruptCheckEvery) {
if (timeout != -1) handleTimeout();
handleInterrupted(checkThreadInterrupt);
interruptCheckCounter = 0;
}
Expand Down Expand Up @@ -447,7 +453,7 @@ private final int executeSb(final boolean checkThreadInterrupt) throws Interrupt
private void handleInterrupted(final boolean checkThreadInterrupt) throws InterruptedException {
if (interrupted || (checkThreadInterrupt && Thread.currentThread().isInterrupted())) {
Thread.interrupted();
throw new InterruptedException();
throw INTERRUPTED_EXCEPTION;
}
interruptCheckEvery = Math.min(interruptCheckEvery << 1, MAX_INTERRUPT_CHECK_EVERY);
}
Expand Down
20 changes: 20 additions & 0 deletions src/org/joni/Matcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@
import org.jcodings.constants.CharacterType;
import org.jcodings.specific.ASCIIEncoding;
import org.joni.constants.internal.AnchorType;
import org.joni.exception.TimeoutException;

public abstract class Matcher extends IntHolder {
static final InterruptedException INTERRUPTED_EXCEPTION = new InterruptedException();
static final InterruptedException TIMEOUT_EXCEPTION = new TimeoutException();
public static final int FAILED = -1;
public static final int INTERRUPTED = -2;

Expand All @@ -49,13 +52,26 @@ public abstract class Matcher extends IntHolder {
protected int msaBegin;
protected int msaEnd;

protected long timeout; // nanoseconds

// nanoseconds since entering searchCommon (underlying machines will check during interrupt checks
// which will cheapen how often we look but also it should be granular enough to not matter).
protected long startTime;

Matcher(Regex regex, Region region, byte[]bytes, int p, int end) {
this(regex, region, bytes, p, end, -1);
}

// FIXME: For next major version this should be the main constructor and MatcherFactory should
// have the abstract method be for this.
Matcher(Regex regex, Region region, byte[]bytes, int p, int end, long timeout) {
this.regex = regex;
this.enc = regex.enc;
this.bytes = bytes;
this.str = p;
this.end = end;
this.msaRegion = region;
this.timeout = timeout;
}

// main matching method
Expand Down Expand Up @@ -323,6 +339,7 @@ public final int searchInterruptible(int gpos, int start, int range, int option)
}

private final int searchCommon(int gpos, int start, int range, int option, boolean interrupt) throws InterruptedException {
if (timeout != -1) startTime = System.nanoTime();
int s, prev;
int origStart = start;
int origRange = range;
Expand Down Expand Up @@ -640,4 +657,7 @@ static void debugSearch(String name, int textP, int textEnd, int textRange) {
Config.log.println(name + ": text: " + textP + ", text_end: " + textEnd + ", text_range: " + textRange);
}

public void setTimeout(long timeout) {
this.timeout = timeout;
}
}
6 changes: 6 additions & 0 deletions src/org/joni/MatcherFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
abstract class MatcherFactory {
abstract Matcher create(Regex regex, Region region, byte[]bytes, int p, int end);

public Matcher create(Regex regex, Region region, byte[]bytes, int p, int end, long timeout) {
Matcher matcher = create(regex, region, bytes, p, end);
matcher.setTimeout(timeout);
return matcher;
}

static final MatcherFactory DEFAULT = new MatcherFactory() {
@Override
Matcher create(Regex regex, Region region, byte[]bytes, int p, int end) {
Expand Down
8 changes: 8 additions & 0 deletions src/org/joni/Regex.java
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,18 @@ public Matcher matcher(byte[]bytes, int p, int end) {
return factory.create(this, numMem == 0 ? null : Region.newRegion(numMem + 1), bytes, p, end);
}

public Matcher matcher(byte[]bytes, int p, int end, long timeout) {
return factory.create(this, numMem == 0 ? null : Region.newRegion(numMem + 1), bytes, p, end, timeout);
}

public Matcher matcherNoRegion(byte[]bytes, int p, int end) {
return factory.create(this, null, bytes, p, end);
}

public Matcher matcherNoRegion(byte[]bytes, int p, int end, long timeout) {
return factory.create(this, null, bytes, p, end, timeout);
}

public int numberOfCaptures() {
return numMem;
}
Expand Down
7 changes: 7 additions & 0 deletions src/org/joni/exception/TimeoutException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.joni.exception;

public class TimeoutException extends InterruptedException {
public TimeoutException() {
super();
}
}
Loading