Skip to content

read / read-into! EOF semantics needs work #1715

@cgay

Description

@cgay

Summary: The number of elements read and whether EOF was reached should not be combined into one return value.


I noticed a situation in which I need to know how many elements read-into! actually read when it reached the end of the stream (that's bug 1) so I started poking around at read methods, which are often implemented in terms of read-into!.

Here's the most basic read method:

define method read (stream :: <stream>, n :: <integer>,
                    #key on-end-of-stream = unsupplied())
 => (elements)
  let elements = make(<vector>, size: n);
  if (supplied?(on-end-of-stream))
    read-into!(stream, n, elements, on-end-of-stream: on-end-of-stream);
  else
    read-into!(stream, n, elements);
  end;
  elements
end method read;

If fewer than n elements are read, there is no indication that the end of the stream was reached and elements has elements at the end that should likely be considered invalid. ([later] Maybe <incomplete-read-error> is signaled indirectly? It's not clear.)

The method on <buffered-stream> takes the other approach and drops the < n elements on the floor:

define method read
    (stream :: <buffered-stream>, n :: <integer>,
     #key on-end-of-stream = unsupplied())
 => (elements)
  let elements = make(stream-sequence-class(stream), size: n);
  let count-or-eof = read-into!(stream, n, elements, on-end-of-stream: on-end-of-stream);
  if (count-or-eof == on-end-of-stream)
    on-end-of-stream
  else
    elements
  end
end method read;

<sequence-stream> signals <incomplete-read-error>:

define method read
    (stream :: <sequence-stream>, n :: <integer>,
     #key on-end-of-stream = unsupplied())
 => (elements)
  ensure-readable(stream);
  let seq   :: <sequence> = stream-sequence(stream);
  let pos   :: <integer>  = stream.current-position;
  let src-n :: <integer>  = (stream-limit(stream) | stream.final-position) - pos;
  if (n > src-n)
    stream.current-position := pos + src-n;
    if (unsupplied?(on-end-of-stream))
      if (zero?(src-n))
        error(make(<end-of-stream-error>, stream: stream))
      else
        error(make(<incomplete-read-error>,
                    stream: stream,
                    count: src-n,
                    sequence: copy-sequence(seq, start: pos, end: pos + src-n)));
      end;
    else
      on-end-of-stream
    end;
  else
    let elements = copy-sequence(seq, start: pos, end: pos + n);
    stream.current-position := pos + n;
    elements
  end;
end method read;

This still doesn't solve the problem if on-end-of-stream is supplied; the last "fewer than n" elements are never returned.

In my opinion, use of <incomplete-read-error> is too complex, in that it expands the I/O API in a non-useful way. Not to mention that in general reading fewer than n bytes is not an error; it's a normal EOF. Instead we should be more similar to C and Go. Specifically, drop the on-end-of-stream keyword parameter and instead have this:

define generic read (stream, n) => (s :: <sequence>, eof? :: <boolean>);
define generic read-into! (stream, sequence, n, #key start) => (n :: <cardinal>, eof? :: <boolean>);

Typical callers would look like this:

iterate loop (done? = #f)
  unless (done?)
    let (count, eof?) = read-into!(stream, n, buffer);
    do-something(buffer, count);
    loop(eof?)
  end
end

If we were to depend on <incomplete-read-error> callers would look something like this:

iterate loop (done? = #f)
  unless (done?)
    block ()
      let count = read-into!(stream, n, buffer)
      do-something(buffer, count);
    exception (ex :: <incomplete-read-error>)
      do-something(buffer, ex.stream-error-count);
      done? := #t;
    end;
    loop(done?)
  end
end

But whatever we do, all read and read-into! methods should have consistent behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions