JetBrains / JetBrainsRuntime

Runtime environment based on OpenJDK for running IntelliJ Platform-based products on Windows, macOS, and Linux
GNU General Public License v2.0
1.32k stars 196 forks source link

"too many open files" when using java.nio.file.WatchService #431

Closed creinig closed 2 months ago

creinig commented 2 months ago

Description

JBRSDK 11 (OSX/aarch64) seems to hold on to a lot of file handles when using java.nio.file.WatchService. When executing the example code given below, the JBRSDK Vm holds on to 3516 file handles, compared to an emulated dcevm-11.0.10 (x64) with 36 file handles and corretto 11.0.24 (aarch64) with 23 file handles.

When using the code on a larger directory hierarchy the VM quickly reaches the maximum number of open files and crashes (or rather fails on all operations requiring file handles).

What's especially interesting is that jbrsdk seems to hold on to a file handle for each directory registered in the Watcher, plus (each time!) each of its parent directories:

COMMAND   PID     USER   FD     TYPE             DEVICE  SIZE/OFF                NODE NAME
[...]
java    91162 username 3445r     DIR               1,16       480            30661143 /private/tmp/assertj-core/src/main/java/org/assertj/core/error/future
java    91162 username 3446r     DIR               1,16      8896            30660817 /private/tmp/assertj-core/src/main/java/org/assertj/core/error
java    91162 username 3447r     DIR               1,16       480            30660376 /private/tmp/assertj-core/src/main/java/org/assertj/core
java    91162 username 3448r     DIR               1,16        96            30660375 /private/tmp/assertj-core/src/main/java/org/assertj
java    91162 username 3449r     DIR               1,16        96            30660374 /private/tmp/assertj-core/src/main/java/org
java    91162 username 3450r     DIR               1,16        96            30660373 /private/tmp/assertj-core/src/main/java
java    91162 username 3451r     DIR               1,16       224            30660369 /private/tmp/assertj-core/src/main
java    91162 username 3452r     DIR               1,16       224            30656107 /private/tmp/assertj-core/src
java    91162 username 3453r     DIR               1,16       672            30655698 /private/tmp/assertj-core
java    91162 username 3454r     DIR               1,16       800            27939290 /private/tmp
java    91162 username 3455r     DIR               1,16       192            26990044 /private
java    91162 username 3456r     DIR               1,16       704 1152921500311879682 /System/Volumes/Data
java    91162 username 3457r     DIR               1,16       448 1152921500312503692 /System/Volumes
java    91162 username 3458r     DIR               1,16       320 1152921500311879701 /System
java    91162 username 3459u  KQUEUE                                                  count=0, state=0x8
java    91162 username 3460u  KQUEUE                                                  count=0, state=0x8
java    91162 username 3461r     DIR               1,16       320            30661184 /private/tmp/assertj-core/src/main/java/org/assertj/core/presentation
java    91162 username 3462r     DIR               1,16       480            30660376 /private/tmp/assertj-core/src/main/java/org/assertj/core
java    91162 username 3463r     DIR               1,16        96            30660375 /private/tmp/assertj-core/src/main/java/org/assertj
java    91162 username 3464r     DIR               1,16        96            30660374 /private/tmp/assertj-core/src/main/java/org
java    91162 username 3465r     DIR               1,16        96            30660373 /private/tmp/assertj-core/src/main/java
java    91162 username 3466r     DIR               1,16       224            30660369 /private/tmp/assertj-core/src/main
java    91162 username 3467r     DIR               1,16       224            30656107 /private/tmp/assertj-core/src
java    91162 username 3468r     DIR               1,16       672            30655698 /private/tmp/assertj-core
java    91162 username 3469r     DIR               1,16       800            27939290 /private/tmp
java    91162 username 3470r     DIR               1,16       192            26990044 /private
java    91162 username 3471r     DIR               1,16       704 1152921500311879682 /System/Volumes/Data
java    91162 username 3472r     DIR               1,16       448 1152921500312503692 /System/Volumes
java    91162 username 3473r     DIR               1,16       320 1152921500311879701 /System
java    91162 username 3474u  KQUEUE                                                  count=0, state=0x8
java    91162 username 3475u  KQUEUE                                                  count=0, state=0x8
[...]

In comparison, classic DCEVM and corretto don't hold on to any of these.

Platform

Observed on Mac (arm64) with jbrsdk_dcevm-11_0_16-osx-aarch64

❯ uname -a Darwin LT-24-264 23.6.0 Darwin Kernel Version 23.6.0: Fri Jul 5 17:55:37 PDT 2024; root:xnu-10063.141.1~2/RELEASE_ARM64_T6030 arm64

❯ java -version openjdk version "11.0.16" 2022-07-19 OpenJDK Runtime Environment JBR-11.0.16.8-2043.64-dcevm (build 11.0.16+8-b2043.64) Dynamic Code Evolution 64-Bit Server VM JBR-11.0.16.8-2043.64-dcevm (build 11.0.16+8-b2043.64, mixed mode)

Example code

import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.WatchService;
import java.util.Objects;

public class Lsof {
  private WatchService watcher;

  public static void main(String[] args) throws IOException {
    new Lsof().doRun();
  }

  public void doRun() throws IOException {
    this.watcher = FileSystems.getDefault().newWatchService();
    setupWatchersRecursive(Path.of("/tmp/assertj-core"));

    System.out.println(lsof());
    System.out.println("#open files: " + lsof().split("\n").length);
  }

  private void setupWatchersRecursive(final Path sourcePath) throws IOException {
    System.out.println("setupWatchersRecursive(" + sourcePath + ")");

    sourcePath.register(watcher, ENTRY_MODIFY, ENTRY_CREATE);

    // handle sub directories recursively
    final String[] children = sourcePath.toFile().list();
    if (children == null) {
      long pid = ProcessHandle.current().pid();
      System.out.println("Too many open files in pid " + pid);
    }
    for (final String child : Objects.requireNonNull(children)) {
      File file = sourcePath.resolve(child).toFile();
      if (file.isDirectory() && !file.getName().startsWith(".")) {
        final String subDirName = file.getName();
        setupWatchersRecursive(sourcePath.resolve(subDirName));
      }
    }
  }

  /** execute lsof for the current process & return the output as String */
  private static String lsof() throws IOException {
    final long pid = ProcessHandle.current().pid();
    final StringBuilder output = new StringBuilder();

    Process p = null;
    try {
      p = Runtime.getRuntime().exec("/usr/sbin/lsof -p " + pid);
      BufferedReader b = new BufferedReader(new InputStreamReader(p.getInputStream()));
      String line = "";

      while ((line = b.readLine()) != null) {
        output.append(line).append("\n");
      }

      b.close();
      p.waitFor();

      return output.toString();
    }
    catch (InterruptedException e) {
      throw new IOException(e);
    }
  }
}
mkartashev commented 2 months ago

You can run with -Dwatch.service.polling=true to use the "standard" polling watch service instead.

creinig commented 2 months ago

That workaround works like a charm. Many thanks :)