From 314bcb3d30f29ac0c2782586e53bfaccf943066d Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Sun, 14 Jan 2018 01:46:50 -0800 Subject: [PATCH] ircd::net: Update read()/write() strategies. --- include/ircd/net/README.md | 62 +++++++++++++++++++++----------------- include/ircd/net/read.h | 28 +++++++++++++---- include/ircd/net/socket.h | 44 +++++++++++++++++++++++++-- include/ircd/net/write.h | 21 +++++++++++-- ircd/net.cc | 32 +++++++++++++++++++- 5 files changed, 147 insertions(+), 40 deletions(-) diff --git a/include/ircd/net/README.md b/include/ircd/net/README.md index c348cee02..047b4f913 100644 --- a/include/ircd/net/README.md +++ b/include/ircd/net/README.md @@ -12,40 +12,46 @@ multiple operations to close_notify the SSL session with cryptographic soundness and then disconnect the TCP session. A dedicated options structure is provided to tweak details. -#### read() +### Conveyance -To keep things simple, this system has no notion of non-blocking or even -asynchronous reads. In other words, you call read() when you know there -is something to be read(). If there is nothing your ircd::ctx will yield -until the call is interrupted/canceled by some timeout etc (which is -something you might want too in certain contexts). +*Four* strategies are available to work with the socket. We have it all +covered. The `read()` and `write()` suites contain functions postfixed +by their behavior: -There are two possibilities: either the remote is faster than us or they -are slower than us. +#### _all() -* If the remote is faster than us, or faster than we want: we'll know data -is available but ignore it to slow them down (by not calling read()) -- -a matter in which we may not have a choice anyway. +A call to `read_all()` or `write_all()` will yield the calling `ircd::ctx` +until the supplied buffers are filled or transmitted, respectively. -* If the remote is slower than us: you have to use a timer to put a limit -on how much time (and resource) is going to be spent on receiving their -data; eventually the daemon has to move on to serving other clients. +It is strongly advised to set a timeout in the options structure when making +calls of this type. -#### write() +An `ircd::ctx` is required to make this call, it does not work on the main +stack. -This system uses two different techniques for sending data to remotes -intended for two different categories of transmission: +#### _few() -* write_all() is an intuitive synchronous send which yields the ircd::ctx -until all bytes have been written to the remote. This is intended for -serving simple requests or most other ctx-per-client models. A coarse timer -is generally used to prevent the remote from being too slow to receive but -we have some tolerance to wait a little for them. +A call to `*_few()` also yields the `ircd::ctx`, but the yield only lasts until +some data is sent or received. Once some activity takes place, as much as +possible is accomplished; there will not be any more yielding. -* write_any()/write_one() is a non-blocking send intended for small message -mass-push models or some other custom flow control on larger messages. The -goal here is to figure out what to do when the socket buffer has filled -up because the remote has been too slow to receive data. The choice is -usually to either propagate the slowdown to the source or to drop the -remote so the daemon can move on without using up memory. +It is strongly advised to set a timeout in the options structure when making +calls of this type. +An `ircd::ctx` is required to make this call, it does not work on the main +stack. + +#### _any() + +A call to `*_any()` has true non-blocking behavior. It does not require an +`ircd::ctx`. This call tries to conduct as much IO as possible without +blocking. Internally it may be a loop of the `_one()` strategy that works +through as much of the buffers until error. + +#### _one() + +A call to `*_one()` also has true non-blocking behavior and does not require +an `ircd::ctx`. This call makes one syscall which may not fill or transmit +all of the user buffers even if the kernel has more capacity to do so. To +maximize the amount of data read or written to kernelland use the `_any()` +strategy. diff --git a/include/ircd/net/read.h b/include/ircd/net/read.h index b481f260d..a033d6477 100644 --- a/include/ircd/net/read.h +++ b/include/ircd/net/read.h @@ -28,15 +28,19 @@ namespace ircd::net size_t read_one(socket &, const vector_view &); size_t read_one(socket &, const mutable_buffer &); - // Yields until something is read into buffers. + // Non-blocking; read as much as possible into buffers size_t read_any(socket &, const vector_view &); size_t read_any(socket &, const mutable_buffer &); + // Yields until something is read into buffers. + size_t read_few(socket &, const vector_view &); + size_t read_few(socket &, const mutable_buffer &); + // Yields until buffers are entirely full. size_t read_all(socket &, const vector_view &); size_t read_all(socket &, const mutable_buffer &); - // Alias to read_any(); + // Alias to read_few(); size_t read(socket &, const vector_view &); size_t read(socket &, const mutable_buffer &); @@ -47,20 +51,20 @@ namespace ircd::net size_t discard_all(socket &, const size_t &len); } -/// Alias to read_any(); +/// Alias to read_few(); inline size_t ircd::net::read(socket &socket, const mutable_buffer &buffer) { - return read_any(socket, buffer); + return read_few(socket, buffer); } -/// Alias to read_any(); +/// Alias to read_few(); inline size_t ircd::net::read(socket &socket, const vector_view &buffers) { - return read_any(socket, buffers); + return read_few(socket, buffers); } inline size_t @@ -75,6 +79,18 @@ ircd::net::read_all(socket &socket, return read_all(socket, buffers); } +inline size_t +ircd::net::read_few(socket &socket, + const mutable_buffer &buffer) +{ + const mutable_buffer buffers[] + { + buffer + }; + + return read_few(socket, buffers); +} + inline size_t ircd::net::read_any(socket &socket, const mutable_buffer &buffer) diff --git a/include/ircd/net/socket.h b/include/ircd/net/socket.h index 31417f43b..7e9986419 100644 --- a/include/ircd/net/socket.h +++ b/include/ircd/net/socket.h @@ -90,11 +90,13 @@ struct ircd::net::socket // low level write suite template size_t write_one(iov&&); // non-blocking template size_t write_any(iov&&); // non-blocking + template size_t write_few(iov&&); // yielding template size_t write_all(iov&&); // yielding // low level read suite template size_t read_one(iov&&); // non-blocking - template size_t read_any(iov&&); // yielding + template size_t read_any(iov&&); // non-blocking + template size_t read_few(iov&&); // yielding template size_t read_all(iov&&); // yielding // low level wait suite @@ -173,7 +175,7 @@ ircd::net::socket::read_all(iov&& bufs) /// Yields ircd::ctx until remote has sent at least some data. template size_t -ircd::net::socket::read_any(iov&& bufs) +ircd::net::socket::read_few(iov&& bufs) { const size_t ret { @@ -191,6 +193,27 @@ ircd::net::socket::read_any(iov&& bufs) return ret; } +/// Non-blocking; as much as possible without blocking +template +size_t +ircd::net::socket::read_any(iov&& bufs) +{ + assert(!blocking(*this)); + static const auto completion + { + asio::transfer_all() + }; + + const size_t ret + { + asio::read(ssl, std::forward(bufs), completion) + }; + + in.bytes += ret; + ++in.calls; + return ret; +} + /// Non-blocking; One system call only; never throws eof; template size_t @@ -227,7 +250,22 @@ ircd::net::socket::write_all(iov&& bufs) return ret; } -/// Non-blocking; writes as much as possible by with multiple write_one()'s +/// Yields ircd::ctx until one or more bytes are sent. +template +size_t +ircd::net::socket::write_few(iov&& bufs) +{ + const size_t ret + { + ssl.async_write_some(std::forward(bufs), yield_context{to_asio{}}) + }; + + out.bytes += ret; + ++out.calls; + return ret; +} + +/// Non-blocking; writes as much as possible without blocking template size_t ircd::net::socket::write_any(iov&& bufs) diff --git a/include/ircd/net/write.h b/include/ircd/net/write.h index b6b7dfa17..83faf51df 100644 --- a/include/ircd/net/write.h +++ b/include/ircd/net/write.h @@ -34,7 +34,12 @@ namespace ircd::net size_t write_any(socket &, const vector_view &); size_t write_any(socket &, const const_buffer &); - // Blocking; Yields your ircd::ctx until all bytes have been written; + // Yields your ircd::ctx until at least some bytes have been written; + // advise one uses a timeout when calling. + size_t write_few(socket &, const vector_view &); + size_t write_few(socket &, const const_buffer &); + + // Yields your ircd::ctx until all bytes have been written; // advise one uses a timeout in conjunction to prevent DoS. size_t write_all(socket &, const vector_view &); size_t write_all(socket &, const const_buffer &); @@ -75,6 +80,18 @@ ircd::net::write_all(socket &socket, return write_all(socket, buffers); } +inline size_t +ircd::net::write_few(socket &socket, + const const_buffer &buffer) +{ + const const_buffer buffers[] + { + buffer + }; + + return write_few(socket, buffers); +} + inline size_t ircd::net::write_any(socket &socket, const const_buffer &buffer) @@ -84,7 +101,7 @@ ircd::net::write_any(socket &socket, buffer }; - return write_all(socket, buffers); + return write_any(socket, buffers); } inline size_t diff --git a/ircd/net.cc b/ircd/net.cc index 32cc9e25d..bea0b685c 100644 --- a/ircd/net.cc +++ b/ircd/net.cc @@ -156,6 +156,24 @@ ircd::net::write_all(socket &socket, return socket.write_all(buffers); } +/// Yields ircd::ctx until at least some buffers are sent. +/// +/// This is blocking behavior; use this if the following are true: +/// +/// * You put a timer on the socket so if the remote slows us down the data +/// will not occupy the daemon's memory for a long time. +/// +/// * You are willing to dedicate the ircd::ctx to sending the data to +/// the remote. The ircd::ctx will be yielding until the kernel has at least +/// some space to consume at least something from the supplied buffers. +/// +size_t +ircd::net::write_few(socket &socket, + const vector_view &buffers) +{ + return socket.write_few(buffers); +} + /// Writes as much as possible until one of the following is true: /// /// * The kernel buffer for the socket is full. @@ -285,6 +303,18 @@ ircd::net::read_all(socket &socket, /// blocking if this call is made in the blind. /// size_t +ircd::net::read_few(socket &socket, + const vector_view &buffers) +{ + return socket.read_few(buffers); +} + +/// Reads as much as possible. Non-blocking behavior. +/// +/// This is intended for lowest-level/custom control and not preferred by +/// default for most users on an ircd::ctx. +/// +size_t ircd::net::read_any(socket &socket, const vector_view &buffers) { @@ -294,7 +324,7 @@ ircd::net::read_any(socket &socket, /// Reads one message or less in a single syscall. Non-blocking behavior. /// /// This is intended for lowest-level/custom control and not preferred by -/// default. +/// default for most users on an ircd::ctx. /// size_t ircd::net::read_one(socket &socket,