8#include <core/async/Task.hpp>
9#include <core/net/EventLoop.hpp>
10#include <core/net/ISocket.hpp>
23#include "ws_handshake.hpp"
36namespace morph::net::detail {
44 std::size_t counted{0};
48struct LoopConnection {
52 LoopConnection(::core::net::EventLoop& eventLoop, std::unique_ptr<::core::net::ISocket> connected,
53 std::chrono::milliseconds writeTimeout)
54 : loop{eventLoop}, socket{std::move(connected)}, sendTimeout{writeTimeout} {}
56 LoopConnection(
const LoopConnection&) =
delete;
57 LoopConnection& operator=(
const LoopConnection&) =
delete;
58 LoopConnection(LoopConnection&&) =
delete;
59 LoopConnection& operator=(LoopConnection&&) =
delete;
60 ~LoopConnection() =
default;
63 ::core::net::EventLoop& loop;
65 std::unique_ptr<::core::net::ISocket> socket;
67 std::chrono::milliseconds sendTimeout;
69 std::deque<OutboundFrame> outbox;
81 std::shared_ptr<std::atomic<std::size_t>>
const pending{std::make_shared<std::atomic<std::size_t>>(0)};
85 std::function<void()> onDrained;
91 bool closeWhenFlushed{
false};
103inline void releaseBacklog(LoopConnection& conn, std::size_t bytes) {
107 std::size_t
const before = conn.pending->fetch_sub(bytes);
109 before >= ::morph::backend::kEventBacklogLow && before - bytes < ::morph::backend::kEventBacklogLow;
110 if (crossed && !conn.closed && !conn.closeWhenFlushed && conn.onDrained) {
122inline void closeConnection(LoopConnection& conn)
noexcept {
127 std::size_t dropped = 0;
128 for (
auto const& frame : conn.outbox) {
129 dropped += frame.counted;
132 conn.pending->fetch_sub(dropped);
133 conn.socket->close();
139inline constexpr std::size_t kWriteChunk = std::size_t{64} * 1024;
143inline void closeOnDeadline(
void* connPtr) { closeConnection(*
static_cast<LoopConnection*
>(connPtr)); }
154inline ::core::async::Task<void> writerFlow(std::shared_ptr<LoopConnection> conn) {
155 while (!conn->closed && !conn->outbox.empty()) {
156 OutboundFrame
const next = std::move(conn->outbox.front());
157 conn->outbox.pop_front();
158 std::string
const& frame = next.bytes;
159 std::size_t offset = 0;
160 while (offset < frame.size() && !conn->closed) {
161 std::size_t
const length = std::min(kWriteChunk, frame.size() - offset);
162 ::core::net::TimerId timer{};
163 if (conn->sendTimeout.count() > 0) {
165 conn->loop.addTimer(conn->loop.clock().now() + conn->sendTimeout, &closeOnDeadline, conn.get());
168 co_await conn->socket->write(std::as_bytes(std::span<char const>{frame}.subspan(offset, length)));
169 static_cast<void>(conn->loop.cancelTimer(timer));
170 if (!written || *written == 0) {
171 closeConnection(*conn);
176 releaseBacklog(*conn, next.counted);
178 conn->writing =
false;
179 if (conn->closeWhenFlushed) {
180 closeConnection(*conn);
191inline bool enqueueFrame(std::shared_ptr<LoopConnection>
const& conn, std::string frame, std::size_t counted = 0) {
196 if (conn->closed || conn->closeWhenFlushed) {
197 releaseBacklog(*conn, counted);
200 conn->outbox.push_back(OutboundFrame{.bytes = std::move(frame), .counted = counted});
201 if (!conn->writing) {
202 conn->writing =
true;
203 conn->loop.spawn(writerFlow(conn));
210inline void closeAfterFlush(std::shared_ptr<LoopConnection>
const& conn) {
212 conn->closeWhenFlushed =
true;
215 closeConnection(*conn);
229inline ::core::async::Task<std::optional<HandshakeReadResult>> readHeaderBlockAsync(
230 std::shared_ptr<LoopConnection> conn, std::chrono::milliseconds timeout) {
231 ::core::net::TimerId timer{};
232 if (timeout.count() > 0) {
233 timer = conn->loop.addTimer(conn->loop.clock().now() + timeout, &closeOnDeadline, conn.get());
236 std::array<std::byte, 4096> chunk{};
237 std::optional<HandshakeReadResult> result;
239 if (
auto const pos = buf.find(
"\r\n\r\n"); pos != std::string::npos) {
240 result = HandshakeReadResult{.header = buf.substr(0, pos), .leftover = buf.substr(pos + 4)};
243 if (buf.size() > std::size_t{64} * 1024 || conn->closed) {
246 auto const got =
co_await conn->socket->read(chunk);
247 if (!got || *got == 0) {
251 buf.append(
reinterpret_cast<char const*
>(chunk.data()), *got);
253 static_cast<void>(conn->loop.cancelTimer(timer));
What a transport hands RemoteServer to write to one connection, and the backlog bounds both sides app...
Compile-time profiler instrumentation: Tracy zones, plots, messages and thread names,...
#define MORPH_ZONE(name)
Opens a profiler zone named name that ends with the enclosing scope. At most one per scope.