Skip to content

fill the lookahead buffer on short reads from a chunked source#625

Open
saleemno1 wants to merge 1 commit into
apache:masterfrom
saleemno1:chunked-reader-lookahead
Open

fill the lookahead buffer on short reads from a chunked source#625
saleemno1 wants to merge 1 commit into
apache:masterfrom
saleemno1:chunked-reader-lookahead

Conversation

@saleemno1

Copy link
Copy Markdown

Thanks for maintaining this! ExtendedBufferedReader.peek(char[]) and read(char[], int, int) inherit a buffered read that returns as soon as the source reports it is not ready, so a Reader backed by a socket or a pipe hands back a short read and the delimiter lookahead is matched against a half-filled buffer.

Two things go wrong once the buffer is short:

  • Lexer.isDelimiter / isEscapeDelimiter compare every slot, so the still-zeroed tail makes a multi-character delimiter that is really present look like ordinary text. Parsing a[|]b with delimiter [|] yields one field a[|]b from a chunked reader and two fields a, b from a StringReader for the same bytes, which is a parser differential if the split feeds any filtering or routing decision.
  • CSVFormat.printWithEscapes(Reader, Appendable) builds its lookahead the same way, so printing the single value x[|]y from a chunked reader writes the delimiter unescaped and the record reads back as two fields.

Both call sites share the same reader, so the loop lives there rather than in each caller. Added a regression test at the reader level and one at the parser level; both fail without the change. mvn is green including the coverage gate.

A Reader that delivers data in chunks makes the buffered read return early, so the delimiter lookahead was compared against a partially filled buffer and multi-character delimiters were missed.

@garydgregory garydgregory left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @saleemno1
Please see my questions and comments.

@Test
void testParseWithDelimiterStringFromChunkedReader() throws IOException {
// A reader that hands out one character at a time and never reports itself ready, like a socket or pipe.
final Reader chunked = new Reader() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is the same as the behavior of the ChunkedReader in the other test, then refactor for reuse.

/**
* A reader that returns one character per call and never reports itself ready, like a socket or pipe that delivers data in chunks.
*/
private static final class ChunkedReader extends java.io.Reader {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be simpler to extend StringReader and override those methods?

final int len = super.read(buf, offset, length);
int len = super.read(buf, offset, length);
// The underlying buffered reader stops early once the source reports it is not ready, so a stream that delivers data in chunks (a socket or a pipe)
// yields a short read. Callers match multi-character sequences against this buffer, so keep reading until it is full or the source is exhausted.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't there still a risk that even filling to the end of the buffer breaks up multi-character sequences?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants