Skip to content

pxf: DatasetReader returns a complete row from a stream that stays open (#171) - #173

Merged
trendvidia merged 1 commit into
mainfrom
dataset-live-stream-171
Nov 7, 2026
Merged

trendvidia merged 1 commit into
mainfrom
dataset-live-stream-171

Conversation

@bfrancojr

@bfrancojr bfrancojr commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #171. Stacked on #172 (#168). #167 has merged. It also carries #170's commit (95ab86f, the #169 timestamp fix, identical to #170's), because one of the live rows, (2026-10-23T00:00:00Z), needs it. Merge order: #172, #170, then this. Rebasing after #170 merges drops the duplicate commit.

What was wrong

On a pipe, a socket or a file still being appended to, Next could hold a row that had already arrived. It waited for 4 KiB more input, or for the stream to end. Two causes:

  1. Pull used std::istream::read, which waits for all 4096 bytes.
  2. The scan took a token as complete only once four more bytes followed it. A row whose ) was the last byte read waited.

What changes

  • Pull takes what the stream has buffered (readsome). It blocks only when nothing is buffered, through peek, for one byte or the end. On an istringstream the reads are still 4096 bytes, so the sweep's byte counts are unchanged.

  • A token counts as complete as soon as the bytes the lexer looks at past it have arrived:

    • a one-byte token (punctuation, a newline) needs none;
    • a closed string, bytes literal or block comment needs none, except "", which a third quote would turn into """;
    • any other token needs the byte after it, and the byte after that when the first is ., p, / or the first byte of µ;
    • an error token, or one that starts past ASCII, still waits for four.

    That follows what the lexer reads, path by path. Go's lexer records the same thing as sawEnd.

The issue left the read method as a choice for the PR. I chose readsome plus peek over rdbuf()->sgetn, so the stream's state flags stay right for the caller.

Evidence

  • DatasetReaderLive.CompleteRowIsNotHeldBack feeds protowire-go's 16 rows (TestDatasetReader_CompleteRowIsNotHeldBack) one byte per read. It uses a streambuf that records any read past the bytes written, which stands in for a pipe that stays open, without threads or timeouts. Before this change all 16 rows waited. With it, none does.
  • The first version of the rule still held 2 of the 16: the triple-quoted string and the escaped string, both closed at the last byte read. Treating closed strings as complete fixed both.
  • The 590-case Go sweep from pxf: DatasetReader ends a table only at the end of input or a directive (#164) #167 still matches. That sweep is what would catch a token taken as complete too early, at every read boundary in 14 row shapes.
  • DatasetReaderScan.IsLinearOneByteAtATime keeps the scan linear in one-byte reads, which Pull can now return: 4 shapes at 256 KiB. It also passes on the old reader, which never did one-byte reads, so it guards the new path rather than proving this fix.
  • ctest: 735/735 locally (macOS). clang-format 18 is clean. clang-tidy 23 reports nothing new on the changed files.
  • Throughput on pxf: DatasetReader ends a table only at the end of input or a directive (#164) #167's 300,000-row table: 83–88 ms, against 83–86 ms before (best of 5, local). That's within noise.
  • CI on 4095d75 (head, rebased onto main 42d3c27, which has pxf: DatasetReader lexes a long token once, however many reads it spans (#168) #172): all 11 checks pass, including CodeQL, ASan + UBSan, the differential against protowire-go, clang-tidy and Windows. Locally 735/735.

@trendvidia
trendvidia force-pushed the dataset-scan-linear-168 branch from 2c84b66 to 3f3e817 Compare November 7, 2026 04:23
@trendvidia
trendvidia force-pushed the dataset-live-stream-171 branch from 58937d7 to 5166281 Compare November 7, 2026 04:24
@trendvidia
trendvidia force-pushed the dataset-scan-linear-168 branch from 3f3e817 to 38a9ca2 Compare November 7, 2026 06:49
@trendvidia
trendvidia force-pushed the dataset-live-stream-171 branch from 5166281 to 5bcadcc Compare November 7, 2026 06:50
Base automatically changed from dataset-scan-linear-168 to main November 7, 2026 07:29
…en (#171)

On a stream that stays open, a row that had arrived could wait for more
input or for the end. Two things held it, both now as protowire-go does
(TestDatasetReader_CompleteRowIsNotHeldBack):

- Pull used std::istream::read, which waits for all 4096 bytes. It now
  takes what the stream has buffered (readsome), and blocks, through
  peek, only when nothing is, for one byte or the end.
- The scan took a token as complete only once four bytes followed it.
  It now takes a token as complete once the bytes the lexer looks at
  past it are buffered. A one-byte token needs none. A closed string,
  bytes literal or block comment needs none, except `""`, which a third
  quote would open into `"""`. Any other token needs the byte after it,
  and the byte after that when the first is `.`, `p`, `/` or the first
  byte of `µ`. An error token, or one that starts past ASCII, still
  waits for four.

DatasetReaderLive.CompleteRowIsNotHeldBack feeds Go's 16 rows one byte
per read through a streambuf that records any read past the bytes
written. Before this change all 16 waited. With it, none does, and the
590-case Go sweep still matches. IsLinearOneByteAtATime holds the scan
linear in one-byte reads, which Pull now does.
@trendvidia
trendvidia force-pushed the dataset-live-stream-171 branch from 5bcadcc to 4095d75 Compare November 7, 2026 07:31
@trendvidia
trendvidia merged commit be6645e into main Nov 7, 2026
11 checks passed
@trendvidia
trendvidia deleted the dataset-live-stream-171 branch November 7, 2026 08:40
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.

DatasetReader holds back a complete row on a live stream

2 participants