package org.gbif.nameparser;

import org.gbif.nameparser.utils.NamedThreadFactory;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Set;
import java.util.concurrent.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;

/**
 *
 */
public class InterruptibleCharSequenceTest {
  /**
   * Runs for about 10s
   */
  static class LongRegxJob implements Callable<Long> {
    private static Logger LOG = LoggerFactory.getLogger(LongRegxJob.class);
    private static final String TEMPLATE = "00000000000000000000000000000";
    private final CharSequence input;

    static LongRegxJob interruptable() {
      return new LongRegxJob(true);
    }
    static LongRegxJob regular() {
      return new LongRegxJob(false);
    }

    private LongRegxJob(boolean interruptable) {
      input = interruptable ? new InterruptibleCharSequence(TEMPLATE) : TEMPLATE;
    }

    @Override
    public Long call() throws Exception {
      final Pattern pattern = Pattern.compile("(0*)*A");

      long startTime = System.currentTimeMillis();
      Matcher matcher = pattern.matcher(input);
      matcher.find(); // runs for roughly a minute!
      long duration = System.currentTimeMillis() - startTime;
      LOG.info("Regex finished in {}ms", duration);
      return duration;
    }
  }

  @Test
  public void testRegexTimeout() throws InterruptedException {
    final String threadName = "regex-worker";
    final ExecutorService EXEC = new ThreadPoolExecutor(0, 20, 100L, TimeUnit.MILLISECONDS,
        new SynchronousQueue<Runnable>(),
        new NamedThreadFactory(threadName, Thread.NORM_PRIORITY, true),
        new ThreadPoolExecutor.CallerRunsPolicy());

    for (int x = 0; x<8; x++) {
      System.out.println("Executing task " + x);
      Future<Long> task = EXEC.submit(LongRegxJob.interruptable());
      try {
        Long duration = task.get(250, TimeUnit.MILLISECONDS);
        fail("Expected to timeout but parsed in " + duration + "ms");

      } catch (TimeoutException e) {
        // timeout happend, expected. Add more tasks
        System.out.println("Task " + x + " timed out as expected. Interrupt");
        task.cancel(true);

      } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
      }
    }

    //allow for some time to interrupt
    long sleep = 100;
    System.out.println("Wait for " + sleep + "ms");
    Thread.sleep(sleep);

    // now make sure the regex runner thread is dead!
    Set<Thread> threads = Thread.getAllStackTraces().keySet();
    for (Thread t : threads) {
      if (t.getName().startsWith(threadName)) {
        System.out.println(t.getName() + "  -->  " + t.getState());
      }
    }
    for (Thread t : threads) {
      if (t.getName().startsWith(threadName)) {
        assertFalse("Running executor thread detected", t.getState() == Thread.State.RUNNABLE);
      }
    }

    EXEC.shutdown();
  }

}
@@ -624,44 +624,246 @@
 LATE
- = %220000000000000000000000000000
+;%0A    private static final Pattern PATTERN = Pattern.compile(%22(0*)*A%22);%0A    static %7B%0A      StringBuilder sb = new StringBuilder();%0A      for (int i=1; i%3C10000; i++) %7B%0A        sb.append(%22
 0%22
+)
 ;%0A
+      %7D%0A      TEMPLATE = sb.toString();%0A    %7D%0A%0A
     
@@ -1256,65 +1256,8 @@
 on %7B
-%0A      final Pattern pattern = Pattern.compile(%22(0*)*A%22);
 %0A%0A  
@@ -1333,15 +1333,15 @@
 r = 
-pattern
+PATTERN
 .mat
@@ -1405,16 +1405,59 @@
 minute!%0A
+      System.out.println(matcher.group());%0A
       lo
@@ -2170,10 +2170,10 @@
 get(
-25
+10
 0, T

Make slow regex harder