Phoenix500526 opened a new issue, #11966:
URL: https://github.com/apache/arrow-rs/issues/11966

   ### Describe the bug
   
   `StreamDecoder::decode()` leaves a complete message unprocessed when its 
metadata consumes the remaining input and its `bodyLength` is zero. If there is 
no subsequent input or explicit EOS marker, `finish()` reports `Unexpected End 
of Stream` even though the stream ends at a complete message boundary.
   
   A schema-only stream without EOS is a minimal reproduction: the input is 
consumed, but `schema()` remains `None`. The same state-machine condition also 
applies to record batches whose body length is zero.
   
   Reproduced on apache/arrow-rs at `5db01b1fa7ba6f70d5f63abf31c48fabed40929b`, 
before applying a local fix.
   
   ### To Reproduce
   
   ```rust
   use arrow_buffer::Buffer;
   use arrow_ipc::{reader::StreamDecoder, writer::StreamWriter};
   use arrow_schema::{ArrowError, DataType, Field, Schema};
   
   fn main() -> Result<(), ArrowError> {
       let schema = Schema::new(vec![Field::new("id", DataType::Int32, false)]);
       let mut bytes = Vec::new();
   
       // try_new writes the schema, whose body length is zero.
       // Drop the unbuffered writer without finish() to omit the EOS marker.
       drop(StreamWriter::try_new(&mut bytes, &schema)?);
   
       let mut buffer = Buffer::from(bytes);
       let mut decoder = StreamDecoder::new();
       assert!(decoder.decode(&mut buffer)?.is_none());
       assert!(buffer.is_empty());
   
       println!("schema decoded: {}", decoder.schema().is_some());
       println!("finish: {:?}", decoder.finish());
       Ok(())
   }
   ```
   
   Actual output:
   
   ```text
   schema decoded: false
   finish: Err(IpcError("Unexpected End of Stream"))
   ```
   
   Calling `decode()` again with an empty buffer does not process the pending 
message either.
   
   ### Expected behavior
   
   The schema should be available immediately after `decode()`, and `finish()` 
should succeed. Returning `None` from `decode()` itself is correct here because 
there is no record batch.
   
   An explicit EOS marker is optional:
   
   - The [Arrow IPC streaming 
specification](https://arrow.apache.org/docs/format/Columnar.html#ipc-streaming-format)
 marks EOS as optional and allows a writer to end the stream by closing the 
stream interface instead of writing the marker.
   - The [Schema message 
specification](https://arrow.apache.org/docs/format/Columnar.html#schema-message)
 specifies that a serialized schema has metadata only, with no body.
   - The existing [`MessageReader::read_meta_len()` comment and 
implementation](https://github.com/apache/arrow-rs/blob/5db01b1fa7ba6f70d5f63abf31c48fabed40929b/arrow-ipc/src/reader.rs#L1998-L2007)
 explicitly handle EOF without `0xFFFFFFFF 0x00000000` and cite the 
specification.
   - 
[`StreamDecoder::finish()`](https://github.com/apache/arrow-rs/blob/5db01b1fa7ba6f70d5f63abf31c48fabed40929b/arrow-ipc/src/reader/stream.rs#L298-L310)
 already accepts the initial `Header` state as a valid end, without requiring 
`Finished`.
   
   ### Additional context
   
   The [decode 
loop](https://github.com/apache/arrow-rs/blob/5db01b1fa7ba6f70d5f63abf31c48fabed40929b/arrow-ipc/src/reader/stream.rs#L159-L207)
 runs only while `!buffer.is_empty()`. After parsing metadata, the `Message` 
arm changes the state to `Body`. If that consumes the last input byte, the loop 
exits before the `Body` arm can process the message, even when no body bytes 
are needed.
   
   Appending EOS masks this because the extra bytes allow another iteration. At 
an intermediate chunk boundary, the same condition delays processing of a 
complete zero-body message until another nonempty chunk arrives.
   
   The loop should also advance when a zero-body message is ready to process. 
It must still stop for a nonzero body when more input is needed: an empty input 
buffer does not imply a zero declared body length.
   
   Related issues and PRs:
   
   - #9837 and #11374: the proposed `BufferStreamReader` wraps `StreamDecoder`; 
#11374 explicitly intends to accept streams ending at a message boundary 
without EOS. This edge case is relevant to its schema-only input handling.
   - #6420 and #7488: exposed `StreamDecoder::schema()`, including the use case 
of obtaining a schema when there are zero record batches.
   - #5531: introduced `StreamDecoder`; the original implementation already 
contains this loop condition and `Message` to `Body` transition, so this 
behavior predates the recent buffering changes.
   
   A local fix allowing the zero-body transition makes the schema-only 
regression test pass; all six stream-decoder tests, including the new 
regression test, pass locally.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to