apache/dubbo · error · IllegalArgumentException

Read-ahead limit < 0

Error message

Read-ahead limit < 0

What it means

Thrown by UnsafeStringReader.mark(int) when the readAheadLimit argument is negative. UnsafeStringReader is a thread-unsafe Reader over a String that supports mark/reset; the limit parameter is accepted to honor the java.io.Reader contract but the implementation only records the current position (mMark = mPosition), so a negative value has no valid meaning and is rejected up front before ensureOpen() runs. This mirrors the behavior of java.io.StringReader.

Source

Thrown at dubbo-common/src/main/java/org/apache/dubbo/common/io/UnsafeStringReader.java:94

        mPosition += n;
        return n;
    }

    @Override
    public boolean ready() throws IOException {
        ensureOpen();
        return true;
    }

    @Override
    public boolean markSupported() {
        return true;
    }

    @Override
    public void mark(int readAheadLimit) throws IOException {
        if (readAheadLimit < 0) {
            throw new IllegalArgumentException("Read-ahead limit < 0");
        }

        ensureOpen();
        mMark = mPosition;
    }

    @Override
    public void reset() throws IOException {
        ensureOpen();
        mPosition = mMark;
    }

    @Override
    public void close() throws IOException {
        mString = null;
    }

    private void ensureOpen() throws IOException {

View on GitHub (pinned to 3a3043227f)

Solutions

  1. Pass a non-negative readAheadLimit (the actual byte budget you intend to re-read after reset), e.g. mark(buffer.length()).
  2. If the limit is computed, clamp it with Math.max(0, computed) before calling mark.
  3. Guard the call: if (readAheadLimit >= 0) reader.mark(readAheadLimit); else skip marking.
  4. Note UnsafeStringReader ignores the magnitude of the limit (it only stores mPosition), so any non-negative value works and 'unlimited' mark is effectively free.

Example fix

// before
reader.mark(-1);
// after
reader.mark(Integer.MAX_VALUE); // value is stored but not enforced by this impl
Defensive patterns

Strategy: validation

Validate before calling

if (readAheadLimit < 0) {
    // UnsafeStringReader stores position only; pass any non-negative value
    readAheadLimit = 0;
}
reader.mark(readAheadLimit);

Try / catch

try {
    reader.mark(readAheadLimit);
} catch (IllegalArgumentException e) {
    if ("Read-ahead limit < 0".equals(e.getMessage())) {
        reader.mark(0); // recover: zero is valid for this impl
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling reader.mark(readAheadLimit) on an UnsafeStringReader instance where readAheadLimit < 0 (e.g. mark(-1), mark(-1024)). The check at line 93 fires before the stream-open check, so even an open reader throws this first.

Common situations: Caller computes readAheadLimit from a length/offset that underflows to negative; passing a constant -1 as a 'no limit' sentinel (this API has no such sentinel); unit tests asserting mark behavior; code ported from a Reader that tolerates negative limits.

Related errors


AI-assisted analysis of apache/dubbo@3a3043227f (2026-08-14). Data as JSON: /api/errors/c1c84146073f40af. Report an issue: GitHub.