<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pablo Baxter</title>
    <description>The latest articles on DEV Community by Pablo Baxter (@pablobaxter).</description>
    <link>https://dev.to/pablobaxter</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1187829%2F3f58a963-ad28-42c9-b8f1-6a7f16b698b2.jpeg</url>
      <title>DEV Community: Pablo Baxter</title>
      <link>https://dev.to/pablobaxter</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pablobaxter"/>
    <language>en</language>
    <item>
      <title>Detecting File Leak in the Kotlin Daemon</title>
      <dc:creator>Pablo Baxter</dc:creator>
      <pubDate>Wed, 19 Feb 2025 02:54:02 +0000</pubDate>
      <link>https://dev.to/pablobaxter/detecting-file-leak-in-the-kotlin-daemon-2130</link>
      <guid>https://dev.to/pablobaxter/detecting-file-leak-in-the-kotlin-daemon-2130</guid>
      <description>&lt;p&gt;This was originally posted in &lt;a href="https://frybits.com/blog/detecting-file-leak-in-the-kotlin-daemon/" rel="noopener noreferrer"&gt;https://frybits.com/blog/detecting-file-leak-in-the-kotlin-daemon/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;File handle leaks are notoriously difficult to debug, so much so that most of the “fixes” for them are “increase the file descriptor limit”. However, this is not a fix. This is as close as you can get to covering your ears and closing your eyes, then screaming “I CAN’T HEAR YOU” to a bug. The file handle leak will still exist, but now it’s ignored.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F54i9ig23fssjyc3oxqbc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F54i9ig23fssjyc3oxqbc.gif" alt="Characters from Dumb and Dumber: Lloyd is covering his ears and eyes, while Harry attempts to argue with him, while Joe Mentalino fumes in the middle of them" width="400" height="200"&gt;&lt;/a&gt;&lt;/p&gt;
Seriously, it's exactly this



&lt;p&gt;In this case study, I decided to take the approach of finding out why the leak was occurring, and finding the best fix for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Signs of a File Handle Leak
&lt;/h2&gt;

&lt;p&gt;The hardest part of debugging an issue is noticing it to begin with and file handle leaks are very hard to notice until an error is thrown, and even then it may not be noticed since the error could be handled by some logic that changes the exception cause or swallows it altogether. So how did I notice the one in the Kotlin daemon? Dumb luck. I had been tracking another file handle leak in the Gradle daemon, and just got curious about how many files remained open in the Kotlin daemon after compilation.&lt;/p&gt;

&lt;p&gt;There are some signs that your application may have a file handle leak, but even these signs are easily missed. A few common signs are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Errors relating to files unable to be created&lt;/li&gt;
&lt;li&gt;Any error about a file being deleted (especially on Windows)&lt;/li&gt;
&lt;li&gt;Unable to open files for any reason&lt;/li&gt;
&lt;li&gt;Files having garbage data written or being corrupted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These issues don't always appear on every run and could be intermittent as well. &lt;/p&gt;

&lt;h2&gt;
  
  
  Noticing the Kotlin Daemon File Handle Leak
&lt;/h2&gt;

&lt;p&gt;Going back to how I found the Kotlin daemon file handle leak, as I had mentioned previously, I found it due to curiosity and dumb luck. When Paul Klauser (&lt;a href="https://github.com/PaulKlauser" rel="noopener noreferrer"&gt;https://github.com/PaulKlauser&lt;/a&gt;) reported a &lt;a href="https://youtrack.jetbrains.com/issue/KT-72169/Kotlin-Daemon-Metaspace-leak" rel="noopener noreferrer"&gt;metaspace leak&lt;/a&gt; occuring in the Kotlin daemon, I was curious if this metaspace leak was potentially due to a file handle leak, as I had been tracking one in the Gradle daemon (which I still haven't found).&lt;/p&gt;

&lt;p&gt;To go over how I did this at a high-level (more details later on), I attached Jenkin's &lt;a href="https://github.com/jenkinsci/lib-file-leak-detector" rel="noopener noreferrer"&gt;file-leak-detector&lt;/a&gt; to the Kotlin daemon and ran &lt;code&gt;./gradlew clean assembleDebug --rerun-tasks&lt;/code&gt; several times, capturing the output of the file leak tool and using a post-processor (see &lt;a href="https://github.com/centic9/file-leak-postprocess" rel="noopener noreferrer"&gt;https://github.com/centic9/file-leak-postprocess&lt;/a&gt;) to make the logs easier to read. I ended up with a file containing many stack traces that show where the file was opened. Many of them looked like the following (shortened for easy reading):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Several hundred other open files up here with the same stacktrace
...
#1295 /home/pablo/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.9.0/e000bd084353d84c9e888f6fb341dc1f5b79d948/kotlin-stdlib-jdk8-1.9.0.jar by thread:RMI TCP Connection(10)-127.0.0.1 on Wed Oct 09 18:35:43 PDT 2024
#542 /home/pablo/.gradle/caches/modules-2/files-2.1/org.checkerframework/checker-qual/3.41.0/8be6df7f1e9bccb19f8f351b3651f0bac2f5e0c/checker-qual-3.41.0.jar by thread:RMI TCP Connection(21)-127.0.0.1 on Wed Oct 09 18:35:52 PDT 2024
#341 /home/pablo/.gradle/caches/modules-2/files-2.1/com.google.guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/b421526c5f297295adef1c886e5246c39d4ac629/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar by thread:RMI TCP Connection(140)-127.0.0.1 on Wed Oct 09 18:36:13 PDT 2024
    at java.base/java.util.zip.ZipFile.&amp;lt;init&amp;gt;(ZipFile.java:181)
    at java.base/java.util.jar.JarFile.&amp;lt;init&amp;gt;(JarFile.java:346)
    at java.base/jdk.internal.loader.URLClassPath$JarLoader.getJarFile(URLClassPath.java:825)
    at java.base/jdk.internal.loader.URLClassPath$JarLoader$1.run(URLClassPath.java:769)
    at java.base/jdk.internal.loader.URLClassPath$JarLoader$1.run(URLClassPath.java:762)
    ...
    at java.base/java.net.URLClassLoader$3.next(URLClassLoader.java:659)
    at java.base/java.net.URLClassLoader$3.hasMoreElements(URLClassLoader.java:684)
    at java.base/java.lang.CompoundEnumeration.next(ClassLoader.java:2730)
    at java.base/java.lang.CompoundEnumeration.hasMoreElements(ClassLoader.java:2739)
    at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.nextProviderClass(ServiceLoader.java:1210)
    at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNextService(ServiceLoader.java:1228)
    at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNext(ServiceLoader.java:1273)
    at java.base/java.util.ServiceLoader$2.hasNext(ServiceLoader.java:1309)
    at java.base/java.util.ServiceLoader$3.hasNext(ServiceLoader.java:1393)
    at com.google.common.collect.ImmutableSet.copyOf(ImmutableSet.java:280)
    at com.google.common.collect.ImmutableSet.copyOf(ImmutableSet.java:265)
    at dagger.internal.codegen.ServiceLoaders.loadServices(ServiceLoaders.java:35)
    at dagger.internal.codegen.DelegateComponentProcessor.lambda$initialize$0(DelegateComponentProcessor.java:89)
    ...
    at dagger.internal.codegen.DelegateComponentProcessor.initialize(DelegateComponentProcessor.java:89)
    at dagger.internal.codegen.KspComponentProcessor.initialize(KspComponentProcessor.java:49)
    at dagger.spi.internal.shaded.androidx.room.compiler.processing.ksp.KspBasicAnnotationProcessor.process(KspBasicAnnotationProcessor.kt:57)
    at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$8$1.invoke(KotlinSymbolProcessingExtension.kt:310)
    at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$8$1.invoke(KotlinSymbolProcessingExtension.kt:308)
    at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.handleException(KotlinSymbolProcessingExtension.kt:414)
    at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:308)
    at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:112)
    at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;So what's going on here and how does this tell the story of a file leak?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Well, this stacktrace actually represents 417 files that were opened and never closed when the Gradle task was completed, but for brevity, I only listed the final three files left open (lines 3-5). All these files were opened at the &lt;code&gt;ZipFile&lt;/code&gt; constructor (line 6), however that is not where the origin of the file was at. It actually originated elsewhere in this stacktrace, which required some investigation. Also, this is just one stack trace for the given set of files. There were 1380 files left open at the end of the Gradle task run, and about another 1k extra files were opened after each run. In the following sections, I'll get into the details of how I debugged this file leak and found the fix for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the file-leak-detector
