[Bug] StreamUtils treats InputStream.available() as the remaining stream length

Aperta Adatta ai principianti
#16,510 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@hickey-ai ci sta già lavorando.

Dal 16/10/2026.

  • #16511 di @hickey-ai — aperta

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
75/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
java
Ambito
backend

Direzione di ricerca

Inizia da skipUnusedStream e limitedInputStream in dubbo-common/src/main/java/org/apache/dubbo/common/io/StreamUtils.java; entrambi si basano su available(). Leggi la riproduzione nell'issue, che contiene due casi JUnit con un InputStream personalizzato il cui available() restituisce 0 oppure resta indietro rispetto a skip(). È fatto quando entrambi i casi passano: i cicli di svuotamento avanzano con read()/skip() fino a EOF e il limite non viene ricavato da available().

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

component/need-triage type/need-triage
Pre-check
  • I am sure that all the content I provide is in English.
Search before asking
  • I had searched in the issues and found no similar issues.
Apache Dubbo Component

Java SDK (apache/dubbo)

Dubbo Version

Apache Dubbo source: 3.3.7-SNAPSHOT, branch 3.3, commit ae809de2fc81b408194240965eb20bf27e6971df.
Windows 11, OpenJDK 21, Maven 3.6.3.

Two focused utility regressions were run in an isolated Maven/JUnit harness using byte-for-byte source copies. No registry or network service is required.

Steps to reproduce this issue

Component

Java SDK (apache/dubbo), dubbo-common.

Summary

StreamUtils.skipUnusedStream(InputStream) uses InputStream.available() to decide whether to skip data, and performs only one skip call. This is incorrect for general InputStream implementations: available() may legally return zero even when a read would produce data, and skip(n) may skip fewer than n bytes. The method can leave request/serialization bytes unread.

The same available() assumption also appears in limitedInputStream, which sets its limit to Math.min(limit, is.available()); a stream with data but available() == 0 is exposed as immediately exhausted.

Version and verification scope

  • Apache Dubbo 3.3.7-SNAPSHOT, branch 3.3, commit ae809de2fc81b408194240965eb20bf27e6971df.
  • Windows 11, OpenJDK 21, Maven 3.6.3.
  • Two implementation files were copied byte-for-byte from the commit into an isolated Maven/JUnit harness. No network service or registry is required.
  • This is a focused utility regression, not a full reactor build or production protocol validation.

Reproduction

static class AvailableZeroStream extends InputStream {
    private final byte[] data = "payload".getBytes();
    private int index;
    public int available() { return 0; }
    public int read() { return index < data.length ? data[index++] & 0xff : -1; }
    public long skip(long n) {
        long amount = Math.min(n, Math.max(0, data.length - index));
        index += (int) amount;
        return amount;
    }
}

@Test
void skipUnusedStreamMustDrainStreamsWhoseAvailableIsZero() throws Exception {
    AvailableZeroStream in = new AvailableZeroStream();
    StreamUtils.skipUnusedStream(in);
    assertEquals(-1, in.read()); // fails: returns 'p'
}

@Test
void skipUnusedStreamMustContinueWhenSkipReturnsLessThanAvailable() throws Exception {
    InputStream in = new InputStream() {
        private int remaining = 5;
        public int available() { return remaining; }
        public int read() { if (remaining == 0) return -1; remaining--; return 'x'; }
        public long skip(long n) { long skipped = Math.min(1, n); remaining -= skipped; return skipped; }
    };
    StreamUtils.skipUnusedStream(in);
    assertEquals(-1, in.read()); // fails: returns 'x'
}

Executed result: both assertions failed; the normal JUnit control for Ring passed. The failure is caused by the actual upstream StreamUtils class.

Expected behavior

A utility that drains or limits an input stream should use read()/skip() progress until EOF or the requested limit is reached. available() is only an estimate of non-blocking bytes and must not be used as the stream's total length. If the intended contract is only “skip currently available bytes”, the method name and documentation should state that explicitly and the one-shot skip should still handle partial progress.

Root cause

skipUnusedStream guards the only skip call with is.available() > 0. limitedInputStream similarly uses is.available() as its initial limit. Both paths break for standard custom/network/decompressing streams where available() is zero or smaller than the remaining data.

Deduplication

Searched open issues for StreamUtils, available skip, skipUnusedStream, and stream payload handling. I did not find a report of this root cause in the searched results. This is separate from the existing Bytes/InputStream MD5 report #16451.

What you expected to happen

Stream utility methods should not use InputStream.available() as the total remaining length. A valid stream with available()==0 must not be treated as empty, and a partial skip must be retried until the intended data is consumed.

Both focused regressions failed against the unchanged upstream implementation.

Anything else

Root cause: skipUnusedStream checks available()>0 and performs one skip call; limitedInputStream also derives its limit from available(). InputStream.available() is only a non-blocking estimate and skip() may return fewer bytes than requested.

Searched open issues for StreamUtils, available skip, skipUnusedStream and stream payload handling; no matching root-cause report was found. This is separate from the existing Bytes/InputStream MD5 report #16451.

Do you have a (mini) reproduction demo?
  • Yes, I have a minimal reproduction demo to help resolve this issue more effectively!
Are you willing to submit a pull request to fix on your own?
  • Yes I am willing to submit a pull request on my own!
Code of Conduct
Lingua principale
Java
Stelle
41.6k
Fork
26.3k
Merge medio
1g 22h
PR unite (30g)
19

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di apache/dubbo

Tutte le issue di apache/dubbo

Issue simili

Altre issue su Java

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.