@Override
  public void intercept(final IMethodInvocation invocation) throws Throwable {
    final Thread mainThread = Thread.currentThread();
    final SynchronousQueue<StackTraceElement[]> sync = new SynchronousQueue<>();
    final CountDownLatch startLatch = new CountDownLatch(2);
    final String methodName = invocation.getMethod().getName();
    final double timeoutSeconds = TimeUtil.toSeconds(timeout);

    ThreadSupport.virtualThreadIfSupported(String.format("[spock.lang.Timeout] Watcher for method '%s'", methodName), () -> {
        StackTraceElement[] stackTrace = new StackTraceElement[0];
        long waitMillis = timeout.toMillis();
        boolean synced = false;
        long timeoutAt = 0;
        int unsuccessfulInterruptAttempts = 0;

        syncWithThread(startLatch, "feature", methodName);

        while (!synced) {
          try {
            synced = sync.offer(stackTrace, waitMillis, TimeUnit.MILLISECONDS);
          } catch (InterruptedException ignored) {
            // The mission of this thread is to repeatedly interrupt the main thread until
            // the latter returns. Once this mission has been accomplished, this thread will die quickly
          }
          if (!synced) {
            long now = System.nanoTime();
            if (stackTrace.length == 0) {
              logMethodTimeout(methodName, timeoutSeconds);
              stackTrace = mainThread.getStackTrace();
              waitMillis = 250;
              timeoutAt = now;
            } else {
              waitMillis *= 2;
              logUnsuccessfulInterrupt(methodName, now, timeoutAt, waitMillis, ++unsuccessfulInterruptAttempts);
            }
            mainThread.interrupt();
          }
      }
    }).start();

    syncWithThread(startLatch, "watcher", methodName);

    Throwable saved = null;
    try {
      invocation.proceed();
    } catch (Throwable t) {
      saved = t;
    }
    StackTraceElement[] stackTrace = null;
    while (stackTrace == null) {
      try {
        stackTrace = sync.take();
      } catch (InterruptedException e) {
        // There is a small chance that this came from the watcher thread,
        // i.e. the two threads narrowly missed each other at the sync point.
        // Therefore, let's sync again to learn whether this thread has timed out or not.
        // As this won't take long, it should also be acceptable if this thread
        // got interrupted by some other thread. To report on the latter case,
        // we save off the exception, overriding any previously saved exception.
        if (saved != null) {
          saved.addSuppressed(e);
        } else {
          saved = e;
        }
      }
    }
    if (stackTrace.length > 0) {
      // We know that this thread got timed out (and interrupted) by the watcher thread and
      // act accordingly. We gloss over the fact that some other thread might also have tried to
      // interrupt this thread. This shouldn't be a problem in practice, in particular because
      // throwing an InterruptedException wouldn't abort the whole test run anyway.
      String msg = String.format("Method timed out after %1.2f seconds", timeoutSeconds);
      SpockTimeoutError error = new SpockTimeoutError(timeoutSeconds, msg);
      error.setStackTrace(stackTrace);
      throw error;
    }
    if (saved != null) {
      throw saved;
    }
  }
Times out a method invocation if it takes too long. The method invocation
will occur on the regular test framework thread. This can be important
for integration tests with thread-local state.

@author Peter Niederwieser
spock-core/src/main/java/org/spockframework/runtime/extension/builtin/TimeoutInterceptor.java