-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
Description
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 forBufferStreamandTCPSocket. - For
Filesystem.File,IOStream, andPipeBuffer,eof()does not block to wait for more data. Instead it returnstruewithout blocking if there is not currently data available to be read. - For
IOBufferit seems thateof()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
BufferStreamandTCPSocket,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
BufferStreamandIOStream,isreadable()returns true afterclose()is called. - For
BufferStream,iswriteable()returns true afterclose(). - For
BufferStreamandIOStream,read()afterclose()returns empty data, whereas the other types throw an error. - For
BufferStreamandTCPSocket,read(io, String)blocks until the stream is closed (this seems consistent with the blocking behaviour ofread(io, nb), however the other types return immediately with a string containing however many bytes are available at the time. -
mark/resetdon't work forBufferStreammark/reset broken for BufferStream #24465 - There is no API for sending TCP FIN, e.g.
shutdown(fd, SHUT_WR)oruv_shutdown(). If a TCP server waits for a request to be sent before responding, a Julia client would have to callclose()to signal end of request, but would then be unable to read the response. - Perhaps the missing
shutdownis related to the inconsistencies withisreadable()andiswriteable(). It seem like maybe there should be acloseread()andclosewrite()that respectively causeisreadable()andiswriteable()to return false. - Calling
closeonBufferStreamcauseseof()= true andisopen()= false, butiswriteable()andisreadable()are both still true, and in fact reads and writes continue to work with the closed stream. It seems thatBufferStreamwould benefit from a seperateclosewrite()for signallingeof()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) | "" | "" |