1

In my application I'm using timeouts to exit out of certain endless loops if it is uncertain if the condition will be met in reasonable time.

For the timekeeping I'm using the system's time since it's straightforward:

public class BenchmarkTimer
{
    private long startTime;

    public BenchmarkTimer()
    {
        startTime = System.currentTimeMillis();
    }

    public boolean isExpired(long milliseconds)
    {
        long executionTime = currentTimeMillis() - startTime;
        return executionTime > milliseconds;
    }
}

However, when debugging, I don't want the time to continue while the application is frozen. This obviously doesn't work by default since the system time will continue even during breakpoints. I don't want to run into a timeout due to me taking too long on a breakpoint to continue because under normal application execution this would behave differently (e.g. it would execute more tries before "giving up").

Is there any kind of solution to this problem? Can IntelliJ IDEA halt the system time visible to the Java application only during breakpoints? I want debugging to behave like a regular execution including the timeouts so I'm not sure how to accomplish this in general.

BullyWiiPlaza
  • 17,329
  • 10
  • 113
  • 185
  • 1
    Looks like this should be helpful. https://stackoverflow.com/a/39399216/3655191 – Ran Jun 18 '18 at 19:59

2 Answers2

0

The link commented by Ran gave me the right idea: Run a thread which keeps double-checking if the Thread.sleep() slept short enough, otherwise things are fishy and we might be on a breakpoint so we can "deter" the clock and return an older time when the system time is requested again.

import lombok.val;

import static com.mycompany.SystemTimeBreakpointFixer.getCurrentTimeMillis;
import static com.mycompany.SystemTimeBreakpointFixer.considerRunningTimerFixerThread;
import static java.lang.System.currentTimeMillis;
import static java.lang.management.ManagementFactory.getRuntimeMXBean;

public class BenchmarkTimer
{
    private long startTime;

    static
    {
        considerRunningTimerFixerThread();
    }

    public BenchmarkTimer()
    {
        startTime = getCurrentTimeMillis();
    }

    public boolean isExpired(long milliseconds)
    {
        val currentTimeMillis = getCurrentTimeMillis();
        val executionTime = currentTimeMillis - startTime;
        return executionTime > milliseconds;
    }

    static class SystemTimeBreakpointFixer
    {
        private static final String THREAD_NAME = "System Time Fixer";
        private static final int CHECKING_DELAY = 100;
        private static final int NON_BREAKPOINT_DELAY = 100;
        private static final String DEBUGGING_INPUT_ARGUMENT = "-agentlib:jdwp";
        private static final boolean ARE_WE_DEBUGGING;

        static
        {
            ARE_WE_DEBUGGING = areWeDebugging();
        }

        private static boolean areWeDebugging()
        {
            val runtimeMXBean = getRuntimeMXBean();
            val inputArguments = runtimeMXBean.getInputArguments();
            val jvmArguments = inputArguments.toString();
            return jvmArguments.contains(DEBUGGING_INPUT_ARGUMENT);
        }

        private static long totalPausedTime = 0;
        private static Thread thread;

        static long getCurrentTimeMillis()
        {
            sleepThread();
            val currentTimeMillis = currentTimeMillis();
            return currentTimeMillis - totalPausedTime;
        }

        static void considerRunningTimerFixerThread()
        {
            if (ARE_WE_DEBUGGING && thread == null)
            {
                startTimerFixerThread();
            }
        }

        private static void startTimerFixerThread()
        {
            thread = new Thread(() ->
            {
                //noinspection InfiniteLoopStatement
                while (true)
                {
                    val currentMilliseconds = currentTimeMillis();
                    sleepThread();
                    val updatedMilliseconds = currentTimeMillis();
                    val currentPausedTime = updatedMilliseconds - currentMilliseconds - CHECKING_DELAY;
                    if (currentPausedTime > NON_BREAKPOINT_DELAY)
                    {
                        totalPausedTime += currentPausedTime;
                    }
                }
            });

            thread.setName(THREAD_NAME);
            thread.start();
        }

        private static void sleepThread()
        {
            try
            {
                Thread.sleep(CHECKING_DELAY);
            } catch (InterruptedException exception)
            {
                exception.printStackTrace();
            }
        }
    }
}

Also credits to this article for the debugger detection.

BullyWiiPlaza
  • 17,329
  • 10
  • 113
  • 185
0

use a breakpoint inside isExpired on the line with return with the settings:

  • set "evaluate and log" expression like executionTime = 0
  • unset "suspend"

Now in debug mode it will modify executionTime so that isExpired always return false

Egor
  • 2,474
  • 17
  • 19