Skip to content

Review of IO blocking behaviour  #24526

Open
@samoconnor

Description

@samoconnor

I've recently been helping @quinnj to debug his new HTTP.jl package, in particular a new FIFOBuffer type that is intended to behave like the IO buffers in Base (#87, #86, #76, #75, #74). The process of trying to be consistent with Base has highlighted a number of IO behaviour inconsistencies. I've hacked up a script that runs through a sequence of IO operations for various types and produces a MD table of the results (see below).

The two main issues are with the blocking behaviour of read() and eof().

The spec for eof() says: "If the stream is not yet exhausted, this function will block to wait for more data if necessary, and then return false."

  • eof() works per spec for BufferStream and TCPSocket.
  • For Filesystem.File, IOStream, and PipeBuffer, eof() does not block to wait for more data. Instead it returns true without blocking if there is not currently data available to be read.
  • For IOBuffer it seems that eof() just always returns true.

The spec for read(::IO, ::Int) says: "[By default] this function will block repeatedly trying to read all requested bytes, until an error or end-of-file occurs."

  • For BufferStream and TCPSocket, read() behaves as specified.
  • However, for the other types read() seems to just return however many bytes are available at the time and does not block.
  • For IOBuffer, read() always returns an empty array.

Other issues:

  • For BufferStream and IOStream, isreadable() returns true after close() is called.
  • For BufferStream, iswriteable() returns true after close().
  • For BufferStream and IOStream, read() after close() returns empty data, whereas the other types throw an error.
  • For BufferStream and TCPSocket, read(io, String) blocks until the stream is closed (this seems consistent with the blocking behaviour of read(io, nb) , however the other types return immediately with a string containing however many bytes are available at the time.
  • mark/reset don't work for BufferStream mark/reset broken for BufferStream #24465
  • There is no API for sending TCP FIN, e.g. shutdown(fd, SHUT_WR) or uv_shutdown(). If a TCP server waits for a request to be sent before responding, a Julia client would have to call close() to signal end of request, but would then be unable to read the response.
  • Perhaps the missing shutdown is related to the inconsistencies with isreadable() and iswriteable(). It seem like maybe there should be a closeread() and closewrite() that respectively cause isreadable() and iswriteable() to return false.
  • Calling close on BufferStream causes eof() = true and isopen() = false, but iswriteable() and isreadable() are both still true, and in fact reads and writes continue to work with the closed stream. It seems that BufferStream would benefit from a seperate closewrite() for signalling eof() to the reader.
type IOBuffer PipeBuffer BufferStream File (IOStream) Filesystem
init io = IOBuffer() io = PipeBuffer() io = BufferStream() echo Hello > file echo Hello > file
write(io, "Hello") write(io, "Hello") write(io, "Hello") open("file") Filesystem. open("file")
isreadable() true true true true MethodError
isopen() true true true true true
eof() true❗️ false false false false
position 5 0 MethodError 0 0
read(io, 5) ""❗️ "Hello" "Hello" "Hello" "Hello"
eof() true true❗️ blocked true true
"Again" write(io, "Again") write(io, "Again") write(io, "Again") echo Again >> file echo Again >> file
position(io) 10 0 MethodError 5 5
eof(io) true❗️ false false false false
read(io, 5) ""❗️ "Again" "Again" "Again" "Again"
eof(io) true true❗️ blocked true true
"Again" write(io, "Again") write(io, "Again") write(io, "Again") echo Again >> file echo Again >> file
eof(io) true❗️ false false false false
read(io, String) ""❗️ "Again" blocked "Again" "Again"
iswritable(io) true true true false MethodError
isreadable(io) true true true true MethodError
close(io); isopen(io) false false false false false
iswritable(io) false false true❗️ false MethodError
isreadable(io) false false true❗️ true❗️ MethodError
eof(io) true true true true Base.UVError
read(io, 5) Argument Error Argument Error ""❗️ ""❗️ Base.UVError

Note that TCPSocket behaves almost the same as BufferStream but without the isopen() and isreadable() issues:

type BufferStream TCPSocket
init io = BufferStream() srv = accept(); io = connect()
write(io, "Hello") write(srv, "Hello")
isreadable() true true
isopen() true true
eof() false false
position MethodError MethodError
read(io, 5) "Hello" "Hello"
eof(io) blocked blocked
close(io); isopen(io) true ❗️ false
eof(io) true true
isreadable(io) true❗️ false
read(io, 5) "" ""

Metadata

Metadata

Assignees

No one assigned

    Labels

    ioInvolving the I/O subsystem: libuv, read, write, etc.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions