Gradle -Dorg.java.jvm can't be put in the background

Gradle Version: 3.0
Operating System: OS X 10.11.6

When using gradle with the -Dorg.gradle.jvmargs option to pass arguments to the JVM, gradle can’t be run in the background unless standard input is set to /dev/null.

I created a trivial build.gradle:

task hello {
  doLast {
    println "Sleeping..."
    100.times {
      println "Counting..."
      sleep 1000
    }
    println "task2"
  }
}

If I run gradle without passing the -Dorg.gradle.jvmargs option, it works as expected.

$ gradle --no-daemon hello >out.log 2>&1 & tail -f out.log
[1] 67218
:hello
Sleeping...
Counting...
Counting...
Counting...
Counting...
Counting...
Counting...
Counting...
^C
$ jobs
[1]+  Running                 gradle --no-daemon hello > out.log 2>&1 &
$ kill %1
[1]+  Exit 143                gradle --no-daemon hello > out.log 2>&1

However, if I pass an argument to the JVM using the -Dopg.gradle.jvmargs, you can’t background gradle.

$ gradle -Dorg.gradle.jvmargs=-Xms256m --no-daemon hello >out.log 2>&1 & tail -f out.log
[1] 67311
To honour the JVM settings for this build a new JVM will be forked. Please consider using the daemon: https://docs.gradle.org/3.0/userguide/gradle_daemon.html.
^C

[1]+  Stopped                 gradle -Dorg.gradle.jvmargs=-Xms256m --no-daemon hello > out.log 2>&1
$ jobs
[1]+  Stopped                 gradle -Dorg.gradle.jvmargs=-Xms256m --no-daemon hello > out.log 2>&1
$ bg
[1]+ gradle -Dorg.gradle.jvmargs=-Xms256m --no-daemon hello > out.log 2>&1 &

[1]+  Stopped                 gradle -Dorg.gradle.jvmargs=-Xms256m --no-daemon hello > out.log 2>&1
$ bg
[1]+ gradle -Dorg.gradle.jvmargs=-Xms256m --no-daemon hello > out.log 2>&1 &

[1]+  Stopped                 gradle -Dorg.gradle.jvmargs=-Xms256m --no-daemon hello > out.log 2>&1
$ bg
[1]+ gradle -Dorg.gradle.jvmargs=-Xms256m --no-daemon hello > out.log 2>&1 &

[1]+  Stopped                 gradle -Dorg.gradle.jvmargs=-Xms256m --no-daemon hello > out.log 2>&1

At this point, gradle can’t be put in the background.

If I redirect stdin however, it works as expected:

$ gradle -Dorg.gradle.jvmargs=-Xms256m --no-daemon hello >out.log 2>&1 </dev/null & tail -f out.log
[2] 67483
To honour the JVM settings for this build a new JVM will be forked. Please consider using the daemon: https://docs.gradle.org/3.0/userguide/gradle_daemon.html.
Daemon will be stopped at the end of the build stopping after processing
:hello
Sleeping...
Counting...
Counting...
Counting...
Counting...
^C

So it seems to me that the original gradle process is blocking on stdin somehow.

So what’s happening here is that to honor the -Dorg.gradle.jvmargs, Gradle is spawning a subprocess with the specified settings and then forwarding STDIN to that subprocess. Because it’s in the background, when the initial Gradle process tries to read STDIN, a SIGTTIN signal is being sent to all processes associated with the terminal session (see here for a brief description of SIGTTIN and here to understand why it does this and learn more than you ever wanted to know about TTYs). To properly handle this on the Gradle side, it would have to know that you do not want to use STDIN from the terminal (and that you don’t intend to foreground the process at some point in the future and resume sending STDIN from the terminal). Obviously, he can’t determine that on his own, so one way to communicate that is to redirect STDIN from some place else (e.g. from /dev/null). In this case it avoids the SIGTTIN (since it doesn’t attempt to read from the terminal). So why does this not happen in the case where we don’t use -Dorg.gradle.jvmargs? Because we aren’t spawning a subprocess, and so we don’t have to read from the terminal in order to forward STDIN.

So, the point is that redirecting STDIN is actually the proper way to launch Gradle in the background when setting the JVM arguments this way, since it disconnects STDIN from the terminal. Another workaround would be to use GRADLE_OPTS to set the jvm args instead of org.gradle.jvmargs (see the user guide for discussion of the options here). This would avoid spawning the subprocess and therefore the read from the terminal.