&lt;/h2&gt;

&lt;p&gt;To begin with, I downloaded the &lt;a href="https://github.com/jenkinsci/lib-file-leak-detector" rel="noopener noreferrer"&gt;file-leak-detector&lt;/a&gt; jar, and in the root &lt;code&gt;gradle.properties&lt;/code&gt; file of the &lt;a href="https://github.com/android/nowinandroid" rel="noopener noreferrer"&gt;NowInAndroid&lt;/a&gt; repo, I appended the following line to the JVM args of the Kotlin daemon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;kotlin.daemon.jvmargs&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;other args&amp;gt; -javaagent:/path/to/file-leak-detector-jar-with-dependencies.jar=http=19999&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this does is attach the file-leak-detector Java Agent to each Kotlin daemon that is launched. Since I only want one Kotlin daemon running, I made sure to kill the other Kotlin daemon processes before running the Gradle command.&lt;/p&gt;

&lt;p&gt;The flag &lt;code&gt;http=19999&lt;/code&gt; creates a local web server on port &lt;code&gt;19999&lt;/code&gt; that the file-leak-detector uses to provide a list of all the open files and stack traces to them. This is important as the Kotlin daemon is a long lived process, so it doesn't exit when the build has completed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capturing the File Handle Leak
&lt;/h2&gt;

&lt;p&gt;Getting this file handle leak to occur was a bit tricky. I had to determine the task that would trigger the Kotlin compiler, and ensure that consecutive executions were re-run properly. For this reason I chose to run &lt;code&gt;./gradlew clean assembleDebug --rerun-tasks&lt;/code&gt; in order to ensure that the compilation occurred each time.&lt;/p&gt;

&lt;p&gt;So what does each part do?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;clean&lt;/code&gt; ensures that no generated code exists, and gives me a clean build directory.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;assembleDebug&lt;/code&gt; triggers the compilation of the Android app in NowInAndroid.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--rerun-tasks&lt;/code&gt; tells the Gradle daemon to ignore any up-to-date checks from tasks and rerun them all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the first successful build of NowInAndroid using the above command, I was able to go to &lt;code&gt;localhost:19999&lt;/code&gt;, which outputs all the open files left on the Kotlin daemon. That's where I got my first indication that something may be wrong, as I was left with 1380 file handles left open. This didn't necessarily mean there was a file leak, as these open file handles could be cached. To be sure, I stored this result as a text file, and re-ran the Gradle command while making sure the Kotlin daemon was not restarted or killed.&lt;/p&gt;

&lt;p&gt;The next run left me with 2694 open file handles, with each run opening another ~1k files. This was obviously a leak. I stored the results of each run in a text file for post-processing, to make it easier to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analyzing the Results
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/centic9/file-leak-postprocess" rel="noopener noreferrer"&gt;file-leak-postprocess&lt;/a&gt; tool is useful as it gets all the open file handles that share a similar stacktrace and groups them together (see output above). I ran this post-processor tool for each output I collected, and took a quick look to see where most of these files were being opened. Visual Studio Code does a great job visualizing which stack traces have the most open file handles.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn6qlyecnrumkwierb52u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn6qlyecnrumkwierb52u.png" alt="img" width="800" height="562"&gt;&lt;/a&gt;&lt;/p&gt;
That's a lot of open files!



&lt;p&gt;With this, I saw that there was a cluster of open files that shared common stacktraces. What I needed to identify was which of these grew on consecutive runs, as that would determine the location of the leaks. Again, Visual Studio Code came handy with their "Compare Selected" option when 2 files are selected in the file navigation. However, this comparison was a bit confusing to me at first.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ukqwvxbqwx3n5bxiqx1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ukqwvxbqwx3n5bxiqx1.png" alt="img" width="800" height="312"&gt;&lt;/a&gt;&lt;/p&gt;
Comparing the base to another run



&lt;p&gt;Typically, I would only see new files added to specific stack traces that pointed out the leaks, yet here we had the previous files being closed and an increasing number of new files being opened. At first, I believed this to be due to a collection that was growing after each run, however I was not able to find any evidence to support this theory. The next approach I took was looking at the stacktraces and understanding a common root for them all. Thankfully, I had &lt;a href="https://www.jasonpearson.dev/" rel="noopener noreferrer"&gt;Jason Pearson&lt;/a&gt; in a Slack thread helping me out identify this common root, which he pointed out to be &lt;code&gt;org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now that I knew the general area of where the file leaks were occurring, I was able to dig a bit deeper to understand the root cause, and discovered that both the &lt;a href="https://github.com/JetBrains/kotlin/blob/6af99c83470813023dace7d3bd850c6fef8e50c0/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/plugins/PluginCliParser.kt#L161-L163" rel="noopener noreferrer"&gt;Kotlin source code&lt;/a&gt; and &lt;a href="https://github.com/google/ksp/blob/1ca8ca1793afdea491d9afebd12a27388c500874/compiler-plugin/src/main/kotlin/com/google/devtools/ksp/KotlinSymbolProcessingExtension.kt#L86-L95" rel="noopener noreferrer"&gt;KSP source code&lt;/a&gt; were creating &lt;code&gt;URLClassloader&lt;/code&gt; objects, but never closing them. By default, &lt;code&gt;URLClassloader&lt;/code&gt; caches the underlying &lt;code&gt;.jar&lt;/code&gt; files being opened, and keeps them open until the &lt;code&gt;URLClassloader&lt;/code&gt; has closed or has been garbage collected (see: &lt;a href="https://docs.oracle.com/javase/7/docs/technotes/guides/net/ClassLoader.html" rel="noopener noreferrer"&gt;Closing a URLClassLoader&lt;/a&gt;). With this finding, I filed a bug with the &lt;a href="https://youtrack.jetbrains.com/issue/KT-72172/File-Leak-occurring-in-Kotlin-Daemon" rel="noopener noreferrer"&gt;Kotlin team&lt;/a&gt; and with the &lt;a href="https://github.com/google/ksp/issues/2159" rel="noopener noreferrer"&gt;Google/KSP team&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Patching the File Handle Leaks
&lt;/h2&gt;

&lt;p&gt;The fix for these file leaks was rather easy for KSP. I just needed to call &lt;code&gt;URLClassloader.close()&lt;/code&gt;. This was quickly achieved in &lt;a href="https://github.com/google/ksp/pull/2164" rel="noopener noreferrer"&gt;this PR&lt;/a&gt;. Testing was a bit more difficult, but after working with the KSP team, I was able to test a local build of it on NowInAndroid and verified the file leaks for KSP were gone!&lt;/p&gt;

&lt;p&gt;As for the Kotlin project fix, I made an attempt at a &lt;a href="https://github.com/JetBrains/kotlin/pull/5372" rel="noopener noreferrer"&gt;fix&lt;/a&gt;, but it turned that it would require quite a bit &lt;a href="https://github.com/JetBrains/kotlin/commit/8c6390b1e9ba16c1bf05df34205584f80530f34e" rel="noopener noreferrer"&gt;more effort&lt;/a&gt;. Essentially, there were many paths to open a &lt;code&gt;URLClassloader&lt;/code&gt;, and my PR didn't cover all of those. Thankfully, &lt;a href="https://github.com/bnorm" rel="noopener noreferrer"&gt;Brian Norman&lt;/a&gt; on the Kotlin team took the charge on this fix!&lt;/p&gt;

