Debugging Grails Forked Mode

| Comments

Recent versions of grails introduce forked execution as the default mode.

There are benefits to forked execution (reducing memory strain, classpath isolation, and removing metaclass conflicts), but there are also downsides with the current implementation. The biggest of which is that it makes debugging more painful.

You can no longer simply run the “debug” task in IntelliJ and have it stop at your breakpoints. If you do that, you’ll be debugging the parent launcher process, not your grails application so your breakpoints will never be exercised.

The standard way to get debugging working with forked execution is to use the --debug-fork flag on your grails run-app or grails test-app command. Then, create a remote debugger task in your IDE and attach it once the grails app opens up port 5005.

The Problem

This sucks (IMO) for 3 reasons:

1. It assumes you know you want to debug before you start the app up.

Forget to set the flag and you need to bounce your server.

2. Under the covers --debug-fork uses the suspend=y debug flag which causes grails to halt starting up till you attach a debugger.

If you get distracted while grails is starting up, you’ll often come back to a halted process that still has 90% of the startup to do before it’s ready to serve the app.

3. You can’t attach a debugger till grails forks the process and actually opens up port 5005.

This often takes 10+ seconds to compile all your code, launch the process and finally open the port.

All of this means that you need to babysit your grails app while it starts up, or forego debugging unless you know you need it.

My Solution

The easiest way to solve this is to turn forked execution off (if everyone on your team is willing to give up the benefits). This is easily done by modifying your BuildConfig.groovy so that the grails.project.fork section is empty:

grails.project.fork = []

What if you (or your team) are not willing to give up forked execution mode? You can eliminate most of the downsides with a few tweaks.

1. Always Be Debugging

There really isn’t any performance penalty in dev mode to just always run with the debug flags enabled and this lets you connect a debugger at-will. You don’t need to remember to start a different run target (or run a different command). You can change your BuildConfig.groovy to always run in debug mode with the debug: true map variable, ex:

grails.project.fork = [
    test: [maxMemory: 768, minMemory: 64, debug: true, maxPerm: 256, daemon:true],

Unfortunately, while that will always debug, it also has the suspend=y flag hard coded into it, so you’ll pause execution on every run which violates issue #2.

2. Use the suspend=n debug flag so that grails doesn’t pause till you connect a debugger.

This is harder to configure than you’d expect. As mentioned above, both the --debug-fork command line switch and debug: true flag in BuildConfig.groovy cause suspend=y flag to be used. To get around this, you need to specify the actual JVM debug flags in your BuildConfig.groovy, ex:

// jvmArgs make it so that we can run in forked mode without having to use the `--debug-fork` flag
// and also has suspend=n so that it will start up without forcing you to connect a remote debugger first
def jvmArgs = ['-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005']

grails.project.fork = [
    test: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, daemon:true, jvmArgs: jvmArgs],
    run: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, forkReserve:false, jvmArgs: jvmArgs],
    war: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, forkReserve:false, jvmArgs: jvmArgs],
    console: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, jvmArgs: jvmArgs]

With that, we’ve solved problems #1 and #2. Our grails app will always be running in debug mode and will also not pause halfway through startup to force us to always connect a debugger. We can connect a debugger whenever we want to actually debug.

3. Monitor the port before starting the remote debugger

If you create a new remote debug target in intellij and run it at the same time as you run your app or tests, it’ll fail because the debug port isn’t open yet. If you do it manually, you need to wait for the log message saying that the debug port is open. That’s more babysitting that we can avoid through a little shell script trickery.

Here is a shell script that uses the nc (netcat) command to monitor a localhost port, and will only continue once the port is available. Save it as somewhere in your path:

#!/usr/bin/env bash

function usage {
    echo "usage: ${0##*/} "
    echo "ex: ${0##*/} 5005"

if [ -z $PORT_NUMBER ]; then
    exit 1

echo "waiting for port $PORT_NUMBER to open up"

while ! nc -z localhost $PORT_NUMBER; do sleep 0.1; done;

Now, create a new IntelliJ remote debug task:

IntelliJ Remote Debug

have it use this script as an “external tool” to monitor port 5005 before it tries to connect.

External Tool Creation

Now, you can start up your grails run-app task and immediately start the debug task without having to wait for the debugger port to be open:

Run and Debug in IntelliJ at the same time

You might see an error in the grails log about how the debugger disconnected, but that was actually netcat connecting then quitting out right away and it’s harmless.

Now you have all of the pieces necessary to make grails forked execution pretty painless while still getting all of it’s benefits.