Towards a unified IO buffering approach in Ruby #108
Replies: 2 comments 2 replies
-
I think you are looking in the right direction. My long term goal is to use |
Beta Was this translation helpful? Give feedback.
-
FWIW In Polyphony, all backend APIs support working with fds instead of |
Beta Was this translation helpful? Give feedback.
-
attn @ioquatix
Ruby includes multiple APIs and mechanism for buffering data read or written to IOs (or fds):
IO
class includes a buffering mechanism for both reading and writing.StringIO
API which mimics that ofIO
on top of a string, can effectively be used as a buffer for reading.IO::Buffer
API implements a subset of theIO
API of top of its internal storage.Net::BufferedIO
API implements its own API, used by gems such asnet-http
OpenSSL::Buffering::Buffer
API mimics theIO
API, in theopenssl
gem.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
IO
class.The Proposal
What I propose is to basically take the
IO
internal buffering mechanism, and turn it inside-out: instead of having the read and write buffers as an integral part of theIO
implementation, we separate them out into separate objects. Each buffer object holds a reference to theIO
instance, and implements the entireIO
read/write API (e.g.getc
,gets
,each_line
etc). 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
IO
instance 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::Buffer
andStringIO
classes as the base for such functionality. TheIO::Buffer
class already provides the functionality for storing bytes, and theStringIO
class 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
IO
instance.In fact, since the different
Socket
classes implement the same API asIO
, they can just delegate the entire read/write API to the default buffers, just like theIO
class, except that the buffer will be filled by callingrecv
instead ofread
.Advantages
There are multiple advantages to doing this:
IO
class will greatly simplify theio.c
implementation.IO
API over different transports (sockets, SSL sockets, strings etc).Beta Was this translation helpful? Give feedback.
All reactions