yegor256/rultor

`InputStream` from `Profile.assets()` is not closed in `StartsDaemon.upload()`

Offen

#2.352 geöffnet am 02.06.2026

 (1 Kommentar) (0 Reaktionen) (0 zugewiesene Personen)Java (165 Forks)github user discovery
bughelp wanted

Repository-Metriken

Stars
 (575 Sterne)
PR-Merge-Metriken
 (PR-Metriken ausstehend)

Beschreibung

In src/main/java/com/rultor/agents/daemons/StartsDaemon.java, lines 209-218, the upload() method iterates over the entries of Profile.assets() and pipes each value to a remote shell:

for (final Map.Entry<String, InputStream> asset
    : this.profile.assets().entrySet()) {
    shell.exec(
        String.format(
            "cat > %s",
            Ssh.escape(String.format("%s/%s", dir, asset.getKey()))
        ),
        asset.getValue(),
        Logger.stream(Level.INFO, true),
        Logger.stream(Level.WARNING, true)
    );
    ...
}

Profile.assets() is declared in src/main/java/com/rultor/spi/Profile.java as returning Map<String, InputStream>, where the values are raw InputStream instances that the caller owns. Shell.exec() consumes the stream but does not close it. The loop discards each InputStream reference at the end of an iteration without ever calling close().

Every asset declared in .rultor.yml therefore leaks one InputStream per build start. With memory-backed profiles the leak is cheap, but when the Profile implementation reads the asset from disk or fetches it over HTTP — as the GitHub-backed profile does — this accumulates real file descriptors or network connections across many builds, and slow exhaustion of those resources will surface as confusing Too many open files or socket failures unrelated to the immediate build.

This is the same defective pattern that #2324 reports in PfShell.key() and applies to the same Profile.assets() contract.

A small fix is to wrap each asset.getValue() in try-with-resources for the duration of the shell.exec() call:

for (final Map.Entry<String, InputStream> asset
    : this.profile.assets().entrySet()) {
    try (InputStream stream = asset.getValue()) {
        shell.exec(..., stream, ...);
    }
    ...
}

That guarantees deterministic close regardless of the underlying Profile implementation.

Contributor Guide