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
- Pass a non-negative readAheadLimit (the actual byte budget you intend to re-read after reset), e.g. mark(buffer.length()).
- If the limit is computed, clamp it with Math.max(0, computed) before calling mark.
- Guard the call: if (readAheadLimit >= 0) reader.mark(readAheadLimit); else skip marking.
- 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
- Clamp any computed read-ahead limit with Math.max(0, value) before calling mark.
- Remember UnsafeStringReader ignores the magnitude of the limit (it only records mPosition), so 'unlimited' mark is effectively free — never pass a sentinel -1.
- Do not use negative numbers as 'no limit' sentinels for Reader.mark APIs.
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
- Stream closed
- Negative buffer size
- Mark buffer is full!
- should mark before reset!
- Negative initial size: ${size}
AI-assisted analysis of apache/dubbo@3a3043227f (2026-08-14).
Data as JSON: /api/errors/c1c84146073f40af.
Report an issue: GitHub.