Java Debuggers and Timeouts

How to use your debugger in the presence of timeouts in your code.

Thu 10 July 2014

How to use your debugger in the presence of timeouts in your code.

My kingdom for a debugger!

So you've been coding away merrily on a project and everything is going well until a bug appears. You reach into your developer's toolbox and pull out a debugger. It's great - you can set breakpoints, you can interrupt when there's an exception and you can inspect expressions at runtime. Whatever challenge awaits, you can be sure that a debugger will help!

Unfortunately life isn't that easy. A lot of code needs to have some kind of timeout - an event that happens after a period of time. The problem with this is that timeouts tend to ruin the debugging experience. You're sitting there looking at your breakpoint, thinking "Now why is x 2 instead of 1?" Poof! The timeout kicks in and you are no longer able to continue. Even worse the JVM itself quits! So you go through the process of increasing your timeout, debugging and fixing your problem. Afterwards you either return the timeout to its original setting and have to go through the same tedious process again or accidentally commit the fix into your source tree thus breaking a test or maybe even production. To me this seems less than ideal.

"For somehow this is timeout's disease, to trust no friends"

There's a many reasons that people introduce timeouts. I've listed a few below, a couple good and a couple bad, and I'm sure you can think of a few more yourself.

  • Checking that an asynchronous event has been responded to within a certain period of time.
  • Avoiding starvation of a time based resource, such as a thread pool.
  • You've got a race condition that needs a quick fix.
  • You are waiting for an event to happen and decide to hard code an assumption about how long it'll take. (Can be most frequently spotted in tests)

Now obviously if your timeout has been introduced as a hack then it's a good time to clean and boy-scout the code. If you need to rely on an event happening in tests then you should treat those tests as clients of your API and be able to know when the event has occurred. This might involve injecting a mock which gets called when an event happens or subscribing to a stream of events. If you've got a race condition - fix it! I know it's painful and hard but do you really want a ticking timebomb in your codebase ready to generate a support call at 3am?

Managing your timeouts

Having said that we should remove the bad uses of timeouts, it's pretty clear that are perfectly legitimate uses of timeouts. They are especially common in event driven and asynchronous code. It would still be good to be able to debug with them around. Good practice regardless of other factors is to be able to standardise your timeouts into configuration properties which can be set at runtime. This lets you easily alter them when running in a local IDE vs production. It can also help with managing the different performance properties that you encounter from differing hardware setups.

Having externalised your timeouts into configuration from your code, you can then detect whether your code is running inside a debugger and set timeouts to significantly longer periods if this is the case. The trick to doing this is to recognise that a debugger involves running a Java agent, which modifies the command-line arguments of the program that it runs under. You can check whether these command-line arguments contain the right agent matcher. The following code snippet shows how to do this and has been tested to work under both eclipse and Intellij IDEA.

        RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
        String jvmArguments = runtimeMXBean.getInputArguments().toString();
        boolean hasDebuggerAttached = jvmArguments.contains("-agentlib:jdwp");

I can see why some people would view it as a hack as well, you're actively discovering something about your environment by looking at your own command-line arguments and then adapting around it. From my perspective, I've found this to be a useful technique. It does make it easier to debug in the presence of timeouts.