yegor256/rultor

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

Aberta

#2.352 aberto em 2 de jun. de 2026

 (1 comentário) (0 reação) (0 responsável)Java (165 forks)github user discovery
bughelp wanted

Métricas do repositório

Stars
 (575 estrelas)
Métricas de merge de PR
 (Métricas PR pendentes)

Description

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.

Guia do colaborador