</description>
      <category>kotlin</category>
    </item>
    <item>
      <title>"Too Many Open Files" - Debugging Leaking Files on the Gradle Daemon</title>
      <dc:creator>Pablo Baxter</dc:creator>
      <pubDate>Mon, 30 Oct 2023 04:30:57 +0000</pubDate>
      <link>https://dev.to/pablobaxter/too-many-open-files-debugging-leaking-files-on-the-gradle-daemon-3hlg</link>
      <guid>https://dev.to/pablobaxter/too-many-open-files-debugging-leaking-files-on-the-gradle-daemon-3hlg</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Banner image created with Dall-E using the prompt &lt;em&gt;"elephant surrounded by files"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;Too many open files&lt;/code&gt; issue can be ignored by increasing the file descriptor limit, but there is no magic bullet to &lt;em&gt;fix&lt;/em&gt; the error that may have brought you here. It's going to take some debugging and it is tedious. This article isn't to show you how to increase the file descriptor limit on &lt;a href="https://www.cyberciti.biz/faq/linux-increase-the-maximum-number-of-open-files/" rel="noopener noreferrer"&gt;Linux&lt;/a&gt;, or workaround the recent MacOS issue with increasing the file descriptor limit (see &lt;a href="https://developer.apple.com/forums//thread/735798" rel="noopener noreferrer"&gt;https://developer.apple.com/forums//thread/735798&lt;/a&gt;). There is no snippet of code that will fix this Gradle issue glaring at you. We'll be looking at how to identify file leaks on the Gradle daemon with the hope that it will provide you with the skills and tools to debug (and possibly fix) your build.&lt;/p&gt;




&lt;h2&gt;
  
  
  Let's Talk About Gradle
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Daemon
&lt;/h3&gt;

&lt;p&gt;The Gradle build tool uses a long-lived &lt;a href="https://docs.gradle.org/current/userguide/gradle_daemon.html#understanding_wrapper" rel="noopener noreferrer"&gt;daemon process&lt;/a&gt; that performs all Gradle builds to cache information in memory about previous builds, making subsequent builds faster. However, this also allows for open file descriptors to persist between builds, with subsequent builds potentially opening new file descriptors. Eventually this can lead to the dreaded &lt;code&gt;Too many open files&lt;/code&gt; exception, which provides basically zero information about what file has leaked or where the leak is occurring.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwse3b9u5akk6ekp7j6g0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwse3b9u5akk6ekp7j6g0.jpg" alt="meme showing useless error message" width="500" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Due to the nature of the daemon being long-lived, detecting file leaks can be problematic, as you'll be looking for an errant open file in a collection of other validly open files. However, there are some techniques that can be used to find these errant open files, so long as we have a basic understanding of the Gradle build lifecycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build Lifecycle
&lt;/h3&gt;

&lt;p&gt;Gradle has three distinct &lt;a href="https://docs.gradle.org/current/userguide/build_lifecycle.html#sec:build_phases" rel="noopener noreferrer"&gt;build phases&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initialization: Evaluates the &lt;code&gt;settings.gradle&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Configuration: Evaluates the &lt;code&gt;build.gradle&lt;/code&gt; files of projects.&lt;/li&gt;
&lt;li&gt;Execution: Performs a run of the Gradle tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every phase requires the previous to occur and each of these phases can be triggered in different ways via IntelliJ or the command line. For the purposes of this article we'll only be using IntelliJ to trigger each of these phases.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tools
&lt;/h2&gt;

&lt;h3&gt;
  
  
  File Leak Detector
&lt;/h3&gt;

