I was watching this talk about implementing Async IO in Rust and Carl mentions two potential models. Readiness and Completion.
Readiness Model:
- you tell the kernel you want to read from a socket
- do other things for awhile…
- the kernel tells you when the socket is ready
- you read (fill a buffer)
- do whatever you need
- free the buffer (happens automatically with Rust)
Completion Model:
- you allocate a buffer for the kernel to fill
- do other things for awhile…
- the kernel tells you when the buffer has been filled
- do whatever you need to with the data
- free the buffer
In Carl's example of using the readiness model you could iterate over ready sockets filling and freeing a global buffer which makes it seem like it would use much less memory.
Now my assumptions:
Under the hood (in kernel space) when a socket is said to be "ready", the data already exists. It has come into the socket over the network (or from wherever) and the OS is holding onto the data.
It's not as if that memory allocation magically doesn't happen in the readiness model. It's just that the OS is abstracting it from you. In the Completion model, the OS is asking you to allocate memory before data actually flows in and it's obvious what's happening.
Here's my amended version of the Readiness Model:
- you tell the kernel you want to read from a socket
- do other things for awhile…
- AMENDMENT: data comes in to the OS (some place in kernel memory)
- the kernel tells you the socket is ready
- you read (fill another buffer separate from the abover kernel buffer (or you get a pointer to it?))
- do whatever you need
- free the buffer (happens automatically with Rust)
/My assumptions
I happen to like keeping the user-space program small but I just wanted some clarification on what is, in reality, happening here. I don't see that one model would inherently use less memory or support a higher level of concurrent IO. I'd love to hear thoughts and deeper explanation of this.