Towards a unified IO buffering approach in Ruby #108
noteflakes
started this conversation in
General
Replies: 2 comments 2 replies
|
I think you are looking in the right direction. My long term goal is to use |
1 reply
FWIW In Polyphony, all backend APIs support working with fds instead of |
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
attn @ioquatix
Ruby includes multiple APIs and mechanism for buffering data read or written to IOs (or fds):
IOclass includes a buffering mechanism for both reading and writing.StringIOAPI which mimics that ofIOon top of a string, can effectively be used as a buffer for reading.IO::BufferAPI implements a subset of theIOAPI of top of its internal storage.Net::BufferedIOAPI implements its own API, used by gems such asnet-httpOpenSSL::Buffering::BufferAPI mimics theIOAPI, in theopensslgem.It is telling that there are so many similar buffering mechanisms and APIs used across core Ruby and its bundled gems. There's a clear overlap of functionality across these different APIs and mechanisms. The big question is: can we come up with a single API that will be able to handle all those different uses, including the internal buffering layer used by the
IOclass.The Proposal
What I propose is to basically take the
IOinternal buffering mechanism, and turn it inside-out: instead of having the read and write buffers as an integral part of theIOimplementation, we separate them out into separate objects. Each buffer object holds a reference to theIOinstance, and implements the entireIOread/write API (e.g.getc,gets,each_lineetc). The buffer has a position and invoking any of its read/write methods moves the position accordingly, just like a regular file. For read methods, whenever the buffer is exhausted, it is refilled from the associatedIO. For write methods, whenever the buffer position crosses a certain threshold (which might be configurable), the buffer content is flushed to the associated IO.Eeach
IOinstance has default read and write buffers, to which it delegates all of its read/write methods calls (except forsysread/pread). Here's a little sketch to show how the implementation can look:Coalescing the Different Buffering APIs
Most if not all of the buffering APIs enumerated above can be coalesced into this single class. In fact, we can use the existing
IO::BufferandStringIOclasses as the base for such functionality. TheIO::Bufferclass already provides the functionality for storing bytes, and theStringIOclass already implements the different read/write methods on top of what is essentially a buffer of bytes.Finally, any other APIs that need some specialized way of reading or writing data, could be reimplemented as subclasses of this unified buffer class.
Transport-agnostic IO
Such a unified buffering approach will also enable the creation of transport-agnostic protocol implementations. Here's an example: the h1p gem (of which I'm the author) implements a transport-agnostic way of parsing and sending an HTTP/1 request/response. It does so by detecting the read/write methods used by the associated
IOinstance.In fact, since the different
Socketclasses implement the same API asIO, they can just delegate the entire read/write API to the default buffers, just like theIOclass, except that the buffer will be filled by callingrecvinstead ofread.Advantages
There are multiple advantages to doing this:
IOclass will greatly simplify theio.cimplementation.IOAPI over different transports (sockets, SSL sockets, strings etc).All reactions