&lt;p&gt;The tool I found effective for detecting these file leaks is the File Leak Detector by Jenkins (&lt;a href="https://www.github.com/jenkinsci/lib-file-leak-detector" rel="noopener noreferrer"&gt;jenkinsci/lib-file-leak-detector&lt;/a&gt;). It is a &lt;a href="https://stackify.com/what-are-java-agents-and-how-to-profile-with-them/" rel="noopener noreferrer"&gt;Java Agent&lt;/a&gt; that can be applied via JVM arguments or attached after the JVM app has started. This library instruments several Java APIs to track all open file descriptors and either provides a simple HTTP server to show all currently open files or dumps them after the JVM has exited (which isn't helpful in our case, since Gradle is long-lived).&lt;br&gt;
The output from this tool is very noisy. Each file descriptor, the thread it was opened in, time/date, and stacktrace are listed by default. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#885 /home/pablo/.gradle/caches/transforms-3/c762f17d69b567ffc4fbadfe65190e1a/transformed/unzipped-distribution/gradle-8.2/subprojects by thread:Unconstrained build operations Thread 20 on Mon Oct 16 22:50:48 PDT 2023&lt;/span&gt;
 at java.base/java.nio.file.Files.newDirectoryStream&lt;span class="o"&gt;(&lt;/span&gt;Files.java:482&lt;span class="o"&gt;)&lt;/span&gt;
 at java.base/java.nio.file.FileTreeWalker.visit&lt;span class="o"&gt;(&lt;/span&gt;FileTreeWalker.java:301&lt;span class="o"&gt;)&lt;/span&gt;
 at java.base/java.nio.file.FileTreeWalker.next&lt;span class="o"&gt;(&lt;/span&gt;FileTreeWalker.java:374&lt;span class="o"&gt;)&lt;/span&gt;
 at java.base/java.nio.file.Files.walkFileTree&lt;span class="o"&gt;(&lt;/span&gt;Files.java:2844&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.internal.snapshot.impl.DirectorySnapshotter.snapshot&lt;span class="o"&gt;(&lt;/span&gt;DirectorySnapshotter.java:119&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.internal.vfs.impl.DefaultFileSystemAccess.lambda&lt;span class="nv"&gt;$snapshot$8&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;DefaultFileSystemAccess.java:155&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.internal.vfs.impl.AbstractVirtualFileSystem.store&lt;span class="o"&gt;(&lt;/span&gt;AbstractVirtualFileSystem.java:84&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.internal.vfs.impl.DefaultFileSystemAccess.snapshot&lt;span class="o"&gt;(&lt;/span&gt;DefaultFileSystemAccess.java:145&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.internal.vfs.impl.DefaultFileSystemAccess.lambda&lt;span class="nv"&gt;$read$0&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;DefaultFileSystemAccess.java:82&lt;span class="o"&gt;)&lt;/span&gt;
 at java.base/java.util.Optional.orElseGet&lt;span class="o"&gt;(&lt;/span&gt;Optional.java:364&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.internal.vfs.impl.DefaultFileSystemAccess.lambda&lt;span class="nv"&gt;$readSnapshotFromLocation$9&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;DefaultFileSystemAccess.java:187&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.internal.vfs.impl.DefaultFileSystemAccess&lt;span class="nv"&gt;$StripedProducerGuard&lt;/span&gt;.guardByKey&lt;span class="o"&gt;(&lt;/span&gt;DefaultFileSystemAccess.java:221&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.internal.vfs.impl.DefaultFileSystemAccess.lambda&lt;span class="nv"&gt;$readSnapshotFromLocation$10&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;DefaultFileSystemAccess.java:184&lt;span class="o"&gt;)&lt;/span&gt;
 at java.base/java.util.Optional.orElseGet&lt;span class="o"&gt;(&lt;/span&gt;Optional.java:364&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.internal.vfs.impl.DefaultFileSystemAccess.readSnapshotFromLocation&lt;span class="o"&gt;(&lt;/span&gt;DefaultFileSystemAccess.java:184&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.internal.vfs.impl.DefaultFileSystemAccess.readSnapshotFromLocation&lt;span class="o"&gt;(&lt;/span&gt;DefaultFileSystemAccess.java:169&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.internal.vfs.impl.DefaultFileSystemAccess.read&lt;span class="o"&gt;(&lt;/span&gt;DefaultFileSystemAccess.java:82&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="o"&gt;(&lt;/span&gt;... Stacktrace manually collapsed &lt;span class="k"&gt;for &lt;/span&gt;brevity...&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread&lt;span class="o"&gt;(&lt;/span&gt;DefaultWorkerLeaseService.java:109&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.internal.operations.DefaultBuildOperationQueue&lt;span class="nv"&gt;$WorkerRunnable&lt;/span&gt;.runBatch&lt;span class="o"&gt;(&lt;/span&gt;DefaultBuildOperationQueue.java:224&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.internal.operations.DefaultBuildOperationQueue&lt;span class="nv"&gt;$WorkerRunnable&lt;/span&gt;.run&lt;span class="o"&gt;(&lt;/span&gt;DefaultBuildOperationQueue.java:192&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.internal.concurrent.ExecutorPolicy&lt;span class="nv"&gt;$CatchAndRecordFailures&lt;/span&gt;.onExecute&lt;span class="o"&gt;(&lt;/span&gt;ExecutorPolicy.java:64&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.internal.concurrent.AbstractManagedExecutor&lt;span class="nv"&gt;$1&lt;/span&gt;.run&lt;span class="o"&gt;(&lt;/span&gt;AbstractManagedExecutor.java:47&lt;span class="o"&gt;)&lt;/span&gt;
 at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker&lt;span class="o"&gt;(&lt;/span&gt;ThreadPoolExecutor.java:1144&lt;span class="o"&gt;)&lt;/span&gt;
 at java.base/java.util.concurrent.ThreadPoolExecutor&lt;span class="nv"&gt;$Worker&lt;/span&gt;.run&lt;span class="o"&gt;(&lt;/span&gt;ThreadPoolExecutor.java:642&lt;span class="o"&gt;)&lt;/span&gt;
 at java.base/java.lang.Thread.run&lt;span class="o"&gt;(&lt;/span&gt;Thread.java:1589&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although descriptive, a single IDE sync can have upwards of 1k files opened with each file having a log entry as shown above. Thankfully, there is another tool that parses these raw logs and matches file descriptors with similar stacktraces.&lt;/p&gt;

&lt;h3&gt;
  
  
  File Leak Postprocessor
&lt;/h3&gt;

&lt;p&gt;We can use the File Leak Postprocessor (&lt;a href="https://www.github.com/centic9/file-leak-postprocess" rel="noopener noreferrer"&gt;centic9/file-leak-postprocess&lt;/a&gt;) to collect file descriptors with similar stacktraces and batch them as such:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#885 /home/pablo/.gradle/caches/transforms-3/c762f17d69b567ffc4fbadfe65190e1a/transformed/unzipped-distribution/gradle-8.2/subprojects by thread:Unconstrained build operations Thread 20 on Mon Oct 16 22:50:48 PDT 2023&lt;/span&gt;
&lt;span class="c"&gt;#886 /home/pablo/.gradle/caches/transforms-3/c762f17d69b567ffc4fbadfe65190e1a/transformed/unzipped-distribution/gradle-8.2/subprojects/core by thread:Unconstrained build operations Thread 20 on Mon Oct 16 22:50:49 PDT 2023&lt;/span&gt;
&lt;span class="c"&gt;#884 /home/pablo/.gradle/caches/transforms-3/c762f17d69b567ffc4fbadfe65190e1a/transformed/unzipped-distribution/gradle-8.2 by thread:Unconstrained build operations Thread 20 on Mon Oct 16 22:50:48 PDT 2023&lt;/span&gt;
&lt;span class="c"&gt;#888 /home/pablo/.gradle/caches/transforms-3/c762f17d69b567ffc4fbadfe65190e1a/transformed/unzipped-distribution/gradle-8.2/subprojects/core/src/main by thread:Unconstrained build operations Thread 20 on Mon Oct 16 22:50:49 PDT 2023&lt;/span&gt;
&lt;span class="c"&gt;#889 /home/pablo/.gradle/caches/transforms-3/c762f17d69b567ffc4fbadfe65190e1a/transformed/unzipped-distribution/gradle-8.2/subprojects/core/src/main/java by thread:Unconstrained build operations Thread 20 on Mon Oct 16 22:50:49 PDT 2023&lt;/span&gt;
&lt;span class="c"&gt;#892 /home/pablo/.gradle/caches/transforms-3/c762f17d69b567ffc4fbadfe65190e1a/transformed/unzipped-distribution/gradle-8.2/subprojects/core/src/main/java/org/gradle/process by thread:Unconstrained build operations Thread 20 on Mon Oct 16 22:50:49 PDT 2023&lt;/span&gt;
&lt;span class="c"&gt;#893 /home/pablo/.gradle/caches/transforms-3/c762f17d69b567ffc4fbadfe65190e1a/transformed/unzipped-distribution/gradle-8.2/subprojects/core/src/main/java/org/gradle/process/internal by thread:Unconstrained build operations Thread 20 on Mon Oct 16 22:50:49 PDT 2023&lt;/span&gt;
&lt;span class="c"&gt;#894 /home/pablo/.gradle/caches/transforms-3/c762f17d69b567ffc4fbadfe65190e1a/transformed/unzipped-distribution/gradle-8.2/subprojects/core/src/main/java/org/gradle/process/internal/util by thread:Unconstrained build operations Thread 20 on Mon Oct 16 22:50:49 PDT 2023&lt;/span&gt;
&lt;span class="c"&gt;#890 /home/pablo/.gradle/caches/transforms-3/c762f17d69b567ffc4fbadfe65190e1a/transformed/unzipped-distribution/gradle-8.2/subprojects/core/src/main/java/org by thread:Unconstrained build operations Thread 20 on Mon Oct 16 22:50:49 PDT 2023&lt;/span&gt;
&lt;span class="c"&gt;#883 /home/pablo/.gradle/caches/transforms-3/c762f17d69b567ffc4fbadfe65190e1a/transformed/unzipped-distribution by thread:Unconstrained build operations Thread 20 on Mon Oct 16 22:50:48 PDT 2023&lt;/span&gt;
&lt;span class="c"&gt;#887 /home/pablo/.gradle/caches/transforms-3/c762f17d69b567ffc4fbadfe65190e1a/transformed/unzipped-distribution/gradle-8.2/subprojects/core/src by thread:Unconstrained build operations Thread 20 on Mon Oct 16 22:50:49 PDT 2023&lt;/span&gt;
&lt;span class="c"&gt;#891 /home/pablo/.gradle/caches/transforms-3/c762f17d69b567ffc4fbadfe65190e1a/transformed/unzipped-distribution/gradle-8.2/subprojects/core/src/main/java/org/gradle by thread:Unconstrained build operations Thread 20 on Mon Oct 16 22:50:49 PDT 2023&lt;/span&gt;
 at java.base/java.nio.file.Files.newDirectoryStream&lt;span class="o"&gt;(&lt;/span&gt;Files.java:482&lt;span class="o"&gt;)&lt;/span&gt;
 at java.base/java.nio.file.FileTreeWalker.visit&lt;span class="o"&gt;(&lt;/span&gt;FileTreeWalker.java:301&lt;span class="o"&gt;)&lt;/span&gt;
 at java.base/java.nio.file.FileTreeWalker.next&lt;span class="o"&gt;(&lt;/span&gt;FileTreeWalker.java:374&lt;span class="o"&gt;)&lt;/span&gt;
 at java.base/java.nio.file.Files.walkFileTree&lt;span class="o"&gt;(&lt;/span&gt;Files.java:2844&lt;span class="o"&gt;)&lt;/span&gt;
 ...
 at java.base/java.util.Optional.map&lt;span class="o"&gt;(&lt;/span&gt;Optional.java:260&lt;span class="o"&gt;)&lt;/span&gt;
 ...
 at org.gradle.cache.internal.LockOnDemandCrossProcessCacheAccess.withFileLock&lt;span class="o"&gt;(&lt;/span&gt;LockOnDemandCrossProcessCacheAccess.java:90&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.cache.internal.DefaultCacheCoordinator.withFileLock&lt;span class="o"&gt;(&lt;/span&gt;DefaultCacheCoordinator.java:219&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.cache.internal.DefaultPersistentDirectoryStore.withFileLock&lt;span class="o"&gt;(&lt;/span&gt;DefaultPersistentDirectoryStore.java:188&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.cache.internal.DefaultCacheFactory&lt;span class="nv"&gt;$RHunting&lt;/span&gt; File LeakseferenceTrackingCache.withFileLock&lt;span class="o"&gt;(&lt;/span&gt;DefaultCacheFactory.java:214&lt;span class="o"&gt;)&lt;/span&gt;
 ...
 at org.gradle.cache.Cache.lambda&lt;span class="nv"&gt;$get$0&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Cache.java:31&lt;span class="o"&gt;)&lt;/span&gt;
 at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent&lt;span class="o"&gt;(&lt;/span&gt;ConcurrentHashMap.java:1708&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.cache.ManualEvictionInMemoryCache.get&lt;span class="o"&gt;(&lt;/span&gt;ManualEvictionInMemoryCache.java:30&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.cache.internal.DefaultCrossBuildInMemoryCacheFactory&lt;span class="nv"&gt;$CrossBuildCacheRetainingDataFromPreviousBuild&lt;/span&gt;.get&lt;span class="o"&gt;(&lt;/span&gt;DefaultCrossBuildInMemoryCacheFactory.java:255&lt;span class="o"&gt;)&lt;/span&gt;
 at org.gradle.cache.Cache.get&lt;span class="o"&gt;(&lt;/span&gt;Cache.java:31&lt;span class="o"&gt;)&lt;/span&gt;
 ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As shown above, the postprocessor tool collapses the stacktraces of many of these file descriptors and batches the ones that have similar traces. This makes it easier to analyze a diff of the processed outputs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Let's Learn Some Debugging Skills
&lt;/h2&gt;

&lt;p&gt;I have created a simple project (&lt;a href="https://www.github.com/pablobaxter/file-leak-example" rel="noopener noreferrer"&gt;pablobaxter/file-leak-example&lt;/a&gt;) which will be used for the rest of this article to showcase how to find file leaks that may occur either in a build script directly or within the plugin of another library. These approaches can be applied to any Gradle project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attaching the File Leak Detector
&lt;/h3&gt;

&lt;p&gt;One of the first steps to detecting leaks is by using a... detector. Thankfully, one is available here: &lt;a href="https://repo.jenkins-ci.org/releases/org/kohsuke/file-leak-detector/" rel="noopener noreferrer"&gt;https://repo.jenkins-ci.org/releases/org/kohsuke/file-leak-detector/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Regardless of what version you choose, you'll want to download the &lt;code&gt;file-leak-detector-&amp;lt;VERSION&amp;gt;-jar-with-dependencies.jar&lt;/code&gt; file, and store it somewhere that is accessible.&lt;/p&gt;

&lt;p&gt;Next, you'll need to attach the Java agent from this Jar to the Gradle daemon of the project. This can be done by adding &lt;code&gt;-javaagent:&lt;/code&gt; to the &lt;code&gt;org.gradle.jvmargs&lt;/code&gt; parameter of the &lt;code&gt;gradle.properties&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;org.gradle.jvmargs=-Xmx1536m -javaagent:/path/to/file-leak-detector-&amp;lt;VERSION&amp;gt;-jar-with-dependencies.jar=http=19999,strong
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we also add in the arguments to start the HTTP server (&lt;code&gt;http=19999&lt;/code&gt;) and to not let the JVM garbage collector clean up any open file descriptors (&lt;code&gt;strong&lt;/code&gt;). Once this property is added, kill any currently running Gradle daemon and re-sync the project on IntelliJ. This will start a new Gradle daemon and &lt;a href="http://localhost:19999" rel="noopener noreferrer"&gt;http://localhost:19999&lt;/a&gt; will now start displaying open file descriptors or crashing the web browser if it's too much, but this isn't an issue for us. This output is what we will use for postprocessing. Once the sync has completed, save a copy of this output as a text file, as it will be used to create the baseline of the open file descriptors for the Gradle daemon.&lt;/p&gt;

&lt;p&gt;At this point we have accomplished the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instrumented the Gradle daemon with the File Leak Detector.&lt;/li&gt;
&lt;li&gt;Started a simple web service that shows all currently open files on the Gradle daemon.&lt;/li&gt;
&lt;li&gt;Stored a raw baseline of all open files to compare against.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we can start our search for leaking files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Establishing the Baseline
&lt;/h3&gt;

&lt;p&gt;So far, by syncing Gradle via IntelliJ, we have basically only run the Initialization and Configuration phases of the Gradle build. This means the &lt;code&gt;settings.gradle&lt;/code&gt; and &lt;code&gt;build.gradle&lt;/code&gt; files have been parsed and evaluated.&lt;/p&gt;

&lt;p&gt;Now, let's do some postprocessing of the baseline output. The steps are listed in the &lt;a href="https://www.github.com/centic9/file-leak-postprocess" rel="noopener noreferrer"&gt;centic9/file-leak-postprocess&lt;/a&gt; repository, except you will also store the baseline postprocess output somewhere that is easily accessible. With this, we finally have our true baseline file that we will use to diff against. We can now start looking for file leaks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hunting File Leaks
&lt;/h3&gt;

&lt;p&gt;With the baseline postprocess file, we can now trigger the Gradle sync on IntelliJ a number of times. I typically start with 5 syncs, but any number is fine, so long as a count of the runs is kept. You'll notice these syncs are slightly faster, which is expected. Wait for the syncs to complete before continuing with the next.&lt;/p&gt;

&lt;p&gt;If this is done on the example project, you may notice that the &lt;code&gt;X descriptors are open&lt;/code&gt; line on the generated webpage (&lt;a href="http://localhost:19999" rel="noopener noreferrer"&gt;http://localhost:19999&lt;/a&gt;) has increased (probably not by a whole lot), but this may differ if you are testing on a different project. Let's store the new output, and run the postprocess job on it. Now if we look at the diff between the baseline and the new postprocess output, we'll notice a few things here.&lt;/p&gt;

&lt;p&gt;On the example project, the first and most obvious difference is that we have multiple &lt;code&gt;mycustom.properties&lt;/code&gt; files open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;1139a1140,1261
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#898 /home/pablo/Development/file-leak-example/mycustom.properties by thread:Daemon worker Thread 2 on Tue Oct 17 14:26:01 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#878 /home/pablo/Development/file-leak-example/mycustom.properties by thread:Daemon worker Thread 2 on Tue Oct 17 14:25:57 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#888 /home/pablo/Development/file-leak-example/mycustom.properties by thread:Daemon worker Thread 2 on Tue Oct 17 14:25:59 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#883 /home/pablo/Development/file-leak-example/mycustom.properties by thread:Daemon worker Thread 2 on Tue Oct 17 14:25:58 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#893 /home/pablo/Development/file-leak-example/mycustom.properties by thread:Daemon worker Thread 2 on Tue Oct 17 14:26:00 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  at java.base/java.io.FileInputStream.&amp;lt;init&amp;gt;&lt;span class="o"&gt;(&lt;/span&gt;FileInputStream.java:160&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  at Build_gradle.&amp;lt;init&amp;gt;&lt;span class="o"&gt;(&lt;/span&gt;build.gradle.kts:8&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  at Program.execute&lt;span class="o"&gt;(&lt;/span&gt;Unknown Source&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  at org.gradle.kotlin.dsl.execution.Interpreter&lt;span class="nv"&gt;$ProgramHost&lt;/span&gt;.eval&lt;span class="o"&gt;(&lt;/span&gt;Interpreter.kt:523&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  at org.gradle.kotlin.dsl.execution.Interpreter&lt;span class="nv"&gt;$ProgramHost&lt;/span&gt;.evaluateSecondStageOf&lt;span class="o"&gt;(&lt;/span&gt;Interpreter.kt:434&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  at Program.execute&lt;span class="o"&gt;(&lt;/span&gt;Unknown Source&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  at org.gradle.kotlin.dsl.execution.Interpreter&lt;span class="nv"&gt;$ProgramHost&lt;/span&gt;.eval&lt;span class="o"&gt;(&lt;/span&gt;Interpreter.kt:523&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  at org.gradle.kotlin.dsl.execution.Interpreter.eval&lt;span class="o"&gt;(&lt;/span&gt;Interpreter.kt:198&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  at org.gradle.kotlin.dsl.provider.StandardKotlinScriptEvaluator.evaluate&lt;span class="o"&gt;(&lt;/span&gt;KotlinScriptEvaluator.kt:121&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  at org.gradle.kotlin.dsl.provider.KotlinScriptPluginFactory&lt;span class="nv"&gt;$create$1&lt;/span&gt;.invoke&lt;span class="o"&gt;(&lt;/span&gt;KotlinScriptPluginFactory.kt:51&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  at org.gradle.kotlin.dsl.provider.KotlinScriptPluginFactory&lt;span class="nv"&gt;$create$1&lt;/span&gt;.invoke&lt;span class="o"&gt;(&lt;/span&gt;KotlinScriptPluginFactory.kt:48&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  at org.gradle.kotlin.dsl.provider.KotlinScriptPlugin.apply&lt;span class="o"&gt;(&lt;/span&gt;KotlinScriptPlugin.kt:35&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  at org.gradle.configuration.BuildOperationScriptPlugin&lt;span class="nv"&gt;$1&lt;/span&gt;.run&lt;span class="o"&gt;(&lt;/span&gt;BuildOperationScriptPlugin.java:65&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  ...
&lt;span class="o"&gt;(&lt;/span&gt;Rest of stacktrace omitted &lt;span class="k"&gt;for &lt;/span&gt;brevity&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As can be seen, this file was opened 5 times and has yet to close. The number of times this file has opened coincides with how many syncs you have triggered. This is an obvious file leak, which you can verify by triggering more runs of the IntelliJ sync, downloading the raw logs, running the postprocess job, and diff-ing against the baseline again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding and Fixing the First File Leak!
&lt;/h3&gt;

&lt;p&gt;Now that we have found our file leak, we can begin looking at what may be causing it. If we examine the log closely, we'll see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  at java.base/java.io.FileInputStream.&amp;lt;init&amp;gt;&lt;span class="o"&gt;(&lt;/span&gt;FileInputStream.java:160&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  at Build_gradle.&amp;lt;init&amp;gt;&lt;span class="o"&gt;(&lt;/span&gt;build.gradle.kts:8&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;  at Program.execute&lt;span class="o"&gt;(&lt;/span&gt;Unknown Source&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells us that this leak is occurring in a &lt;code&gt;build.gradle.kts&lt;/code&gt; file. Unfortunately, it doesn't tell us &lt;em&gt;which&lt;/em&gt; project this belongs to, which means we'll have to dig into the build files for all the projects to find any code that opens a &lt;code&gt;FileInputStream&lt;/code&gt;. Since I wrote the file leak in the example project, I can tell you that it is in the &lt;code&gt;:someproject&lt;/code&gt; project, but it is very likely file leaks you encounter could also be in 3rd party libraries/plugins, &lt;code&gt;buildSrc&lt;/code&gt; plugins, and/or included builds. Further investigations will be required on your part to find the location of these leaks.&lt;/p&gt;

&lt;p&gt;Coming back to the current leak we found...&lt;/p&gt;

&lt;p&gt;&lt;code&gt;someproject/build.gradle.kts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Properties&lt;/span&gt;

&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;kotlin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"jvm"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;customProps&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rootProject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mycustom.properties"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;inputStream&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;afterEvaluate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SOME_VALUE=${customProps["&lt;/span&gt;&lt;span class="nc"&gt;SOME_VALUE&lt;/span&gt;&lt;span class="s"&gt;"]}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looking at this script, we can see that the custom properties file, &lt;code&gt;mycustom.properties&lt;/code&gt;, is loaded via the call to &lt;code&gt;inputStream()&lt;/code&gt;. This custom properties object is then used to output the value of &lt;code&gt;SOME_VALUE&lt;/code&gt; after the project is evaluated. However, on line 8, the &lt;code&gt;inputStream()&lt;/code&gt; is never properly closed. This can easily be remedied by utilizing the &lt;code&gt;use {}&lt;/code&gt; Kotlin lambda:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;someproject/build.gradle.kts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Properties&lt;/span&gt;

&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;kotlin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"jvm"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;customProps&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rootProject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mycustom.properties"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inputStream&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;afterEvaluate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SOME_VALUE=${customProps["&lt;/span&gt;&lt;span class="nc"&gt;SOME_VALUE&lt;/span&gt;&lt;span class="s"&gt;"]}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can verify this change by killing the Gradle daemon, re-creating our baseline postprocess file, re-running the syncs, capturing the test postprocess results, and doing a diff of these files. As you can see, there are still more files being opened.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;5365a5366,5369
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#873 /home/pablo/.gradle/caches/jars-9/851490ff745ba1a7abc0a483e543c397/cp_init.jar by thread:Daemon worker on Tue Oct 17 16:20:12 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#885 /home/pablo/.gradle/caches/jars-9/9449ad0b53bb85542ad161a6def1ef0b/cp_init.jar by thread:Daemon worker on Tue Oct 17 16:20:15 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#881 /home/pablo/.gradle/caches/jars-9/01fde95ef1bd4e20158029345491b272/cp_init.jar by thread:Daemon worker on Tue Oct 17 16:20:14 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#889 /home/pablo/.gradle/caches/jars-9/15e95a482b204e0b70b9ef9625b482b2/cp_init.jar by thread:Daemon worker on Tue Oct 17 16:20:16 PDT 2023&lt;/span&gt;
5366a5371
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#877 /home/pablo/.gradle/caches/jars-9/866081816ac4706c0b764862ea82a630/cp_init.jar by thread:Daemon worker on Tue Oct 17 16:20:13 PDT 2023&lt;/span&gt;
5500a5506
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#875 /home/pablo/.gradle/caches/jars-9/ab8e4f5ceb792f868ec42ef3d07eee30/init.jar by thread:Daemon worker on Tue Oct 17 16:20:12 PDT 2023&lt;/span&gt;
5501a5508
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#879 /home/pablo/.gradle/caches/jars-9/bc1404c99cc8606ca09377f830411b42/init.jar by thread:Daemon worker on Tue Oct 17 16:20:13 PDT 2023&lt;/span&gt;
5503a5511,5513
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#883 /home/pablo/.gradle/caches/jars-9/b429404e655ec8107d5d074223d8a436/init.jar by thread:Daemon worker on Tue Oct 17 16:20:14 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#887 /home/pablo/.gradle/caches/jars-9/2c575d0a492aeab5fafd052018371556/init.jar by thread:Daemon worker on Tue Oct 17 16:20:15 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#891 /home/pablo/.gradle/caches/jars-9/5f04e79805608b2dfa1b7c741b6dd56a/init.jar by thread:Daemon worker on Tue Oct 17 16:20:16 PDT 2023&lt;/span&gt;
11426a11437,11439
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#874 /home/pablo/.gradle/caches/jars-9/851490ff745ba1a7abc0a483e543c397/cp_init.jar by thread:Daemon worker on Tue Oct 17 16:20:12 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#886 /home/pablo/.gradle/caches/jars-9/9449ad0b53bb85542ad161a6def1ef0b/cp_init.jar by thread:Daemon worker on Tue Oct 17 16:20:15 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#882 /home/pablo/.gradle/caches/jars-9/01fde95ef1bd4e20158029345491b272/cp_init.jar by thread:Daemon worker on Tue Oct 17 16:20:14 PDT 2023&lt;/span&gt;
11427a11441,11442
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#878 /home/pablo/.gradle/caches/jars-9/866081816ac4706c0b764862ea82a630/cp_init.jar by thread:Daemon worker on Tue Oct 17 16:20:13 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#890 /home/pablo/.gradle/caches/jars-9/15e95a482b204e0b70b9ef9625b482b2/cp_init.jar by thread:Daemon worker on Tue Oct 17 16:20:16 PDT 2023&lt;/span&gt;
11556a11572
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#624 /home/pablo/.gradle/caches/jars-9/a008d0781e8859dd03f02bf926b9fbcf/init.jar by thread:Daemon worker on Tue Oct 17 16:19:11 PDT 2023&lt;/span&gt;
11557a11574,11577
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#880 /home/pablo/.gradle/caches/jars-9/bc1404c99cc8606ca09377f830411b42/init.jar by thread:Daemon worker on Tue Oct 17 16:20:13 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#884 /home/pablo/.gradle/caches/jars-9/b429404e655ec8107d5d074223d8a436/init.jar by thread:Daemon worker on Tue Oct 17 16:20:14 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#888 /home/pablo/.gradle/caches/jars-9/2c575d0a492aeab5fafd052018371556/init.jar by thread:Daemon worker on Tue Oct 17 16:20:15 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#892 /home/pablo/.gradle/caches/jars-9/5f04e79805608b2dfa1b7c741b6dd56a/init.jar by thread:Daemon worker on Tue Oct 17 16:20:16 PDT 2023&lt;/span&gt;
11559c11579
&amp;lt; &lt;span class="c"&gt;#624 /home/pablo/.gradle/caches/jars-9/a008d0781e8859dd03f02bf926b9fbcf/init.jar by thread:Daemon worker on Tue Oct 17 16:19:11 PDT 2023&lt;/span&gt;
&lt;span class="nt"&gt;---&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#876 /home/pablo/.gradle/caches/jars-9/ab8e4f5ceb792f868ec42ef3d07eee30/init.jar by thread:Daemon worker on Tue Oct 17 16:20:12 PDT 2023&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These files are not part of the example project (nor a part of any other project you may be evaluating). They are actually &lt;code&gt;init&lt;/code&gt; script files injected by IntelliJ into the Gradle daemon. As you can see, the number of these files coincide with the number of times the IntelliJ sync was run. Currently, this issue is filed here: &lt;a href="https://youtrack.jetbrains.com/issue/IDEA-335172/Potential-file-leak-with-Gradle-init-scripts" rel="noopener noreferrer"&gt;https://youtrack.jetbrains.com/issue/IDEA-335172/Potential-file-leak-with-Gradle-init-scripts&lt;/a&gt;. For the purpose of this article, we'll ignore these leaking files.&lt;/p&gt;

&lt;h3&gt;
  
  
  The File Leak Hunt Continues!
&lt;/h3&gt;

&lt;p&gt;So far, we have looked at the Initialization and Configuration phases of the Gradle build. This would mainly look at plugins and build scripts, but we haven't performed any task runs such as a test or build.&lt;/p&gt;

&lt;p&gt;Searching for file leaks in the Execution phase can be tricky due to build caching, configuration caching, etc. Each task could also have their own file leak, meaning you'd have to run through all your tasks to detect them. In the example project, we'll just focus on the &lt;code&gt;:generateProtos&lt;/code&gt; task, though I would implore you investigate other tasks.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why &lt;code&gt;:generateProtos&lt;/code&gt;?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Spoiler Alert:&lt;/strong&gt; In the example project, there is another file leak with this specific task. We'll use the skills and tools learned from this article to find it. We can use IntelliJ to run the tasks to keep using the same daemon, but the command line can also be used, though you'll need to kill the current daemon, and re-establish the baseline again. You can do this by just running the task once.&lt;/p&gt;

&lt;p&gt;As with the Gradle sync, if we run &lt;code&gt;:generateProtos&lt;/code&gt; a number of times (about 5 times is what I go for), capture the logs, run postprocessing on them, and diff them against the baseline, you may see a number of files opened. However, if we investigate this diff, we'll see a new batch of files opened several times with a common stacktrace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#935 /home/pablo/Development/file-leak-example/someapplication/../../../.gradle/caches/modules-2/files-2.1/com.squareup.wire/wire-reflector/4.9.1/39f775c2f78dd29b2aecceb61ef42c41a45bb54b/wire-reflector-4.9.1.jar by thread:Execution worker on Tue Oct 24 14:30:51 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#938 /home/pablo/Development/file-leak-example/someapplication/../../../.gradle/caches/modules-2/files-2.1/com.squareup.wire/wire-reflector/4.9.1/39f775c2f78dd29b2aecceb61ef42c41a45bb54b/wire-reflector-4.9.1.jar by thread:Execution worker on Tue Oct 24 14:30:51 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#932 /home/pablo/Development/file-leak-example/someapplication/../../../.gradle/caches/modules-2/files-2.1/com.squareup.wire/wire-reflector/4.9.1/39f775c2f78dd29b2aecceb61ef42c41a45bb54b/wire-reflector-4.9.1.jar by thread:Execution worker on Tue Oct 24 14:30:51 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#933 /home/pablo/Development/file-leak-example/someapplication/../../../.gradle/caches/modules-2/files-2.1/com.squareup.wire/wire-reflector/4.9.1/39f775c2f78dd29b2aecceb61ef42c41a45bb54b/wire-reflector-4.9.1.jar by thread:Execution worker on Tue Oct 24 14:30:51 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#936 /home/pablo/Development/file-leak-example/someapplication/../../../.gradle/caches/modules-2/files-2.1/com.squareup.wire/wire-reflector/4.9.1/39f775c2f78dd29b2aecceb61ef42c41a45bb54b/wire-reflector-4.9.1.jar by thread:Execution worker on Tue Oct 24 14:30:51 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#934 /home/pablo/Development/file-leak-example/someapplication/../../../.gradle/caches/modules-2/files-2.1/com.squareup.wire/wire-reflector/4.9.1/39f775c2f78dd29b2aecceb61ef42c41a45bb54b/wire-reflector-4.9.1.jar by thread:Execution worker on Tue Oct 24 14:30:51 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#937 /home/pablo/Development/file-leak-example/someapplication/../../../.gradle/caches/modules-2/files-2.1/com.squareup.wire/wire-reflector/4.9.1/39f775c2f78dd29b2aecceb61ef42c41a45bb54b/wire-reflector-4.9.1.jar by thread:Execution worker on Tue Oct 24 14:30:51 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c"&gt;#931 /home/pablo/Development/file-leak-example/someapplication/../../../.gradle/caches/modules-2/files-2.1/com.squareup.wire/wire-reflector/4.9.1/39f775c2f78dd29b2aecceb61ef42c41a45bb54b/wire-reflector-4.9.1.jar by thread:Execution worker on Tue Oct 24 14:30:51 PDT 2023&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at java.base/java.io.RandomAccessFile.&amp;lt;init&amp;gt;&lt;span class="o"&gt;(&lt;/span&gt;RandomAccessFile.java:215&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at okio.JvmSystemFileSystem.openReadOnly&lt;span class="o"&gt;(&lt;/span&gt;JvmSystemFileSystem.kt:83&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at okio.ZipFileSystem.metadataOrNull&lt;span class="o"&gt;(&lt;/span&gt;ZipFileSystem.kt:102&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at okio.internal.-FileSystem.commonMetadata&lt;span class="o"&gt;(&lt;/span&gt;FileSystem.kt:36&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at okio.FileSystem.metadata&lt;span class="o"&gt;(&lt;/span&gt;FileSystem.kt:33&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at okio.internal.-FileSystem.symlinkTarget&lt;span class="o"&gt;(&lt;/span&gt;FileSystem.kt:152&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at okio.internal.-FileSystem.collectRecursively&lt;span class="o"&gt;(&lt;/span&gt;FileSystem.kt:126&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at okio.internal.-FileSystem&lt;span class="nv"&gt;$collectRecursively$1&lt;/span&gt;.invokeSuspend&lt;span class="o"&gt;(&lt;/span&gt;FileSystem.kt&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith&lt;span class="o"&gt;(&lt;/span&gt;ContinuationImpl.kt:33&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at kotlin.sequences.SequenceBuilderIterator.hasNext&lt;span class="o"&gt;(&lt;/span&gt;SequenceBuilder.kt:129&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at kotlin.sequences.FilteringSequence&lt;span class="nv"&gt;$iterator$1&lt;/span&gt;.calcNext&lt;span class="o"&gt;(&lt;/span&gt;Sequences.kt:169&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at kotlin.sequences.FilteringSequence&lt;span class="nv"&gt;$iterator$1&lt;/span&gt;.hasNext&lt;span class="o"&gt;(&lt;/span&gt;Sequences.kt:194&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at kotlin.sequences.TransformingSequence&lt;span class="nv"&gt;$iterator$1&lt;/span&gt;.hasNext&lt;span class="o"&gt;(&lt;/span&gt;Sequences.kt:214&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at kotlin.sequences.SequencesKt___SequencesKt.toCollection&lt;span class="o"&gt;(&lt;/span&gt;_Sequences.kt:787&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at kotlin.sequences.SequencesKt___SequencesKt.toSet&lt;span class="o"&gt;(&lt;/span&gt;_Sequences.kt:828&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at com.squareup.wire.schema.DirectoryRoot.allProtoFiles&lt;span class="o"&gt;(&lt;/span&gt;Root.kt:128&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at com.squareup.wire.schema.internal.CommonSchemaLoader.loadSourcePathFiles&lt;span class="nv"&gt;$wire_schema&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;CommonSchemaLoader.kt:117&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at com.squareup.wire.schema.internal.CommonSchemaLoader.loadSchema&lt;span class="o"&gt;(&lt;/span&gt;CommonSchemaLoader.kt:101&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at com.squareup.wire.schema.SchemaLoader.loadSchema&lt;span class="o"&gt;(&lt;/span&gt;SchemaLoader.kt:71&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at com.squareup.wire.schema.WireRun.execute&lt;span class="nv"&gt;$wire_schema&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;WireRun.kt:234&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at com.squareup.wire.schema.WireRun.execute&lt;span class="o"&gt;(&lt;/span&gt;WireRun.kt:221&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at com.squareup.wire.gradle.WireTask.generateWireFiles&lt;span class="o"&gt;(&lt;/span&gt;WireTask.kt:189&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   ...
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at org.gradle.execution.plan.LocalTaskNodeExecutor.execute&lt;span class="o"&gt;(&lt;/span&gt;LocalTaskNodeExecutor.java:42&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph&lt;span class="nv"&gt;$InvokeNodeExecutorsAction&lt;/span&gt;.execute&lt;span class="o"&gt;(&lt;/span&gt;DefaultTaskExecutionGraph.java:337&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph&lt;span class="nv"&gt;$InvokeNodeExecutorsAction&lt;/span&gt;.execute&lt;span class="o"&gt;(&lt;/span&gt;DefaultTaskExecutionGraph.java:324&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph&lt;span class="nv"&gt;$BuildOperationAwareExecutionAction&lt;/span&gt;.execute&lt;span class="o"&gt;(&lt;/span&gt;DefaultTaskExecutionGraph.java:317&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph&lt;span class="nv"&gt;$BuildOperationAwareExecutionAction&lt;/span&gt;.execute&lt;span class="o"&gt;(&lt;/span&gt;DefaultTaskExecutionGraph.java:303&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at org.gradle.execution.plan.DefaultPlanExecutor&lt;span class="nv"&gt;$ExecutorWorker&lt;/span&gt;.execute&lt;span class="o"&gt;(&lt;/span&gt;DefaultPlanExecutor.java:463&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   at org.gradle.execution.plan.DefaultPlanExecutor&lt;span class="nv"&gt;$ExecutorWorker&lt;/span&gt;.run&lt;span class="o"&gt;(&lt;/span&gt;DefaultPlanExecutor.java:380&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   ...
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fixing a File Leak in the Open
&lt;/h3&gt;

&lt;p&gt;As we saw with &lt;code&gt;mycustom.properties&lt;/code&gt;, there are multiple files with a very similar stacktrace being opened. This may not always coincide with how often the task is run (especially if other projects run the same task), but we can see that there is another file leak, as continued runs of the task will open more files. Since this is inside of Okio (&lt;a href="https://github.com/square/okio" rel="noopener noreferrer"&gt;square/okio&lt;/a&gt;), which is open-sourced, this means we can create a &lt;a href="https://github.com/square/okio/pull/1359" rel="noopener noreferrer"&gt;PR fix&lt;/a&gt; for this issue! If you happen to find a leak inside closed-source software, these steps should provide enough data to file bugs as appropriate.&lt;/p&gt;

&lt;p&gt;To lightly touch on the Okio file leak and the repercussions of it, when a protos Jar is used as the &lt;code&gt;srcJar&lt;/code&gt; in Wire (&lt;a href="https://github.com/square/wire/" rel="noopener noreferrer"&gt;square/wire&lt;/a&gt;), Wire will make a call to &lt;code&gt;allProtos&lt;/code&gt;, which calls Okio to do a recursive iteration of all the files inside the Jar. However, to determine for symbolic links in Okio, each file's metadata is read, opening a &lt;code&gt;RandomAcessFile&lt;/code&gt;, but it is not closed properly. Since this would happen for each project on each run of &lt;code&gt;:generateProtos&lt;/code&gt;, this can quickly leak file descriptors to the limit, especially if the Jar file contained a large number of files!&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;File leak detection is not easy, although the fixes may be. My goal with this article is to provide enough guidance to developers, so that they may continue finding and fixing these file leak issues, instead of just looking for ways to cover them up. Also, the skills and tools learned in this article can be applied to JVM webservices, long running JVM applications, etc. I chose to write this article with Gradle in mind due to how ubiquitous Gradle is for Android developers, but don't think this is just a Gradle (or even JVM) specific issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  File Leak Bug Finds
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/gradle/gradle/issues/26650" rel="noopener noreferrer"&gt;https://github.com/gradle/gradle/issues/26650&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtrack.jetbrains.com/issue/IDEA-335172/Potential-file-leak-with-Gradle-init-scripts" rel="noopener noreferrer"&gt;https://youtrack.jetbrains.com/issue/IDEA-335172/Potential-file-leak-with-Gradle-init-scripts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/square/okio/issues/1364" rel="noopener noreferrer"&gt;https://github.com/square/okio/issues/1364&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gradle/gradle/issues/26115#issuecomment-1746244227" rel="noopener noreferrer"&gt;https://github.com/gradle/gradle/issues/26115#issuecomment-1746244227&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/detekt/detekt/issues/6518" rel="noopener noreferrer"&gt;https://github.com/detekt/detekt/issues/6518&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feel free to post your bug finds below!&lt;/p&gt;

</description>
      <category>gradle</category>
      <category>java</category>
      <category>kotlin</category>
    </item>
  </channel>
</rss>
