Bug in org.gradle.process.internal.streams.ExecOutputHandleRunner?


(Nikita Salnikov-Tarnovski) #1

I run the following task from my Gradle build

task restartJetty(type:Exec){
      commandLine '/mnt/jetty/bin/jetty.sh', 'restart'
    }

Gradle 1.4 executes that shell script and then just sits there waiting for something. In the thread dump I see the following:

“Forward streams with process: command ‘/mnt/jetty/bin/jetty.sh’ Thread 2” prio=5 tid=7ff7e5998000 nid=0x115980000 runnable [11597f000]

java.lang.Thread.State: RUNNABLE

at java.io.FileInputStream.readBytes(Native Method)

at java.io.FileInputStream.read(FileInputStream.java:220)

at java.lang.UNIXProcess$DeferredCloseInputStream.read(UNIXProcess.java:227)

at java.io.BufferedInputStream.fill(BufferedInputStream.java:218)

at java.io.BufferedInputStream.read1(BufferedInputStream.java:258)

at java.io.BufferedInputStream.read(BufferedInputStream.java:317)

  • locked <7f3a42000> (a java.io.BufferedInputStream)

at java.io.FilterInputStream.read(FilterInputStream.java:90)

at org.gradle.process.internal.streams.ExecOutputHandleRunner.run(ExecOutputHandleRunner.java:48)

at org.gradle.internal.concurrent.DefaultExecutorFactory$StoppableExecutorImpl$1.run(DefaultExecutorFactory.java:66)

at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)

at java.lang.Thread.run(Thread.java:680)

“Forward streams with process: command ‘/mnt/jetty/bin/jetty.sh’” prio=5 tid=7ff7e3bbe000 nid=0x11587d000 runnable [11587c000]

java.lang.Thread.State: RUNNABLE

at java.io.FileInputStream.readBytes(Native Method)

at java.io.FileInputStream.read(FileInputStream.java:198)

at java.lang.UNIXProcess$DeferredCloseInputStream.read(UNIXProcess.java:218)

at org.gradle.process.internal.streams.ExecOutputHandleRunner.run(ExecOutputHandleRunner.java:48)

at org.gradle.internal.concurrent.DefaultExecutorFactory$StoppableExecutorImpl$1.run(DefaultExecutorFactory.java:66)

at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)

at java.lang.Thread.run(Thread.java:680)

“Run command ‘/mnt/jetty/bin/jetty.sh’” prio=5 tid=7ff7e623b000 nid=0x115542000 waiting on condition [115541000]

java.lang.Thread.State: TIMED_WAITING (parking)

at sun.misc.Unsafe.park(Native Method)

  • parking to wait for <7f3a4f9d8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)

at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:196)

at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2025)

at java.util.concurrent.ThreadPoolExecutor.awaitTermination(ThreadPoolExecutor.java:1253)

at org.gradle.internal.concurrent.DefaultExecutorFactory$StoppableExecutorImpl.stop(DefaultExecutorFactory.java:93)

at org.gradle.internal.concurrent.DefaultExecutorFactory$StoppableExecutorImpl.stop(DefaultExecutorFactory.java:83)

at org.gradle.process.internal.streams.StreamsForwarder.stop(StreamsForwarder.java:76)

at org.gradle.process.internal.ExecHandleRunner.run(ExecHandleRunner.java:86)

at org.gradle.internal.concurrent.DefaultExecutorFactory$StoppableExecutorImpl$1.run(DefaultExecutorFactory.java:66)

at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)

at java.lang.Thread.run(Thread.java:680)

From what I see I can deduce the following: 1. ExecHandleRunner has detected that process it has spawn has ended and went to line 86 to stop streamsHandler. 2. StreamsForwarder tries to stop his executor 3. And Executor just sits there waiting for ExecOutputHandleRunner-s to finish. But they are blocked in inputStream.read(buffer), which is, AFAIU, are not interruptible.

And thus Gradle invocation never ends…


(Luke Daley) #2

Thanks for the investigation.

Are you able to supply an equivalent shell script that we can use to have a look at this problem? The simpler the better.


(Nikita Salnikov-Tarnovski) #3

I will try to construct a minimal shell script. The one I have troubles with is a standard startup script from jetty8 distribution


(Nikita Salnikov-Tarnovski) #4

And here is minimal shell script :slight_smile:

#!/bin/sh
sleep 100 &

Gradle is stuck for 100 second until sleep ends.

It is my expectation, that as process (sleep in this case) is run on the background, it shouldn’t prevent Gradle process to exit.


(Luke Daley) #5

Many thanks, we are looking into it.


(Luke Daley) #6

The problem is that the sleep process is inheriting the stdio streams, that Gradle is still listening to. I’m not sure we want to change this. Strictly speaking, if the script is trying to fully detach it should not inherit those streams.

If you do need to work with such script, the workaround is to use the Java Process API directly and throw away the stdio streams.


(Nikita Salnikov-Tarnovski) #7

By “use Java Process API directly” do you mean writing my own Gradle plugin/task emulating Gradle Exec task’s functionality? Seems to be great waste…

Second option is to use ExecHandleBuilder.setDaemon method, am I right? But I do not understand how to set that option from my Gradle script.


(Luke Daley) #8

By “use Java Process API directly” do you mean writing my own Gradle plugin/task emulating Gradle Exec task’s functionality? Seems to be great waste…

Not exactly, I mean use it for your focussed purpose.

Second option is to use ExecHandleBuilder.setDaemon method, am I right? But I do not understand how to set that option from my Gradle script.

I can’t recall why this isn’t exposed to user launched processes. I’ll check.


(Nikita Salnikov-Tarnovski) #9

Hi, Luke. Any news regarding ExecHandleBuilder.setDaemon method?


(Luke Daley) #10

Hi,

I had a look and this is not quite what you want as it’s coupled to the implementation of the Gradle Daemon. It’s not exposed as a user specifiable option for this reason.

At this point in time you either need to fix the script so that it detaches the sub process in such a way that it does not inherit the streams, or use an alternative method to launch it (such as the Java Process API).