# java.io.IOException: Stream Closed in project.exec

**URL:** <https://discuss.gradle.org/t/java-io-ioexception-stream-closed-in-project-exec/46392>\
**Category:** Help/Discuss\
**Created:** [August 31, 2023, 1:18pm UTC](https://discuss.gradle.org/t/java-io-ioexception-stream-closed-in-project-exec/46392 "2023-08-31T13:18:30Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![rafaelrezend](https://sea1.discourse-cdn.com/gradle/user_avatar/discuss.gradle.org/rafaelrezend/32/9991_2.png) [@rafaelrezend](https://discuss.gradle.org/u/rafaelrezend)\
**Post date:** [August 31, 2023, 1:18pm UTC](https://discuss.gradle.org/t/java-io-ioexception-stream-closed-in-project-exec/46392/1 "2023-08-31T13:18:30Z")

</div>

I’m implementing a custom task that uses `project.exec` in its `@TaskAction`.  
This task is part of a custom plugin.  
Here is a simplified implementation of the `@TaskAction`

```java
@TaskAction
fun run(){
	// OutputStream to where the scanner std and err outputs are written
	val consoleLogFile: File = project.layout.buildDirectory.file("logs/console.log").get().asFile
	consoleLogFile.parentFile.mkdirs()
	val fileOutStream: FileOutputStream = FileOutputStream(consoleLogFile)

	// Execute scanner
	val result = project.exec {
		// Set both std and err outputs to a single file
		it.setStandardOutput(fileOutStream)
		it.setErrorOutput(fileOutStream)
		it.commandLine(composeScannerCommand())
	}
}

```

The `composeScannerCommand()` creates an array with executable and parameters. Simple. It does detect check whether it’s running on Win or Unix, which translates either into `cmd /c scanner.bat` or just `scanner`. But I believe this info isn’t relevant because my tests fail after the tool’s execution…

As for the functional tests, I simply read that `logs/console.log` and do a content assertion (omitted).

```java
val runner = GradleRunner.create()
runner.forwardOutput()
runner.withPluginClasspath()
runner.withArguments("runScanner", "--info")
runner.withProjectDir(projectDir)
val result = runner.build()

// Read build console.log file for the mocked scanner, which simply prints the parameters
val console = projectDir.resolve("build/logs/console.log").readText()

```

As a result, I’m getting the following Exception:

```gradle
Could not read standard output of command '/tmp/junit9281083532723264258/scanner/bin/scanner'.
java.io.IOException: Stream Closed
   at java.base/java.io.FileOutputStream.writeBytes(Native Method)
   at java.base/java.io.FileOutputStream.write(FileOutputStream.java:354)
   at org.gradle.process.internal.streams.ExecOutputHandleRunner.writeBuffer(ExecOutputHandleRunner.java:97)
   at org.gradle.process.internal.streams.ExecOutputHandleRunner.lambda$forwardContent$0(ExecOutputHandleRunner.java:81)
   at org.gradle.internal.operations.CurrentBuildOperationRef.with(CurrentBuildOperationRef.java:70)
   at org.gradle.process.internal.streams.ExecOutputHandleRunner.forwardContent(ExecOutputHandleRunner.java:80)
   at org.gradle.process.internal.streams.ExecOutputHandleRunner.run(ExecOutputHandleRunner.java:64)
   at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
   at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47)
   at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
   at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
   at java.base/java.lang.Thread.run(Thread.java:829)

```

What’s most interesting is that:

- The Exception happens in Linux only! The same tests work fine in Windows.
- If I run two tests with the test block above (each in its own temporary directory!), one will succeed, the other will throw the Exception. If I run the successful test alone, it then throws the Exception itself. That’s one test is influencing the other, although the `console.log` are different, in separate temp dir.

**Info**

```gradle
------------------------------------------------------------
Gradle 8.3
------------------------------------------------------------

Build time: 2023-08-17 07:06:47 UTC
Revision: 8afbf24b469158b714b36e84c6f4d4976c86fcd5

Kotlin: 1.9.0
Groovy: 3.0.17
Ant: Apache Ant(TM) version 1.10.13 compiled on January 4 2023
JVM: 11.0.20 (Ubuntu 11.0.20+8-post-Ubuntu-1ubuntu120.04)
OS: Linux 5.10.16.3-microsoft-standard-WSL2 amd64

```

**Any idea what could be causing the issue?**

As an alternative, if I use a `ByteArrayOutputStream` instead of `FileOutputStream` and then write it into a file after `project.exec` (using again a `FileOutputStream`), then it works fine. But looks _workaroundish_.

---

<div class="post-metadata">

**Author:** ![rafaelrezend](https://sea1.discourse-cdn.com/gradle/user_avatar/discuss.gradle.org/rafaelrezend/32/9991_2.png) [@rafaelrezend](https://discuss.gradle.org/u/rafaelrezend)\
**Post date:** [August 31, 2023, 1:28pm UTC](https://discuss.gradle.org/t/java-io-ioexception-stream-closed-in-project-exec/46392/2 "2023-08-31T13:28:14Z")

</div>

While writing the question, it occurred to me that the issue might be related to using the same `FileOutputStream` for both _standardOutput_ and _errorOutput_.

The `ExecSpec` is known to close the Streams automatically, which would explain the issue (to a certain extent, see below)  
So I update my `@TaskAction` as follows:

```java
@TaskAction
fun run(){
	// Create logs folder
	val consoleLogDir: File = project.layout.buildDirectory.file("logs/").get().asFile
	consoleLogDir.mkdirs()

	// Execute scanner
	val result = project.exec {
		it.setStandardOutput(FileOutputStream(consoleLogDir.resolve("console.log")))
        it.setErrorOutput(FileOutputStream(consoleLogDir.resolve("console.err")))
		it.commandLine(composeScannerCommand())
	}
}

```

It works, and it’s actually good to have the std and err logs separate.  
I still wrote both the question and answer here because it might help other people.

* * *

STILL, I cannot explain why it worked 100% in Windows, while, in Linux, if I create two tests reading the `console.log`, one of them works, the other doesn’t.

If anybody wants to add anything to that, please feel free 🙂

---

<div class="post-metadata">

**Author:** ![Vampire](https://sea1.discourse-cdn.com/gradle/user_avatar/discuss.gradle.org/vampire/32/9082_2.png) [@Vampire](https://discuss.gradle.org/u/Vampire)\
**Post date:** [September 1, 2023, 12:13pm UTC](https://discuss.gradle.org/t/java-io-ioexception-stream-closed-in-project-exec/46392/3 "2023-09-01T12:13:01Z")

</div>

Without investigating deeper, I’d say it is because of the different file handling in both.  
If you for example have a file handle open in Windows, it is locked and you for example cannot delete it.  
If you have a file handle open in Linux and try to delete it, the filesystem entry is happily deleted, while you can still have the file open. The underlying inode that you have open stays there until you close the open file handle, but the filesystem entry is already gone.

Besides that, you should not use `project.` at execution time, this is deprecated and will fail in the future. Better `@Inject` `ExecOperations` for `exec` and `ProjectLayout` for `layout`. 🙂

---

<div class="post-metadata">

**Author:** ![rafaelrezend](https://sea1.discourse-cdn.com/gradle/user_avatar/discuss.gradle.org/rafaelrezend/32/9991_2.png) [@rafaelrezend](https://discuss.gradle.org/u/rafaelrezend)\
**Post date:** [September 7, 2023, 9:43pm UTC](https://discuss.gradle.org/t/java-io-ioexception-stream-closed-in-project-exec/46392/4 "2023-09-07T21:43:32Z")

</div>

Oh ok. Let me see if I understood it…  
I’ll add a little twist to my snippet: assume I also need to print the project name and version.

Would this be the correct?

```java
@get:Inject
abstract val proj: Project

@TaskAction
fun run(){
	logger.info( "${proj.name}_${proj.version}" )

	// Create logs folder
	val consoleLogDir: File = proj.layout.buildDirectory.file("logs/").get().asFile
	consoleLogDir.mkdirs()

	// Execute scanner
	val result = proj.exec {
		it.setStandardOutput(FileOutputStream(consoleLogDir.resolve("console.log")))
		it.setErrorOutput(FileOutputStream(consoleLogDir.resolve("console.err")))
		it.commandLine(composeScannerCommand())
	}
}

```

Since I need `name` and `version`, I injected the `Project proj`.  
Then, I used both `layout` and `exec` from `proj`, because… well… it’s already there.

I assume I don’t need to inject `ExecOperations` and `ProjectLayout` separately, right?  
Or did I get it all wrong? 😃

---

<div class="post-metadata">

**Author:** ![Vampire](https://sea1.discourse-cdn.com/gradle/user_avatar/discuss.gradle.org/vampire/32/9082_2.png) [@Vampire](https://discuss.gradle.org/u/Vampire)\
**Post date:** [September 7, 2023, 10:06pm UTC](https://discuss.gradle.org/t/java-io-ioexception-stream-closed-in-project-exec/46392/5 "2023-09-07T22:06:45Z")

</div>

I don’t think you can inject `Project`.  
And even if you could, you must not use it at execution time.  
So no, you didn’t get it right. 🙂  
Create two `Property<String>` that you set from project name and version, inject `ExecOperations` and `ProjectLayout` and use those.

---

<div class="post-metadata">

**Author:** ![rafaelrezend](https://sea1.discourse-cdn.com/gradle/user_avatar/discuss.gradle.org/rafaelrezend/32/9991_2.png) [@rafaelrezend](https://discuss.gradle.org/u/rafaelrezend)\
**Post date:** [September 7, 2023, 10:43pm UTC](https://discuss.gradle.org/t/java-io-ioexception-stream-closed-in-project-exec/46392/6 "2023-09-07T22:43:41Z")

</div>

I see…  
Injecting `Project` technically worked for me, but that just means I found another path to deprecation 😃  
I misunderstood earlier that only the existing `project` was an issue, and assumed that an injected one would be ok. Now I see: execution time = no `Project`. Period.

Thanks for the help!!

EDIT: When is it getting deprecated? I get no warning at all about using `project` yet. Even with all warnings enabled. Using Gradle 8.3

---

<div class="post-metadata">

**Author:** ![Vampire](https://sea1.discourse-cdn.com/gradle/user_avatar/discuss.gradle.org/vampire/32/9082_2.png) [@Vampire](https://discuss.gradle.org/u/Vampire)\
**Post date:** [September 7, 2023, 11:10pm UTC](https://discuss.gradle.org/t/java-io-ioexception-stream-closed-in-project-exec/46392/7 "2023-09-07T23:10:10Z")

</div>

Probably only if you enable configuration cache, or if you enable the STABLE\_CONFIGURATION\_CACHE feature currently
