0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-11-29 02:02:38 +01:00

ircd::net: Update read()/write() strategies.

This commit is contained in:
Jason Volk 2018-01-14 01:46:50 -08:00
parent 7b62568ff0
commit 314bcb3d30
5 changed files with 147 additions and 40 deletions

View file

@ -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.

View file

@ -28,15 +28,19 @@ namespace ircd::net
size_t read_one(socket &, const vector_view<const mutable_buffer> &);
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<const mutable_buffer> &);
size_t read_any(socket &, const mutable_buffer &);
// Yields until something is read into buffers.
size_t read_few(socket &, const vector_view<const mutable_buffer> &);
size_t read_few(socket &, const mutable_buffer &);
// Yields until buffers are entirely full.
size_t read_all(socket &, const vector_view<const mutable_buffer> &);
size_t read_all(socket &, const mutable_buffer &);
// Alias to read_any();
// Alias to read_few();
size_t read(socket &, const vector_view<const mutable_buffer> &);
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<const mutable_buffer> &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)

View file

@ -90,11 +90,13 @@ struct ircd::net::socket
// low level write suite
template<class iov> size_t write_one(iov&&); // non-blocking
template<class iov> size_t write_any(iov&&); // non-blocking
template<class iov> size_t write_few(iov&&); // yielding
template<class iov> size_t write_all(iov&&); // yielding
// low level read suite
template<class iov> size_t read_one(iov&&); // non-blocking
template<class iov> size_t read_any(iov&&); // yielding
template<class iov> size_t read_any(iov&&); // non-blocking
template<class iov> size_t read_few(iov&&); // yielding
template<class iov> 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<class iov>
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<class iov>
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<iov>(bufs), completion)
};
in.bytes += ret;
++in.calls;
return ret;
}
/// Non-blocking; One system call only; never throws eof;
template<class iov>
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<class iov>
size_t
ircd::net::socket::write_few(iov&& bufs)
{
const size_t ret
{
ssl.async_write_some(std::forward<iov>(bufs), yield_context{to_asio{}})
};
out.bytes += ret;
++out.calls;
return ret;
}
/// Non-blocking; writes as much as possible without blocking
template<class iov>
size_t
ircd::net::socket::write_any(iov&& bufs)

View file

@ -34,7 +34,12 @@ namespace ircd::net
size_t write_any(socket &, const vector_view<const const_buffer> &);
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<const const_buffer> &);
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<const const_buffer> &);
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

View file

@ -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<const const_buffer> &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<const mutable_buffer> &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<const mutable_buffer> &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,