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:
parent
7b62568ff0
commit
314bcb3d30
5 changed files with 147 additions and 40 deletions
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
32
ircd/net.cc
32
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<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,
|
||||
|
|
Loading…
Reference in a new issue