diff --git a/.clang-format b/.clang-format index 2d41352..a235b07 100644 --- a/.clang-format +++ b/.clang-format @@ -11,6 +11,7 @@ AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: false AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false @@ -20,24 +21,8 @@ AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: Yes BinPackArguments: true BinPackParameters: true -BraceWrapping: - AfterClass: true - AfterControlStatement: true - AfterEnum: true - AfterFunction: false - AfterNamespace: false - AfterObjCDeclaration: true - AfterStruct: true - AfterUnion: true - AfterCaseLabel: true - BeforeCatch: true - BeforeElse: true - IndentBraces: false - SplitEmptyFunction: true - SplitEmptyRecord: true - SplitEmptyNamespace: true BreakBeforeBinaryOperators: None -BreakBeforeBraces: Custom +BreakBeforeBraces: Attach BreakBeforeInheritanceComma: true BreakInheritanceList: BeforeColon BreakBeforeTernaryOperators: true diff --git a/AGENTS.md b/AGENTS.md index d4dcf43..4cbf1e9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -76,8 +76,10 @@ branch or worktree when the existing checkout contains uncommitted changes. Call these dependencies, not backends. Preserve machine-readable identifiers unless an explicit API or data-format migration is part of the task. - Follow `.clang-format` and `scripts/format_spacing.sh`, using clang-format 18. - Keep function opening braces on the declaration line, expand short function - bodies, use braces for control statements and keep names in `snake_case`. + Attach opening braces to declarations and control statements, including classes, + structs, enums, unions and functions. Keep `else` and `catch` on the closing-brace + line, expand short function and enum bodies, use braces for control statements + and keep names in `snake_case`. - Group includes as system, third-party, then project headers. Keep required platform-header dependencies in a valid order, including Winsock 2 before `windows.h` when both are needed. Separate include blocks, namespaces and diff --git a/benchmarks/asio_connections.cpp b/benchmarks/asio_connections.cpp index 9818105..bf67a29 100644 --- a/benchmarks/asio_connections.cpp +++ b/benchmarks/asio_connections.cpp @@ -65,8 +65,7 @@ using connection_load::frame_kind; using connection_load::header_bytes; using connection_load::identity; -struct options -{ +struct options { std::string role, scenario = "idle", host; std::uint64_t run{}, client = 1; std::size_t port{}, connections = 1, expected{}, maximum = 5000; @@ -80,8 +79,7 @@ struct options std::uint64_t integer(std::string_view text) { std::uint64_t value{}; const auto parsed = std::from_chars(text.data(), text.data() + text.size(), value); - if (text.empty() || parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size()) - { + if (text.empty() || parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size()) { throw std::invalid_argument("expected an unsigned decimal integer"); } return value; @@ -90,41 +88,34 @@ std::uint64_t integer(std::string_view text) { double seconds(const std::string& text) { double value{}; const auto parsed = std::from_chars(text.data(), text.data() + text.size(), value); - if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size() || !std::isfinite(value)) - { + if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size() || !std::isfinite(value)) { throw std::invalid_argument("expected finite decimal seconds"); } return value; } void validate(const options& o) { - if (o.socket_observer && !o.operation_trace) - { + if (o.socket_observer && !o.operation_trace) { throw std::invalid_argument("socket-observer requires operation-trace on"); } #if defined(_WIN32) - if (o.socket_observer) - { + if (o.socket_observer) { throw std::invalid_argument("socket-observer is unavailable on Windows"); } #endif - if (o.tcp_kernel_samples && !o.operation_trace) - { + if (o.tcp_kernel_samples && !o.operation_trace) { throw std::invalid_argument("tcp-kernel-samples requires operation-trace on"); } - if (o.socket_operation_trace && !o.operation_trace) - { + if (o.socket_operation_trace && !o.operation_trace) { throw std::invalid_argument("socket-operation-trace requires operation-trace on"); } if ((o.role != "server" && o.role != "client") || (o.scenario != "idle" && o.scenario != "traffic") || !o.run || - !o.client || o.host.empty() || o.port > 65535 || (o.role == "client" && !o.port)) - { + !o.client || o.host.empty() || o.port > 65535 || (o.role == "client" && !o.port)) { throw std::invalid_argument("specify role, listen/target, port and nonzero run/client IDs"); } error_code error; const auto address = asio::ip::make_address(o.host, error); - if (error || address.is_multicast() || (o.role == "client" && address.is_unspecified())) - { + if (error || address.is_multicast() || (o.role == "client" && address.is_unspecified())) { throw std::invalid_argument("address must be numeric unicast IPv4 or IPv6"); } if (!o.maximum || o.maximum > 200000 || !o.connections || o.connections > o.maximum || !o.expected || @@ -133,21 +124,18 @@ void validate(const options& o) { o.inflight_bytes < header_bytes + o.payload || o.inflight_bytes > 1024ULL * 1024 * 1024 || o.budget > 16ULL * 1024 * 1024 * 1024 || !o.connect_ms || o.connect_ms > 3600000 || !o.request_ms || o.request_ms > 3600000 || !std::isfinite(o.hold) || o.hold <= 0 || !std::isfinite(o.deadline) || - o.deadline <= o.hold || o.deadline > 86400 || !std::isfinite(o.cleanup) || o.cleanup < 1 || o.cleanup > 60) - { + o.deadline <= o.hold || o.deadline > 86400 || !std::isfinite(o.cleanup) || o.cleanup < 1 || o.cleanup > 60) { throw std::invalid_argument("argument out of range (server requires a nonzero expected-connections)"); } const auto count = o.role == "client" ? o.connections : o.maximum; const auto slots = std::uint64_t(count) * o.window; - if (o.scenario == "traffic" && (slots > o.inflight || slots * (header_bytes + o.payload) > o.inflight_bytes)) - { + if (o.scenario == "traffic" && (slots > o.inflight || slots * (header_bytes + o.payload) > o.inflight_bytes)) { throw std::invalid_argument("declared connection windows exceed data inflight count/byte limits"); } // Same declared receive/send/timestamp reservation as the arknet benchmark. const auto reserved = std::uint64_t(count) * 4096 + o.inflight_bytes + (o.role == "client" && o.scenario == "traffic" ? slots * sizeof(clock_type::time_point) : 0); - if (reserved > o.budget) - { + if (reserved > o.budget) { throw std::invalid_argument("declared buffer reservation exceeds buffer-budget-bytes"); } } @@ -177,136 +165,77 @@ options arguments(int argc, char** argv) { std::set seen; bool port_set = false; std::string address_option; - for (int index = 1; index < argc; ++index) - { + for (int index = 1; index < argc; ++index) { const std::string key = argv[index]; - if (!seen.insert(key).second || index + 1 == argc) - { + if (!seen.insert(key).second || index + 1 == argc) { throw std::invalid_argument("duplicate option or missing value: " + key); } const std::string value = argv[++index]; - if (key == "--role") - { + if (key == "--role") { o.role = value; - } - else if (key == "--scenario") - { + } else if (key == "--scenario") { o.scenario = value; - } - else if (key == "--listen" || key == "--target") - { + } else if (key == "--listen" || key == "--target") { o.host = value; address_option = key; - } - else if (key == "--port") - { + } else if (key == "--port") { o.port = integer(value); port_set = true; - } - else if (key == "--run-id") - { + } else if (key == "--run-id") { o.run = integer(value); - } - else if (key == "--client-id") - { + } else if (key == "--client-id") { o.client = integer(value); - } - else if (key == "--connections") - { + } else if (key == "--connections") { o.connections = integer(value); - } - else if (key == "--expected-connections") - { + } else if (key == "--expected-connections") { o.expected = integer(value); - } - else if (key == "--max-connections") - { + } else if (key == "--max-connections") { o.maximum = integer(value); - } - else if (key == "--connect-rate") - { + } else if (key == "--connect-rate") { o.rate = integer(value); - } - else if (key == "--connect-parallel") - { + } else if (key == "--connect-parallel") { o.parallel = integer(value); - } - else if (key == "--payload") - { + } else if (key == "--payload") { o.payload = integer(value); - } - else if (key == "--window") - { + } else if (key == "--window") { o.window = integer(value); - } - else if (key == "--connect-timeout-ms") - { + } else if (key == "--connect-timeout-ms") { o.connect_ms = integer(value); - } - else if (key == "--request-timeout-ms") - { + } else if (key == "--request-timeout-ms") { o.request_ms = integer(value); - } - else if (key == "--max-inflight") - { + } else if (key == "--max-inflight") { o.inflight = integer(value); - } - else if (key == "--max-inflight-bytes") - { + } else if (key == "--max-inflight-bytes") { o.inflight_bytes = integer(value); - } - else if (key == "--buffer-budget-bytes") - { + } else if (key == "--buffer-budget-bytes") { o.budget = integer(value); - } - else if (key == "--hold-seconds") - { + } else if (key == "--hold-seconds") { o.hold = seconds(value); - } - else if (key == "--deadline-seconds") - { + } else if (key == "--deadline-seconds") { o.deadline = seconds(value); - } - else if (key == "--cleanup-seconds") - { + } else if (key == "--cleanup-seconds") { o.cleanup = seconds(value); - } - else if (key == "--operation-trace" && (value == "on" || value == "off")) - { + } else if (key == "--operation-trace" && (value == "on" || value == "off")) { o.operation_trace = value == "on"; - } - else if (key == "--socket-operation-trace" && (value == "on" || value == "off")) - { + } else if (key == "--socket-operation-trace" && (value == "on" || value == "off")) { o.socket_operation_trace = value == "on"; - } - else if (key == "--tcp-kernel-samples" && (value == "on" || value == "off")) - { + } else if (key == "--tcp-kernel-samples" && (value == "on" || value == "off")) { o.tcp_kernel_samples = value == "on"; - } - else if (key == "--socket-observer" && (value == "on" || value == "off")) - { + } else if (key == "--socket-observer" && (value == "on" || value == "off")) { o.socket_observer = value == "on"; - } - else if (key == "--io-threads" && integer(value) == 1) - { - } - else if (key == "--max-buffer-bytes" && integer(value) == 4096) - { - } - else - { + } else if (key == "--io-threads" && integer(value) == 1) { + } else if (key == "--max-buffer-bytes" && integer(value) == 4096) { + } else { throw std::invalid_argument("unsupported option or value: " + key); } } if (!port_set || address_option != (o.role == "server" ? "--listen" : "--target") || (seen.contains("--listen") && seen.contains("--target")) || (o.role == "client" && seen.contains("--expected-connections")) || - (o.role == "server" && (seen.contains("--connections") || seen.contains("--client-id")))) - { + (o.role == "server" && (seen.contains("--connections") || seen.contains("--client-id")))) { throw std::invalid_argument("missing port or role-inappropriate option"); } - if (o.role == "client") - { + if (o.role == "client") { o.expected = o.connections; } validate(o); @@ -315,34 +244,28 @@ options arguments(int argc, char** argv) { // Independent dgram decoder: inspect a bounded prefix first, then require exactly // the declared payload before accepting a frame. It never invokes arknet's matcher or receive helpers. -struct dgram_match -{ +struct dgram_match { using result_type = std::pair, bool>; template std::pair operator()(Iterator begin, Iterator end) const { auto cursor = begin; - if (cursor == end) - { + if (cursor == end) { return {begin, false}; } const auto marker = static_cast(*cursor++); const unsigned width = marker == 255 ? 8 : marker == 254 ? 2 : 0; std::uint64_t size = width ? 0 : marker; - for (unsigned byte = 0; byte < width; ++byte) - { - if (cursor == end) - { + for (unsigned byte = 0; byte < width; ++byte) { + if (cursor == end) { return {begin, false}; } size |= std::uint64_t(static_cast(*cursor++)) << (byte * 8); } - if ((width == 2 && size < 254) || (width == 8 && size < 65536) || size > 4096 - 1 - width) - { + if ((width == 2 && size < 254) || (width == 8 && size < 65536) || size > 4096 - 1 - width) { return {begin, true}; } - if (static_cast(std::distance(cursor, end)) < size) - { + if (static_cast(std::distance(cursor, end)) < size) { return {begin, false}; } std::advance(cursor, static_cast::difference_type>(size)); @@ -351,8 +274,7 @@ struct dgram_match }; // Diagnostic-only interpretation of the existing matcher; never consumes bytes. -struct frame_boundary -{ +struct frame_boundary { const char* status = "empty"; std::array prefix{}; std::size_t prefix_bytes{}, buffered_bytes{}; @@ -365,10 +287,8 @@ struct frame_boundary void write_json(std::ostream& out) const { out << "{\"status\":\"" << status << "\",\"prefix\":["; - for (std::size_t index = 0; index < prefix_bytes; ++index) - { - if (index) - { + for (std::size_t index = 0; index < prefix_bytes; ++index) { + if (index) { out << ','; } out << unsigned(prefix[index]); @@ -376,12 +296,9 @@ struct frame_boundary out << "],\"buffered_bytes\":" << buffered_bytes; const auto field = [&](const char* name, const auto& value) { out << ",\"" << name << "\":"; - if (value) - { + if (value) { out << *value; - } - else - { + } else { out << "null"; } }; @@ -395,31 +312,26 @@ struct frame_boundary frame_boundary inspect_frame_boundary(std::string_view bytes) { frame_boundary result; result.buffered_bytes = bytes.size(); - if (bytes.empty()) - { + if (bytes.empty()) { return result; } const auto marker = static_cast(bytes[0]); const std::size_t width = marker == 255 ? 8 : marker == 254 ? 2 : 0; result.prefix_bytes = (std::min)(bytes.size(), width + 1); - for (std::size_t index = 0; index < result.prefix_bytes; ++index) - { + for (std::size_t index = 0; index < result.prefix_bytes; ++index) { result.prefix[index] = static_cast(bytes[index]); } - if (result.prefix_bytes < width + 1) - { + if (result.prefix_bytes < width + 1) { result.status = "incomplete_prefix"; return result; } std::uint64_t size = width ? 0 : marker; - for (std::size_t index = 0; index < width; ++index) - { + for (std::size_t index = 0; index < width; ++index) { size |= std::uint64_t(result.prefix[index + 1]) << (index * 8); } result.declared_body_bytes = size; const auto match = dgram_match{}(bytes.begin(), bytes.end()); - if (match.second && match.first == bytes.begin()) - { + if (match.second && match.first == bytes.begin()) { result.status = "invalid_prefix"; return result; } @@ -432,15 +344,13 @@ frame_boundary inspect_frame_boundary(std::string_view bytes) { return result; } -struct wire_frame -{ +struct wire_frame { explicit wire_frame(std::string value) : body(std::move(value)) { const auto size = body.size(); prefix_size = size < 254 ? 1 : size <= 65535 ? 3 : 9; prefix[0] = static_cast(prefix_size == 1 ? size : prefix_size == 3 ? 254 : 255); - for (std::size_t byte = 1; byte < prefix_size; ++byte) - { + for (std::size_t byte = 1; byte < prefix_size; ++byte) { prefix[byte] = static_cast(std::uint64_t(size) >> ((byte - 1) * 8)); } } @@ -455,43 +365,36 @@ struct wire_frame }; // Shared by runtime and self-test; rejects a wrong owner, phase or sequence. -struct protocol_state -{ +struct protocol_state { identity owner{}; bool hello{}, started{}, finish_sent{}, finished{}; std::uint64_t next_send = 1, next_receive = 1; bool accept(const connection_load::frame_header& h, const options& o, bool server) { if (server && h.kind == frame_kind::hello && !hello && h.owner.run == o.run && h.owner.client && - h.owner.connection && h.sequence == (o.scenario == "traffic" ? 1 : 0)) - { + h.owner.connection && h.sequence == (o.scenario == "traffic" ? 1 : 0)) { owner = h.owner; hello = true; return true; } - if (h.owner != owner) - { + if (h.owner != owner) { return false; } - if (!server && h.kind == frame_kind::hello_ack && !hello && !h.sequence) - { + if (!server && h.kind == frame_kind::hello_ack && !hello && !h.sequence) { hello = true; return true; } - if (h.kind == (server ? frame_kind::start : frame_kind::start_ack) && hello && !started && !h.sequence) - { + if (h.kind == (server ? frame_kind::start : frame_kind::start_ack) && hello && !started && !h.sequence) { started = true; return true; } if (h.kind == frame_kind::data && started && !finished && o.scenario == "traffic" && - h.body_bytes == o.payload && h.sequence == next_receive && (server || h.sequence < next_send)) - { + h.body_bytes == o.payload && h.sequence == next_receive && (server || h.sequence < next_send)) { ++next_receive; return true; } if (h.kind == (server ? frame_kind::finish : frame_kind::finish_ack) && started && !finished && - (server || finish_sent) && h.sequence == (server ? next_receive : next_send) - 1) - { + (server || finish_sent) && h.sequence == (server ? next_receive : next_send) - 1) { finished = true; return true; } @@ -501,8 +404,7 @@ struct protocol_state struct application; -struct session : std::enable_shared_from_this -{ +struct session : std::enable_shared_from_this { session(application& owner, asio::io_context& context); void read(); void send(std::string body); @@ -533,8 +435,7 @@ struct session : std::enable_shared_from_this clock_type::time_point last_read_at{}, last_write_at{}; }; -struct application -{ +struct application { explicit application(options value) : o(std::move(value)) , operations(o.operation_trace, o.role == "client" ? o.connections : o.expected, o.socket_operation_trace, @@ -603,16 +504,14 @@ session::session(application& owner, asio::io_context& context) } void application::fail(const std::string& scope, const std::string& reason) { - if (!failed.exchange(true)) - { + if (!failed.exchange(true)) { failure = scope + ": " + reason; } } void application::drained() { if (cleanup_started && !callbacks && !pending_writes && !pending_bytes && !pending_data_writes && !active && - !connecting) - { + !connecting) { drained_at = clock_type::now(); cleanup.cancel(); work.reset(); @@ -620,8 +519,7 @@ void application::drained() { } void session::close() { - if (closed) - { + if (closed) { return; } closed = true; @@ -629,18 +527,15 @@ void session::close() { error_code ignored; socket.cancel(ignored); socket.close(ignored); - if (active) - { + if (active) { active = false; --app.active; ++app.disconnected; } - for (const auto& value : queued) - { + for (const auto& value : queued) { --app.pending_writes; app.pending_bytes -= value->body.size(); - if (value->is_data()) - { + if (value->is_data()) { --app.pending_data_writes; } ++app.cancelled_writes; @@ -650,27 +545,21 @@ void session::close() { void application::stop() { // Only the owner thread calls stop, after admission has quiesced. - if (stopping.exchange(true)) - { + if (stopping.exchange(true)) { return; } stopped_at = clock_type::now(); asio::post(io, [this] { cleanup_started = true; - if (failed) - { - if (operations.enabled()) - { + if (failed) { + if (operations.enabled()) { trace_failure(); } std::size_t incomplete{}, emitted{}; - for (const auto& value : sessions) - { - if (!value->state.finished || value->writing || !value->queued.empty()) - { + for (const auto& value : sessions) { + if (!value->state.finished || value->writing || !value->queued.empty()) { ++incomplete; - if (emitted < 16) - { + if (emitted < 16) { value->diagnose(); ++emitted; } @@ -687,15 +576,13 @@ void application::stop() { cleanup.expires_after( std::chrono::duration_cast(std::chrono::duration(o.cleanup))); cleanup.async_wait([this](error_code error) { - if (!error) - { + if (!error) { cleanup_timeout = true; fail("cleanup", "socket callbacks did not drain"); io.stop(); } }); - for (const auto& value : sessions) - { + for (const auto& value : sessions) { asio::dispatch(value->strand, [value] { value->close(); value->app.drained(); @@ -706,13 +593,11 @@ void application::stop() { } void session::send(std::string body) { - if (closed || app.stopping || app.failed) - { + if (closed || app.stopping || app.failed) { return; } const bool data = connection_load::read_integer(body, 8) == static_cast(frame_kind::data); - if ((data && app.pending_data_writes >= app.o.inflight) || body.size() > app.o.inflight_bytes - app.pending_bytes) - { + if ((data && app.pending_data_writes >= app.o.inflight) || body.size() > app.o.inflight_bytes - app.pending_bytes) { ++app.rejected; app.fail("write admission", "data count or send-byte limit exceeded"); return; @@ -720,8 +605,7 @@ void session::send(std::string body) { const auto value = std::make_shared(std::move(body)); queued.push_back(value); ++app.pending_writes; - if (data) - { + if (data) { ++app.pending_data_writes; } app.pending_bytes += value->body.size(); @@ -730,8 +614,7 @@ void session::send(std::string body) { } void session::write() { - if (writing || queued.empty() || closed) - { + if (writing || queued.empty() || closed) { return; } writing = queued.front(); @@ -740,76 +623,60 @@ void session::write() { const std::array buffers{ asio::buffer(value->prefix.data(), value->prefix_size), asio::buffer(value->body)}; ++app.callbacks; - if (operations) - { + if (operations) { trace(connection_load::operation_phase::write_start, value->prefix_size + value->body.size(), connection_load::read_integer(value->body, 40)); } auto completion = asio::bind_executor(strand, [self = shared_from_this(), value](error_code error, std::size_t size) { auto& a = self->app; - if (self->operations) - { + if (self->operations) { self->trace(connection_load::operation_phase::write_complete, size, connection_load::read_integer(value->body, 40), error.value()); } --a.callbacks; --a.pending_writes; a.pending_bytes -= value->body.size(); - if (value->is_data()) - { + if (value->is_data()) { --a.pending_data_writes; } self->writing.reset(); - if (error || size != value->body.size() + value->prefix_size) - { - if (a.stopping && error == asio::error::operation_aborted) - { + if (error || size != value->body.size() + value->prefix_size) { + if (a.stopping && error == asio::error::operation_aborted) { ++a.cancelled_writes; - } - else - { + } else { ++a.write_errors; a.fail("write callback", error ? error.message() : "short async_write"); } - } - else - { + } else { const auto kind = static_cast(connection_load::read_integer(value->body, 8)); ++self->write_completions; self->last_write_at = clock_type::now(); self->last_write_kind = static_cast(kind); self->last_write_sequence = connection_load::read_integer(value->body, 40); - if (kind == frame_kind::data) - { + if (kind == frame_kind::data) { ++a.data_sent; } - if (a.o.role == "server" && kind == frame_kind::finish_ack) - { + if (a.o.role == "server" && kind == frame_kind::finish_ack) { ++a.finished; } self->write(); - if (self->peer_eof && !self->writing && self->queued.empty()) - { + if (self->peer_eof && !self->writing && self->queued.empty()) { self->close(); } } a.drained(); }); - if (socket_trace) - { + if (socket_trace) { socket_trace->write_sequence(connection_load::read_integer(value->body, 40)); asio::async_write(*socket_trace, buffers, std::move(completion)); - } - else - { + } else { asio::async_write(socket, buffers, std::move(completion)); } } void session::read() { - if (closed || app.stopping || app.failed) - { + if (closed || app.stopping || app.failed) { return; } ++app.callbacks; @@ -822,31 +689,21 @@ void session::read() { self->read_pending = false; ++self->read_completions; self->last_read_at = clock_type::now(); - if (error) - { - if (!a.stopping && error == asio::error::eof && self->state.finished) - { + if (error) { + if (!a.stopping && error == asio::error::eof && self->state.finished) { self->peer_eof = true; - if (!self->writing && self->queued.empty()) - { + if (!self->writing && self->queued.empty()) { self->close(); } - } - else if (!a.stopping) - { + } else if (!a.stopping) { ++a.state_errors; a.fail("read callback", error.message()); } - } - else if (!a.stopping) - { - if (!size) - { + } else if (!a.stopping) { + if (!size) { ++a.state_errors; a.fail("read frame", "invalid or oversized dgram prefix"); - } - else - { + } else { // basic_streambuf supplies one contiguous input sequence. // Borrow it only during receive; queued writes own their copies. const auto buffers = self->input.data(); @@ -861,13 +718,10 @@ void session::read() { } a.drained(); }); - if (socket_trace) - { + if (socket_trace) { socket_trace->read_sequence(state.next_receive); asio::async_read_until(*socket_trace, input, dgram_match{}, std::move(completion)); - } - else - { + } else { asio::async_read_until(socket, input, dgram_match{}, std::move(completion)); } } @@ -875,16 +729,14 @@ void session::read() { void session::enable_trace() { connection_load::operation_ring* socket_operations{}; operations = app.operations.acquire(&socket_operations); - if (socket_operations) - { + if (socket_operations) { socket_trace = std::make_unique>(socket, *socket_operations, app.began); } } void session::trace(connection_load::operation_phase phase, std::uint64_t bytes, std::uint64_t sequence, int error) { - if (operations) - { + if (operations) { operations->record(phase, app.elapsed_us(), bytes, sequence, error); } } @@ -897,13 +749,10 @@ void application::trace_failure() { connection_load::trace_output output; std::size_t incomplete{}, emitted{}, histories_incomplete{}, socket_histories_incomplete{}, kernel_histories_available{}, kernel_histories_emitted{}; - for (const auto& value : sessions) - { - if (!value->state.finished || value->writing || !value->queued.empty()) - { + for (const auto& value : sessions) { + if (!value->state.finished || value->writing || !value->queued.empty()) { ++incomplete; - if (emitted < 16) - { + if (emitted < 16) { const bool retained = value->operations && value->operations->history_complete(); const bool written = output.append([&](std::ostream& out) { out << "{\"event\":\"operation_trace\",\"schema\":1,\"role\":" << std::quoted(o.role) @@ -912,34 +761,26 @@ void application::trace_failure() { << ",\"snapshot_phase\":\"before_socket_cancel\",\"operation_scope\":" << (o.socket_operation_trace ? "\"composed_and_socket\"" : "\"composed_only\"") << ",\"trace\":"; - if (value->operations) - { + if (value->operations) { out << '{'; value->operations->write_json(out); out << '}'; - } - else - { + } else { out << "null"; } - if (o.socket_operation_trace) - { + if (o.socket_operation_trace) { out << ",\"socket_trace\":"; - if (value->socket_trace) - { + if (value->socket_trace) { out << '{'; value->socket_trace->ring().write_json(out); out << '}'; - } - else - { + } else { out << "null"; } } out << '}'; }); - if (written) - { + if (written) { ++emitted; histories_incomplete += !retained; socket_histories_incomplete += @@ -947,8 +788,7 @@ void application::trace_failure() { (!value->socket_trace || !value->socket_trace->ring().history_complete()); } } - if (o.tcp_kernel_samples && kernel_samples.contains(value->state.owner)) - { + if (o.tcp_kernel_samples && kernel_samples.contains(value->state.owner)) { ++kernel_histories_available; kernel_histories_emitted += output.append([&](std::ostream& out) { out << "{\"event\":\"tcp_kernel_sample_history\",\"schema\":" @@ -971,8 +811,7 @@ void application::trace_failure() { << ",\"incomplete_socket_histories\":" << socket_histories_incomplete << ",\"output_truncated\":" << (output.truncated() ? "true" : "false") << ','; operations.write_json(out); - if (o.tcp_kernel_samples) - { + if (o.tcp_kernel_samples) { out << ",\"tcp_kernel_histories_emitted\":" << kernel_histories_emitted << ",\"tcp_kernel_histories_omitted\":" << kernel_histories_available - kernel_histories_emitted << ",\"tcp_kernel_samples\":"; @@ -1006,8 +845,7 @@ void session::diagnose() { socket.get_option(send_buffer, send_error); const auto buffers = input.data(); std::string_view input_bytes; - if (input.size()) - { + if (input.size()) { const auto buffer = *asio::buffer_sequence_begin(buffers); input_bytes = {static_cast(buffer.data()), buffer.size()}; } @@ -1038,17 +876,14 @@ void session::diagnose() { #else const auto info = connection_load::query_tcp_kernel_info(-1); #endif - if (info.result == connection_load::tcp_kernel_info::status::available) - { + if (info.result == connection_load::tcp_kernel_info::status::available) { std::cout << ",\"tcp_info\":{\"state\":" << info.state << ",\"send_buffer_bytes\":" << info.send_buffer_bytes << ",\"send_window_bytes\":" << info.send_window_bytes << ",\"receive_window_bytes\":" << info.receive_window_bytes << ",\"rto_ms\":" << info.rto_ms << ",\"tx_bytes\":" << info.tx_bytes << ",\"rx_bytes\":" << info.rx_bytes << ",\"retransmit_bytes\":" << info.retransmit_bytes << ",\"out_of_order_bytes\":" << info.out_of_order_bytes << '}'; - } - else - { + } else { std::cout << ",\"tcp_info\":null,\"tcp_info_error\":" << info.error << ",\"tcp_info_status\":" << static_cast(info.result); } @@ -1061,45 +896,33 @@ void session::receive(std::string_view bytes) { auto& a = app; connection_load::frame_header h; const bool server = a.o.role == "server"; - if (!connection_load::decode(bytes, h) || !state.accept(h, a.o, server)) - { + if (!connection_load::decode(bytes, h) || !state.accept(h, a.o, server)) { ++a.state_errors; a.fail("protocol", "identity, phase, sequence or body mismatch"); return; } - if (h.kind == frame_kind::hello || h.kind == frame_kind::hello_ack) - { - if (server && !a.identities.emplace(h.owner.client, h.owner.connection).second) - { + if (h.kind == frame_kind::hello || h.kind == frame_kind::hello_ack) { + if (server && !a.identities.emplace(h.owner.client, h.owner.connection).second) { ++a.state_errors; a.fail("protocol", "duplicate client/connection identity"); return; } ++a.ready; pending_since = clock_type::time_point{}; - if (server) - { + if (server) { send(connection_load::make_frame(frame_kind::hello_ack, state.owner)); } - } - else if (h.kind == frame_kind::start || h.kind == frame_kind::start_ack) - { + } else if (h.kind == frame_kind::start || h.kind == frame_kind::start_ack) { ++a.started; pending_since = clock_type::time_point{}; - if (server) - { + if (server) { send(connection_load::make_frame(frame_kind::start_ack, state.owner)); } - } - else if (h.kind == frame_kind::data) - { + } else if (h.kind == frame_kind::data) { ++a.data_received; - if (server) - { + if (server) { send(std::string(bytes)); - } - else - { + } else { --a.outstanding; a.data_bytes -= header_bytes + a.o.payload; pending_since = state.next_receive < state.next_send ? sent_at[(state.next_receive - 1) % a.o.window] @@ -1107,13 +930,9 @@ void session::receive(std::string_view bytes) { fill(); finish(); } - } - else if (server) - { + } else if (server) { send(connection_load::make_frame(frame_kind::finish_ack, state.owner, h.sequence)); - } - else - { + } else { ++a.finished; pending_since = clock_type::time_point{}; } @@ -1122,24 +941,20 @@ void session::receive(std::string_view bytes) { void session::fill() { auto& a = app; while (a.traffic && !a.draining && !a.stopping && !a.failed && a.o.scenario == "traffic" && - state.next_send - state.next_receive < a.o.window) - { - if (a.outstanding >= a.o.inflight || header_bytes + a.o.payload > a.o.inflight_bytes - a.data_bytes) - { + state.next_send - state.next_receive < a.o.window) { + if (a.outstanding >= a.o.inflight || header_bytes + a.o.payload > a.o.inflight_bytes - a.data_bytes) { ++a.rejected; a.fail("data admission", "global inflight count/byte limit exceeded"); return; } - if (state.next_send == (std::numeric_limits::max)()) - { + if (state.next_send == (std::numeric_limits::max)()) { ++a.state_errors; a.fail("protocol", "sequence exhausted"); return; } const auto sequence = state.next_send++; sent_at[(sequence - 1) % a.o.window] = clock_type::now(); - if (sequence == state.next_receive) - { + if (sequence == state.next_receive) { pending_since = clock_type::now(); } ++a.outstanding; @@ -1150,8 +965,7 @@ void session::fill() { } void session::finish() { - if (app.draining && !app.stopping && !app.failed && !state.finish_sent && state.next_receive == state.next_send) - { + if (app.draining && !app.stopping && !app.failed && !state.finish_sent && state.next_receive == state.next_send) { state.finish_sent = true; pending_since = clock_type::now(); send(connection_load::make_frame(frame_kind::finish, state.owner, state.next_send - 1)); @@ -1159,30 +973,23 @@ void session::finish() { } void application::accept() { - if (stopping || failed) - { + if (stopping || failed) { return; } auto value = std::make_shared(*this, io); ++callbacks; acceptor.async_accept(value->socket, asio::bind_executor(value->strand, [this, value](error_code error) { --callbacks; - if (!stopping && error) - { + if (!stopping && error) { ++connect_errors; fail("accept callback", error.message()); - } - else if (!stopping) - { + } else if (!stopping) { ++accepted; - if (sessions.size() >= o.maximum || accepted > o.expected) - { + if (sessions.size() >= o.maximum || accepted > o.expected) { ++rejected; value->close(); fail("accept admission", "expected/max connection count exceeded"); - } - else - { + } else { sessions.push_back(value); value->enable_trace(); value->active = true; @@ -1190,12 +997,9 @@ void application::accept() { peak_active = (std::max)(peak_active.load(), active.load()); error_code option_error; value->socket.set_option(tcp::no_delay(true), option_error); - if (option_error) - { + if (option_error) { fail("TCP_NODELAY", option_error.message()); - } - else - { + } else { value->read(); accept(); } @@ -1206,8 +1010,7 @@ void application::accept() { } void application::launch() { - if (clients.size() >= o.maximum || connecting >= o.parallel) - { + if (clients.size() >= o.maximum || connecting >= o.parallel) { ++rejected; fail("connect admission", "active/connecting limit exceeded"); return; @@ -1221,8 +1024,7 @@ void application::launch() { ++connecting; peak_connecting = (std::max)(peak_connecting.load(), connecting.load()); asio::post(value->strand, [this, value] { - if (stopping || failed) - { + if (stopping || failed) { --connecting; value->connecting = false; value->close(); @@ -1237,26 +1039,20 @@ void application::launch() { --callbacks; --connecting; value->connecting = false; - if (!stopping && error) - { + if (!stopping && error) { ++connect_errors; connect_resources.capture(error.value(), error.category().name(), established.load()); fail("connect callback", error.message()); - } - else if (!stopping) - { + } else if (!stopping) { ++established; ++active; peak_active = (std::max)(peak_active.load(), active.load()); value->active = true; error_code option_error; value->socket.set_option(tcp::no_delay(true), option_error); - if (option_error) - { + if (option_error) { fail("TCP_NODELAY", option_error.message()); - } - else - { + } else { value->pending_since = clock_type::now(); value->read(); value->send(connection_load::make_frame( @@ -1269,76 +1065,60 @@ void application::launch() { } void application::poll() { - if (stopping || failed) - { + if (stopping || failed) { return; } const auto now = clock_type::now(); - if (o.role == "client") - { - if (attempted < o.connections && connecting < o.parallel && now >= next_launch) - { + if (o.role == "client") { + if (attempted < o.connections && connecting < o.parallel && now >= next_launch) { launch(); next_launch = now + std::chrono::nanoseconds(1000000000 / o.rate); } - if (!starting && ready == o.connections) - { + if (!starting && ready == o.connections) { starting = true; - for (const auto& value : clients) - { + for (const auto& value : clients) { asio::post(value->strand, [value] { value->pending_since = clock_type::now(); value->send(connection_load::make_frame(frame_kind::start, value->state.owner)); }); } } - if (!traffic && started == o.connections) - { + if (!traffic && started == o.connections) { traffic = true; until = now + std::chrono::duration_cast(std::chrono::duration(o.hold)); - for (const auto& value : clients) - { + for (const auto& value : clients) { asio::post(value->strand, [value] { value->fill(); }); } } - if (traffic && !draining && now >= until) - { + if (traffic && !draining && now >= until) { draining = true; - for (const auto& value : clients) - { + for (const auto& value : clients) { asio::post(value->strand, [value] { value->finish(); }); } } - if (finished == o.connections && !outstanding && !pending_writes) - { + if (finished == o.connections && !outstanding && !pending_writes) { stop(); return; } - if (now < next_timeout_scan) - { + if (now < next_timeout_scan) { return; } const auto timeout = std::chrono::milliseconds((std::min)(o.connect_ms, o.request_ms)); next_timeout_scan = now + std::clamp(timeout / 4, std::chrono::milliseconds(1), std::chrono::milliseconds(50)); - for (const auto& value : clients) - { - if (value->connecting && now - value->connect_since.load() >= std::chrono::milliseconds(o.connect_ms)) - { + for (const auto& value : clients) { + if (value->connecting && now - value->connect_since.load() >= std::chrono::milliseconds(o.connect_ms)) { ++timeouts; fail("connect timeout", "pending connect exceeded connect-timeout-ms"); return; } const auto pending = value->pending_since.load(); - if (pending != clock_type::time_point{} && now - pending >= std::chrono::milliseconds(o.request_ms)) - { + if (pending != clock_type::time_point{} && now - pending >= std::chrono::milliseconds(o.request_ms)) { ++timeouts; fail("request timeout", "pending response exceeded request-timeout-ms"); return; } } - } - else if (finished == o.expected && disconnected == o.expected && !pending_writes) - { + } else if (finished == o.expected && disconnected == o.expected && !pending_writes) { stop(); return; } @@ -1347,8 +1127,7 @@ void application::poll() { void application::sample_kernel() { // Reuse the existing marker on the single IO worker. Release recovered slots // before admission so persistent stalls are never evicted by transient ones. - if (stopping || failed || o.scenario != "traffic") - { + if (stopping || failed || o.scenario != "traffic") { return; } const auto now = clock_type::now(); @@ -1361,17 +1140,13 @@ void application::sample_kernel() { return connection_load::tcp_kernel_samples::stalled( value.active, value.state.finished, value.closed, value.state.next_receive > 1, idle(value)); }; - for (const auto& value : sessions) - { + for (const auto& value : sessions) { kernel_samples.prune(value->state.owner, stalled(*value), value->read_completions + value->write_completions); socket_observer.prune(value->state.owner, stalled(*value), value->read_completions + value->write_completions); } - for (const auto& value : sessions) - { - if (stalled(*value)) - { - if (o.socket_observer) - { + for (const auto& value : sessions) { + if (stalled(*value)) { + if (o.socket_observer) { error_code local_error, remote_error; const auto local = value->socket.local_endpoint(local_error); const auto remote = value->socket.remote_endpoint(remote_error); @@ -1380,8 +1155,7 @@ void application::sample_kernel() { }; const auto bytes = value->input.data(); std::string_view input; - if (value->input.size()) - { + if (value->input.size()) { const auto buffer = *asio::buffer_sequence_begin(bytes); input = {static_cast(buffer.data()), buffer.size()}; } @@ -1391,8 +1165,7 @@ void application::sample_kernel() { value->state.next_receive, value->state.next_send, value->input.size(), value->read_pending, inspect_frame_boundary(input).complete(), endpoint(local, local_error), endpoint(remote, remote_error)}); - if (probe.generation) - { + if (probe.generation) { asio::post(value->strand, [this, probe] { socket_observer.complete(probe, elapsed_us()); }); } #endif @@ -1413,18 +1186,15 @@ void application::sample_kernel() { void application::sample() { const auto now = clock_type::now(); - if (now - sampled < std::chrono::seconds(1)) - { + if (now - sampled < std::chrono::seconds(1)) { return; } - if (operations.request_marker()) - { + if (operations.request_marker()) { const auto submitted = elapsed_us(); asio::post(io, [this, submitted] { operations.complete_marker(submitted, elapsed_us()); marker_completed_us = elapsed_us(); - if (o.tcp_kernel_samples || o.socket_observer) - { + if (o.tcp_kernel_samples || o.socket_observer) { sample_kernel(); } }); @@ -1446,8 +1216,7 @@ void application::sample() { void application::run() { connection_load::write_resource_startup(std::cout, resources.initial_snapshot(), o.role, o.run, o.client); - if (o.role == "server") - { + if (o.role == "server") { const tcp::endpoint endpoint(asio::ip::make_address(o.host), static_cast(o.port)); acceptor.open(endpoint.protocol()); acceptor.bind(endpoint); @@ -1456,33 +1225,25 @@ void application::run() { std::cout << "{\"event\":\"ready\",\"port\":" << acceptor.local_endpoint().port() << "}\n" << std::flush; } signals.async_wait([this](error_code error, int) { - if (!error) - { + if (!error) { fail("signal", "interrupted"); } }); const auto until_deadline = began + std::chrono::duration_cast(std::chrono::duration(o.deadline)); std::thread worker([this] { io.run(); }); - while (!stopping) - { - if (failed) - { + while (!stopping) { + if (failed) { connect_resources.write_event(std::cout, o.role, o.run, o.client); stop(); - } - else if (clock_type::now() >= until_deadline) - { + } else if (clock_type::now() >= until_deadline) { ++timeouts; fail("deadline", "target incomplete"); stop(); - } - else - { + } else { poll(); } - if (!stopping) - { + if (!stopping) { sample(); socket_observer.sample(elapsed_us(), marker_completed_us.load()); std::this_thread::sleep_for(std::chrono::milliseconds(1)); @@ -1529,52 +1290,43 @@ void application::result() const { << (drained_at == clock_type::time_point{} ? -1 : std::chrono::duration(drained_at - stopped_at).count()); - if (operations.enabled()) - { + if (operations.enabled()) { std::cout << ",\"operation_trace_enabled\":true,\"operation_trace_emitted\":" << (trace_emitted ? "true" : "false"); - if (o.socket_operation_trace) - { + if (o.socket_operation_trace) { std::cout << ",\"socket_operation_trace_enabled\":true"; } - if (o.tcp_kernel_samples) - { + if (o.tcp_kernel_samples) { std::cout << ",\"tcp_kernel_samples_enabled\":true"; } - if (o.socket_observer) - { + if (o.socket_observer) { std::cout << ",\"socket_observer_enabled\":true"; } } connect_resources.write_fields(std::cout); std::cout << "}\n"; - if (!failure.empty() || !clean || !complete) - { + if (!failure.empty() || !clean || !complete) { throw std::runtime_error(failure.empty() ? "terminal state incomplete" : failure); } } void self_test() { auto require = [](bool ok) { - if (!ok) - { + if (!ok) { throw std::runtime_error("native control self-test failed"); } }; auto parse = [](const std::string& mode, bool socket_trace = false, bool kernel_samples = false) { std::vector values{"control", "--role", "client", "--target", "127.0.0.1", "--port", "1", "--run-id", "1", "--operation-trace", mode}; - if (socket_trace) - { + if (socket_trace) { values.insert(values.end(), {"--socket-operation-trace", "on"}); } - if (kernel_samples) - { + if (kernel_samples) { values.insert(values.end(), {"--tcp-kernel-samples", "on"}); } std::vector argv; - for (auto& value : values) - { + for (auto& value : values) { argv.push_back(value.data()); } return arguments(static_cast(argv.size()), argv.data()); @@ -1589,8 +1341,7 @@ void self_test() { // Synthetic queries only: exercise retention/JSON without sockets or load. connection_load::tcp_kernel_samples retained(true); unsigned kernel_queries{}; - for (unsigned index = 1; index <= 10; ++index) - { + for (unsigned index = 1; index <= 10; ++index) { retained.sample({7, 1, 1}, 1000000 + index, 1000000, 4, 2, 3, [&] { ++kernel_queries; return connection_load::tcp_kernel_info{}; @@ -1609,8 +1360,7 @@ void self_test() { require(kernel_history.str().find("\"head_sample_capacity\":4,\"tail_sample_capacity\":4") != std::string::npos); require(kernel_history.str().find("\"total_samples\":10,\"overwritten_samples\":2}") != std::string::npos); std::size_t kernel_position{}; - for (const auto ordinal : {1, 2, 3, 4, 7, 8, 9, 10}) - { + for (const auto ordinal : {1, 2, 3, 4, 7, 8, 9, 10}) { kernel_position = kernel_history.str().find("\"sample_ordinal\":" + std::to_string(ordinal) + ",\"sample_time_us\":" + std::to_string(1000000 + ordinal), kernel_position); @@ -1622,10 +1372,11 @@ void self_test() { require(kernel_summary.str().starts_with("{\"schema\":2,\"retention_policy\":\"first_4_latest_4\",")); std::cout << "native control kernel retention schema 2 checks passed\n"; bool kernel_rejected{}; - try - { parse("off", false, true); } - catch (const std::invalid_argument&) - { kernel_rejected = true; } + try { + parse("off", false, true); + } catch (const std::invalid_argument&) { + kernel_rejected = true; + } require(kernel_rejected); require(!disabled.operation_trace && enabled.operation_trace); require( @@ -1634,10 +1385,11 @@ void self_test() { socket_enabled.connections == enabled.connections && socket_enabled.window == enabled.window && socket_enabled.payload == enabled.payload); bool socket_rejected{}; - try - { parse("off", true); } - catch (const std::invalid_argument&) - { socket_rejected = true; } + try { + parse("off", true); + } catch (const std::invalid_argument&) { + socket_rejected = true; + } require(socket_rejected); require(disabled.deadline == enabled.deadline && disabled.request_ms == enabled.request_ms && disabled.connections == enabled.connections && disabled.window == enabled.window && @@ -1653,10 +1405,11 @@ void self_test() { o.window = 4; validate(o); auto rejected = [&](options value) { - try - { validate(value); } - catch (const std::invalid_argument&) - { return true; } + try { + validate(value); + } catch (const std::invalid_argument&) { + return true; + } return false; }; auto invalid = o; @@ -1677,21 +1430,19 @@ void self_test() { invalid = o; invalid.payload = 4024; require(rejected(invalid)); - for (const auto size : {std::size_t{64}, std::size_t{253}, std::size_t{254}, std::size_t{1088}, std::size_t{4093}}) - { + for (const auto size : + {std::size_t{64}, std::size_t{253}, std::size_t{254}, std::size_t{1088}, std::size_t{4093}}) { const wire_frame value(std::string(size, 'x')); const std::string wire(value.prefix.data(), value.prefix_size); const auto bytes = wire + value.body + "tail"; - for (std::size_t length = 0; length < wire.size() + size; ++length) - { + for (std::size_t length = 0; length < wire.size() + size; ++length) { require(!dgram_match{}(bytes.begin(), bytes.begin() + length).second); } const auto match = dgram_match{}(bytes.begin(), bytes.end()); require(match.second && std::size_t(match.first - bytes.begin()) == wire.size() + size); } for (const std::string bad : {std::string("\xfe\x01\x00", 3), std::string("\xff\x01\0\0\0\0\0\0\0", 9), - std::string("\xfe\x00\x10", 3), std::string("\xff\0\0\x01\0\0\0\0\0", 9)}) - { + std::string("\xfe\x00\x10", 3), std::string("\xff\0\0\x01\0\0\0\0\0", 9)}) { const auto match = dgram_match{}(bad.begin(), bad.end()); require(match.second && match.first == bad.begin()); } @@ -1705,8 +1456,7 @@ void self_test() { }; const auto empty = boundary("", "empty"); require(!empty.declared_body_bytes && !empty.required_wire_bytes && !empty.missing_frame_bytes); - for (const auto bytes : {std::string("\xfe", 1), std::string("\xfe\x40", 2)}) - { + for (const auto bytes : {std::string("\xfe", 1), std::string("\xfe\x40", 2)}) { const auto value = boundary(bytes, "incomplete_prefix"); require(value.prefix_bytes == bytes.size() && !value.declared_body_bytes && !value.required_wire_bytes && !value.missing_frame_bytes); @@ -1727,8 +1477,7 @@ void self_test() { const auto trailing = boundary(prefix + std::string(1088, 'x') + "\x01x", "complete_frame"); require( trailing.required_wire_bytes == 1091 && trailing.missing_frame_bytes == 0 && trailing.buffered_bytes == 1093); - for (const auto size : {std::size_t{0}, std::size_t{253}, std::size_t{254}, std::size_t{4093}}) - { + for (const auto size : {std::size_t{0}, std::size_t{253}, std::size_t{254}, std::size_t{4093}}) { const wire_frame frame(std::string(size, 'x')); const auto value = boundary(std::string(frame.prefix.data(), frame.prefix_size) + frame.body, "complete_frame"); require(value.prefix_bytes == frame.prefix_size && value.declared_body_bytes == size && @@ -1739,16 +1488,13 @@ void self_test() { // Canonical nine-byte prefixes start at 65536 bytes, beyond the unchanged // 4096-byte wire cap. Decode them safely, but never report an allowed frame. for (const auto size : - {std::uint64_t{0}, std::uint64_t{65535}, std::uint64_t{65536}, (std::numeric_limits::max)()}) - { + {std::uint64_t{0}, std::uint64_t{65535}, std::uint64_t{65536}, (std::numeric_limits::max)()}) { std::string extended(9, '\0'); extended[0] = static_cast(255); - for (std::size_t index = 0; index < 8; ++index) - { + for (std::size_t index = 0; index < 8; ++index) { extended[index + 1] = static_cast(size >> (index * 8)); } - for (std::size_t length = 1; length < extended.size(); ++length) - { + for (std::size_t length = 1; length < extended.size(); ++length) { const auto value = boundary(extended.substr(0, length), "incomplete_prefix"); require(value.prefix_bytes == length && !value.declared_body_bytes && !value.required_wire_bytes); } @@ -1827,15 +1573,12 @@ void self_test() { } // namespace int main(int argc, char** argv) { - try - { - if (argc == 2 && std::string_view(argv[1]) == "--help") - { + try { + if (argc == 2 && std::string_view(argv[1]) == "--help") { help(); return 0; } - if (argc == 2 && std::string_view(argv[1]) == "--self-test") - { + if (argc == 2 && std::string_view(argv[1]) == "--self-test") { self_test(); return 0; } @@ -1850,18 +1593,14 @@ int main(int argc, char** argv) { if (!wake.wait_until(lock, app.began + std::chrono::duration_cast( std::chrono::duration(app.o.deadline + app.o.cleanup)), - [&] { return done; })) - { + [&] { return done; })) { std::_Exit(124); } }); - try - { + try { app.run(); app.result(); - } - catch (...) - { + } catch (...) { { std::lock_guard lock(mutex); done = true; @@ -1877,9 +1616,7 @@ int main(int argc, char** argv) { wake.notify_one(); watchdog.join(); return 0; - } - catch (const std::exception& error) - { + } catch (const std::exception& error) { std::cerr << "native control: " << error.what() << '\n'; return 1; } diff --git a/benchmarks/connections.cpp b/benchmarks/connections.cpp index 12c436a..a7aad31 100644 --- a/benchmarks/connections.cpp +++ b/benchmarks/connections.cpp @@ -56,8 +56,7 @@ void request_interrupt(int signal) { interrupted.store(signal, std::memory_order_relaxed); } -struct options -{ +struct options { std::string role, scenario = "idle", host; std::uint64_t run{}, client{}; std::size_t port{}, connections = 1, expected{}, maximum = 4096; @@ -70,8 +69,7 @@ struct options std::uint64_t integer(std::string_view text) { std::uint64_t value{}; const auto parsed = std::from_chars(text.data(), text.data() + text.size(), value); - if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size()) - { + if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size()) { throw std::invalid_argument("expected an unsigned decimal integer"); } return value; @@ -80,8 +78,7 @@ std::uint64_t integer(std::string_view text) { double duration(const std::string& text) { char* end{}; const auto value = std::strtod(text.c_str(), &end); - if (text.empty() || end != text.c_str() + text.size() || !std::isfinite(value)) - { + if (text.empty() || end != text.c_str() + text.size() || !std::isfinite(value)) { throw std::invalid_argument("expected a finite duration"); } return value; @@ -112,129 +109,77 @@ options arguments(int argc, char** argv) { #endif bool port_set = false; std::string address_option; - for (int index = 1; index < argc; ++index) - { + for (int index = 1; index < argc; ++index) { const std::string key = argv[index]; - if (key == "--help") - { + if (key == "--help") { help(); std::exit(0); } - if (++index == argc) - { + if (++index == argc) { throw std::invalid_argument("missing value for " + key); } const std::string value = argv[index]; - if (key == "--role") - { + if (key == "--role") { result.role = value; - } - else if (key == "--scenario") - { + } else if (key == "--scenario") { result.scenario = value; - } - else if (key == "--listen" || key == "--target") - { + } else if (key == "--listen" || key == "--target") { result.host = value; address_option = key; - } - else if (key == "--port") - { + } else if (key == "--port") { result.port = integer(value); port_set = true; - } - else if (key == "--run-id") - { + } else if (key == "--run-id") { result.run = integer(value); - } - else if (key == "--client-id") - { + } else if (key == "--client-id") { result.client = integer(value); - } - else if (key == "--connections") - { + } else if (key == "--connections") { result.connections = integer(value); - } - else if (key == "--expected-connections") - { + } else if (key == "--expected-connections") { result.expected = integer(value); - } - else if (key == "--max-connections") - { + } else if (key == "--max-connections") { result.maximum = integer(value); - } - else if (key == "--connect-rate") - { + } else if (key == "--connect-rate") { result.rate = integer(value); - } - else if (key == "--connect-parallel") - { + } else if (key == "--connect-parallel") { result.parallel = integer(value); - } - else if (key == "--io-threads") - { + } else if (key == "--io-threads") { result.threads = integer(value); - } - else if (key == "--payload") - { + } else if (key == "--payload") { result.payload = integer(value); - } - else if (key == "--window") - { + } else if (key == "--window") { result.window = integer(value); - } - else if (key == "--connect-timeout-ms") - { + } else if (key == "--connect-timeout-ms") { result.connect_timeout_ms = integer(value); - } - else if (key == "--request-timeout-ms") - { + } else if (key == "--request-timeout-ms") { result.request_timeout_ms = integer(value); - } - else if (key == "--max-buffer-bytes") - { + } else if (key == "--max-buffer-bytes") { result.maximum_buffer = integer(value); - } - else if (key == "--max-inflight-bytes") - { + } else if (key == "--max-inflight-bytes") { result.inflight_limit = integer(value); - } - else if (key == "--buffer-budget-bytes") - { + } else if (key == "--buffer-budget-bytes") { result.buffer_budget = integer(value); - } - else if (key == "--sample-ms") - { + } else if (key == "--sample-ms") { result.sample_ms = integer(value); - } - else if (key == "--hold-seconds") - { + } else if (key == "--hold-seconds") { result.hold_seconds = duration(value); - } - else if (key == "--deadline-seconds") - { + } else if (key == "--deadline-seconds") { result.deadline_seconds = duration(value); - } - else if (key == "--cleanup-seconds") - { + } else if (key == "--cleanup-seconds") { result.cleanup_seconds = duration(value); - } - else - { + } else { throw std::invalid_argument("unknown argument: " + key); } } if ((result.role != "server" && result.role != "client") || (result.scenario != "idle" && result.scenario != "traffic") || !result.run || !result.client || !port_set || result.port > 65535 || (result.role == "client" && !result.port) || result.host.empty() || - address_option != (result.role == "server" ? "--listen" : "--target")) - { + address_option != (result.role == "server" ? "--listen" : "--target")) { throw std::invalid_argument("specify role, matching listen/target address, port and nonzero run-id"); } arknet::error_code error; const auto address = asio::ip::make_address(result.host, error); - if (error || address.is_multicast() || (result.role == "client" && address.is_unspecified())) - { + if (error || address.is_multicast() || (result.role == "client" && address.is_unspecified())) { throw std::invalid_argument("listen/target must be a valid unicast numeric IPv4 or IPv6 address"); } if (result.maximum < 1 || result.maximum > 200000 || result.connections < 1 || @@ -249,8 +194,7 @@ options arguments(int argc, char** argv) { result.deadline_seconds > 86400 || result.cleanup_seconds < 1 || result.cleanup_seconds > 60 || result.sample_ms < 100 || result.sample_ms > 60000 || result.inflight_limit < header_bytes + result.payload || result.inflight_limit > 1024ULL * 1024 * 1024 || result.buffer_budget < 4096 || - result.buffer_budget > 16ULL * 1024 * 1024 * 1024) - { + result.buffer_budget > 16ULL * 1024 * 1024 * 1024) { throw std::invalid_argument("argument out of range; see CONNECTIONS.md for limits"); } const auto count = result.role == "client" ? result.connections : result.maximum; @@ -258,13 +202,11 @@ options arguments(int argc, char** argv) { ? result.connections * result.window * sizeof(clock_type::time_point) : 0; const auto reserved = std::uint64_t(count) * result.maximum_buffer + timestamps + result.inflight_limit; - if (reserved > result.buffer_budget) - { + if (reserved > result.buffer_budget) { throw std::invalid_argument("declared receive/send/timestamp buffer reservation exceeds buffer budget"); } if (result.role == "client" && result.scenario == "traffic" && - std::uint64_t(result.connections) * result.window * (header_bytes + result.payload) > result.inflight_limit) - { + std::uint64_t(result.connections) * result.window * (header_bytes + result.payload) > result.inflight_limit) { throw std::invalid_argument("connection windows exceed max-inflight-bytes"); } return result; @@ -272,18 +214,13 @@ options arguments(int argc, char** argv) { std::string quoted(std::string_view text) { std::string result = "\""; - for (const unsigned char character : text) - { - if (character == '"' || character == '\\') - { + for (const unsigned char character : text) { + if (character == '"' || character == '\\') { result += '\\'; } - if (character < 32) - { + if (character < 32) { result += '?'; - } - else - { + } else { result += static_cast(character); } } @@ -296,13 +233,11 @@ std::int64_t tick() { void raise_peak(std::atomic& peak, std::uint64_t value) { auto current = peak.load(); - while (current < value && !peak.compare_exchange_weak(current, value)) - { + while (current < value && !peak.compare_exchange_weak(current, value)) { } } -struct counters -{ +struct counters { std::atomic failed{}, closing{}; std::atomic attempted{}, connecting{}, established{}, accepted{}, admitted{}, active{}, peak{}; std::atomic ready{}, started{}, finished{}, disconnected{}, connect_failures{}, timeouts{}; @@ -317,26 +252,24 @@ struct counters void fail(std::string_view reason) noexcept { failed = true; - try - { + try { std::lock_guard lock(error_mutex); - if (first_error.empty()) - { + if (first_error.empty()) { first_error = reason; } + } catch (...) { } - catch (...) - {} } template void guard(Function&& function) noexcept { - try - { function(); } - catch (const std::exception& error) - { fail(error.what()); } - catch (...) - { fail("unknown callback exception"); } + try { + function(); + } catch (const std::exception& error) { + fail(error.what()); + } catch (...) { + fail("unknown callback exception"); + } } void connected() { @@ -344,25 +277,21 @@ struct counters } }; -class cleanup_watchdog -{ +class cleanup_watchdog { public: explicit cleanup_watchdog(clock_type::time_point deadline) : worker_([this, deadline] { std::unique_lock lock(mutex_); - if (!changed_.wait_until(lock, deadline, [this] { return complete_; })) - { + if (!changed_.wait_until(lock, deadline, [this] { return complete_; })) { // Neither reporting nor sanitizer exit interceptors may hold // up the last-resort failure path on a full output pipe. #ifdef _WIN32 TerminateProcess(GetCurrentProcess(), 124); #else - if (kill(getpid(), SIGKILL) == 0) - { + if (kill(getpid(), SIGKILL) == 0) { // Signal delivery need not precede kill() returning. Do // not race it with the C-runtime fallback exit below. - for (;;) - { + for (;;) { pause(); } } @@ -388,8 +317,7 @@ class cleanup_watchdog std::thread worker_; }; -class observation -{ +class observation { public: observation(const options& settings, counters& values, clock_type::time_point began) : settings_(settings) @@ -400,8 +328,7 @@ class observation void sample() { const auto now = clock_type::now(); - if (now - sampled_ < std::chrono::milliseconds(settings_.sample_ms)) - { + if (now - sampled_ < std::chrono::milliseconds(settings_.sample_ms)) { return; } // Monotonic phase counts bracket the CPU query: a full START count at @@ -431,8 +358,7 @@ void send_frame( Endpoint& endpoint, std::string bytes, counters& values, const options& settings, Completion completion) { const auto size = bytes.size(); const auto reserved = values.pending_bytes.fetch_add(size) + size; - if (reserved > settings.inflight_limit) - { + if (reserved > settings.inflight_limit) { values.pending_bytes -= size; ++values.rejected; values.fail("global pending-send byte limit exceeded"); @@ -440,38 +366,28 @@ void send_frame( } raise_peak(values.peak_pending_bytes, reserved); ++values.pending_sends; - try - { + try { endpoint.async_send( std::move(bytes), [&values, size, completion = std::move(completion)]( const arknet::error_code& error, std::size_t transferred) mutable noexcept { --values.pending_sends; values.pending_bytes -= size; values.guard([&] { - if (error || transferred != size) - { - if (values.closing) - { + if (error || transferred != size) { + if (values.closing) { ++values.shutdown_cancelled_sends; - } - else - { + } else { ++values.send_errors; } - if (!values.closing) - { + if (!values.closing) { values.fail(error ? error.message() : "short send completion"); } - } - else - { + } else { completion(); } }); }); - } - catch (...) - { + } catch (...) { --values.pending_sends; values.pending_bytes -= size; throw; @@ -483,8 +399,7 @@ void send_frame(Endpoint& endpoint, std::string bytes, counters& values, const o send_frame(endpoint, std::move(bytes), values, settings, [] {}); } -struct client_connection -{ +struct client_connection { client_connection(const options& settings, asio::io_context& context, std::size_t index) : endpoint((std::min)(std::size_t{4096}, settings.maximum_buffer), settings.maximum_buffer, context) , owner{settings.run, settings.client, index} @@ -498,8 +413,7 @@ struct client_connection std::uint64_t next_send = 1, next_receive = 1; }; -class client_run -{ +class client_run { public: client_run(const options& settings, counters& values, arknet::io_pool& runtime) : settings_(settings) @@ -523,15 +437,12 @@ class client_run const auto error = arknet::get_last_error(); values_.guard([&] { --values_.connecting; - if (error) - { + if (error) { ++values_.connect_failures; - if (error == asio::error::timed_out) - { + if (error == asio::error::timed_out) { ++values_.timeouts; } - if (!values_.closing) - { + if (!values_.closing) { values_.connect_resources.capture( error.value(), error.category().name(), values_.established.load()); values_.fail("connect: " + error.message()); @@ -542,8 +453,7 @@ class client_run ++values_.established; values_.connected(); state->endpoint.set_no_delay(true); - if (arknet::get_last_error()) - { + if (arknet::get_last_error()) { throw std::runtime_error("setting TCP_NODELAY failed"); } state->pending_since = tick(); @@ -556,13 +466,11 @@ class client_run state->endpoint.bind_recv( [this, state](std::string_view bytes) noexcept { values_.guard([&] { receive(*state, bytes); }); }); state->endpoint.bind_disconnect([this, state]() noexcept { - if (state->connected) - { + if (state->connected) { state->connected = false; --values_.active; ++values_.disconnected; - if (!values_.closing && !state->finished) - { + if (!values_.closing && !state->finished) { ++values_.unexpected_disconnects; values_.fail("unexpected server disconnect"); } @@ -571,8 +479,7 @@ class client_run connections_.push_back(std::move(connection)); ++values_.attempted; ++values_.connecting; - if (!state->endpoint.async_start(settings_.host, settings_.port, arknet::use_dgram)) - { + if (!state->endpoint.async_start(settings_.host, settings_.port, arknet::use_dgram)) { --values_.connecting; ++values_.connect_failures; values_.connect_resources.capture(0, "connect_submission_rejected", values_.established.load()); @@ -581,8 +488,7 @@ class client_run } void start() { - for (const auto& connection : connections_) - { + for (const auto& connection : connections_) { auto* state = connection.get(); state->endpoint.post([this, state] { values_.guard([&] { @@ -596,20 +502,17 @@ class client_run void traffic() { values_.work_begin = tick(); - if (settings_.scenario == "idle") - { + if (settings_.scenario == "idle") { return; } - for (const auto& connection : connections_) - { + for (const auto& connection : connections_) { auto* state = connection.get(); state->endpoint.post([this, state] { values_.guard([&] { fill(*state); }); }); } } void finish() { - for (const auto& connection : connections_) - { + for (const auto& connection : connections_) { auto* state = connection.get(); state->endpoint.post([this, state] { values_.guard([&] { @@ -622,17 +525,14 @@ class client_run void check_timeouts() { const auto now = tick(); - if (now < next_timeout_scan_) - { + if (now < next_timeout_scan_) { return; } const auto timeout = std::int64_t(settings_.request_timeout_ms) * 1000000; next_timeout_scan_ = now + (std::max)(std::int64_t{1000000}, (std::min)(timeout / 4, std::int64_t{50000000})); - for (const auto& connection : connections_) - { + for (const auto& connection : connections_) { const auto since = connection->pending_since.load(); - if (since && now - since >= timeout) - { + if (since && now - since >= timeout) { ++values_.timeouts; values_.fail("protocol response deadline exceeded"); return; @@ -641,34 +541,28 @@ class client_run } void stop() { - if (stopped_) - { + if (stopped_) { return; } values_.closing = true; std::vector> requests; values_.guard([&] { requests.reserve(connections_.size()); - for (const auto& connection : connections_) - { + for (const auto& connection : connections_) { auto* state = connection.get(); requests.push_back(state->endpoint.post([state] { state->endpoint.stop(); }, asio::use_future)); } }); - for (auto& request : requests) - { + for (auto& request : requests) { values_.guard([&] { request.get(); }); } // Worker calls request shutdown without waiting. Keep every endpoint and // its shared runtime alive until these owner calls drain native callbacks. - for (const auto& connection : connections_) - { + for (const auto& connection : connections_) { connection->endpoint.stop(); } - for (const auto& connection : connections_) - { - if (!connection->endpoint.is_stopped()) - { + for (const auto& connection : connections_) { + if (!connection->endpoint.is_stopped()) { values_.fail("client did not reach stopped state"); } } @@ -678,28 +572,22 @@ class client_run private: void receive(client_connection& state, std::string_view bytes) { frame_header header; - if (!connection_load::decode(bytes, header) || header.owner != state.owner) - { + if (!connection_load::decode(bytes, header) || header.owner != state.owner) { ++values_.content_errors; values_.fail("invalid response identity or body"); return; } - if (header.kind == frame_kind::hello_ack && !state.hello && !state.started && !header.sequence) - { + if (header.kind == frame_kind::hello_ack && !state.hello && !state.started && !header.sequence) { state.hello = true; state.pending_since = 0; ++values_.ready; - } - else if (header.kind == frame_kind::start_ack && state.hello && !state.started && !header.sequence) - { + } else if (header.kind == frame_kind::start_ack && state.hello && !state.started && !header.sequence) { state.started = true; state.pending_since = 0; ++values_.started; - } - else if (header.kind == frame_kind::data && state.started && !state.finished && - header.body_bytes == settings_.payload && header.sequence == state.next_receive && - header.sequence < state.next_send) - { + } else if (header.kind == frame_kind::data && state.started && !state.finished && + header.body_bytes == settings_.payload && header.sequence == state.next_receive && + header.sequence < state.next_send) { const auto sent = state.times[(state.next_receive - 1) % settings_.window]; const auto elapsed = std::chrono::duration_cast(clock_type::now() - sent).count(); @@ -712,25 +600,18 @@ class client_run state.times[(state.next_receive - 1) % settings_.window].time_since_epoch()) .count() : 0; - if (!state.draining && !values_.failed && !values_.closing) - { + if (!state.draining && !values_.failed && !values_.closing) { fill(state); - } - else - { + } else { finish_if_drained(state); } - } - else if (header.kind == frame_kind::finish_ack && state.finish_sent && !state.finished && - header.sequence == state.next_send - 1) - { + } else if (header.kind == frame_kind::finish_ack && state.finish_sent && !state.finished && + header.sequence == state.next_send - 1) { state.finished = true; state.pending_since = 0; values_.work_end = tick(); ++values_.finished; - } - else - { + } else { ++values_.sequence_errors; values_.fail("response kind, sequence or payload size mismatch"); } @@ -738,17 +619,14 @@ class client_run void fill(client_connection& state) { while (!state.draining && !values_.failed && !values_.closing && - state.next_send - state.next_receive < settings_.window) - { - if (state.next_send == (std::numeric_limits::max)()) - { + state.next_send - state.next_receive < settings_.window) { + if (state.next_send == (std::numeric_limits::max)()) { values_.fail("sequence space exhausted"); return; } const auto sequence = state.next_send++; state.times[(sequence - 1) % settings_.window] = clock_type::now(); - if (sequence == state.next_receive) - { + if (sequence == state.next_receive) { state.pending_since = tick(); } ++values_.sent; @@ -760,8 +638,7 @@ class client_run } void finish_if_drained(client_connection& state) { - if (state.draining && !state.finish_sent && state.next_receive == state.next_send && !values_.failed) - { + if (state.draining && !state.finish_sent && state.next_receive == state.next_send && !values_.failed) { state.finish_sent = true; state.pending_since = tick(); send_frame(state.endpoint, @@ -777,15 +654,13 @@ class client_run bool stopped_{}; }; -struct server_connection -{ +struct server_connection { identity owner; bool hello{}, started{}, finished{}; std::uint64_t next_sequence = 1; }; -class server_run -{ +class server_run { public: server_run(const options& settings, counters& values, arknet::io_pool& runtime) : settings_(settings) @@ -795,13 +670,11 @@ class server_run endpoint_.bind_accept([this](auto& session) noexcept { const auto error = arknet::get_last_error(); values_.guard([&] { - if (error) - { + if (error) { return; } ++values_.accepted; - if (values_.admitted >= settings_.maximum) - { + if (values_.admitted >= settings_.maximum) { ++values_.rejected; values_.fail("maximum admitted connection count exceeded"); session->stop(); @@ -818,8 +691,7 @@ class server_run endpoint_.bind_connect([this](auto& session) noexcept { values_.guard([&] { session->set_no_delay(true); - if (arknet::get_last_error()) - { + if (arknet::get_last_error()) { throw std::runtime_error("setting TCP_NODELAY failed"); } }); @@ -829,14 +701,12 @@ class server_run }); endpoint_.bind_disconnect([this](auto& session) noexcept { auto state = session->template get_user_data>(); - if (!state) - { + if (!state) { return; } --values_.active; ++values_.disconnected; - if (!values_.closing && !state->finished) - { + if (!values_.closing && !state->finished) { ++values_.unexpected_disconnects; values_.fail("client disconnected before FIN acknowledgement"); } @@ -856,14 +726,12 @@ class server_run } void stop() { - if (stopped_) - { + if (stopped_) { return; } values_.closing = true; endpoint_.stop(); - if (!endpoint_.is_stopped() || endpoint_.session_count()) - { + if (!endpoint_.is_stopped() || endpoint_.session_count()) { values_.fail("server did not reach empty stopped state"); } stopped_ = true; @@ -872,8 +740,7 @@ class server_run private: static std::vector lanes(arknet::io_pool& runtime) { std::vector contexts; - for (std::size_t index = 0; index < runtime.size(); ++index) - { + for (std::size_t index = 0; index < runtime.size(); ++index) { contexts.push_back(&runtime.get_context(index)); } return contexts; @@ -883,19 +750,16 @@ class server_run auto state = session->get_user_data>(); frame_header header; if (!state || !connection_load::decode(bytes, header) || header.owner.run != settings_.run || - !header.owner.client || !header.owner.connection) - { + !header.owner.client || !header.owner.connection) { ++values_.content_errors; values_.fail("invalid request identity or body"); return; } if (header.kind == frame_kind::hello && !state->hello && - header.sequence == (settings_.scenario == "traffic" ? 1 : 0)) - { + header.sequence == (settings_.scenario == "traffic" ? 1 : 0)) { { std::lock_guard lock(identities_mutex_); - if (!identities_.emplace(header.owner.client, header.owner.connection).second) - { + if (!identities_.emplace(header.owner.client, header.owner.connection).second) { ++values_.content_errors; values_.fail("duplicate client/connection identity"); return; @@ -905,36 +769,28 @@ class server_run state->hello = true; ++values_.ready; send_frame(*session, connection_load::make_frame(frame_kind::hello_ack, state->owner), values_, settings_); - } - else if (state->hello && header.owner == state->owner && header.kind == frame_kind::start && !state->started && - !header.sequence) - { + } else if (state->hello && header.owner == state->owner && header.kind == frame_kind::start && + !state->started && !header.sequence) { state->started = true; std::int64_t unset{}; values_.work_begin.compare_exchange_strong(unset, tick()); ++values_.started; send_frame(*session, connection_load::make_frame(frame_kind::start_ack, state->owner), values_, settings_); - } - else if (state->started && header.owner == state->owner && header.kind == frame_kind::data && - settings_.scenario == "traffic" && !state->finished && header.sequence == state->next_sequence && - header.body_bytes == settings_.payload) - { + } else if (state->started && header.owner == state->owner && header.kind == frame_kind::data && + settings_.scenario == "traffic" && !state->finished && header.sequence == state->next_sequence && + header.body_bytes == settings_.payload) { ++state->next_sequence; ++values_.received; send_frame(*session, std::string(bytes), values_, settings_, [this] { ++values_.sent; }); - } - else if (state->started && header.owner == state->owner && header.kind == frame_kind::finish && - !state->finished && header.sequence == state->next_sequence - 1) - { + } else if (state->started && header.owner == state->owner && header.kind == frame_kind::finish && + !state->finished && header.sequence == state->next_sequence - 1) { state->finished = true; send_frame(*session, connection_load::make_frame(frame_kind::finish_ack, state->owner, header.sequence), values_, settings_, [this] { values_.work_end = tick(); ++values_.finished; }); - } - else - { + } else { ++values_.sequence_errors; values_.fail("request kind, sequence or payload size mismatch"); } @@ -994,23 +850,17 @@ void result(const options& settings, const counters& values, std::string_view ou values.connect_resources.write_fields(std::cout); std::cout << ",\"first_error\":" << quoted(values.first_error) << ",\"latency_observations\":" << values.latency.total(); - for (const auto percent : {50, 95, 99}) - { + for (const auto percent : {50, 95, 99}) { std::cout << ",\"rtt_p" << percent << "_upper_us\":"; - if (values.latency.total()) - { + if (values.latency.total()) { std::cout << values.latency.percentile(percent); - } - else - { + } else { std::cout << "null"; } } std::cout << ",\"rtt_buckets\":["; - for (std::size_t index = 0; index < connection_load::latency_distribution::bucket_count; ++index) - { - if (index) - { + for (std::size_t index = 0; index < connection_load::latency_distribution::bucket_count; ++index) { + if (index) { std::cout << ','; } std::cout << values.latency.count(index); @@ -1030,8 +880,7 @@ int run(const options& settings) { connection_load::write_resource_startup( std::cout, overall_resources.initial_snapshot(), settings.role, settings.run, settings.client, true); arknet::io_pool runtime(settings.threads); - if (!runtime.start()) - { + if (!runtime.start()) { throw std::runtime_error("IO pool start failed"); } double measurement{}; @@ -1039,110 +888,85 @@ int run(const options& settings) { std::size_t actual_port = settings.port; std::string outcome = "failed"; auto wait = [&] { - if (interrupted.load(std::memory_order_relaxed)) - { + if (interrupted.load(std::memory_order_relaxed)) { values.fail("interrupted by signal"); } samples.sample(); std::this_thread::sleep_for(1ms); }; - if (settings.role == "server") - { + if (settings.role == "server") { server_run server(settings, values, runtime); - if (!server.start()) - { + if (!server.start()) { throw std::runtime_error("listen: " + arknet::get_last_error().message()); } actual_port = server.port(); std::cout << "{\"event\":\"ready\",\"role\":\"server\",\"host\":" << quoted(settings.host) << ",\"port\":" << actual_port << ",\"run_id\":" << quoted(std::to_string(settings.run)) << "}\n" << std::flush; - while (!values.failed && clock_type::now() < deadline) - { + while (!values.failed && clock_type::now() < deadline) { if (settings.expected && values.finished == settings.expected && values.disconnected == settings.expected && - values.admitted == settings.expected && !values.active && !values.pending_sends) - { + values.admitted == settings.expected && !values.active && !values.pending_sends) { break; } wait(); } - if (!values.failed) - { - if (!settings.expected) - { + if (!values.failed) { + if (!settings.expected) { outcome = "observation_complete"; - } - else if (values.finished == settings.expected && values.disconnected == settings.expected && - values.admitted == settings.expected && values.ready == settings.expected && - values.started == settings.expected && !values.active && !values.pending_sends) - { + } else if (values.finished == settings.expected && values.disconnected == settings.expected && + values.admitted == settings.expected && values.ready == settings.expected && + values.started == settings.expected && !values.active && !values.pending_sends) { outcome = "target_complete"; - } - else - { + } else { ++values.timeouts; values.fail("server target deadline exceeded"); } } active_before_stop = values.active; server.stop(); - } - else - { + } else { client_run client(settings, values, runtime); auto next_launch = began; - while (!values.failed && values.ready < settings.connections && clock_type::now() < deadline) - { + while (!values.failed && values.ready < settings.connections && clock_type::now() < deadline) { if (values.attempted < settings.connections && values.connecting < settings.parallel && - clock_type::now() >= next_launch) - { + clock_type::now() >= next_launch) { client.launch(); next_launch = clock_type::now() + std::chrono::nanoseconds(1000000000 / settings.rate); } client.check_timeouts(); wait(); } - if (!values.failed && values.ready == settings.connections) - { + if (!values.failed && values.ready == settings.connections) { client.start(); - while (!values.failed && values.started < settings.connections && clock_type::now() < deadline) - { + while (!values.failed && values.started < settings.connections && clock_type::now() < deadline) { client.check_timeouts(); wait(); } } - if (!values.failed && values.started == settings.connections) - { + if (!values.failed && values.started == settings.connections) { const auto started = clock_type::now(); const auto until = started + std::chrono::duration_cast( std::chrono::duration(settings.hold_seconds)); client.traffic(); - while (!values.failed && clock_type::now() < until && clock_type::now() < deadline) - { + while (!values.failed && clock_type::now() < until && clock_type::now() < deadline) { client.check_timeouts(); wait(); } measurement = std::chrono::duration(clock_type::now() - started).count(); - if (!values.failed && clock_type::now() < deadline) - { + if (!values.failed && clock_type::now() < deadline) { client.finish(); while (!values.failed && (values.finished < settings.connections || values.pending_sends) && - clock_type::now() < deadline) - { + clock_type::now() < deadline) { client.check_timeouts(); wait(); } } } - if (!values.failed) - { + if (!values.failed) { if (values.finished == settings.connections && !values.outstanding && !values.pending_sends && - (settings.scenario == "idle" || (values.received > 0 && values.received == values.sent))) - { + (settings.scenario == "idle" || (values.received > 0 && values.received == values.sent))) { outcome = "target_complete"; - } - else - { + } else { ++values.timeouts; values.fail("client target deadline exceeded"); } @@ -1153,12 +977,10 @@ int run(const options& settings) { } runtime.stop(); const bool clean = runtime.stopped() && !values.active && !values.pending_sends && !values.pending_bytes; - if (!clean) - { + if (!clean) { values.fail("cleanup counters did not reach terminal state"); } - if (values.failed) - { + if (values.failed) { outcome = "failed"; } const auto resources = overall_resources.sample(); @@ -1171,15 +993,12 @@ int run(const options& settings) { #ifndef ARKNET_CONNECTION_BENCHMARK_NO_MAIN int main(int argc, char** argv) { - try - { + try { std::signal(SIGINT, request_interrupt); std::signal(SIGTERM, request_interrupt); std::cout << std::setprecision(9); return run(arguments(argc, argv)); - } - catch (const std::exception& error) - { + } catch (const std::exception& error) { std::cerr << "{\"event\":\"error\",\"reason\":" << quoted(error.what()) << "}\n"; return 2; } diff --git a/benchmarks/connections_kernel_samples.hpp b/benchmarks/connections_kernel_samples.hpp index 58ea7ac..73601c7 100644 --- a/benchmarks/connections_kernel_samples.hpp +++ b/benchmarks/connections_kernel_samples.hpp @@ -20,13 +20,11 @@ namespace connection_load { -struct tcp_kernel_info -{ +struct tcp_kernel_info { std::uint32_t state{}, flags{}, rto_ms{}, send_buffer_bytes{}, send_window_bytes{}, receive_window_bytes{}; std::uint32_t congestion_window_bytes{}, slow_start_threshold_bytes{}; std::uint64_t tx_bytes{}, rx_bytes{}, retransmit_bytes{}, out_of_order_bytes{}; - enum class status - { + enum class status { available, unsupported, closed, @@ -44,8 +42,7 @@ inline constexpr std::size_t tcp_kernel_info_required_length = #if defined(__APPLE__) && defined(TCP_CONNECTION_INFO) inline tcp_kernel_info decode_tcp_kernel_info(const tcp_connection_info& info, std::size_t length) noexcept { tcp_kernel_info result; - if (length < tcp_kernel_info_required_length) - { + if (length < tcp_kernel_info_required_length) { result.result = tcp_kernel_info::status::short_response; return result; } @@ -69,15 +66,13 @@ inline tcp_kernel_info decode_tcp_kernel_info(const tcp_connection_info& info, s inline tcp_kernel_info query_tcp_kernel_info(int descriptor) noexcept { tcp_kernel_info result; #if defined(__APPLE__) && defined(TCP_CONNECTION_INFO) - if (descriptor < 0) - { + if (descriptor < 0) { result.result = tcp_kernel_info::status::closed; return result; } tcp_connection_info info{}; socklen_t length = sizeof(info); - if (getsockopt(descriptor, IPPROTO_TCP, TCP_CONNECTION_INFO, &info, &length) != 0) - { + if (getsockopt(descriptor, IPPROTO_TCP, TCP_CONNECTION_INFO, &info, &length) != 0) { result.result = tcp_kernel_info::status::query_failed; result.error = errno; return result; @@ -90,8 +85,7 @@ inline tcp_kernel_info query_tcp_kernel_info(int descriptor) noexcept { } // Single IO-worker ownership. No per-session allocation or work in disabled mode. -class tcp_kernel_samples -{ +class tcp_kernel_samples { public: static constexpr std::size_t session_capacity = 16, sample_capacity = 8, memory_limit = 32 * 1024; static constexpr std::size_t head_sample_capacity = 4, tail_sample_capacity = 4; @@ -106,12 +100,10 @@ class tcp_kernel_samples } void release(identity owner) noexcept { - if (!data_ || frozen_) - { + if (!data_ || frozen_) { return; } - if (auto* value = find(owner)) - { + if (auto* value = find(owner)) { *value = {}; value->ever_used = true; ++released_; @@ -119,10 +111,8 @@ class tcp_kernel_samples } void prune(identity owner, bool eligible, std::uint64_t completions) noexcept { - if (data_ && !frozen_) - { - if (auto* value = find(owner); value && (!eligible || value->completions != completions)) - { + if (data_ && !frozen_) { + if (auto* value = find(owner); value && (!eligible || value->completions != completions)) { release(owner); } } @@ -131,17 +121,13 @@ class tcp_kernel_samples template void sample(identity owner, std::int64_t time_us, std::int64_t idle_us, std::uint64_t completions, std::uint64_t next_receive, std::uint64_t next_send, Query query) { - if (!data_ || frozen_ || idle_us < inactivity_us) - { + if (!data_ || frozen_ || idle_us < inactivity_us) { return; } auto* value = find(owner); - if (!value) - { - for (auto& candidate : data_->slots) - { - if (!candidate.used) - { + if (!value) { + for (auto& candidate : data_->slots) { + if (!candidate.used) { value = &candidate; reused_ += value->ever_used; value->used = value->ever_used = true; @@ -150,8 +136,7 @@ class tcp_kernel_samples } } } - if (!value) - { + if (!value) { ++omitted_; return; // Persistent stalls keep their slots; never query omitted identities. } @@ -184,15 +169,12 @@ class tcp_kernel_samples << head_sample_capacity << ",\"tail_sample_capacity\":" << tail_sample_capacity << ",\"sample_phase\":\"periodic_io_worker\",\"time_unit\":\"microseconds_since_process_start\"," "\"samples\":["; - if (value) - { + if (value) { const auto size = (std::min)(value->total, std::uint64_t{sample_capacity}); const auto head_size = (std::min)(size, std::uint64_t{head_sample_capacity}); const auto tail_size = size - head_size; - for (std::uint64_t index = 0; index < size; ++index) - { - if (index) - { + for (std::uint64_t index = 0; index < size; ++index) { + if (index) { out << ','; } const auto ordinal = index < head_size ? index : value->total - tail_size + index - head_size; @@ -201,8 +183,7 @@ class tcp_kernel_samples out << "{\"sample_ordinal\":" << ordinal + 1 << ",\"sample_time_us\":" << event.time_us << ",\"no_completion_age_us\":" << event.idle_us << ",\"next_receive\":" << event.next_receive << ",\"next_send\":" << event.next_send << ",\"tcp_info\":"; - if (info.result == tcp_kernel_info::status::available) - { + if (info.result == tcp_kernel_info::status::available) { out << "{\"state\":" << info.state << ",\"flags\":" << info.flags << ",\"rto_ms\":" << info.rto_ms << ",\"send_buffer_bytes\":" << info.send_buffer_bytes << ",\"send_window_bytes\":" << info.send_window_bytes @@ -212,36 +193,29 @@ class tcp_kernel_samples << ",\"tx_bytes\":" << info.tx_bytes << ",\"rx_bytes\":" << info.rx_bytes << ",\"retransmit_bytes\":" << info.retransmit_bytes << ",\"out_of_order_bytes\":" << info.out_of_order_bytes << '}'; - } - else - { + } else { const char* reason = info.result == tcp_kernel_info::status::unsupported ? "unsupported_platform" : info.result == tcp_kernel_info::status::closed ? "socket_closed" : info.result == tcp_kernel_info::status::short_response ? "short_tcp_connection_info" : "getsockopt_failed"; out << "null,\"unavailable_reason\":\"" << reason << '\"'; - if (info.result == tcp_kernel_info::status::query_failed) - { + if (info.result == tcp_kernel_info::status::query_failed) { out << ",\"error\":" << info.error; } } out << '}'; } out << "],\"total_samples\":" << value->total << ",\"overwritten_samples\":" << value->total - size; - } - else - { + } else { out << "],\"total_samples\":0,\"overwritten_samples\":0"; } } void write_summary(std::ostream& out) const { std::size_t retained{}; - if (data_) - { - for (const auto& value : data_->slots) - { + if (data_) { + for (const auto& value : data_->slots) { retained += value.used; } } @@ -264,34 +238,29 @@ class tcp_kernel_samples : head_sample_capacity + (ordinal - head_sample_capacity) % tail_sample_capacity; } - struct event - { + struct event { std::int64_t time_us{}, idle_us{}; std::uint64_t next_receive{}, next_send{}; tcp_kernel_info info; }; - struct slot - { + struct slot { identity owner{}; std::array events{}; std::uint64_t total{}, completions{}; bool used{}, ever_used{}; }; - struct storage - { + struct storage { std::array slots{}; }; static_assert(sizeof(storage) <= memory_limit); slot* find(identity owner) const noexcept { - for (auto& value : data_->slots) - { + for (auto& value : data_->slots) { if (value.used && value.owner.run == owner.run && value.owner.client == owner.client && - value.owner.connection == owner.connection) - { + value.owner.connection == owner.connection) { return &value; } } diff --git a/benchmarks/connections_ports.hpp b/benchmarks/connections_ports.hpp index 3d1462c..3b3f1a6 100644 --- a/benchmarks/connections_ports.hpp +++ b/benchmarks/connections_ports.hpp @@ -15,13 +15,11 @@ namespace connection_load { -struct ephemeral_port_range -{ +struct ephemeral_port_range { unsigned first{}, last{}; }; -struct port_resources -{ +struct port_resources { arknet::benchmark::resource_metric ephemeral_ports; std::string range_source = "unavailable", range_scope = "unavailable"; arknet::benchmark::resource_metric tcp_time_wait; @@ -34,8 +32,7 @@ using arknet::benchmark::process_resource_detail::system_failure; using arknet::benchmark::process_resource_detail::unavailable; inline auto checked_range(std::int64_t first, std::int64_t last) { - if (first < 1 || last > 65535 || first > last) - { + if (first < 1 || last > 65535 || first > last) { return unavailable("Invalid ephemeral port range returned by OS query"); } return arknet::benchmark::resource_metric{ @@ -46,8 +43,7 @@ inline auto parse_port_range(const std::string& text) { std::istringstream input(text); std::int64_t first{}, last{}; std::string extra; - if (!(input >> first >> last) || (input >> extra)) - { + if (!(input >> first >> last) || (input >> extra)) { return unavailable("Malformed ip_local_port_range query result"); } return checked_range(first, last); @@ -58,40 +54,32 @@ inline auto parse_time_wait(const std::string& text) { std::string line; std::optional count; bool found_tcp = false; - while (std::getline(input, line)) - { + while (std::getline(input, line)) { std::istringstream fields(line); std::string protocol, key, value; fields >> protocol; - if (protocol != "TCP:") - { + if (protocol != "TCP:") { continue; } - if (found_tcp) - { + if (found_tcp) { return unavailable("Duplicate TCP entry in /proc/net/sockstat"); } found_tcp = true; - while (fields >> key) - { - if (!(fields >> value)) - { + while (fields >> key) { + if (!(fields >> value)) { return unavailable("Truncated TCP entry in /proc/net/sockstat"); } - if (key == "tw") - { + if (key == "tw") { std::uint64_t parsed{}; const auto [end, error] = std::from_chars(value.data(), value.data() + value.size(), parsed); - if (count || error != std::errc{} || end != value.data() + value.size()) - { + if (count || error != std::errc{} || end != value.data() + value.size()) { return unavailable("Invalid TCP tw entry in /proc/net/sockstat"); } count = parsed; } } } - if (!count) - { + if (!count) { return unavailable("TCP tw entry unavailable in /proc/net/sockstat"); } return arknet::benchmark::resource_metric{count, {}}; @@ -99,20 +87,17 @@ inline auto parse_time_wait(const std::string& text) { inline arknet::benchmark::resource_metric read_query_file(const char* path) { const auto file = std::unique_ptr(std::fopen(path, "rb"), &std::fclose); - if (!file) - { + if (!file) { const auto error = errno; return unavailable(system_failure(("fopen(" + std::string(path) + ")").c_str(), error)); } char buffer[4096]; const auto size = std::fread(buffer, 1, sizeof(buffer), file.get()); - if (std::ferror(file.get())) - { + if (std::ferror(file.get())) { const auto error = errno; return unavailable(system_failure(("read(" + std::string(path) + ")").c_str(), error)); } - if (size == sizeof(buffer)) - { + if (size == sizeof(buffer)) { return unavailable("OS query exceeded the 4096-byte diagnostic bound"); } return {std::string(buffer, size), {}}; @@ -122,17 +107,14 @@ template auto read_macos_range(Query query) { int first{}, last{}; for (const auto& [name, value] : - {std::pair{"net.inet.ip.portrange.first", &first}, std::pair{"net.inet.ip.portrange.last", &last}}) - { + {std::pair{"net.inet.ip.portrange.first", &first}, std::pair{"net.inet.ip.portrange.last", &last}}) { std::size_t length = sizeof(*value); - if (query(name, value, &length) != 0) - { + if (query(name, value, &length) != 0) { const auto error = errno; return unavailable( system_failure(("sysctlbyname(" + std::string(name) + ")").c_str(), error)); } - if (length != sizeof(*value)) - { + if (length != sizeof(*value)) { return unavailable( "Unexpected integer size from sysctlbyname(" + std::string(name) + ")"); } diff --git a/benchmarks/connections_protocol.hpp b/benchmarks/connections_protocol.hpp index a04e889..bed96ba 100644 --- a/benchmarks/connections_protocol.hpp +++ b/benchmarks/connections_protocol.hpp @@ -15,8 +15,7 @@ namespace connection_load { constexpr std::size_t header_bytes = 64; constexpr std::string_view magic = "ARKCON01"; -enum class frame_kind : std::uint64_t -{ +enum class frame_kind : std::uint64_t { hello = 1, hello_ack, start, @@ -26,31 +25,27 @@ enum class frame_kind : std::uint64_t finish_ack }; -struct identity -{ +struct identity { std::uint64_t run{}, client{}, connection{}; bool operator==(const identity&) const = default; }; -struct frame_header -{ +struct frame_header { frame_kind kind{}; identity owner{}; std::uint64_t sequence{}, body_bytes{}; }; inline void write_integer(std::string& bytes, std::size_t offset, std::uint64_t value) { - for (std::size_t index = 0; index < 8; ++index) - { + for (std::size_t index = 0; index < 8; ++index) { bytes[offset + index] = static_cast(value >> (index * 8)); } } inline std::uint64_t read_integer(std::string_view bytes, std::size_t offset) { std::uint64_t value{}; - for (std::size_t index = 0; index < 8; ++index) - { + for (std::size_t index = 0; index < 8; ++index) { value |= std::uint64_t(static_cast(bytes[offset + index])) << (index * 8); } return value; @@ -61,8 +56,7 @@ inline char body_byte(const identity& owner, std::uint64_t sequence, std::size_t } inline std::string make_frame(frame_kind kind, identity owner, std::uint64_t sequence = 0, std::size_t body = 0) { - if (body > (std::numeric_limits::max)() - header_bytes) - { + if (body > (std::numeric_limits::max)() - header_bytes) { throw std::length_error("frame size overflow"); } std::string result(header_bytes + body, '\0'); @@ -73,34 +67,28 @@ inline std::string make_frame(frame_kind kind, identity owner, std::uint64_t seq write_integer(result, 32, owner.connection); write_integer(result, 40, sequence); write_integer(result, 48, body); - for (std::size_t index = 0; index < body; ++index) - { + for (std::size_t index = 0; index < body; ++index) { result[header_bytes + index] = body_byte(owner, sequence, index); } return result; } inline bool decode(std::string_view bytes, frame_header& header) { - if (bytes.size() < header_bytes || bytes.substr(0, magic.size()) != magic || read_integer(bytes, 56) != 0) - { + if (bytes.size() < header_bytes || bytes.substr(0, magic.size()) != magic || read_integer(bytes, 56) != 0) { return false; } const auto kind = read_integer(bytes, 8); - if (kind < 1 || kind > 7 || read_integer(bytes, 48) != bytes.size() - header_bytes) - { + if (kind < 1 || kind > 7 || read_integer(bytes, 48) != bytes.size() - header_bytes) { return false; } header = {static_cast(kind), {read_integer(bytes, 16), read_integer(bytes, 24), read_integer(bytes, 32)}, read_integer(bytes, 40), read_integer(bytes, 48)}; - if (header.kind != frame_kind::data && header.body_bytes != 0) - { + if (header.kind != frame_kind::data && header.body_bytes != 0) { return false; } - for (std::size_t index = 0; index < header.body_bytes; ++index) - { - if (bytes[header_bytes + index] != body_byte(header.owner, header.sequence, index)) - { + for (std::size_t index = 0; index < header.body_bytes; ++index) { + if (bytes[header_bytes + index] != body_byte(header.owner, header.sequence, index)) { return false; } } @@ -109,14 +97,12 @@ inline bool decode(std::string_view bytes, frame_header& header) { // Eight subdivisions per power of two. Values are rounded up to the bucket's // integer microsecond upper bound; counters can be added across processes. -class latency_distribution -{ +class latency_distribution { public: static constexpr std::size_t bucket_count = 513; static std::size_t bucket(std::uint64_t microseconds) noexcept { - if (!microseconds) - { + if (!microseconds) { return 0; } const auto exponent = static_cast(std::bit_width(microseconds) - 1); @@ -127,15 +113,13 @@ class latency_distribution } static std::uint64_t upper_bound(std::size_t index) noexcept { - if (!index) - { + if (!index) { return 0; } const auto exponent = (index - 1) / 8; const auto subdivision = (index - 1) % 8; const auto base = std::uint64_t{1} << exponent; - if (exponent < 3) - { + if (exponent < 3) { return base + (base * (subdivision + 1) + 7) / 8 - 1; } const auto extension = base / 8 * (subdivision + 1); @@ -154,8 +138,7 @@ class latency_distribution std::uint64_t total() const noexcept { std::uint64_t result{}; - for (const auto& value : counts_) - { + for (const auto& value : counts_) { result += value.load(); } return result; @@ -164,11 +147,9 @@ class latency_distribution std::uint64_t percentile(unsigned percent) const noexcept { const auto target = (total() / 100) * percent + ((total() % 100) * percent + 99) / 100; std::uint64_t cumulative{}; - for (std::size_t index = 0; index < bucket_count; ++index) - { + for (std::size_t index = 0; index < bucket_count; ++index) { cumulative += count(index); - if (cumulative >= target) - { + if (cumulative >= target) { return upper_bound(index); } } diff --git a/benchmarks/connections_resources.hpp b/benchmarks/connections_resources.hpp index 9489ef2..d44d513 100644 --- a/benchmarks/connections_resources.hpp +++ b/benchmarks/connections_resources.hpp @@ -12,16 +12,14 @@ namespace connection_load { -struct resource_snapshot -{ +struct resource_snapshot { arknet::benchmark::process_resources metrics; double elapsed_seconds{}; std::optional cpu_interval_seconds; }; // Keep independent observers for periodic intervals and the entire run. -class resource_observer -{ +class resource_observer { public: explicit resource_observer(std::chrono::steady_clock::time_point began) : began_(began) @@ -36,8 +34,7 @@ class resource_observer auto metrics = sampler_.sample(); const auto elapsed = std::chrono::duration(metrics.monotonic_time - began_).count(); std::optional interval; - if (metrics.cpu_percent.value) - { + if (metrics.cpu_percent.value) { interval = std::chrono::duration(metrics.monotonic_time - previous_).count(); } previous_ = metrics.monotonic_time; @@ -54,18 +51,13 @@ class resource_observer inline void write_resource_string(std::ostream& output, std::string_view value) { constexpr char hex[] = "0123456789abcdef"; output << '"'; - for (const unsigned char character : value) - { - if (character == '"' || character == '\\') - { + for (const unsigned char character : value) { + if (character == '"' || character == '\\') { output << '\\'; } - if (character < 32) - { + if (character < 32) { output << "\\u00" << hex[character >> 4] << hex[character & 15]; - } - else - { + } else { output << static_cast(character); } } @@ -74,13 +66,10 @@ inline void write_resource_string(std::ostream& output, std::string_view value) inline void write_port_resources(std::ostream& output, const port_resources& sample) { output << "{\"ephemeral_ports\":"; - if (sample.ephemeral_ports.value) - { + if (sample.ephemeral_ports.value) { output << "{\"first\":" << sample.ephemeral_ports.value->first << ",\"last\":" << sample.ephemeral_ports.value->last << '}'; - } - else - { + } else { output << "null"; } output << ",\"range_source\":"; @@ -88,12 +77,9 @@ inline void write_port_resources(std::ostream& output, const port_resources& sam output << ",\"range_scope\":"; write_resource_string(output, sample.range_scope); output << ",\"tcp_time_wait\":"; - if (sample.tcp_time_wait.value) - { + if (sample.tcp_time_wait.value) { output << *sample.tcp_time_wait.value; - } - else - { + } else { output << "null"; } output << ",\"time_wait_source\":"; @@ -109,16 +95,13 @@ inline void write_port_resources(std::ostream& output, const port_resources& sam // First failure is sampled at the connect callback, before cleanup changes the // socket population. Final output does not turn this into an after-cleanup count. -class connect_failure_observer -{ +class connect_failure_observer { public: template void capture(int error, std::string_view category, std::uint64_t established, Query query) noexcept { - try - { + try { const std::lock_guard lock(mutex_); - if (attempted_) - { + if (attempted_) { return; } attempted_ = true; @@ -126,9 +109,8 @@ class connect_failure_observer established_ = established; category_ = category; sample_ = query(); + } catch (...) { } - catch (...) - {} } void capture(int error, std::string_view category, std::uint64_t established) noexcept { @@ -143,8 +125,7 @@ class connect_failure_observer void write_event(std::ostream& output, std::string_view role, std::uint64_t run, std::uint64_t client, bool string_ids = false) const { const std::lock_guard lock(mutex_); - if (!attempted_) - { + if (!attempted_) { return; } output << "{\"event\":\"connect_resource_failure\",\"role\":"; @@ -158,8 +139,7 @@ class connect_failure_observer private: void write_fields_unlocked(std::ostream& output) const { output << ",\"connect_failure_resources\":"; - if (!attempted_) - { + if (!attempted_) { output << "null"; return; } @@ -167,12 +147,9 @@ class connect_failure_observer << ",\"error_code\":" << error_ << ",\"error_category\":"; write_resource_string(output, category_); output << ",\"network_resources\":"; - if (sample_) - { + if (sample_) { write_port_resources(output, *sample_); - } - else - { + } else { output << "{\"unavailable\":\"Failure resource snapshot could not be captured\"}"; } output << '}'; @@ -191,12 +168,9 @@ inline void write_resource_fields(std::ostream& output, const resource_snapshot& output << ','; write_resource_string(output, name); output << ':'; - if (value) - { + if (value) { output << *value; - } - else - { + } else { output << "null"; } }; @@ -214,12 +188,10 @@ inline void write_resource_fields(std::ostream& output, const resource_snapshot& << ",\"resource_unavailable\":{"; bool first = true; const auto reason = [&](std::string_view name, const auto& value) { - if (value.value) - { + if (value.value) { return; } - if (!first) - { + if (!first) { output << ','; } first = false; @@ -235,10 +207,8 @@ inline void write_resource_fields(std::ostream& output, const resource_snapshot& reason("nofile_hard", sample.metrics.nofile_hard); reason("handle_count", sample.metrics.handle_count); reason("thread_count", sample.metrics.thread_count); - if (!sample.cpu_interval_seconds) - { - if (!first) - { + if (!sample.cpu_interval_seconds) { + if (!first) { output << ','; } output << "\"resource_cpu_interval_s\":\"A valid interval CPU percentage is unavailable\""; diff --git a/benchmarks/connections_socket_observer.hpp b/benchmarks/connections_socket_observer.hpp index f02a723..4503407 100644 --- a/benchmarks/connections_socket_observer.hpp +++ b/benchmarks/connections_socket_observer.hpp @@ -25,8 +25,7 @@ namespace connection_load { -struct socket_observation -{ +struct socket_observation { int poll_result{-1}, poll_error{}, readable_bytes{-1}, readable_error{}, peek_bytes{-1}, peek_error{}; short events{}; std::array peek{}; @@ -39,8 +38,7 @@ struct socket_observation inline socket_observation observe_socket(int descriptor) noexcept { socket_observation result; #if !defined(_WIN32) - if (descriptor < 0) - { + if (descriptor < 0) { result.poll_error = result.readable_error = result.peek_error = EBADF; result.tcp = query_tcp_kernel_info(-1); return result; @@ -50,12 +48,9 @@ inline socket_observation observe_socket(int descriptor) noexcept { result.poll_error = result.poll_result < 0 ? errno : 0; result.events = item.revents; int available{}; - if (::ioctl(descriptor, FIONREAD, &available) == 0) - { + if (::ioctl(descriptor, FIONREAD, &available) == 0) { result.readable_bytes = available; - } - else - { + } else { result.readable_error = errno; } const auto bytes = ::recv(descriptor, result.peek.data(), result.peek.size(), MSG_PEEK | MSG_DONTWAIT); @@ -66,15 +61,12 @@ inline socket_observation observe_socket(int descriptor) noexcept { static_assert(sizeof(raw) <= result.tcp_raw.size()); socklen_t length = sizeof(raw); result.tcp_requested = sizeof(raw); - if (::getsockopt(descriptor, IPPROTO_TCP, TCP_CONNECTION_INFO, &raw, &length) == 0) - { + if (::getsockopt(descriptor, IPPROTO_TCP, TCP_CONNECTION_INFO, &raw, &length) == 0) { result.tcp_returned = length; result.tcp = decode_tcp_kernel_info(raw, length); const auto* begin = reinterpret_cast(&raw); std::copy_n(begin, (std::min)(std::size_t(length), sizeof(raw)), result.tcp_raw.begin()); - } - else - { + } else { result.tcp.result = tcp_kernel_info::status::query_failed; result.tcp.error = errno; } @@ -85,8 +77,7 @@ inline socket_observation observe_socket(int descriptor) noexcept { return result; } -struct observation_clock_anchor -{ +struct observation_clock_anchor { std::int64_t steady_before_ns{}, unix_ns{}, steady_after_ns{}; }; @@ -107,14 +98,12 @@ inline observation_clock_anchor observation_anchor() noexcept { // Single IO worker publishes snapshots; the existing controller observes pinned // descriptors. The mutex also serializes dup retirement before socket.close(). -class socket_observer -{ +class socket_observer { public: static constexpr std::size_t session_capacity = 16, sample_capacity = 8; static constexpr std::int64_t interval_us = 100000; - struct snapshot - { + struct snapshot { identity owner{}; std::int64_t time_us{}, idle_us{}; std::uint64_t completions{}, next_receive{}, next_send{}, input_bytes{}; @@ -122,8 +111,7 @@ class socket_observer std::string local, remote; }; - struct token - { + struct token { identity owner{}; std::uint64_t generation{}; }; @@ -132,42 +120,34 @@ class socket_observer : data_(enabled ? std::make_unique() : nullptr) {} ~socket_observer() { - if (data_) - { - for (auto& value : data_->slots) - { + if (data_) { + for (auto& value : data_->slots) { retire(value); } } } token publish(int descriptor, snapshot state) { - if (!data_) - { + if (!data_) { return {}; } std::lock_guard lock(mutex_); auto* value = find(state.owner); const bool created = !value; - if (!value) - { - for (auto& candidate : data_->slots) - { - if (!candidate.generation) - { + if (!value) { + for (auto& candidate : data_->slots) { + if (!candidate.generation) { value = &candidate; break; } } - if (!value) - { + if (!value) { ++omitted_; return {}; } #if !defined(_WIN32) value->descriptor = ::dup(descriptor); - if (value->descriptor < 0) - { + if (value->descriptor < 0) { ++pin_failures_; return {}; } @@ -184,14 +164,12 @@ class socket_observer // Recovered sessions free capacity; histories for still-stalled/closed peers // survive final cleanup. Never confuse a recycled fd/slot with its predecessor. void prune(identity owner, bool eligible, std::uint64_t completions) { - if (!data_) - { + if (!data_) { return; } std::lock_guard lock(mutex_); if (auto* value = find(owner); - value && value->descriptor >= 0 && (!eligible || value->state.completions != completions)) - { + value && value->descriptor >= 0 && (!eligible || value->state.completions != completions)) { retire(*value); *value = {}; ++released_; @@ -199,11 +177,9 @@ class socket_observer } void close(identity owner) { - if (data_) - { + if (data_) { std::lock_guard lock(mutex_); - if (auto* value = find(owner); value && value->descriptor >= 0) - { + if (auto* value = find(owner); value && value->descriptor >= 0) { // This runs before the caller cancels/closes the original socket. value->before_cancel = observation_anchor(); retire(*value); @@ -212,27 +188,22 @@ class socket_observer } void complete(token expected, std::int64_t time_us) { - if (data_) - { + if (data_) { std::lock_guard lock(mutex_); - if (auto* value = find(expected.owner); value && value->generation == expected.generation) - { + if (auto* value = find(expected.owner); value && value->generation == expected.generation) { value->probe_completed = time_us; } } } void sample(std::int64_t time_us, std::int64_t marker_completed) { - if (!data_) - { + if (!data_) { return; } std::lock_guard lock(mutex_); - for (auto& value : data_->slots) - { + for (auto& value : data_->slots) { if (value.descriptor >= 0 && value.count < sample_capacity && - (!value.count || time_us - value.samples[value.count - 1].time_us >= interval_us)) - { + (!value.count || time_us - value.samples[value.count - 1].time_us >= interval_us)) { auto& entry = value.samples[value.count++]; entry.time_us = time_us; entry.marker_completed = marker_completed; @@ -246,14 +217,11 @@ class socket_observer } void write_json(std::ostream& out, const std::string& role, std::int64_t clock_origin_us = 0) const { - if (!data_) - { + if (!data_) { return; } - for (const auto& value : data_->slots) - { - for (std::size_t index = 0; index < value.count; ++index) - { + for (const auto& value : data_->slots) { + for (std::size_t index = 0; index < value.count; ++index) { const auto& entry = value.samples[index]; const auto& q = entry.query; out << "{\"event\":\"socket_observation\",\"schema\":1,\"role\":" << std::quoted(role) @@ -283,8 +251,7 @@ class socket_observer << ",\"tcp_raw_hex\":\""; hex(out, q.tcp_raw.data(), (std::min)(std::size_t(q.tcp_returned), q.tcp_raw.size())); out << "\",\"tcp_info\":"; - if (q.tcp.result == tcp_kernel_info::status::available) - { + if (q.tcp.result == tcp_kernel_info::status::available) { out << "{\"state\":" << q.tcp.state << ",\"flags\":" << q.tcp.flags << ",\"rto_ms\":" << q.tcp.rto_ms << ",\"congestion_window_bytes\":" << q.tcp.congestion_window_bytes @@ -293,15 +260,12 @@ class socket_observer << ",\"receive_window_bytes\":" << q.tcp.receive_window_bytes << ",\"tx_bytes\":" << q.tcp.tx_bytes << ",\"rx_bytes\":" << q.tcp.rx_bytes << ",\"retransmit_bytes\":" << q.tcp.retransmit_bytes << '}'; - } - else - { + } else { out << "null"; } out << "}\n"; } - if (value.before_cancel.unix_ns) - { + if (value.before_cancel.unix_ns) { out << "{\"event\":\"socket_observer_before_cancel\",\"schema\":1,\"role\":" << std::quoted(role) << ",\"run_id\":" << value.state.owner.run << ",\"client_id\":" << value.state.owner.client << ",\"connection_id\":" << value.state.owner.connection << ",\"generation\":" << value.generation @@ -325,16 +289,14 @@ class socket_observer } private: - struct sample_entry - { + struct sample_entry { snapshot state; socket_observation query; std::int64_t time_us{}, marker_completed{}, probe_completed{}, query_completed_steady_ns{}; observation_clock_anchor anchor; }; - struct slot - { + struct slot { int descriptor{-1}; std::uint64_t generation{}; snapshot state; @@ -344,18 +306,15 @@ class socket_observer std::array samples{}; }; - struct storage - { + struct storage { std::array slots{}; }; static_assert(sizeof(storage) < 128 * 1024); slot* find(identity owner) { - for (auto& value : data_->slots) - { - if (value.generation && value.state.owner == owner) - { + for (auto& value : data_->slots) { + if (value.generation && value.state.owner == owner) { return &value; } } @@ -364,8 +323,7 @@ class socket_observer static void retire(slot& value) { #if !defined(_WIN32) - if (value.descriptor >= 0) - { + if (value.descriptor >= 0) { ::close(value.descriptor); value.descriptor = -1; } @@ -381,8 +339,7 @@ class socket_observer static void hex(std::ostream& out, const unsigned char* bytes, std::size_t count) { constexpr char digits[] = "0123456789abcdef"; - for (std::size_t index = 0; index < count; ++index) - { + for (std::size_t index = 0; index < count; ++index) { out << digits[bytes[index] >> 4] << digits[bytes[index] & 15]; } } diff --git a/benchmarks/connections_socket_trace.hpp b/benchmarks/connections_socket_trace.hpp index e43bed8..b08a5db 100644 --- a/benchmarks/connections_socket_trace.hpp +++ b/benchmarks/connections_socket_trace.hpp @@ -32,8 +32,7 @@ using socket_trace_clock = std::chrono::steady_clock; inline constexpr std::size_t socket_handler_reservation = 256; template -struct socket_trace_completion -{ +struct socket_trace_completion { Handler handler; operation_ring* ring; socket_trace_clock::time_point began; @@ -50,8 +49,7 @@ struct socket_trace_completion // Native-control diagnostics only. The unobserved path uses the socket directly. template -class socket_trace_stream -{ +class socket_trace_stream { public: using executor_type = typename Stream::executor_type; @@ -122,8 +120,7 @@ namespace asio { // other association supported by the original Asio composed-operation handler. template class Associator, class Handler, class DefaultCandidate> struct associator, DefaultCandidate> - : Associator -{ + : Associator { static typename Associator::type get( const connection_load::socket_trace_completion& value) noexcept { return Associator::get(value.handler); diff --git a/benchmarks/connections_trace.hpp b/benchmarks/connections_trace.hpp index 3e33ed0..6c95a10 100644 --- a/benchmarks/connections_trace.hpp +++ b/benchmarks/connections_trace.hpp @@ -14,8 +14,7 @@ namespace connection_load { -enum class operation_phase -{ +enum class operation_phase { read_start, read_complete, write_start, @@ -23,8 +22,7 @@ enum class operation_phase }; inline const char* phase_name(operation_phase phase) noexcept { - switch (phase) - { + switch (phase) { case operation_phase::read_start: return "read_start"; case operation_phase::read_complete: @@ -37,44 +35,36 @@ inline const char* phase_name(operation_phase phase) noexcept { return "unknown"; } -struct operation_event -{ +struct operation_event { std::uint64_t ordinal{}, operation_id{}, bytes{}, sequence{}; std::int64_t time_us{}; int error{}; operation_phase phase{}; }; -class operation_ring -{ +class operation_ring { public: static constexpr std::size_t capacity = 16; void record(operation_phase phase, std::int64_t time_us, std::uint64_t bytes, std::uint64_t sequence, int error = 0) noexcept { - if (frozen_) - { + if (frozen_) { return; } const bool read = phase == operation_phase::read_start || phase == operation_phase::read_complete; const bool start = phase == operation_phase::read_start || phase == operation_phase::write_start; auto& pending = read ? read_id_ : write_id_; - if (start) - { - if (pending) - { + if (start) { + if (pending) { ++invalid_transitions_; } pending = ++operations_; - } - else if (!pending) - { + } else if (!pending) { ++invalid_transitions_; } events_[total_ % capacity] = {total_ + 1, pending, bytes, sequence, time_us, error, phase}; ++total_; - if (!start) - { + if (!start) { (read ? read_bytes_ : write_bytes_) += bytes; pending = 0; } @@ -115,11 +105,9 @@ class operation_ring << ",\"pending_read_id\":" << read_id_ << ",\"pending_write_id\":" << write_id_ << ",\"completed_read_bytes\":" << read_bytes_ << ",\"completed_write_bytes\":" << write_bytes_ << ",\"events\":["; - for (std::size_t index = 0; index < size(); ++index) - { + for (std::size_t index = 0; index < size(); ++index) { const auto& event = at(index); - if (index) - { + if (index) { out << ','; } out << "{\"ordinal\":" << event.ordinal << ",\"operation_id\":" << event.operation_id << ",\"phase\":\"" @@ -138,8 +126,7 @@ class operation_ring // Fixed scratch space prevents an oversized record from allocating or emitting // a partial JSON line. Callers serialize only numeric fields and fixed labels. -class trace_record_buffer : public std::streambuf -{ +class trace_record_buffer : public std::streambuf { public: static constexpr std::size_t capacity = 8192; @@ -155,8 +142,7 @@ class trace_record_buffer : public std::streambuf std::array bytes_{}; }; -class trace_output -{ +class trace_output { public: static constexpr std::size_t limit = 256 * 1024; @@ -171,8 +157,7 @@ class trace_output write(out); const auto bytes = record.view(); const auto maximum = summary ? limit : limit - trace_record_buffer::capacity; - if (!out || bytes.size() + 1 > maximum || text_.size() > maximum - bytes.size() - 1) - { + if (!out || bytes.size() + 1 > maximum || text_.size() > maximum - bytes.size() - 1) { truncated_ = true; return false; } @@ -194,8 +179,7 @@ class trace_output bool truncated_{}; }; -class operation_recorder -{ +class operation_recorder { public: static constexpr std::size_t memory_limit = 16 * 1024 * 1024; static constexpr std::size_t scratch_reservation = 32 * 1024; @@ -217,21 +201,17 @@ class operation_recorder , socket_rings_(socket_operations_ && capacity_ ? std::make_unique(capacity_) : nullptr) {} operation_ring* acquire(operation_ring** socket_ring = nullptr) noexcept { - if (socket_ring) - { + if (socket_ring) { *socket_ring = nullptr; } - if (!enabled_ || frozen_) - { + if (!enabled_ || frozen_) { return nullptr; } - if (used_ == capacity_) - { + if (used_ == capacity_) { ++unrecorded_; return nullptr; } - if (socket_ring && socket_operations_) - { + if (socket_ring && socket_operations_) { *socket_ring = &socket_rings_[used_]; } return &rings_[used_++]; @@ -264,12 +244,10 @@ class operation_recorder // Controller thread: allow at most one queued marker even if the IO worker // stops making progress. No marker is posted in disabled mode. bool request_marker() noexcept { - if (!enabled_) - { + if (!enabled_) { return false; } - if (marker_pending_.exchange(true)) - { + if (marker_pending_.exchange(true)) { ++marker_skipped_; return false; } @@ -279,8 +257,7 @@ class operation_recorder // Single IO worker only, like all session ring writes and freeze(). void complete_marker(std::int64_t submitted_us, std::int64_t executed_us) noexcept { - if (!enabled_ || frozen_) - { + if (!enabled_ || frozen_) { return; } markers_[marker_completed_ % marker_capacity] = {submitted_us, executed_us}; @@ -290,11 +267,9 @@ class operation_recorder void freeze() noexcept { frozen_ = true; - for (std::size_t index = 0; index < used_; ++index) - { + for (std::size_t index = 0; index < used_; ++index) { rings_[index].freeze(); - if (socket_operations_) - { + if (socket_operations_) { socket_rings_[index].freeze(); } } @@ -317,11 +292,9 @@ class operation_recorder << (marker_completed_ > marker_capacity ? marker_completed_ - marker_capacity : 0) << ",\"worker_markers\":["; const auto count = (std::min)(marker_completed_, std::uint64_t{marker_capacity}); - for (std::uint64_t index = 0; index < count; ++index) - { + for (std::uint64_t index = 0; index < count; ++index) { const auto& marker = markers_[(marker_completed_ - count + index) % marker_capacity]; - if (index) - { + if (index) { out << ','; } out << "{\"submitted_us\":" << marker.first << ",\"executed_us\":" << marker.second << '}'; diff --git a/benchmarks/coroutine.cpp b/benchmarks/coroutine.cpp index 1505c1c..ef625d9 100644 --- a/benchmarks/coroutine.cpp +++ b/benchmarks/coroutine.cpp @@ -22,8 +22,7 @@ using clock_type = std::chrono::steady_clock; using socket_type = asio::ip::tcp::socket; using namespace std::chrono_literals; -struct options -{ +struct options { std::string model = "shared", execution = "coroutine"; std::string address_family = "ipv4"; std::size_t threads = 1, clients = 16, window = 1, payload = 1024, work = 0; @@ -33,8 +32,7 @@ struct options std::size_t number(std::string_view value) { std::size_t result{}; const auto parsed = std::from_chars(value.data(), value.data() + value.size(), result); - if (parsed.ec != std::errc{} || parsed.ptr != value.data() + value.size()) - { + if (parsed.ec != std::errc{} || parsed.ptr != value.data() + value.size()) { throw std::invalid_argument("invalid integer"); } return result; @@ -43,8 +41,7 @@ std::size_t number(std::string_view value) { double duration(const std::string& value) { std::size_t count{}; const double result = std::stod(value, &count); - if (count != value.size() || !std::isfinite(result)) - { + if (count != value.size() || !std::isfinite(result)) { throw std::invalid_argument("invalid duration"); } return result; @@ -52,70 +49,42 @@ double duration(const std::string& value) { options parse(int argc, char** argv) { options result; - for (int i = 1; i < argc; ++i) - { + for (int i = 1; i < argc; ++i) { const std::string key = argv[i]; - if (key == "--help") - { + if (key == "--help") { std::cout << "arknet_coroutine_benchmark --protocol tcp --io-model shared|sharded " "--execution callback|coroutine " "--io-threads COUNT --clients COUNT --window COUNT --payload BYTES " "--seconds SECONDS --warmup SECONDS --work ITERATIONS --address-family ipv4|ipv6\n"; std::exit(0); } - if (++i == argc) - { + if (++i == argc) { throw std::invalid_argument("missing option value"); } const std::string value = argv[i]; - if (key == "--io-model") - { + if (key == "--io-model") { result.model = value; - } - else if (key == "--address-family") - { + } else if (key == "--address-family") { result.address_family = value; - } - else if (key == "--execution") - { + } else if (key == "--execution") { result.execution = value; - } - else if (key == "--io-threads") - { + } else if (key == "--io-threads") { result.threads = number(value); - } - else if (key == "--clients") - { + } else if (key == "--clients") { result.clients = number(value); - } - else if (key == "--window") - { + } else if (key == "--window") { result.window = number(value); - } - else if (key == "--payload") - { + } else if (key == "--payload") { result.payload = number(value); - } - else if (key == "--work") - { + } else if (key == "--work") { result.work = number(value); - } - else if (key == "--seconds") - { + } else if (key == "--seconds") { result.seconds = duration(value); - } - else if (key == "--warmup") - { + } else if (key == "--warmup") { result.warmup = duration(value); - } - else if (key == "--protocol" && value == "tcp") - { - } - else if (key == "--certs") - { - } - else - { + } else if (key == "--protocol" && value == "tcp") { + } else if (key == "--certs") { + } else { throw std::invalid_argument("unsupported option"); } } @@ -125,34 +94,29 @@ options parse(int argc, char** argv) { result.threads > 64 || result.clients < 1 || result.clients > 64 || result.window < 1 || result.window > 64 || result.payload < 16 || result.payload > 65507 || result.work > 10000000 || result.seconds <= 0 || result.seconds > 600 || result.warmup < 0 || result.warmup > 600 || - result.clients * result.window * result.payload > 256 * 1024 * 1024) - { + result.clients * result.window * result.payload > 256 * 1024 * 1024) { throw std::invalid_argument("option out of range"); } return result; } -struct phase_result -{ +struct phase_result { std::uint64_t messages{}, observations{}; double elapsed{}; std::vector samples; }; -struct client_result -{ +struct client_result { phase_result warmup, measured; }; -struct shared_state -{ +struct shared_state { std::atomic ready{}, errors{}; std::atomic begin{}, stopping{}; clock_type::time_point started; }; -struct client_traffic -{ +struct client_traffic { client_traffic(options settings, std::size_t id, shared_state& state) : settings(std::move(settings)) , id(id) @@ -172,33 +136,27 @@ struct client_traffic clock_type::time_point began; bool next() { - if (state.stopping) - { + if (state.stopping) { return false; } - while (phase != 2) - { + while (phase != 2) { const auto start = state.started + std::chrono::duration_cast( std::chrono::duration(phase == 0 ? 0 : settings.warmup)); const auto deadline = start + std::chrono::duration_cast( std::chrono::duration(phase == 0 ? settings.warmup : settings.seconds)); - if (clock_type::now() < deadline) - { + if (clock_type::now() < deadline) { break; } (phase == 0 ? result.warmup : result.measured).elapsed = std::chrono::duration(clock_type::now() - start).count(); ++phase; } - if (phase == 2) - { + if (phase == 2) { return false; } - for (std::size_t item = 0; item < settings.window; ++item, ++sequence) - { - for (std::size_t byte = 0; byte < settings.payload; ++byte) - { + for (std::size_t item = 0; item < settings.window; ++item, ++sequence) { + for (std::size_t byte = 0; byte < settings.payload; ++byte) { sent[item * settings.payload + byte] = static_cast((sequence * 31 + id * 17 + byte) & 255); } } @@ -207,23 +165,18 @@ struct client_traffic } void received_batch() { - if (sent != received) - { + if (sent != received) { ++state.errors; } auto& stats = phase == 0 ? result.warmup : result.measured; stats.messages += settings.window; const auto latency = std::chrono::duration(clock_type::now() - began).count(); ++stats.observations; - if (stats.samples.size() < 8192) - { + if (stats.samples.size() < 8192) { stats.samples.push_back(latency); - } - else - { + } else { const auto index = std::uniform_int_distribution(0, stats.observations - 1)(random); - if (index < stats.samples.size()) - { + if (index < stats.samples.size()) { stats.samples[index] = latency; } } @@ -231,14 +184,12 @@ struct client_traffic }; void process_batch(std::size_t work, std::size_t payload, std::string_view data) { - for (std::size_t offset = 0; offset < data.size(); offset += payload) - { + for (std::size_t offset = 0; offset < data.size(); offset += payload) { benchmark_work(work, data.substr(offset, payload)); } } -struct callback_session : std::enable_shared_from_this -{ +struct callback_session : std::enable_shared_from_this { callback_session(std::shared_ptr socket, const options& settings, shared_state& state) : socket(std::move(socket)) , state(state) @@ -253,8 +204,7 @@ struct callback_session : std::enable_shared_from_this std::promise completed; void finish(arknet::error_code error) { - if (!state.stopping && error != asio::error::eof) - { + if (!state.stopping && error != asio::error::eof) { ++state.errors; } completed.set_value(); @@ -263,12 +213,9 @@ struct callback_session : std::enable_shared_from_this void start() { arknet::error_code error; socket->set_option(asio::ip::tcp::no_delay(true), error); - if (error) - { + if (error) { finish(error); - } - else - { + } else { read(); } } @@ -276,20 +223,16 @@ struct callback_session : std::enable_shared_from_this void read() { asio::async_read( *socket, asio::buffer(data), [self = shared_from_this()](arknet::error_code error, std::size_t) { - if (error) - { + if (error) { self->finish(error); return; } process_batch(self->work, self->payload, self->data); asio::async_write( *self->socket, asio::buffer(self->data), [self](arknet::error_code error, std::size_t) { - if (error) - { + if (error) { self->finish(error); - } - else - { + } else { self->read(); } }); @@ -297,8 +240,7 @@ struct callback_session : std::enable_shared_from_this } }; -struct callback_acceptor : std::enable_shared_from_this -{ +struct callback_acceptor : std::enable_shared_from_this { callback_acceptor(benchmark_runtime& runtime, asio::ip::tcp::acceptor& listener, const options& settings, std::vector>& sessions, std::vector>& sockets, shared_state& state) @@ -320,10 +262,8 @@ struct callback_acceptor : std::enable_shared_from_this void next(std::size_t index = 0) { auto socket = std::make_shared(asio::make_strand(runtime.context_at(index))); listener.async_accept(*socket, [self = shared_from_this(), socket, index](arknet::error_code error) { - if (error) - { - if (!self->state.stopping) - { + if (error) { + if (!self->state.stopping) { ++self->state.errors; } self->completed.set_value(); @@ -338,8 +278,7 @@ struct callback_acceptor : std::enable_shared_from_this } }; -struct callback_client : std::enable_shared_from_this -{ +struct callback_client : std::enable_shared_from_this { callback_client(std::shared_ptr socket, const options& settings, std::size_t id, shared_state& state) : socket(std::move(socket)) , traffic(settings, id, state) @@ -351,8 +290,7 @@ struct callback_client : std::enable_shared_from_this std::promise completed; void fail(arknet::error_code error) { - if (!traffic.state.stopping) - { + if (!traffic.state.stopping) { ++traffic.state.errors; } completed.set_exception(std::make_exception_ptr(arknet::system_error(error))); @@ -360,63 +298,51 @@ struct callback_client : std::enable_shared_from_this void start(asio::ip::tcp::endpoint endpoint) { socket->async_connect(endpoint, [self = shared_from_this()](arknet::error_code error) { - if (!error) - { + if (!error) { self->socket->set_option(asio::ip::tcp::no_delay(true), error); } ++self->traffic.state.ready; - if (error) - { + if (error) { self->fail(error); - } - else - { + } else { self->wait(); } }); } void wait() { - if (traffic.state.stopping) - { + if (traffic.state.stopping) { completed.set_value(client_result{}); return; } - if (traffic.state.begin) - { + if (traffic.state.begin) { next(); return; } gate.expires_after(1ms); gate.async_wait([self = shared_from_this()](arknet::error_code error) { - if (error) - { + if (error) { self->fail(error); - } - else - { + } else { self->wait(); } }); } void next() { - if (!traffic.next()) - { + if (!traffic.next()) { completed.set_value(std::move(traffic.result)); return; } asio::async_write( *socket, asio::buffer(traffic.sent), [self = shared_from_this()](arknet::error_code error, std::size_t) { - if (error) - { + if (error) { self->fail(error); return; } asio::async_read( *self->socket, asio::buffer(self->traffic.received), [self](arknet::error_code error, std::size_t) { - if (error) - { + if (error) { self->fail(error); return; } @@ -431,19 +357,14 @@ asio::awaitable serve(std::shared_ptr socket, options setting socket->set_option(asio::ip::tcp::no_delay(true)); // Read complete batches before echoing so opposing writes cannot stall each other. std::string data(settings.payload * settings.window, '\0'); - try - { - for (;;) - { + try { + for (;;) { co_await asio::async_read(*socket, asio::buffer(data), asio::use_awaitable); process_batch(settings.work, settings.payload, data); co_await asio::async_write(*socket, asio::buffer(data), asio::use_awaitable); } - } - catch (const arknet::system_error& error) - { - if (!state.stopping && error.code() != asio::error::eof) - { + } catch (const arknet::system_error& error) { + if (!state.stopping && error.code() != asio::error::eof) { ++state.errors; } } @@ -451,21 +372,16 @@ asio::awaitable serve(std::shared_ptr socket, options setting asio::awaitable accept(benchmark_runtime& runtime, asio::ip::tcp::acceptor& listener, options settings, std::vector>& sessions, std::vector>& sockets, shared_state& state) { - try - { - for (std::size_t i = 0;; ++i) - { + try { + for (std::size_t i = 0;; ++i) { auto socket = std::make_shared(asio::make_strand(runtime.context_at(i))); co_await listener.async_accept(*socket, asio::use_awaitable); sockets.push_back(socket); sessions.push_back( asio::co_spawn(socket->get_executor(), serve(socket, settings, state), asio::use_future)); } - } - catch (const arknet::system_error&) - { - if (!state.stopping) - { + } catch (const arknet::system_error&) { + if (!state.stopping) { ++state.errors; } } @@ -476,10 +392,9 @@ asio::awaitable client(std::shared_ptr connection, a auto& socket = *connection; const auto executor = socket.get_executor(); client_traffic traffic(settings, id, state); - try - { co_await socket.async_connect(address, asio::use_awaitable); } - catch (...) - { + try { + co_await socket.async_connect(address, asio::use_awaitable); + } catch (...) { ++state.errors; ++state.ready; throw; @@ -487,17 +402,14 @@ asio::awaitable client(std::shared_ptr connection, a socket.set_option(asio::ip::tcp::no_delay(true)); ++state.ready; asio::steady_timer gate(executor); - while (!state.begin && !state.stopping) - { + while (!state.begin && !state.stopping) { gate.expires_after(1ms); co_await gate.async_wait(asio::use_awaitable); } - if (state.stopping) - { + if (state.stopping) { co_return client_result{}; } - while (traffic.next()) - { + while (traffic.next()) { co_await asio::async_write(socket, asio::buffer(traffic.sent), asio::use_awaitable); co_await asio::async_read(socket, asio::buffer(traffic.received), asio::use_awaitable); traffic.received_batch(); @@ -516,8 +428,7 @@ void run(const options& settings, void (*after_connect)() = nullptr) { std::vector> clients; std::future accepting; - struct runtime_cleanup - { + struct runtime_cleanup { benchmark_runtime& runtime; shared_state& state; asio::ip::tcp::acceptor& listener; @@ -527,8 +438,7 @@ void run(const options& settings, void (*after_connect)() = nullptr) { ~runtime_cleanup() { state.stopping = true; - for (auto& socket : clients) - { + for (auto& socket : clients) { asio::post(socket->get_executor(), [socket] { arknet::error_code ec; socket->close(ec); @@ -539,12 +449,10 @@ void run(const options& settings, void (*after_connect)() = nullptr) { listener.close(ec); }); // Accept is the sole writer of sockets; wait before reading its output. - if (accepting.valid()) - { + if (accepting.valid()) { accepting.wait(); } - for (auto& socket : sockets) - { + for (auto& socket : sockets) { asio::post(socket->get_executor(), [socket] { arknet::error_code ec; socket->close(ec); @@ -554,44 +462,34 @@ void run(const options& settings, void (*after_connect)() = nullptr) { } } cleanup{runtime, state, listener, accepting, sockets, client_sockets}; - if (settings.execution == "coroutine") - { + if (settings.execution == "coroutine") { accepting = asio::co_spawn( listener.get_executor(), accept(runtime, listener, settings, sessions, sockets, state), asio::use_future); - } - else - { + } else { auto acceptor = std::make_shared(runtime, listener, settings, sessions, sockets, state); accepting = acceptor->completed.get_future(); asio::post(listener.get_executor(), [acceptor] { acceptor->next(); }); } - for (std::size_t i = 0; i < settings.clients; ++i) - { + for (std::size_t i = 0; i < settings.clients; ++i) { auto socket = std::make_shared(asio::make_strand(runtime.context_at(i))); client_sockets.push_back(socket); - if (settings.execution == "coroutine") - { + if (settings.execution == "coroutine") { clients.push_back(asio::co_spawn(socket->get_executor(), client(socket, listener.local_endpoint(), settings, i, state), asio::use_future)); - } - else - { + } else { auto peer = std::make_shared(socket, settings, i, state); clients.push_back(peer->completed.get_future()); asio::post(socket->get_executor(), [peer, endpoint = listener.local_endpoint()] { peer->start(endpoint); }); } } const auto connection_deadline = clock_type::now() + 10s; - while (state.ready < settings.clients && clock_type::now() < connection_deadline) - { + while (state.ready < settings.clients && clock_type::now() < connection_deadline) { std::this_thread::sleep_for(1ms); } - if (state.ready != settings.clients || state.errors) - { + if (state.ready != settings.clients || state.errors) { throw std::runtime_error("native TCP connect failed"); } - if (after_connect) - { + if (after_connect) { after_connect(); } const auto cpu_started = clock_type::now(); @@ -601,11 +499,9 @@ void run(const options& settings, void (*after_connect)() = nullptr) { std::uint64_t messages{}, observations{}, warmup_messages{}; double elapsed{}; std::vector samples; - for (auto& future : clients) - { + for (auto& future : clients) { if (future.wait_for(std::chrono::duration(settings.seconds + settings.warmup + 15)) != - std::future_status::ready) - { + std::future_status::ready) { throw std::runtime_error("native TCP traffic timed out"); } auto result = future.get(); @@ -620,22 +516,18 @@ void run(const options& settings, void (*after_connect)() = nullptr) { const auto rss = peak_rss(); state.stopping = true; asio::post(listener.get_executor(), [&] { listener.close(); }); - if (accepting.wait_for(5s) != std::future_status::ready) - { + if (accepting.wait_for(5s) != std::future_status::ready) { throw std::runtime_error("native TCP accept stop timed out"); } accepting.get(); - for (auto& socket : sockets) - { + for (auto& socket : sockets) { asio::post(socket->get_executor(), [socket] { arknet::error_code ec; socket->close(ec); }); } - for (auto& future : sessions) - { - if (future.wait_for(5s) != std::future_status::ready) - { + for (auto& future : sessions) { + if (future.wait_for(5s) != std::future_status::ready) { throw std::runtime_error("native TCP session stop timed out"); } future.get(); @@ -671,8 +563,7 @@ void run(const options& settings, void (*after_connect)() = nullptr) { : "null") << ",\"peak_rss_bytes\":" << (rss ? std::to_string(*rss) : "null") << ",\"content_or_io_errors\":" << state.errors << ",\"ok\":" << (ok ? "true" : "false") << "}\n"; - if (!ok) - { + if (!ok) { throw std::runtime_error("native TCP traffic validation failed"); } } @@ -680,10 +571,9 @@ void run(const options& settings, void (*after_connect)() = nullptr) { #ifndef ARKNET_COROUTINE_BENCHMARK_NO_MAIN int main(int argc, char** argv) { - try - { run(parse(argc, argv)); } - catch (const std::exception& error) - { + try { + run(parse(argc, argv)); + } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return EXIT_FAILURE; } diff --git a/benchmarks/crash_report.hpp b/benchmarks/crash_report.hpp index 3e42194..b7184a1 100644 --- a/benchmarks/crash_report.hpp +++ b/benchmarks/crash_report.hpp @@ -18,8 +18,7 @@ namespace benchmark_diagnostics { inline LONG WINAPI report_exception(EXCEPTION_POINTERS* exception) { // DbgHelp is single-threaded, including when several workers fail together. static std::atomic_flag reporting = ATOMIC_FLAG_INIT; - if (reporting.test_and_set()) - { + if (reporting.test_and_set()) { return EXCEPTION_EXECUTE_HANDLER; } std::fprintf(stderr, "Windows exception code=0x%08lx address=%p\n", @@ -46,33 +45,28 @@ inline LONG WINAPI report_exception(EXCEPTION_POINTERS* exception) { frame.AddrPC.Mode = frame.AddrFrame.Mode = frame.AddrStack.Mode = AddrModeFlat; const auto process = GetCurrentProcess(); SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS); - if (!SymInitialize(process, nullptr, TRUE)) - { + if (!SymInitialize(process, nullptr, TRUE)) { return EXCEPTION_EXECUTE_HANDLER; } alignas(SYMBOL_INFO) unsigned char storage[sizeof(SYMBOL_INFO) + MAX_SYM_NAME]{}; auto* symbol = reinterpret_cast(storage); symbol->SizeOfStruct = sizeof(SYMBOL_INFO); symbol->MaxNameLen = MAX_SYM_NAME; - for (unsigned index = 0; index < 64 && frame.AddrPC.Offset; ++index) - { + for (unsigned index = 0; index < 64 && frame.AddrPC.Offset; ++index) { DWORD64 displacement{}; std::fprintf(stderr, "#%u 0x%llx", index, static_cast(frame.AddrPC.Offset)); - if (SymFromAddr(process, frame.AddrPC.Offset, &displacement, symbol)) - { + if (SymFromAddr(process, frame.AddrPC.Offset, &displacement, symbol)) { std::fprintf(stderr, " %s+0x%llx", symbol->Name, static_cast(displacement)); } IMAGEHLP_LINE64 line{}; line.SizeOfStruct = sizeof(line); DWORD line_offset{}; - if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &line_offset, &line)) - { + if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &line_offset, &line)) { std::fprintf(stderr, " %s:%lu", line.FileName, static_cast(line.LineNumber)); } std::fputc('\n', stderr); if (!StackWalk64(machine, process, GetCurrentThread(), &frame, &context, nullptr, SymFunctionTableAccess64, - SymGetModuleBase64, nullptr)) - { + SymGetModuleBase64, nullptr)) { break; } } diff --git a/benchmarks/impairment.hpp b/benchmarks/impairment.hpp index 957215e..4e0f8bb 100644 --- a/benchmarks/impairment.hpp +++ b/benchmarks/impairment.hpp @@ -23,23 +23,20 @@ namespace arknet_benchmark { -struct impairment_options -{ +struct impairment_options { unsigned loss_percent{}, duplicate_percent{}, reorder_percent{}, delay_ms{}; unsigned burst_count{}, burst_period = 100; std::uint64_t seed = 0x61726b6e6574ULL; void validate() const { if (loss_percent > 100 || duplicate_percent > 100 || reorder_percent > 100 || delay_ms > 1000 || - burst_count >= burst_period || !burst_period || burst_period > 1000000) - { + burst_count >= burst_period || !burst_period || burst_period > 1000000) { throw std::invalid_argument("invalid UDP impairment configuration"); } } }; -struct impairment_statistics -{ +struct impairment_statistics { std::uint64_t received_packets{}, received_bytes{}, forwarded_packets{}, forwarded_bytes{}; std::uint64_t random_drops{}, burst_drops{}, duplicate_packets{}, reordered_packets{}; std::uint64_t overflow_drops{}, errors{}; @@ -47,15 +44,12 @@ struct impairment_statistics }; // A separate serialized context keeps impairment scheduling out of the endpoint IO model. -class udp_impairment -{ +class udp_impairment { using udp = asio::ip::udp; using clock = std::chrono::steady_clock; - struct route : std::enable_shared_from_this - { - struct packet - { + struct route : std::enable_shared_from_this { + struct packet { std::string bytes; udp::endpoint destination; unsigned socket{}; @@ -67,8 +61,7 @@ class udp_impairment , server(std::move(server)) , options(options) , random{std::mt19937_64(options.seed), std::mt19937_64(options.seed ^ 0xa17e34bdULL)} { - for (auto& socket : sockets) - { + for (auto& socket : sockets) { socket.open(udp::v4()); socket.set_option(asio::socket_base::receive_buffer_size(4 * 1024 * 1024)); socket.bind({asio::ip::make_address("127.0.0.1"), 0}); @@ -82,18 +75,16 @@ class udp_impairment void close() noexcept { closed = true; - try - { timer.cancel(); } - catch (...) - {} + try { + timer.cancel(); + } catch (...) { + } arknet::error_code error; - for (auto& socket : sockets) - { + for (auto& socket : sockets) { socket.close(error); } delayed.clear(); - for (auto& queue : ready) - { + for (auto& queue : ready) { queue.clear(); } stats.retained_bytes = stats.retained_packets = 0; @@ -103,23 +94,18 @@ class udp_impairment auto self = shared_from_this(); sockets[socket].async_receive_from(asio::buffer(buffers[socket]), incoming[socket], [self, socket](arknet::error_code error, std::size_t size) { - if (self->closed) - { + if (self->closed) { return; } - if (error) - { + if (error) { ++self->stats.errors; self->close(); return; } - try - { + try { self->received(socket, {self->buffers[socket].data(), size}); self->receive(socket); - } - catch (...) - { + } catch (...) { ++self->stats.errors; self->close(); } @@ -131,54 +117,44 @@ class udp_impairment } void received(unsigned socket, std::string_view bytes) { - if (socket == 0) - { - if (!client) - { + if (socket == 0) { + if (!client) { client = incoming[socket]; } - if (*client != incoming[socket]) - { + if (*client != incoming[socket]) { ++stats.errors; return; } - } - else if (incoming[socket] != server || !client) - { + } else if (incoming[socket] != server || !client) { ++stats.errors; return; } ++stats.received_packets; stats.received_bytes += bytes.size(); const auto sequence = ++sequences[socket]; - if (options.burst_count && sequence % options.burst_period >= options.burst_period - options.burst_count) - { + if (options.burst_count && sequence % options.burst_period >= options.burst_period - options.burst_count) { ++stats.burst_drops; return; } - if (chance(socket, options.loss_percent)) - { + if (chance(socket, options.loss_percent)) { ++stats.random_drops; return; } const bool reorder = chance(socket, options.reorder_percent); - if (reorder) - { + if (reorder) { ++stats.reordered_packets; } const auto due = clock::now() + std::chrono::milliseconds(options.delay_ms + (reorder ? options.delay_ms + 5 : 0)); schedule(bytes, socket, due); - if (chance(socket, options.duplicate_percent)) - { + if (chance(socket, options.duplicate_percent)) { ++stats.duplicate_packets; schedule(bytes, socket, due + std::chrono::milliseconds(1)); } } void schedule(std::string_view bytes, unsigned received_socket, clock::time_point due) { - if (stats.retained_bytes + bytes.size() > max_buffer_bytes || stats.retained_packets >= max_packets) - { + if (stats.retained_bytes + bytes.size() > max_buffer_bytes || stats.retained_packets >= max_packets) { ++stats.overflow_drops; return; } @@ -190,37 +166,30 @@ class udp_impairment stats.retained_bytes += bytes.size(); stats.peak_retained_bytes = std::max(stats.peak_retained_bytes, stats.retained_bytes); stats.peak_retained_packets = std::max(stats.peak_retained_packets, stats.retained_packets); - if (due <= clock::now()) - { + if (due <= clock::now()) { ready[value->socket].push_back(std::move(value)); send(1 - received_socket); - } - else - { + } else { delayed.emplace(due, std::move(value)); arm(); } } void arm() { - if (delayed.empty() || (armed && *armed <= delayed.begin()->first)) - { + if (delayed.empty() || (armed && *armed <= delayed.begin()->first)) { return; } armed = delayed.begin()->first; timer.expires_at(*armed); auto self = shared_from_this(); timer.async_wait([self](arknet::error_code error) { - if (error || self->closed) - { + if (error || self->closed) { return; } - try - { + try { self->armed.reset(); const auto now = clock::now(); - while (!self->delayed.empty() && self->delayed.begin()->first <= now) - { + while (!self->delayed.empty() && self->delayed.begin()->first <= now) { auto value = std::move(self->delayed.begin()->second); self->delayed.erase(self->delayed.begin()); self->ready[value->socket].push_back(std::move(value)); @@ -228,9 +197,7 @@ class udp_impairment self->send(0); self->send(1); self->arm(); - } - catch (...) - { + } catch (...) { ++self->stats.errors; self->close(); } @@ -238,8 +205,7 @@ class udp_impairment } void send(unsigned socket) { - if (sending[socket] || ready[socket].empty() || closed) - { + if (sending[socket] || ready[socket].empty() || closed) { return; } sending[socket] = true; @@ -248,26 +214,21 @@ class udp_impairment auto self = shared_from_this(); sockets[socket].async_send_to(asio::buffer(value->bytes), value->destination, [self, value, socket](arknet::error_code error, std::size_t size) { - if (self->closed) - { + if (self->closed) { return; } self->sending[socket] = false; self->stats.retained_bytes -= value->bytes.size(); --self->stats.retained_packets; - if (error || size != value->bytes.size()) - { + if (error || size != value->bytes.size()) { ++self->stats.errors; - } - else - { + } else { ++self->stats.forwarded_packets; self->stats.forwarded_bytes += size; } - try - { self->send(socket); } - catch (...) - { + try { + self->send(socket); + } catch (...) { ++self->stats.errors; self->close(); } @@ -314,10 +275,9 @@ class udp_impairment settings.seed += routes.size() * 0x9e3779b97f4a7c15ULL; auto value = std::make_shared(context, server, settings); routes.push_back(value); - try - { value->start(); } - catch (...) - { + try { + value->start(); + } catch (...) { value->close(); routes.pop_back(); throw; @@ -329,8 +289,7 @@ class udp_impairment impairment_statistics snapshot(bool reset_peak = false) { return execute([this, reset_peak] { impairment_statistics total; - for (auto& value : routes) - { + for (auto& value : routes) { const auto& current = value->stats; total.received_packets += current.received_packets; total.received_bytes += current.received_bytes; @@ -346,8 +305,7 @@ class udp_impairment total.retained_packets += current.retained_packets; total.peak_retained_bytes += current.peak_retained_bytes; total.peak_retained_packets += current.peak_retained_packets; - if (reset_peak) - { + if (reset_peak) { value->stats.peak_retained_bytes = current.retained_bytes; value->stats.peak_retained_packets = current.retained_packets; } @@ -357,22 +315,19 @@ class udp_impairment } void stop() noexcept { - if (!worker.joinable()) - { + if (!worker.joinable()) { return; } - try - { + try { execute([this] { - for (auto& value : routes) - { + for (auto& value : routes) { value->close(); } routes.clear(); }); + } catch (...) { + context.stop(); } - catch (...) - { context.stop(); } guard.reset(); worker.join(); } @@ -380,16 +335,14 @@ class udp_impairment private: template auto execute(Function function) -> decltype(function()) { - if (!worker.joinable()) - { + if (!worker.joinable()) { throw std::logic_error("UDP relay is stopped"); } using result = decltype(function()); auto task = std::make_shared>(std::move(function)); auto future = task->get_future(); asio::post(context, [task] { (*task)(); }); - if (future.wait_for(std::chrono::seconds(5)) != std::future_status::ready) - { + if (future.wait_for(std::chrono::seconds(5)) != std::future_status::ready) { throw std::runtime_error("UDP relay context did not respond"); } return future.get(); diff --git a/benchmarks/loopback.cpp b/benchmarks/loopback.cpp index 9631005..9636bfb 100644 --- a/benchmarks/loopback.cpp +++ b/benchmarks/loopback.cpp @@ -38,8 +38,7 @@ constexpr auto drain_time = std::chrono::seconds(2); constexpr int udp_send_buffer = 65536; constexpr int udp_receive_buffer = 4 * 1024 * 1024; -struct options -{ +struct options { std::string protocol = "tcp"; std::string address_family = "ipv4"; std::size_t payload = 1024; @@ -57,8 +56,7 @@ struct options std::size_t integer(std::string_view value) { std::size_t parsed{}; const auto result = std::from_chars(value.data(), value.data() + value.size(), parsed); - if (result.ec != std::errc{} || result.ptr != value.data() + value.size()) - { + if (result.ec != std::errc{} || result.ptr != value.data() + value.size()) { throw std::invalid_argument("invalid integer argument"); } return parsed; @@ -67,8 +65,7 @@ std::size_t integer(std::string_view value) { double duration(const std::string& value) { char* end{}; const double parsed = std::strtod(value.c_str(), &end); - if (value.empty() || end != value.c_str() + value.size() || !std::isfinite(parsed)) - { + if (value.empty() || end != value.c_str() + value.size() || !std::isfinite(parsed)) { throw std::invalid_argument("invalid duration argument"); } return parsed; @@ -76,120 +73,82 @@ double duration(const std::string& value) { options arguments(int argc, char** argv) { options result; - for (int i = 1; i < argc; ++i) - { + for (int i = 1; i < argc; ++i) { const std::string key = argv[i]; - if (key == "--help") - { + if (key == "--help") { std::cerr << "arknet_loopback_benchmark --protocol tcp|udp|websocket|tcps|wss|http|https " "--payload BYTES --clients COUNT --window COUNT --seconds SECONDS " "--warmup SECONDS --certs DIRECTORY --io-model shared|sharded|owned --io-threads COUNT " "--work ITERATIONS --address-family ipv4|ipv6 --udp-server-receive-buffer BYTES\n"; std::exit(0); } - if (++i == argc) - { + if (++i == argc) { throw std::invalid_argument("missing value for " + key); } const std::string value = argv[i]; - if (key == "--protocol") - { + if (key == "--protocol") { result.protocol = value; - } - else if (key == "--address-family") - { + } else if (key == "--address-family") { result.address_family = value; - } - else if (key == "--payload") - { + } else if (key == "--payload") { result.payload = integer(value); - } - else if (key == "--clients") - { + } else if (key == "--clients") { result.clients = integer(value); - } - else if (key == "--window") - { + } else if (key == "--window") { result.window = integer(value); - } - else if (key == "--seconds") - { + } else if (key == "--seconds") { result.seconds = duration(value); - } - else if (key == "--warmup") - { + } else if (key == "--warmup") { result.warmup = duration(value); - } - else if (key == "--certs") - { + } else if (key == "--certs") { result.certs = value; - } - else if (key == "--io-model") - { + } else if (key == "--io-model") { result.io_model = value; - } - else if (key == "--io-threads") - { + } else if (key == "--io-threads") { result.io_threads = integer(value); - } - else if (key == "--work") - { + } else if (key == "--work") { result.work = integer(value); - } - else if (key == "--udp-server-receive-buffer") - { + } else if (key == "--udp-server-receive-buffer") { result.udp_server_receive_buffer = integer(value); - } - else - { + } else { throw std::invalid_argument("unknown argument: " + key); } } if (result.protocol != "tcp" && result.protocol != "udp" && result.protocol != "websocket" && result.protocol != "tcps" && result.protocol != "wss" && result.protocol != "http" && - result.protocol != "https") - { + result.protocol != "https") { throw std::invalid_argument("unsupported protocol"); } if (result.payload < 16 || result.payload > 65507 || result.clients < 1 || result.clients > 1024 || result.window < 1 || result.window > 1024 || result.seconds <= 0 || result.seconds > 3600 || - result.warmup < 0 || result.warmup > 3600) - { + result.warmup < 0 || result.warmup > 3600) { throw std::invalid_argument("argument out of range"); } - if (std::uint64_t(result.clients) * result.window * result.payload > 256 * 1024 * 1024) - { + if (std::uint64_t(result.clients) * result.window * result.payload > 256 * 1024 * 1024) { throw std::invalid_argument("requested outstanding payloads exceed 256 MiB"); } - if (result.io_model != "shared" && result.io_model != "sharded" && result.io_model != "owned") - { + if (result.io_model != "shared" && result.io_model != "sharded" && result.io_model != "owned") { throw std::invalid_argument("unsupported IO model"); } - if (result.io_threads < 1 || result.io_threads > 64 || (result.io_model == "owned" && result.io_threads != 1)) - { + if (result.io_threads < 1 || result.io_threads > 64 || (result.io_model == "owned" && result.io_threads != 1)) { throw std::invalid_argument("invalid IO thread count; owned uses one worker per context"); } - if (result.work > 10000000) - { + if (result.work > 10000000) { throw std::invalid_argument("handler work exceeds limit"); } - if (result.udp_server_receive_buffer < 1024 || result.udp_server_receive_buffer > 64 * 1024 * 1024) - { + if (result.udp_server_receive_buffer < 1024 || result.udp_server_receive_buffer > 64 * 1024 * 1024) { throw std::invalid_argument("UDP server receive buffer must be between 1 KiB and 64 MiB"); } - if (result.address_family != "ipv4" && result.address_family != "ipv6") - { + if (result.address_family != "ipv4" && result.address_family != "ipv6") { throw std::invalid_argument("unsupported address family"); } - if ((result.protocol == "tcps" || result.protocol == "wss" || result.protocol == "https") && result.certs.empty()) - { + if ((result.protocol == "tcps" || result.protocol == "wss" || result.protocol == "https") && result.certs.empty()) { throw std::invalid_argument("TLS protocols require --certs"); } return result; } -struct metrics -{ +struct metrics { explicit metrics(unsigned tag_value) : tag(tag_value) { samples.reserve(sample_limit); @@ -222,39 +181,30 @@ struct metrics void record_failure(const char* operation, const arknet::error_code& error, std::size_t bytes = 0, std::size_t expected = 0) noexcept { - try - { + try { std::lock_guard lock(failure_mutex); - if (!first_failure.empty()) - { + if (!first_failure.empty()) { return; } first_failure = operation; - if (error) - { + if (error) { first_failure += error.message(); - } - else - { + } else { first_failure += "completion byte count " + std::to_string(bytes) + " != " + std::to_string(expected); } + } catch (...) { + ++callback_exceptions; } - catch (...) - { ++callback_exceptions; } } void observe(double microseconds) { std::lock_guard lock(samples_mutex); ++observations; - if (samples.size() < sample_limit) - { + if (samples.size() < sample_limit) { samples.push_back(microseconds); - } - else - { + } else { const auto index = std::uniform_int_distribution(0, observations - 1)(random); - if (index < sample_limit) - { + if (index < sample_limit) { samples[static_cast(index)] = microseconds; } } @@ -267,8 +217,7 @@ struct metrics } }; -struct context -{ +struct context { std::array, 2> phases{std::make_shared(0), std::make_shared(1)}; std::atomic closing{}; std::array server_buffers{}; @@ -279,8 +228,7 @@ std::array configure_udp(asio::ip::udp::socket& socket, const std::share int receive_buffer = udp_receive_buffer) noexcept { arknet::error_code error; const auto failure = [&](const char* operation) { - if (error) - { + if (error) { ++owner->phases[0]->callback_exceptions; owner->phases[0]->record_failure(operation, error); } @@ -299,8 +247,7 @@ std::array configure_udp(asio::ip::udp::socket& socket, const std::share } // A rejection may invoke its completion before async_send returns its acceptance result. -struct send_completion -{ +struct send_completion { send_completion(std::shared_ptr state, bool server_side, std::size_t expected, bool wire_bytes = false) : state(std::move(state)) , server_side(server_side) @@ -317,21 +264,16 @@ struct send_completion bool known{}, accepted{}, completed{}, failed{}, accounted{}; void account() noexcept { - if (!known || !completed || accounted) - { + if (!known || !completed || accounted) { return; } accounted = true; - if (!accepted) - { + if (!accepted) { ++(server_side ? state->server_rejections : state->client_rejections); - } - else if (failed) - { + } else if (failed) { ++(server_side ? state->server_completion_errors : state->client_completion_errors); } - if (state->pending_completions.fetch_sub(1) == 1) - { + if (state->pending_completions.fetch_sub(1) == 1) { state->notify(); } } @@ -345,15 +287,13 @@ struct send_completion void completion(const arknet::error_code& error, std::size_t bytes) noexcept { std::lock_guard lock(mutex); - if (completed) - { + if (completed) { ++state->duplicate_completions; return; } completed = true; failed = static_cast(error) || (wire_bytes ? bytes < expected : bytes != expected); - if (failed) - { + if (failed) { state->record_failure(server_side ? "server send: " : "client send: ", error, bytes, expected); } account(); @@ -365,29 +305,23 @@ bool owned_send(Sender& sender, const std::shared_ptr& payloa const std::shared_ptr& state, bool server_side) { constexpr bool http_sender = requires { typename Sender::recv_message_type; }; auto completion = std::make_shared(state, server_side, payload->size(), http_sender); - try - { + try { auto data = [&] { #if ARKNET_USE_BOOST_ASIO - if constexpr (http_sender) - { - if constexpr (Sender::is_client()) - { + if constexpr (http_sender) { + if constexpr (Sender::is_client()) { http::request request(http::verb::post, "/echo", 11); request.set(http::field::host, "127.0.0.1"); request.body() = *payload; request.prepare_payload(); return request; - } - else - { + } else { http::response response(http::status::ok, 11); response.body() = *payload; response.prepare_payload(); return response; } - } - else + } else #endif { return *payload; @@ -399,9 +333,7 @@ bool owned_send(Sender& sender, const std::shared_ptr& payloa }); completion->acceptance(accepted); return accepted; - } - catch (...) - { + } catch (...) { ++state->callback_exceptions; completion->acceptance(false); completion->completion(asio::error::operation_aborted, 0); @@ -410,26 +342,22 @@ bool owned_send(Sender& sender, const std::shared_ptr& payloa } void put_integer(std::string& message, std::size_t offset, std::uint64_t value, std::size_t bytes) { - for (std::size_t i = 0; i < bytes; ++i) - { + for (std::size_t i = 0; i < bytes; ++i) { message[offset + i] = static_cast((value >> ((bytes - 1 - i) * 8)) & 255); } } std::uint64_t get_integer(std::string_view message, std::size_t offset, std::size_t bytes) { std::uint64_t result{}; - for (std::size_t i = 0; i < bytes; ++i) - { + for (std::size_t i = 0; i < bytes; ++i) { result = (result << 8) | static_cast(message[offset + i]); } return result; } template -struct client_state -{ - struct pending - { +struct client_state { + struct pending { std::shared_ptr payload; clock_type::time_point sent; std::uint64_t sequence{}; @@ -453,40 +381,31 @@ struct client_state bool have_sequence{}, stopped_sending{}, finished{}; void finish(bool force = false) noexcept { - if (!phase || finished) - { + if (!phase || finished) { return; } - if (force) - { - if (settings.protocol == "udp") - { + if (force) { + if (settings.protocol == "udp") { phase->udp_lost += inflight; - } - else - { + } else { phase->unfinished += inflight; } - for (auto& slot : slots) - { + for (auto& slot : slots) { slot.payload.reset(); } inflight = 0; } - if (inflight) - { + if (inflight) { return; } finished = true; - if (phase->remaining.fetch_sub(1) == 1) - { + if (phase->remaining.fetch_sub(1) == 1) { phase->notify(); } } void send(std::size_t index) { - if (stopped_sending || clock_type::now() >= phase->deadline) - { + if (stopped_sending || clock_type::now() >= phase->deadline) { return; } auto payload = std::make_shared(settings.payload, '\0'); @@ -497,8 +416,7 @@ struct client_state const auto sequence = next_sequence++; put_integer(*payload, 4, id, 4); put_integer(*payload, 8, sequence, 8); - for (std::size_t i = 16; i < payload->size(); ++i) - { + for (std::size_t i = 16; i < payload->size(); ++i) { (*payload)[i] = static_cast((sequence + i * 17 + std::uint64_t(id) * 31) & 255); } auto& slot = slots[index]; @@ -506,12 +424,9 @@ struct client_state slot.payload = payload; slot.sent = clock_type::now(); ++inflight; - if (owned_send(client, slot.payload, phase, false)) - { + if (owned_send(client, slot.payload, phase, false)) { ++phase->sent; - } - else - { + } else { slot.payload.reset(); --inflight; stopped_sending = true; @@ -525,22 +440,17 @@ struct client_state have_sequence = false; inflight = 0; expected_sequence = next_sequence; - try - { - if (!client.is_started()) - { + try { + if (!client.is_started()) { ++phase->disconnects; stopped_sending = true; finish(true); return; } - for (std::size_t i = 0; i < slots.size(); ++i) - { + for (std::size_t i = 0; i < slots.size(); ++i) { send(i); } - } - catch (...) - { + } catch (...) { ++phase->callback_exceptions; stopped_sending = true; finish(true); @@ -548,45 +458,34 @@ struct client_state } void receive(std::string_view message) noexcept { - try - { + try { if (message.size() != settings.payload || message.substr(0, 3) != "ANB" || - static_cast(message[3]) > 1 || get_integer(message, 4, 4) != id) - { + static_cast(message[3]) > 1 || get_integer(message, 4, 4) != id) { ++(phase ? phase : owner->phases[0])->content_errors; return; } const auto incoming_phase = owner->phases[static_cast(message[3])]; - if (!phase || incoming_phase != phase || finished) - { + if (!phase || incoming_phase != phase || finished) { ++incoming_phase->unexpected; return; } const auto sequence = get_integer(message, 8, 8); auto found = std::find_if(slots.begin(), slots.end(), [sequence](const pending& slot) { return slot.payload && slot.sequence == sequence; }); - if (found == slots.end()) - { + if (found == slots.end()) { ++phase->unexpected; return; } const auto now = clock_type::now(); - if (message != *found->payload) - { + if (message != *found->payload) { ++phase->content_errors; - } - else - { - if (settings.protocol == "udp") - { - if (have_sequence && sequence <= last_sequence) - { + } else { + if (settings.protocol == "udp") { + if (have_sequence && sequence <= last_sequence) { ++phase->order_errors; } last_sequence = (std::max)(last_sequence, sequence); - } - else if (sequence != expected_sequence) - { + } else if (sequence != expected_sequence) { ++phase->order_errors; } expected_sequence = sequence + 1; @@ -597,25 +496,19 @@ struct client_state const auto index = static_cast(found - slots.begin()); found->payload.reset(); --inflight; - if (now < phase->deadline) - { + if (now < phase->deadline) { send(index); - } - else - { + } else { finish(); } - } - catch (...) - { + } catch (...) { ++(phase ? phase : owner->phases[0])->callback_exceptions; stopped_sending = true; } } void disconnect() noexcept { - if (!owner->closing && phase) - { + if (!owner->closing && phase) { ++phase->disconnects; stopped_sending = true; finish(true); @@ -626,8 +519,7 @@ struct client_state template void run_phase( const std::vector>& clients, const std::shared_ptr& phase, double seconds) { - if (seconds == 0) - { + if (seconds == 0) { return; } phase->remaining = clients.size(); @@ -636,13 +528,11 @@ void run_phase( phase->started + std::chrono::duration_cast(std::chrono::duration(seconds)); const auto cpu_started = clock_type::now(); const auto cpu_start = process_cpu(); - for (auto& state : clients) - { + for (auto& state : clients) { state->client.post([state, phase]() noexcept { state->begin(phase); }); } std::this_thread::sleep_until(phase->deadline); - for (auto& state : clients) - { + for (auto& state : clients) { state->client.post([state]() noexcept { state->stopped_sending = true; state->finish(); @@ -651,14 +541,12 @@ void run_phase( { std::unique_lock lock(phase->done_mutex); if (!phase->done.wait_until(lock, phase->deadline + drain_time, - [&] { return phase->remaining == 0 && phase->pending_completions == 0; })) - { + [&] { return phase->remaining == 0 && phase->pending_completions == 0; })) { ++phase->drain_timeouts; } } std::vector> barriers; - for (auto& state : clients) - { + for (auto& state : clients) { barriers.push_back(state->client.post( [state]() noexcept { state->stopped_sending = true; @@ -666,10 +554,8 @@ void run_phase( }, asio::use_future)); } - for (auto& barrier : barriers) - { - if (barrier.wait_for(std::chrono::seconds(5)) != std::future_status::ready) - { + for (auto& barrier : barriers) { + if (barrier.wait_for(std::chrono::seconds(5)) != std::future_status::ready) { throw std::runtime_error("client IO barrier timed out"); } barrier.get(); @@ -677,8 +563,7 @@ void run_phase( phase->elapsed = std::chrono::duration(clock_type::now() - phase->started).count(); const auto cpu_end = process_cpu(); phase->cpu_elapsed = std::chrono::duration(clock_type::now() - cpu_started).count(); - if (cpu_start && cpu_end) - { + if (cpu_start && cpu_end) { phase->cpu = *cpu_end - *cpu_start; } } @@ -686,8 +571,7 @@ void run_phase( template void benchmark(const options& settings, const std::shared_ptr& owner) { std::unique_ptr runtime; - if (settings.io_model != "owned") - { + if (settings.io_model != "owned") { runtime = std::make_unique(settings.io_model, settings.io_threads); } auto server_ptr = runtime ? std::make_unique(1024, 128 * 1024, runtime->server_lanes(settings.clients)) @@ -698,83 +582,66 @@ void benchmark(const options& settings, const std::shared_ptr& owner) { clients.reserve(settings.clients); states.reserve(settings.clients); std::exception_ptr failure; - try - { - if constexpr (!Framed && !WebSocket && !Http) - { + try { + if constexpr (!Framed && !WebSocket && !Http) { owner->client_buffers.resize(settings.clients); server.bind_init( [&server, owner, receive_buffer = static_cast(settings.udp_server_receive_buffer)]() noexcept { owner->server_buffers = configure_udp(server.acceptor(), owner, receive_buffer); }); } - if constexpr (Secure) - { + if constexpr (Secure) { server.set_cert_file("", settings.certs + "/server.pem", settings.certs + "/server-key.pem", ""); - if (arknet::get_last_error()) - { + if (arknet::get_last_error()) { throw std::runtime_error("server certificate loading failed"); } } - if constexpr (Framed || WebSocket || Http) - { + if constexpr (Framed || WebSocket || Http) { server.bind_connect([owner](auto& session) noexcept { session->set_no_delay(true); - if (arknet::get_last_error()) - { + if (arknet::get_last_error()) { ++owner->phases[0]->callback_exceptions; } }); } server.bind_recv([owner, work = settings.work](auto& session, const auto& incoming) noexcept { const std::string_view message = [&]() -> std::string_view { - if constexpr (Http) - { + if constexpr (Http) { return incoming.body(); - } - else - { + } else { return incoming; } }(); const auto tag = message.size() >= 16 ? static_cast(message[3]) : 2; const auto state = owner->phases[tag < 2 ? tag : 0]; - try - { - if (tag > 1 || message.substr(0, 3) != "ANB") - { + try { + if (tag > 1 || message.substr(0, 3) != "ANB") { ++state->content_errors; return; } ++state->server_received; - if constexpr (WebSocket) - { + if constexpr (WebSocket) { session->ws_stream().binary(true); } benchmark_work(work, message); auto payload = std::make_shared(message); owned_send(*session, payload, state, true); + } catch (...) { + ++state->callback_exceptions; } - catch (...) - { ++state->callback_exceptions; } }); const auto host = settings.address_family == "ipv6" ? "::1" : "127.0.0.1"; const bool listening = [&] { - if constexpr (Framed) - { + if constexpr (Framed) { return server.start(host, 0, arknet::use_dgram); - } - else - { + } else { return server.start(host, 0); } }(); - if (!listening) - { + if (!listening) { throw std::runtime_error("server start failed: " + arknet::get_last_error().message()); } - for (std::size_t i = 0; i < settings.clients; ++i) - { + for (std::size_t i = 0; i < settings.clients; ++i) { clients.push_back(runtime ? std::make_unique(1024, 128 * 1024, runtime->context_at(i)) : std::make_unique(1024, 128 * 1024, 1)); auto& client = clients.back(); @@ -783,65 +650,48 @@ void benchmark(const options& settings, const std::shared_ptr& owner) { states.push_back(state); client->set_auto_reconnect(false); client->set_connect_timeout(std::chrono::seconds(10)); - if constexpr (!Framed && !WebSocket && !Http) - { + if constexpr (!Framed && !WebSocket && !Http) { client->bind_init([state]() noexcept { state->owner->client_buffers[state->id] = configure_udp(state->client.socket(), state->owner); }); } - if constexpr (Secure) - { + if constexpr (Secure) { client->load_verify_file(settings.certs + "/ca.pem"); } - if constexpr (Framed || WebSocket || Http) - { + if constexpr (Framed || WebSocket || Http) { client->bind_connect([state]() noexcept { state->client.set_no_delay(true); - if (arknet::get_last_error()) - { + if (arknet::get_last_error()) { ++state->owner->phases[0]->callback_exceptions; } - if constexpr (WebSocket) - { + if constexpr (WebSocket) { state->client.ws_stream().binary(true); } }); } client->bind_recv([state](const auto& incoming) noexcept { - if constexpr (Http) - { + if constexpr (Http) { state->receive(incoming.body()); - } - else - { + } else { state->receive(incoming); } }); client->bind_disconnect([state]() noexcept { state->disconnect(); }); const bool connected = [&] { - if constexpr (Framed) - { + if constexpr (Framed) { return client->start(host, server.get_listen_port(), arknet::use_dgram); - } - else if constexpr (WebSocket) - { + } else if constexpr (WebSocket) { return client->start(host, server.get_listen_port(), "/echo"); - } - else - { + } else { return client->start(host, server.get_listen_port()); } }(); - if (!connected) - { + if (!connected) { throw std::runtime_error("client start failed: " + arknet::get_last_error().message()); } - if constexpr (!Framed && !WebSocket && !Http) - { - for (std::size_t previous = 0; previous < i; ++previous) - { - if (clients[previous]->get_local_port() == client->get_local_port()) - { + if constexpr (!Framed && !WebSocket && !Http) { + for (std::size_t previous = 0; previous < i; ++previous) { + if (clients[previous]->get_local_port() == client->get_local_port()) { throw std::runtime_error("UDP clients share local endpoint " + std::string(host) + ':' + std::to_string(client->get_local_port())); } @@ -850,22 +700,19 @@ void benchmark(const options& settings, const std::shared_ptr& owner) { } run_phase(states, owner->phases[0], settings.warmup); run_phase(states, owner->phases[1], settings.seconds); + } catch (...) { + failure = std::current_exception(); } - catch (...) - { failure = std::current_exception(); } owner->closing = true; - for (auto& client : clients) - { + for (auto& client : clients) { client->stop(); } server.stop(); - if (runtime) - { + if (runtime) { // Keep endpoints and their captures alive until the borrowed contexts finish all callbacks. runtime->stop(); } - if (failure) - { + if (failure) { std::rethrow_exception(failure); } } @@ -873,18 +720,12 @@ void benchmark(const options& settings, const std::shared_ptr& owner) { std::string quoted(std::string_view value) { std::ostringstream stream; stream << '"'; - for (const unsigned char c : value) - { - if (c == '"' || c == '\\') - { + for (const unsigned char c : value) { + if (c == '"' || c == '\\') { stream << '\\' << static_cast(c); - } - else if (c < 32) - { + } else if (c < 32) { stream << "\\u00" << std::hex << std::setw(2) << std::setfill('0') << unsigned(c); - } - else - { + } else { stream << static_cast(c); } } @@ -897,21 +738,17 @@ bool output(const options& settings, const std::shared_ptr& owner, cons auto samples = state->samples; std::sort(samples.begin(), samples.end()); const auto percentile = [&](double fraction) { - if (samples.empty()) - { + if (samples.empty()) { return 0.0; } return samples[static_cast(std::ceil(fraction * samples.size())) - 1]; }; const auto messages = state->messages.load(); std::array minimum_buffers{}, maximum_buffers{}; - if (!owner->client_buffers.empty()) - { + if (!owner->client_buffers.empty()) { minimum_buffers = maximum_buffers = owner->client_buffers.front(); - for (const auto& buffers : owner->client_buffers) - { - for (std::size_t i = 0; i < buffers.size(); ++i) - { + for (const auto& buffers : owner->client_buffers) { + for (std::size_t i = 0; i < buffers.size(); ++i) { minimum_buffers[i] = (std::min)(minimum_buffers[i], buffers[i]); maximum_buffers[i] = (std::max)(maximum_buffers[i], buffers[i]); } @@ -970,51 +807,33 @@ bool output(const options& settings, const std::shared_ptr& owner, cons << ",\"udp_client_send_buffer_max_bytes\":" << udp_value(maximum_buffers[0]) << ",\"udp_client_receive_buffer_min_bytes\":" << udp_value(minimum_buffers[1]) << ",\"udp_client_receive_buffer_max_bytes\":" << udp_value(maximum_buffers[1]) << ",\"cpu_seconds\":"; - if (state->cpu) - { + if (state->cpu) { std::cout << *state->cpu; - } - else - { + } else { std::cout << "null"; } std::cout << ",\"cpu_elapsed_seconds\":" << state->cpu_elapsed << ",\"cpu_percent\":"; - if (state->cpu && state->cpu_elapsed > 0) - { + if (state->cpu && state->cpu_elapsed > 0) { std::cout << *state->cpu / state->cpu_elapsed * 100; - } - else - { + } else { std::cout << "null"; } std::cout << ",\"peak_rss_bytes\":"; - if (const auto rss = peak_rss()) - { + if (const auto rss = peak_rss()) { std::cout << *rss; - } - else - { + } else { std::cout << "null"; } std::cout << ",\"ok\":" << (ok ? "true" : "false") << ",\"error\":"; - if (!error.empty()) - { + if (!error.empty()) { std::cout << quoted(error); - } - else if (!state->first_failure.empty()) - { + } else if (!state->first_failure.empty()) { std::cout << quoted(state->first_failure); - } - else if (!owner->phases[0]->first_failure.empty()) - { + } else if (!owner->phases[0]->first_failure.empty()) { std::cout << quoted("warmup " + owner->phases[0]->first_failure); - } - else if (!ok) - { + } else if (!ok) { std::cout << "\"benchmark validation failed\""; - } - else - { + } else { std::cout << "null"; } std::cout << "}\n"; @@ -1027,52 +846,40 @@ int main(int argc, char** argv) { options settings; auto owner = std::make_shared(); std::string error; - try - { + try { settings = arguments(argc, argv); - if (settings.protocol == "tcp") - { + if (settings.protocol == "tcp") { benchmark(settings, owner); - } - else if (settings.protocol == "udp") - { + } else if (settings.protocol == "udp") { benchmark(settings, owner); } #if ARKNET_USE_BOOST_ASIO - else if (settings.protocol == "websocket") - { + else if (settings.protocol == "websocket") { benchmark(settings, owner); - } - else if (settings.protocol == "http") - { + } else if (settings.protocol == "http") { benchmark(settings, owner); } #endif #ifdef ARKNET_ENABLE_SSL - else if (settings.protocol == "tcps") - { + else if (settings.protocol == "tcps") { benchmark(settings, owner); } #if ARKNET_USE_BOOST_ASIO - else if (settings.protocol == "wss") - { + else if (settings.protocol == "wss") { benchmark(settings, owner); - } - else if (settings.protocol == "https") - { + } else if (settings.protocol == "https") { benchmark(settings, owner); } #endif #endif - else - { + else { throw std::invalid_argument( "protocol unavailable: HTTP/WebSocket require Boost; secure protocols require TLS"); } + } catch (const std::exception& failure) { + error = failure.what(); + } catch (...) { + error = "unknown benchmark failure"; } - catch (const std::exception& failure) - { error = failure.what(); } - catch (...) - { error = "unknown benchmark failure"; } return output(settings, owner, error) ? 0 : 1; } diff --git a/benchmarks/mysql.cpp b/benchmarks/mysql.cpp index cc01aec..a182634 100644 --- a/benchmarks/mysql.cpp +++ b/benchmarks/mysql.cpp @@ -26,8 +26,7 @@ namespace { using clock_type = std::chrono::steady_clock; -struct options -{ +struct options { arknet::mysql::connect_options connection; std::size_t clients = 4; std::size_t window = 1; @@ -41,8 +40,7 @@ struct options std::size_t integer(std::string_view text) { std::size_t value = 0; const auto parsed = std::from_chars(text.data(), text.data() + text.size(), value); - if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size()) - { + if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size()) { throw std::invalid_argument("Invalid integer option"); } return value; @@ -51,8 +49,7 @@ std::size_t integer(std::string_view text) { double duration(std::string_view text) { double value = 0; const auto parsed = std::from_chars(text.data(), text.data() + text.size(), value); - if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size() || !std::isfinite(value)) - { + if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size() || !std::isfinite(value)) { throw std::invalid_argument("Invalid duration option"); } return value; @@ -60,11 +57,9 @@ double duration(std::string_view text) { options arguments(int argc, char** argv) { options result; - for (int i = 1; i < argc; ++i) - { + for (int i = 1; i < argc; ++i) { const std::string key = argv[i]; - if (key == "--help") - { + if (key == "--help") { std::cout << "arknet_mysql_benchmark --host HOST --port PORT --username USER --database DB " "--ca FILE --plain --mode text|prepared --clients N --window N --payload BYTES " @@ -76,104 +71,71 @@ options arguments(int argc, char** argv) { "CPU is the client process only: one fully occupied core is 100%.\n"; std::exit(0); } - if (key == "--plain") - { + if (key == "--plain") { result.connection.tls = arknet::mysql::tls_mode::disabled; continue; } - if (++i == argc) - { + if (++i == argc) { throw std::invalid_argument("Missing value for " + key); } const std::string value = argv[i]; - if (key == "--host") - { + if (key == "--host") { result.connection.host = value; - } - else if (key == "--port") - { + } else if (key == "--port") { const auto port = integer(value); - if (!port || port > 65535) - { + if (!port || port > 65535) { throw std::invalid_argument("Port must be in 1..65535"); } result.connection.port = static_cast(port); - } - else if (key == "--username") - { + } else if (key == "--username") { result.connection.username = value; - } - else if (key == "--database") - { + } else if (key == "--database") { result.connection.database = value; - } - else if (key == "--ca") - { + } else if (key == "--ca") { result.connection.ca_file = value; - } - else if (key == "--mode" && (value == "text" || value == "prepared")) - { + } else if (key == "--mode" && (value == "text" || value == "prepared")) { result.prepared = value == "prepared"; - } - else if (key == "--clients") - { + } else if (key == "--clients") { result.clients = integer(value); - } - else if (key == "--window") - { + } else if (key == "--window") { result.window = integer(value); - } - else if (key == "--payload") - { + } else if (key == "--payload") { result.payload = integer(value); - } - else if (key == "--rounds") - { + } else if (key == "--rounds") { result.rounds = integer(value); - } - else if (key == "--seconds") - { + } else if (key == "--seconds") { result.seconds = duration(value); - } - else if (key == "--warmup") - { + } else if (key == "--warmup") { result.warmup = duration(value); - } - else - { + } else { throw std::invalid_argument("Unknown option or invalid value: " + key); } } if (result.connection.host.empty() || !result.clients || result.clients > 128 || !result.window || result.window > 64 || result.payload > 1024 * 1024 || !result.rounds || result.rounds > 100 || result.seconds <= 0 || result.seconds > 3600 || result.warmup < 0 || result.warmup > 3600 || - (result.connection.tls == arknet::mysql::tls_mode::disabled && !result.connection.ca_file.empty())) - { + (result.connection.tls == arknet::mysql::tls_mode::disabled && !result.connection.ca_file.empty())) { throw std::invalid_argument("Invalid benchmark dimensions; use --help"); } // Text mode encodes each payload byte as two hex digits. Include request, // response and retained parameter storage in the conservative budget. constexpr std::size_t budget = 64 * 1024 * 1024; - if (result.window > budget / (result.payload * 4 + 4096)) - { + if (result.window > budget / (result.payload * 4 + 4096)) { throw std::invalid_argument("In-flight data exceeds the 64 MiB per-connection budget"); } - if (const auto* password = std::getenv("ARKNET_MYSQL_PASSWORD")) - { + if (const auto* password = std::getenv("ARKNET_MYSQL_PASSWORD")) { result.connection.password = password; } result.connection.max_pending_commands = result.window; return result; } -struct client_state -{ +struct client_state { arknet::mysql::connection connection; arknet::mysql::prepared_statement statement; }; -struct phase -{ +struct phase { clock_type::time_point stop; std::atomic remaining; std::promise completion; @@ -182,8 +144,7 @@ struct phase : remaining(chains) {} }; -struct request_chain : std::enable_shared_from_this -{ +struct request_chain : std::enable_shared_from_this { std::shared_ptr client; std::shared_ptr measurement; std::shared_ptr payload; @@ -202,43 +163,30 @@ struct request_chain : std::enable_shared_from_this ? std::get_if(&response.rows.front().front()) : nullptr; const bool valid = value && *value == *self->payload; - if (valid) - { + if (valid) { ++self->successes; const auto latency = std::chrono::duration(clock_type::now() - began).count(); - if (self->samples.size() < self->sample_capacity) - { + if (self->samples.size() < self->sample_capacity) { self->samples.push_back(latency); - } - else - { + } else { const auto index = std::uniform_int_distribution(0, self->successes - 1)(self->random); - if (index < self->samples.size()) - { + if (index < self->samples.size()) { self->samples[static_cast(index)] = latency; } } - } - else - { + } else { ++self->failures; } - if (valid && clock_type::now() < self->measurement->stop) - { + if (valid && clock_type::now() < self->measurement->stop) { self->submit(); - } - else if (self->measurement->remaining.fetch_sub(1) == 1) - { + } else if (self->measurement->remaining.fetch_sub(1) == 1) { self->measurement->completion.set_value(); } }; - if (prepared) - { + if (prepared) { client->connection.async_execute(client->statement, {*payload}, std::move(handler)); - } - else - { + } else { client->connection.async_query(*sql, std::move(handler)); } } @@ -252,10 +200,8 @@ bool measure(const options& settings, const std::vectorcompletion.get_future(); std::vector> chains; chains.reserve(count); - for (const auto& client : clients) - { - for (std::size_t lane = 0; lane < settings.window; ++lane) - { + for (const auto& client : clients) { + for (std::size_t lane = 0; lane < settings.window; ++lane) { auto chain = std::make_shared(); chain->client = client; chain->measurement = measurement; @@ -272,14 +218,11 @@ bool measure(const options& settings, const std::vectorstop = began + std::chrono::duration_cast(std::chrono::duration(seconds)); - for (const auto& chain : chains) - { + for (const auto& chain : chains) { chain->submit(); } - if (finished.wait_for(std::chrono::duration(seconds + 15)) != std::future_status::ready) - { - for (const auto& client : clients) - { + if (finished.wait_for(std::chrono::duration(seconds + 15)) != std::future_status::ready) { + for (const auto& client : clients) { client->connection.close(); } throw std::runtime_error("Benchmark phase exceeded its deadline"); @@ -289,19 +232,16 @@ bool measure(const options& settings, const std::vector samples; - for (const auto& chain : chains) - { + for (const auto& chain : chains) { successes += chain->successes; failures += chain->failures; - for (const auto sample : chain->samples) - { + for (const auto sample : chain->samples) { samples.push_back({sample, double(chain->successes) / chain->samples.size()}); } } @@ -309,18 +249,15 @@ bool measure(const options& settings, const std::vector= fraction * double(successes)) - { + if (weight >= fraction * double(successes)) { return sample.latency; } } return samples.empty() ? 0 : samples.back().latency; }; - if (report) - { + if (report) { #if defined(ARKNET_USE_BOOST_ASIO) && ARKNET_USE_BOOST_ASIO constexpr const char* dependency = "boost"; #else @@ -336,21 +273,15 @@ bool measure(const options& settings, const std::vector(settings.payload, '\0'); auto sql = std::make_shared("SELECT X'"); constexpr std::string_view hex = "0123456789abcdef"; - for (std::size_t i = 0; i < payload->size(); ++i) - { + for (std::size_t i = 0; i < payload->size(); ++i) { const auto byte = static_cast(i % 256); (*payload)[i] = static_cast(byte); sql->push_back(hex[byte >> 4]); @@ -371,30 +301,25 @@ int run(const options& settings) { } sql->push_back('\''); std::vector> clients; - for (std::size_t i = 0; i < settings.clients; ++i) - { + for (std::size_t i = 0; i < settings.clients; ++i) { auto client = std::make_shared(); auto connected = std::make_shared>(); auto connection = connected->get_future(); client->connection.async_connect( settings.connection, [connected](auto result) { connected->set_value(std::move(result)); }); - if (connection.wait_for(std::chrono::seconds(15)) != std::future_status::ready || !connection.get()) - { + if (connection.wait_for(std::chrono::seconds(15)) != std::future_status::ready || !connection.get()) { throw std::runtime_error("MySQL connection or authentication failed"); } - if (settings.prepared) - { + if (settings.prepared) { auto prepared = std::make_shared>(); auto preparation = prepared->get_future(); client->connection.async_prepare( "SELECT ?", [prepared](auto result) { prepared->set_value(std::move(result)); }); - if (preparation.wait_for(std::chrono::seconds(15)) != std::future_status::ready) - { + if (preparation.wait_for(std::chrono::seconds(15)) != std::future_status::ready) { throw std::runtime_error("MySQL statement preparation timed out"); } auto result = preparation.get(); - if (!result) - { + if (!result) { throw std::runtime_error("MySQL statement preparation failed"); } client->statement = std::move(result.statement); @@ -403,19 +328,15 @@ int run(const options& settings) { } std::ostringstream records; records.imbue(std::locale::classic()); - if (settings.warmup > 0 && !measure(settings, clients, payload, sql, settings.warmup, 0, false, records)) - { + if (settings.warmup > 0 && !measure(settings, clients, payload, sql, settings.warmup, 0, false, records)) { throw std::runtime_error("Warmup failed; no valid performance results"); } - for (std::size_t round = 1; round <= settings.rounds; ++round) - { - if (!measure(settings, clients, payload, sql, settings.seconds, round, true, records)) - { + for (std::size_t round = 1; round <= settings.rounds; ++round) { + if (!measure(settings, clients, payload, sql, settings.seconds, round, true, records)) { throw std::runtime_error("Measurement failed; discard this invocation's results"); } } - for (const auto& client : clients) - { + for (const auto& client : clients) { client->connection.close(); } std::cout << records.str(); @@ -425,13 +346,10 @@ int run(const options& settings) { } // namespace int main(int argc, char** argv) { - try - { + try { std::cout.imbue(std::locale::classic()); return run(arguments(argc, argv)); - } - catch (const std::exception& error) - { + } catch (const std::exception& error) { std::cerr << "MySQL benchmark: " << error.what() << '\n'; return 1; } diff --git a/benchmarks/process_resources.hpp b/benchmarks/process_resources.hpp index ba3b8ea..4f9a82e 100644 --- a/benchmarks/process_resources.hpp +++ b/benchmarks/process_resources.hpp @@ -43,28 +43,24 @@ namespace arknet::benchmark { template -struct resource_metric -{ +struct resource_metric { std::optional value; std::string unavailable_reason; }; -struct file_descriptor_limit -{ +struct file_descriptor_limit { std::uint64_t descriptors{}; bool unlimited{}; }; inline std::ostream& operator<<(std::ostream& output, const file_descriptor_limit& value) { - if (value.unlimited) - { + if (value.unlimited) { return output << "\"unlimited\""; } return output << value.descriptors; } -struct process_resources -{ +struct process_resources { std::chrono::steady_clock::time_point monotonic_time; resource_metric cpu_percent; resource_metric current_rss_bytes; @@ -92,8 +88,7 @@ inline std::string system_failure(const char* operation, int error) { template void read_file_descriptor_limits(process_resources& result, Query query) { rlimit limits{}; - if (query(&limits) != 0) - { + if (query(&limits) != 0) { const auto reason = system_failure("getrlimit(RLIMIT_NOFILE)", errno); result.nofile_soft = unavailable(reason); result.nofile_hard = unavailable(reason); @@ -125,21 +120,17 @@ inline void read_file_descriptor_limits(process_resources& result) { } inline resource_metric interval_cpu_percent(double previous, double current, double seconds) { - if (!std::isfinite(previous) || !std::isfinite(current) || previous < 0 || current < 0) - { + if (!std::isfinite(previous) || !std::isfinite(current) || previous < 0 || current < 0) { return unavailable("Process CPU counter is not finite and nonnegative"); } - if (!std::isfinite(seconds) || seconds <= 0) - { + if (!std::isfinite(seconds) || seconds <= 0) { return unavailable("Monotonic sampling interval is not positive and finite"); } - if (current < previous) - { + if (current < previous) { return unavailable("Process CPU counter moved backwards"); } const double percentage = 100.0 * ((current - previous) / seconds); - if (!std::isfinite(percentage)) - { + if (!std::isfinite(percentage)) { return unavailable("CPU percentage is outside the representable range"); } return {percentage, {}}; @@ -149,8 +140,7 @@ inline resource_metric read_cpu(process_resources& result) { #ifdef _WIN32 (void)result; FILETIME created{}, exited{}, kernel{}, user{}; - if (!GetProcessTimes(GetCurrentProcess(), &created, &exited, &kernel, &user)) - { + if (!GetProcessTimes(GetCurrentProcess(), &created, &exited, &kernel, &user)) { return unavailable(system_failure("GetProcessTimes", static_cast(GetLastError()))); } const auto seconds = [](FILETIME value) { @@ -160,8 +150,7 @@ inline resource_metric read_cpu(process_resources& result) { return {seconds(kernel) + seconds(user), {}}; #elif defined(__APPLE__) || defined(__linux__) rusage usage{}; - if (getrusage(RUSAGE_SELF, &usage) != 0) - { + if (getrusage(RUSAGE_SELF, &usage) != 0) { const auto reason = system_failure("getrusage(RUSAGE_SELF)", errno); result.peak_rss_bytes = unavailable(reason); return unavailable(reason); @@ -172,12 +161,9 @@ inline resource_metric read_cpu(process_resources& result) { #else constexpr std::uint64_t rss_scale = 1024; #endif - if (usage.ru_maxrss < 0 || static_cast(usage.ru_maxrss) > maximum / rss_scale) - { + if (usage.ru_maxrss < 0 || static_cast(usage.ru_maxrss) > maximum / rss_scale) { result.peak_rss_bytes = unavailable("getrusage returned an invalid peak RSS"); - } - else - { + } else { result.peak_rss_bytes = {static_cast(usage.ru_maxrss) * rss_scale, {}}; } const double seconds = @@ -194,31 +180,25 @@ inline resource_metric read_cpu(process_resources& result) { inline resource_metric read_fd_count() { errno = 0; const int required = proc_pidinfo(getpid(), PROC_PIDLISTFDS, 0, nullptr, 0); - if (required < 0 || (required == 0 && errno != 0)) - { + if (required < 0 || (required == 0 && errno != 0)) { return unavailable(system_failure("proc_pidinfo(PROC_PIDLISTFDS)", errno)); } std::size_t capacity = static_cast(required) / sizeof(proc_fdinfo) + 32; - for (int attempt = 0; attempt != 3; ++attempt) - { - if (capacity > static_cast((std::numeric_limits::max)()) / sizeof(proc_fdinfo)) - { + for (int attempt = 0; attempt != 3; ++attempt) { + if (capacity > static_cast((std::numeric_limits::max)()) / sizeof(proc_fdinfo)) { return unavailable("FD list exceeds the proc_pidinfo buffer size limit"); } std::vector entries(capacity); const auto bytes = static_cast(entries.size() * sizeof(proc_fdinfo)); errno = 0; const int received = proc_pidinfo(getpid(), PROC_PIDLISTFDS, 0, entries.data(), bytes); - if (received < 0 || (received == 0 && errno != 0)) - { + if (received < 0 || (received == 0 && errno != 0)) { return unavailable(system_failure("proc_pidinfo(PROC_PIDLISTFDS)", errno)); } - if (received % sizeof(proc_fdinfo) != 0) - { + if (received % sizeof(proc_fdinfo) != 0) { return unavailable("proc_pidinfo returned an incomplete FD record"); } - if (received < bytes) - { + if (received < bytes) { return {static_cast(received / sizeof(proc_fdinfo)), {}}; } capacity *= 2; @@ -228,29 +208,24 @@ inline resource_metric read_fd_count() { #elif defined(__linux__) inline resource_metric read_fd_count() { std::unique_ptr directory(opendir("/proc/self/fd"), &closedir); - if (!directory) - { + if (!directory) { return unavailable(system_failure("opendir(/proc/self/fd)", errno)); } const int own_fd = dirfd(directory.get()); - if (own_fd < 0) - { + if (own_fd < 0) { return unavailable(system_failure("dirfd(/proc/self/fd)", errno)); } std::uint64_t count = 0; errno = 0; - while (const auto* entry = readdir(directory.get())) - { + while (const auto* entry = readdir(directory.get())) { char* end = nullptr; const auto descriptor = std::strtoll(entry->d_name, &end, 10); - if (end != entry->d_name && *end == '\0' && descriptor >= 0 && descriptor != own_fd) - { + if (end != entry->d_name && *end == '\0' && descriptor >= 0 && descriptor != own_fd) { ++count; } errno = 0; } - if (errno != 0) - { + if (errno != 0) { return unavailable(system_failure("readdir(/proc/self/fd)", errno)); } return {count, {}}; @@ -262,52 +237,38 @@ inline void read_memory_and_counts(process_resources& result) { result.fd_count = unavailable("POSIX file-descriptor count is unavailable on Windows"); PROCESS_MEMORY_COUNTERS memory{}; memory.cb = sizeof(memory); - if (GetProcessMemoryInfo(GetCurrentProcess(), &memory, sizeof(memory))) - { + if (GetProcessMemoryInfo(GetCurrentProcess(), &memory, sizeof(memory))) { result.current_rss_bytes = {static_cast(memory.WorkingSetSize), {}}; result.peak_rss_bytes = {static_cast(memory.PeakWorkingSetSize), {}}; - } - else - { + } else { const auto reason = system_failure("GetProcessMemoryInfo", static_cast(GetLastError())); result.current_rss_bytes = unavailable(reason); result.peak_rss_bytes = unavailable(reason); } DWORD handles = 0; - if (GetProcessHandleCount(GetCurrentProcess(), &handles)) - { + if (GetProcessHandleCount(GetCurrentProcess(), &handles)) { result.handle_count = {handles, {}}; - } - else - { + } else { result.handle_count = unavailable(system_failure("GetProcessHandleCount", static_cast(GetLastError()))); } #if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0603 HPSS snapshot = nullptr; const DWORD captured = PssCaptureSnapshot(GetCurrentProcess(), PSS_CAPTURE_THREADS, 0, &snapshot); - if (captured == ERROR_SUCCESS) - { + if (captured == ERROR_SUCCESS) { PSS_THREAD_INFORMATION threads{}; const DWORD queried = PssQuerySnapshot(snapshot, PSS_QUERY_THREAD_INFORMATION, &threads, sizeof(threads)); const DWORD freed = PssFreeSnapshot(GetCurrentProcess(), snapshot); - if (queried != ERROR_SUCCESS) - { + if (queried != ERROR_SUCCESS) { result.thread_count = unavailable(system_failure("PssQuerySnapshot", static_cast(queried))); - } - else if (freed != ERROR_SUCCESS) - { + } else if (freed != ERROR_SUCCESS) { result.thread_count = unavailable(system_failure("PssFreeSnapshot", static_cast(freed))); - } - else - { + } else { result.thread_count = {threads.ThreadsCaptured, {}}; } - } - else - { + } else { result.thread_count = unavailable(system_failure("PssCaptureSnapshot", static_cast(captured))); } @@ -318,15 +279,12 @@ inline void read_memory_and_counts(process_resources& result) { result.handle_count = unavailable("Windows handle count is unavailable on macOS"); proc_taskinfo task{}; errno = 0; - if (proc_pidinfo(getpid(), PROC_PIDTASKINFO, 0, &task, sizeof(task)) == sizeof(task)) - { + if (proc_pidinfo(getpid(), PROC_PIDTASKINFO, 0, &task, sizeof(task)) == sizeof(task)) { result.current_rss_bytes = {task.pti_resident_size, {}}; result.thread_count = task.pti_threadnum > 0 ? resource_metric{static_cast(task.pti_threadnum), {}} : unavailable("proc_pidinfo returned an invalid thread count"); - } - else - { + } else { const auto reason = errno != 0 ? system_failure("proc_pidinfo(PROC_PIDTASKINFO)", errno) : "proc_pidinfo(PROC_PIDTASKINFO) returned an unexpected structure size"; result.current_rss_bytes = unavailable(reason); @@ -340,36 +298,27 @@ inline void read_memory_and_counts(process_resources& result) { const int open_error = errno; unsigned long long virtual_pages = 0, resident_pages = 0; const auto page_size = sysconf(_SC_PAGESIZE); - if (!statm) - { + if (!statm) { result.current_rss_bytes = unavailable(system_failure("fopen(/proc/self/statm)", open_error)); - } - else if (std::fscanf(statm.get(), "%llu %llu", &virtual_pages, &resident_pages) != 2 || page_size <= 0 || - resident_pages > (std::numeric_limits::max)() / static_cast(page_size)) - { + } else if (std::fscanf(statm.get(), "%llu %llu", &virtual_pages, &resident_pages) != 2 || page_size <= 0 || + resident_pages > + (std::numeric_limits::max)() / static_cast(page_size)) { result.current_rss_bytes = unavailable("Invalid /proc/self/statm RSS or system page size"); - } - else - { + } else { result.current_rss_bytes = {resident_pages * static_cast(page_size), {}}; } } { std::unique_ptr status(std::fopen("/proc/self/status", "r"), &std::fclose); result.thread_count = unavailable("/proc/self/status has no valid Threads entry"); - if (!status) - { + if (!status) { result.thread_count = unavailable(system_failure("fopen(/proc/self/status)", errno)); - } - else - { + } else { char line[512]; - while (std::fgets(line, sizeof(line), status.get())) - { + while (std::fgets(line, sizeof(line), status.get())) { unsigned long long threads = 0; - if (std::sscanf(line, "Threads: %llu", &threads) == 1 && threads > 0) - { + if (std::sscanf(line, "Threads: %llu", &threads) == 1 && threads > 0) { result.thread_count = {threads, {}}; break; } @@ -389,8 +338,7 @@ inline void read_memory_and_counts(process_resources& result) { // Call sample() from one thread. The individual OS counters are not an atomic snapshot. // No timer, worker thread, PID selection or modification of system limits is performed. -class process_resource_sampler -{ +class process_resource_sampler { public: process_resources sample() { process_resources result; @@ -398,20 +346,14 @@ class process_resource_sampler const auto cpu = process_resource_detail::read_cpu(result); const auto after = std::chrono::steady_clock::now(); result.monotonic_time = before + (after - before) / 2; - if (!cpu.value) - { + if (!cpu.value) { result.cpu_percent = process_resource_detail::unavailable(cpu.unavailable_reason); previous_.reset(); - } - else - { - if (previous_) - { + } else { + if (previous_) { result.cpu_percent = process_resource_detail::interval_cpu_percent(previous_->second, *cpu.value, std::chrono::duration(result.monotonic_time - previous_->first).count()); - } - else - { + } else { result.cpu_percent = process_resource_detail::unavailable( "A previous successful CPU sample is required to establish an interval"); } diff --git a/benchmarks/process_resources_probe.cpp b/benchmarks/process_resources_probe.cpp index 728f5a4..17f31ed 100644 --- a/benchmarks/process_resources_probe.cpp +++ b/benchmarks/process_resources_probe.cpp @@ -32,18 +32,12 @@ using clock_type = std::chrono::steady_clock; void json_string(std::string_view value) { constexpr char hex[] = "0123456789abcdef"; std::cout << '"'; - for (const unsigned char character : value) - { - if (character == '"' || character == '\\') - { + for (const unsigned char character : value) { + if (character == '"' || character == '\\') { std::cout << '\\' << static_cast(character); - } - else if (character < 0x20) - { + } else if (character < 0x20) { std::cout << "\\u00" << hex[character >> 4] << hex[character & 15]; - } - else - { + } else { std::cout << static_cast(character); } } @@ -55,12 +49,9 @@ void write_metric(std::string_view name, const resource_metric& metr std::cout << ','; json_string(name); std::cout << ":{\"value\":"; - if (metric.value) - { + if (metric.value) { std::cout << *metric.value; - } - else - { + } else { std::cout << "null"; } std::cout << ",\"unavailable_reason\":"; @@ -83,8 +74,7 @@ void write_sample(const process_resources& sample, clock_type::time_point first, } void require(bool condition, std::string_view description, unsigned& checks) { - if (!condition) - { + if (!condition) { throw std::runtime_error(std::string(description)); } ++checks; @@ -105,15 +95,13 @@ void check_shape(const process_resources& sample, unsigned& checks) { check_metric(sample.nofile_hard, checks); check_metric(sample.handle_count, checks); check_metric(sample.thread_count, checks); - if (sample.cpu_percent.value) - { + if (sample.cpu_percent.value) { require(std::isfinite(*sample.cpu_percent.value) && *sample.cpu_percent.value >= 0, "CPU percentage must be finite and nonnegative", checks); } } -class resident_allocation -{ +class resident_allocation { public: static constexpr std::size_t size = 32 * 1024 * 1024; @@ -122,16 +110,13 @@ class resident_allocation address_ = VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); #elif defined(__APPLE__) || defined(__linux__) address_ = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (address_ == MAP_FAILED) - { + if (address_ == MAP_FAILED) { address_ = nullptr; } #endif - if (address_) - { + if (address_) { auto* bytes = static_cast(address_); - for (std::size_t offset = 0; offset < size; offset += 4096) - { + for (std::size_t offset = 0; offset < size; offset += 4096) { bytes[offset] = 1; } } @@ -141,8 +126,7 @@ class resident_allocation resident_allocation& operator=(const resident_allocation&) = delete; ~resident_allocation() { - if (address_) - { + if (address_) { #ifdef _WIN32 VirtualFree(address_, 0, MEM_RELEASE); #elif defined(__APPLE__) || defined(__linux__) @@ -167,8 +151,7 @@ void self_check() { require(interval_cpu_percent(2, 2, 1).value == 0.0, "Idle CPU interval must remain a real zero", checks); for (const auto& value : {interval_cpu_percent(1, 2, 0), interval_cpu_percent(1, 2, -1), interval_cpu_percent(2, 1, 1), - interval_cpu_percent(-1, 2, 1), interval_cpu_percent(1, (std::numeric_limits::infinity)(), 1)}) - { + interval_cpu_percent(-1, 2, 1), interval_cpu_percent(1, (std::numeric_limits::infinity)(), 1)}) { require(!value.value && !value.unavailable_reason.empty(), "Invalid CPU interval needs a reason", checks); } @@ -176,13 +159,11 @@ void self_check() { const auto first = sampler.sample(); check_shape(first, checks); require(!first.cpu_percent.value, "First CPU sample must not invent a percentage", checks); - for (int round = 0; round < 16; ++round) - { + for (int round = 0; round < 16; ++round) { const auto next = sampler.sample(); check_shape(next, checks); require(next.monotonic_time >= first.monotonic_time, "Monotonic time moved backwards", checks); - if (first.fd_count.value && next.fd_count.value) - { + if (first.fd_count.value && next.fd_count.value) { require(*next.fd_count.value == *first.fd_count.value, "Repeated sampling leaked or counted a temporary FD", checks); } @@ -192,32 +173,27 @@ void self_check() { process_resources open_files; { std::vector> files; - for (int index = 0; index < 8; ++index) - { + for (int index = 0; index < 8; ++index) { files.emplace_back(std::tmpfile(), &std::fclose); require(files.back() != nullptr, "Could not create an owned temporary file", checks); } open_files = sampler.sample(); check_shape(open_files, checks); - if (before_files.fd_count.value && open_files.fd_count.value) - { + if (before_files.fd_count.value && open_files.fd_count.value) { require(*open_files.fd_count.value >= *before_files.fd_count.value + files.size(), "Owned files were not reflected in the FD count", checks); } - if (before_files.handle_count.value && open_files.handle_count.value) - { + if (before_files.handle_count.value && open_files.handle_count.value) { require(*open_files.handle_count.value >= *before_files.handle_count.value + files.size(), "Owned files were not reflected in the Windows handle count", checks); } } const auto closed_files = sampler.sample(); check_shape(closed_files, checks); - if (open_files.fd_count.value && closed_files.fd_count.value) - { + if (open_files.fd_count.value && closed_files.fd_count.value) { require(*open_files.fd_count.value >= *closed_files.fd_count.value + 8, "Owned FDs did not close", checks); } - if (open_files.handle_count.value && closed_files.handle_count.value) - { + if (open_files.handle_count.value && closed_files.handle_count.value) { require(*open_files.handle_count.value >= *closed_files.handle_count.value + 8, "Owned handles did not close", checks); } @@ -227,8 +203,7 @@ void self_check() { std::latch started(2); const auto work = [&started](std::stop_token stop) { started.count_down(); - while (!stop.stop_requested()) - { + while (!stop.stop_requested()) { std::this_thread::sleep_for(std::chrono::milliseconds(5)); } }; @@ -237,8 +212,7 @@ void self_check() { started.wait(); const auto running = sampler.sample(); check_shape(running, checks); - if (before_threads.thread_count.value && running.thread_count.value) - { + if (before_threads.thread_count.value && running.thread_count.value) { require(*running.thread_count.value >= *before_threads.thread_count.value + 2, "Owned workers were not reflected in the thread count", checks); } @@ -254,23 +228,18 @@ void self_check() { } const auto released = sampler.sample(); check_shape(released, checks); - if (rss_release_checked && released.current_rss_bytes.value) - { + if (rss_release_checked && released.current_rss_bytes.value) { require(*resident.current_rss_bytes.value >= *released.current_rss_bytes.value + resident_allocation::size / 2, "Current RSS did not distinguish released memory from peak RSS", checks); - } - else - { + } else { rss_release_checked = false; } sampler.sample(); std::uint64_t work = 1; const auto until = clock_type::now() + std::chrono::milliseconds(150); - while (clock_type::now() < until) - { - for (int index = 0; index < 1024; ++index) - { + while (clock_type::now() < until) { + for (int index = 0; index < 1024; ++index) { work = work * 6364136223846793005ULL + 1; } } @@ -278,8 +247,7 @@ void self_check() { sink.store(work, std::memory_order_relaxed); const auto busy = sampler.sample(); check_shape(busy, checks); - if (busy.cpu_percent.value) - { + if (busy.cpu_percent.value) { require(*busy.cpu_percent.value > 0, "Owned CPU work was not reflected in interval CPU", checks); } std::cout << "{\"event\":\"process_resources_self_check\",\"checks\":" << checks @@ -291,8 +259,7 @@ void self_check() { unsigned parse_number(std::string_view text, unsigned minimum, unsigned maximum) { unsigned value = 0; const auto parsed = std::from_chars(text.data(), text.data() + text.size(), value); - if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size() || value < minimum || value > maximum) - { + if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size() || value < minimum || value > maximum) { throw std::invalid_argument("Numeric argument is outside its allowed range"); } return value; @@ -302,58 +269,43 @@ unsigned parse_number(std::string_view text, unsigned minimum, unsigned maximum) int main(int argc, char** argv) { std::cout << std::setprecision(9); - try - { + try { unsigned samples = 5, interval_ms = 1000; bool check = false; - for (int index = 1; index < argc; ++index) - { + for (int index = 1; index < argc; ++index) { const std::string_view argument(argv[index]); - if (argument == "--help") - { + if (argument == "--help") { std::cout << "Usage: arknet_process_resources_probe [--samples 1..3600] [--interval-ms 100..60000]\n" " arknet_process_resources_probe --self-check\n" "Samples only this process; JSON Lines output. CPU 100% means one logical core.\n"; return 0; } - if (argument == "--self-check") - { + if (argument == "--self-check") { check = true; - } - else if ((argument == "--samples" || argument == "--interval-ms") && index + 1 < argc) - { + } else if ((argument == "--samples" || argument == "--interval-ms") && index + 1 < argc) { const auto value = std::string_view(argv[++index]); - if (argument == "--samples") - { + if (argument == "--samples") { samples = parse_number(value, 1, 3600); - } - else - { + } else { interval_ms = parse_number(value, 100, 60000); } - } - else - { + } else { throw std::invalid_argument("Unknown argument or missing value; use --help"); } } - if (check) - { + if (check) { self_check(); return 0; } process_resource_sampler sampler; const auto first = sampler.sample(); write_sample(first, first.monotonic_time, 0); - for (unsigned index = 1; index < samples; ++index) - { + for (unsigned index = 1; index < samples; ++index) { std::this_thread::sleep_for(std::chrono::milliseconds(interval_ms)); write_sample(sampler.sample(), first.monotonic_time, index); } return 0; - } - catch (const std::exception& error) - { + } catch (const std::exception& error) { std::cerr << "process resources probe: " << error.what() << '\n'; return 1; } diff --git a/benchmarks/redis.cpp b/benchmarks/redis.cpp index f7d2400..dcd8b4e 100644 --- a/benchmarks/redis.cpp +++ b/benchmarks/redis.cpp @@ -25,8 +25,7 @@ namespace { using clock_type = std::chrono::steady_clock; -struct options -{ +struct options { std::string host = "127.0.0.1"; std::string port = "6379"; std::string username; @@ -44,8 +43,7 @@ struct options std::size_t integer(std::string_view text) { std::size_t value = 0; const auto parsed = std::from_chars(text.data(), text.data() + text.size(), value); - if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size()) - { + if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size()) { throw std::invalid_argument("Invalid integer option"); } return value; @@ -54,8 +52,7 @@ std::size_t integer(std::string_view text) { double duration(std::string_view text) { double value = 0; const auto parsed = std::from_chars(text.data(), text.data() + text.size(), value); - if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size() || !std::isfinite(value)) - { + if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size() || !std::isfinite(value)) { throw std::invalid_argument("Invalid duration option"); } return value; @@ -63,11 +60,9 @@ double duration(std::string_view text) { options arguments(int argc, char** argv) { options result; - for (int i = 1; i < argc; ++i) - { + for (int i = 1; i < argc; ++i) { const std::string key = argv[i]; - if (key == "--help") - { + if (key == "--help") { std::cout << "arknet_redis_benchmark --host HOST --port PORT --username USER --tls --ca FILE " "--clients N --pipeline N --window N --payload BYTES --rounds N " "--seconds SECONDS --warmup SECONDS\n" @@ -78,83 +73,55 @@ options arguments(int argc, char** argv) { "In-flight command payload plus per-command overhead is capped at 64 MiB per connection.\n"; std::exit(0); } - if (key == "--tls") - { + if (key == "--tls") { result.tls = true; continue; } - if (++i == argc) - { + if (++i == argc) { throw std::invalid_argument("Missing value for " + key); } const std::string value = argv[i]; - if (key == "--host") - { + if (key == "--host") { result.host = value; - } - else if (key == "--port") - { + } else if (key == "--port") { result.port = value; - } - else if (key == "--username") - { + } else if (key == "--username") { result.username = value; - } - else if (key == "--ca") - { + } else if (key == "--ca") { result.ca = value; - } - else if (key == "--clients") - { + } else if (key == "--clients") { result.clients = integer(value); - } - else if (key == "--pipeline") - { + } else if (key == "--pipeline") { result.pipeline = integer(value); - } - else if (key == "--window") - { + } else if (key == "--window") { result.window = integer(value); - } - else if (key == "--payload") - { + } else if (key == "--payload") { result.payload = integer(value); - } - else if (key == "--rounds") - { + } else if (key == "--rounds") { result.rounds = integer(value); - } - else if (key == "--seconds") - { + } else if (key == "--seconds") { result.seconds = duration(value); - } - else if (key == "--warmup") - { + } else if (key == "--warmup") { result.warmup = duration(value); - } - else - { + } else { throw std::invalid_argument("Unknown option: " + key); } } if (result.host.empty() || result.port.empty() || !result.clients || result.clients > 128 || !result.pipeline || result.pipeline > 1024 || !result.window || result.window > 64 || result.payload > 1024 * 1024 || !result.rounds || result.rounds > 100 || result.seconds <= 0 || result.seconds > 3600 || result.warmup < 0 || - result.warmup > 3600 || (!result.tls && !result.ca.empty())) - { + result.warmup > 3600 || (!result.tls && !result.ca.empty())) { throw std::invalid_argument("Invalid benchmark dimensions; use --help"); } constexpr std::size_t budget = 64 * 1024 * 1024; const auto bytes_per_command = result.payload + std::max(64, sizeof(arknet::redis::value)); - if (result.pipeline > budget / bytes_per_command || result.window > budget / bytes_per_command / result.pipeline) - { + if (result.pipeline > budget / bytes_per_command || result.window > budget / bytes_per_command / result.pipeline) { throw std::invalid_argument("In-flight data exceeds the 64 MiB per-connection budget"); } return result; } -struct phase -{ +struct phase { clock_type::time_point stop; std::atomic running; std::promise finished; @@ -166,8 +133,7 @@ struct phase }; template -struct request_chain : std::enable_shared_from_this> -{ +struct request_chain : std::enable_shared_from_this> { std::shared_ptr client; std::shared_ptr measurement; std::vector commands; @@ -184,53 +150,36 @@ struct request_chain : std::enable_shared_from_this> const auto began = clock_type::now(); client->async_pipeline(commands, [self = this->shared_from_this(), began](arknet::redis::result result) { bool valid = bool(result) && result.replies.size() == self->commands.size(); - for (const auto& reply : result.replies) - { + for (const auto& reply : result.replies) { valid = valid && reply.type == arknet::redis::value_type::bulk_string && reply.text == self->payload; } - if (valid) - { + if (valid) { self->successes += self->commands.size(); ++self->batches; const auto latency = std::chrono::duration(clock_type::now() - began).count(); - if (self->samples.size() < self->sample_capacity) - { + if (self->samples.size() < self->sample_capacity) { self->samples.push_back(latency); - } - else - { + } else { const auto index = std::uniform_int_distribution(0, self->batches - 1)(self->random); - if (index < self->samples.size()) - { + if (index < self->samples.size()) { self->samples[static_cast(index)] = latency; } } - } - else - { + } else { self->failures += self->commands.size(); - if (self->failure_reason.empty()) - { - if (result.transport_error) - { + if (self->failure_reason.empty()) { + if (result.transport_error) { self->failure_reason = result.transport_error.message(); - } - else if (result.error != arknet::redis::client_errc::none) - { + } else if (result.error != arknet::redis::client_errc::none) { self->failure_reason = "client error " + std::to_string(static_cast(result.error)); - } - else - { + } else { self->failure_reason = "ECHO reply verification failed"; } } } - if (valid && clock_type::now() < self->measurement->stop) - { + if (valid && clock_type::now() < self->measurement->stop) { self->submit(); - } - else if (self->measurement->running.fetch_sub(1) == 1) - { + } else if (self->measurement->running.fetch_sub(1) == 1) { self->measurement->finished.set_value(); } }); @@ -244,14 +193,11 @@ bool measure(const options& settings, const std::vector> auto finished = measurement->finished.get_future(); std::vector>> chains; std::string payload(settings.payload, '\0'); - for (std::size_t i = 0; i < payload.size(); ++i) - { + for (std::size_t i = 0; i < payload.size(); ++i) { payload[i] = static_cast(i % 256); } - for (const auto& client : clients) - { - for (std::size_t i = 0; i < settings.window; ++i) - { + for (const auto& client : clients) { + for (std::size_t i = 0; i < settings.window; ++i) { auto chain = std::make_shared>(); chain->client = client; chain->measurement = measurement; @@ -268,14 +214,11 @@ bool measure(const options& settings, const std::vector> const auto cpu_measured_start = process_cpu(); measurement->stop = started + std::chrono::duration_cast(std::chrono::duration(seconds)); - for (const auto& chain : chains) - { + for (const auto& chain : chains) { chain->submit(); } - if (finished.wait_for(std::chrono::duration(seconds + 10)) != std::future_status::ready) - { - for (const auto& client : clients) - { + if (finished.wait_for(std::chrono::duration(seconds + 10)) != std::future_status::ready) { + for (const auto& client : clients) { client->close(); } finished.wait_for(std::chrono::seconds(10)); @@ -286,28 +229,23 @@ bool measure(const options& settings, const std::vector> std::uint64_t successes = 0; std::uint64_t failures = 0; - struct latency_sample - { + struct latency_sample { double microseconds; double weight; }; std::vector samples; std::string failure_reason; - for (const auto& chain : chains) - { + for (const auto& chain : chains) { successes += chain->successes; failures += chain->failures; - if (!chain->samples.empty()) - { + if (!chain->samples.empty()) { const auto weight = static_cast(chain->batches) / chain->samples.size(); - for (const auto latency : chain->samples) - { + for (const auto latency : chain->samples) { samples.push_back({latency, weight}); } } - if (failure_reason.empty()) - { + if (failure_reason.empty()) { failure_reason = chain->failure_reason; } } @@ -315,25 +253,21 @@ bool measure(const options& settings, const std::vector> [](const auto& left, const auto& right) { return left.microseconds < right.microseconds; }); const auto percentile = [&](double fraction) { long double total = 0; - for (const auto& sample : samples) - { + for (const auto& sample : samples) { total += sample.weight; } const auto target = total * fraction; long double cumulative = 0; - for (const auto& sample : samples) - { + for (const auto& sample : samples) { cumulative += sample.weight; - if (cumulative >= target) - { + if (cumulative >= target) { return sample.microseconds; } } return samples.empty() ? 0.0 : samples.back().microseconds; }; const bool passed = failures == 0 && successes != 0; - if (report && passed) - { + if (report && passed) { std::ostringstream output; output.imbue(std::locale::classic()); output << std::setprecision(12) @@ -353,21 +287,15 @@ bool measure(const options& settings, const std::vector> << ",\"commands_per_second\":" << successes / elapsed << ",\"batch_roundtrip_p50_us\":" << percentile(0.50) << ",\"batch_roundtrip_p99_us\":" << percentile(0.99) << ",\"client_cpu_percent_of_one_core\":"; - if (cpu_measured_start && cpu_end) - { + if (cpu_measured_start && cpu_end) { output << (*cpu_end - *cpu_measured_start) / elapsed * 100; - } - else - { + } else { output << "null"; } output << ",\"process_peak_rss_bytes\":"; - if (const auto memory = peak_rss()) - { + if (const auto memory = peak_rss()) { output << *memory; - } - else - { + } else { output << "null"; } output << ",\"latency_sample_count\":" << samples.size() @@ -375,12 +303,10 @@ bool measure(const options& settings, const std::vector> << (!failures && successes ? "true" : "false") << "}\n"; reports.push_back(output.str()); } - if (!passed) - { + if (!passed) { std::cerr << (report ? "Measurement round " + std::to_string(round) : "Warmup") << " failed: " << failures << " commands failed, " << successes << " commands succeeded"; - if (!failure_reason.empty()) - { + if (!failure_reason.empty()) { std::cerr << " (" << failure_reason << ')'; } std::cerr << '\n'; @@ -396,8 +322,7 @@ int run(const options& settings) { target.port = settings.port; target.username = settings.username; target.client_name = "arknet_benchmark"; - if (const auto* password = std::getenv("ARKNET_REDIS_PASSWORD")) - { + if (const auto* password = std::getenv("ARKNET_REDIS_PASSWORD")) { target.password = password; } arknet::redis::client_options configuration; @@ -405,33 +330,27 @@ int run(const options& settings) { configuration.max_pending_bytes = 64 * 1024 * 1024; configuration.command_timeout = std::chrono::seconds(5); configuration.tls_certificate_authority = settings.ca; - for (std::size_t i = 0; i < settings.clients; ++i) - { + for (std::size_t i = 0; i < settings.clients; ++i) { auto client = std::make_shared(configuration); auto completion = std::make_shared>(); auto connected = completion->get_future(); client->async_connect( target, [completion](arknet::redis::result result) { completion->set_value(bool(result)); }); clients.push_back(std::move(client)); - if (connected.wait_for(std::chrono::seconds(10)) != std::future_status::ready || !connected.get()) - { + if (connected.wait_for(std::chrono::seconds(10)) != std::future_status::ready || !connected.get()) { throw std::runtime_error("Redis connection or authentication failed"); } } std::vector reports; bool passed = settings.warmup == 0 || measure(settings, clients, settings.warmup, 0, false, reports); - for (std::size_t round = 1; passed && round <= settings.rounds; ++round) - { + for (std::size_t round = 1; passed && round <= settings.rounds; ++round) { passed = measure(settings, clients, settings.seconds, round, true, reports); } - for (const auto& client : clients) - { + for (const auto& client : clients) { client->close(); } - if (passed) - { - for (const auto& report : reports) - { + if (passed) { + for (const auto& report : reports) { std::cout << report; } } @@ -441,12 +360,10 @@ int run(const options& settings) { } // namespace int main(int argc, char** argv) { - try - { + try { std::cout.imbue(std::locale::classic()); const auto settings = arguments(argc, argv); - if (settings.tls) - { + if (settings.tls) { #if defined(ARKNET_ENABLE_SSL) return run(settings); #else @@ -454,9 +371,7 @@ int main(int argc, char** argv) { #endif } return run(settings); - } - catch (const std::exception& error) - { + } catch (const std::exception& error) { std::cerr << "Redis benchmark: " << error.what() << '\n'; return 1; } diff --git a/benchmarks/resources.hpp b/benchmarks/resources.hpp index 8093c7b..c271199 100644 --- a/benchmarks/resources.hpp +++ b/benchmarks/resources.hpp @@ -15,16 +15,14 @@ inline std::optional process_cpu() { #ifdef _WIN32 FILETIME created{}, exited{}, kernel{}, user{}; - if (!GetProcessTimes(GetCurrentProcess(), &created, &exited, &kernel, &user)) - { + if (!GetProcessTimes(GetCurrentProcess(), &created, &exited, &kernel, &user)) { return std::nullopt; } const auto ticks = [](FILETIME value) { return (std::uint64_t(value.dwHighDateTime) << 32) | value.dwLowDateTime; }; return double(ticks(kernel) + ticks(user)) / 10000000.0; #else rusage usage{}; - if (getrusage(RUSAGE_SELF, &usage) != 0) - { + if (getrusage(RUSAGE_SELF, &usage) != 0) { return std::nullopt; } return double(usage.ru_utime.tv_sec + usage.ru_stime.tv_sec) + @@ -35,15 +33,13 @@ inline std::optional process_cpu() { inline std::optional peak_rss() { #ifdef _WIN32 PROCESS_MEMORY_COUNTERS usage{}; - if (!GetProcessMemoryInfo(GetCurrentProcess(), &usage, sizeof(usage))) - { + if (!GetProcessMemoryInfo(GetCurrentProcess(), &usage, sizeof(usage))) { return std::nullopt; } return static_cast(usage.PeakWorkingSetSize); #else rusage usage{}; - if (getrusage(RUSAGE_SELF, &usage) != 0) - { + if (getrusage(RUSAGE_SELF, &usage) != 0) { return std::nullopt; } #ifdef __APPLE__ diff --git a/benchmarks/rudp.cpp b/benchmarks/rudp.cpp index c4d1140..1f67201 100644 --- a/benchmarks/rudp.cpp +++ b/benchmarks/rudp.cpp @@ -36,8 +36,7 @@ constexpr std::size_t sample_limit = 8192; constexpr auto drain_time = std::chrono::seconds(10); constexpr int receive_buffer = 4 * 1024 * 1024; -struct options -{ +struct options { std::size_t payload = 1024; std::size_t clients = 1; std::size_t window = 1; @@ -53,8 +52,7 @@ struct options std::size_t integer(std::string_view text) { std::size_t result{}; const auto parsed = std::from_chars(text.data(), text.data() + text.size(), result); - if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size()) - { + if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size()) { throw std::invalid_argument("invalid integer argument"); } return result; @@ -63,8 +61,7 @@ std::size_t integer(std::string_view text) { double duration(const std::string& text) { char* end{}; const auto result = std::strtod(text.c_str(), &end); - if (text.empty() || end != text.c_str() + text.size() || !std::isfinite(result)) - { + if (text.empty() || end != text.c_str() + text.size() || !std::isfinite(result)) { throw std::invalid_argument("invalid duration argument"); } return result; @@ -73,11 +70,9 @@ double duration(const std::string& text) { options arguments(int argc, char** argv) { options result; bool explicit_profile = false, explicit_loss = false, explicit_burst = false; - for (int i = 1; i < argc; ++i) - { + for (int i = 1; i < argc; ++i) { const std::string key = argv[i]; - if (key == "--help") - { + if (key == "--help") { std::cout << "arknet_rudp_benchmark --payload BYTES --clients COUNT --window COUNT " "--seconds SECONDS --warmup SECONDS --rounds COUNT " "--io-model shared|sharded --io-threads COUNT " @@ -87,133 +82,86 @@ options arguments(int argc, char** argv) { "--seed INTEGER\n"; std::exit(0); } - if (++i == argc) - { + if (++i == argc) { throw std::invalid_argument("missing value for " + key); } const std::string value = argv[i]; - if (key == "--payload") - { + if (key == "--payload") { result.payload = integer(value); - } - else if (key == "--clients") - { + } else if (key == "--clients") { result.clients = integer(value); - } - else if (key == "--window") - { + } else if (key == "--window") { result.window = integer(value); - } - else if (key == "--io-threads") - { + } else if (key == "--io-threads") { result.io_threads = integer(value); - } - else if (key == "--rounds") - { + } else if (key == "--rounds") { result.rounds = integer(value); - } - else if (key == "--seconds") - { + } else if (key == "--seconds") { result.seconds = duration(value); - } - else if (key == "--warmup") - { + } else if (key == "--warmup") { result.warmup = duration(value); - } - else if (key == "--io-model") - { + } else if (key == "--io-model") { result.io_model = value; - } - else if (key == "--fec") - { + } else if (key == "--fec") { result.fec = value; - } - else if (key == "--profile") - { + } else if (key == "--profile") { result.profile = value; explicit_profile = true; - } - else if (key == "--seed") - { + } else if (key == "--seed") { result.impairment.seed = integer(value); - } - else if (key == "--loss-percent" || key == "--duplicate-percent" || key == "--reorder-percent" || - key == "--delay-ms" || key == "--burst-count" || key == "--burst-period") - { + } else if (key == "--loss-percent" || key == "--duplicate-percent" || key == "--reorder-percent" || + key == "--delay-ms" || key == "--burst-count" || key == "--burst-period") { const auto number = integer(value); - if (number > 1000000) - { + if (number > 1000000) { throw std::invalid_argument("impairment argument out of range"); } - if (key == "--loss-percent") - { + if (key == "--loss-percent") { result.impairment.loss_percent = static_cast(number); explicit_loss = true; - } - else if (key == "--duplicate-percent") - { + } else if (key == "--duplicate-percent") { result.impairment.duplicate_percent = static_cast(number); - } - else if (key == "--reorder-percent") - { + } else if (key == "--reorder-percent") { result.impairment.reorder_percent = static_cast(number); - } - else if (key == "--delay-ms") - { + } else if (key == "--delay-ms") { result.impairment.delay_ms = static_cast(number); - } - else if (key == "--burst-count") - { + } else if (key == "--burst-count") { result.impairment.burst_count = static_cast(number); explicit_burst = true; - } - else - { + } else { result.impairment.burst_period = static_cast(number); } - } - else - { + } else { throw std::invalid_argument("unknown argument: " + key); } } if (result.payload < 16 || result.payload > 16384 || result.clients < 1 || result.clients > 1024 || result.window < 1 || result.window > 1024 || result.io_threads < 1 || result.io_threads > 64 || result.rounds < 1 || result.rounds > 100 || result.seconds <= 0 || result.seconds > 3600 || result.warmup < 0 || - result.warmup > 3600) - { + result.warmup > 3600) { throw std::invalid_argument("argument out of range"); } - if (std::uint64_t(result.clients) * result.window * result.payload > 256 * 1024 * 1024) - { + if (std::uint64_t(result.clients) * result.window * result.payload > 256 * 1024 * 1024) { throw std::invalid_argument("requested outstanding payloads exceed 256 MiB"); } - if (result.io_model != "shared" && result.io_model != "sharded") - { + if (result.io_model != "shared" && result.io_model != "sharded") { throw std::invalid_argument("unsupported IO model"); } - if (result.fec != "off" && result.fec != "fixed" && result.fec != "adaptive") - { + if (result.fec != "off" && result.fec != "fixed" && result.fec != "adaptive") { throw std::invalid_argument("unsupported FEC policy"); } - if (!explicit_profile) - { + if (!explicit_profile) { result.profile = result.impairment.burst_count ? "burst" : result.impairment.loss_percent ? "random" : "clean"; } - if (result.profile != "clean" && result.profile != "random" && result.profile != "burst") - { + if (result.profile != "clean" && result.profile != "random" && result.profile != "burst") { throw std::invalid_argument("unsupported impairment profile"); } - if (result.profile == "random" && !explicit_loss) - { + if (result.profile == "random" && !explicit_loss) { result.impairment.loss_percent = 3; } - if (result.profile == "burst" && !explicit_burst) - { + if (result.profile == "burst" && !explicit_burst) { result.impairment.burst_count = 3; } - if (result.profile == "clean" && (result.impairment.loss_percent || result.impairment.burst_count)) - { + if (result.profile == "clean" && (result.impairment.loss_percent || result.impairment.burst_count)) { throw std::invalid_argument("clean profile cannot include packet loss"); } result.impairment.validate(); @@ -226,8 +174,7 @@ arknet::fec::options fec_options(const options& settings) { : arknet::fec::options{}; } -struct transport_totals -{ +struct transport_totals { arknet::rudp::statistics value; unsigned endpoints{}, parity_min = 16, parity_max{}; }; @@ -264,29 +211,23 @@ transport_totals snapshot(arknet::rudp_server& server, const std::vector> sessions; server.foreach_session([&](auto& session) { sessions.push_back(session); }); std::vector> futures; - for (const auto& client : clients) - { + for (const auto& client : clients) { futures.push_back(client->get_rudp_statistics()); } - for (const auto& session : sessions) - { + for (const auto& session : sessions) { futures.push_back(session->get_rudp_statistics()); } transport_totals totals; - for (auto& future : futures) - { - if (future.wait_for(std::chrono::seconds(5)) != std::future_status::ready) - { + for (auto& future : futures) { + if (future.wait_for(std::chrono::seconds(5)) != std::future_status::ready) { throw std::runtime_error("RUDP statistics snapshot timed out"); } const auto value = future.get(); - if (value.fec.retained_bytes > settings.max_buffer_bytes || value.fec.retained_groups > settings.max_groups) - { + if (value.fec.retained_bytes > settings.max_buffer_bytes || value.fec.retained_groups > settings.max_groups) { throw std::runtime_error("FEC retention limit exceeded"); } if (value.arq_pending_bytes > arknet::arq::options{}.max_pending_bytes || - value.raw_queued_bytes > arknet::arq::options{}.max_pending_bytes) - { + value.raw_queued_bytes > arknet::arq::options{}.max_pending_bytes) { throw std::runtime_error("RUDP queue limit exceeded"); } add(totals, value); @@ -294,8 +235,7 @@ transport_totals snapshot(arknet::rudp_server& server, const std::vector(0, observations - 1)(random); - if (index < sample_limit) - { + if (index < sample_limit) { samples[static_cast(index)] = microseconds; } } @@ -343,8 +279,7 @@ struct metrics } }; -struct round_state -{ +struct round_state { std::array, 2> phases{std::make_shared(0), std::make_shared(1)}; std::atomic closing{}; int server_receive_buffer{}; @@ -362,31 +297,24 @@ bool send(Sender& sender, std::string message, const std::shared_ptr& s auto completed = std::make_shared>(false); ++state->pending_completions; const auto completion = [state, completed, size](const arknet::error_code& error, std::size_t bytes) noexcept { - if (completed->exchange(true)) - { + if (completed->exchange(true)) { ++state->duplicate_completions; return; } - if (error || bytes != size) - { + if (error || bytes != size) { ++state->send_errors; } - if (state->pending_completions.fetch_sub(1) == 1) - { + if (state->pending_completions.fetch_sub(1) == 1) { state->notify(); } }; - try - { + try { const bool accepted = sender.async_send(std::move(message), completion); - if (!accepted) - { + if (!accepted) { ++state->rejected; } return accepted; - } - catch (...) - { + } catch (...) { ++state->callback_exceptions; completion(asio::error::operation_aborted, 0); return false; @@ -394,25 +322,21 @@ bool send(Sender& sender, std::string message, const std::shared_ptr& s } void put_integer(std::string& message, std::size_t offset, std::uint64_t value, std::size_t bytes) { - for (std::size_t i = 0; i < bytes; ++i) - { + for (std::size_t i = 0; i < bytes; ++i) { message[offset + i] = static_cast((value >> ((bytes - 1 - i) * 8)) & 255); } } std::uint64_t get_integer(std::string_view message, std::size_t offset, std::size_t bytes) { std::uint64_t value{}; - for (std::size_t i = 0; i < bytes; ++i) - { + for (std::size_t i = 0; i < bytes; ++i) { value = (value << 8) | static_cast(message[offset + i]); } return value; } -struct client_state -{ - struct pending - { +struct client_state { + struct pending { std::string message; clock_type::time_point sent; std::uint64_t sequence{}; @@ -437,33 +361,27 @@ struct client_state bool stopped{}, finished{}; void finish(bool force = false) noexcept { - if (!phase || finished) - { + if (!phase || finished) { return; } - if (force) - { + if (force) { phase->unfinished += inflight; - for (auto& slot : slots) - { + for (auto& slot : slots) { slot.message.clear(); } inflight = 0; } - if (inflight) - { + if (inflight) { return; } finished = true; - if (phase->remaining.fetch_sub(1) == 1) - { + if (phase->remaining.fetch_sub(1) == 1) { phase->notify(); } } void transmit(std::size_t index) { - if (stopped || clock_type::now() >= phase->deadline) - { + if (stopped || clock_type::now() >= phase->deadline) { return; } auto& slot = slots[index]; @@ -473,18 +391,14 @@ struct client_state slot.sequence = next_sequence++; put_integer(slot.message, 4, id, 4); put_integer(slot.message, 8, slot.sequence, 8); - for (std::size_t i = 16; i < slot.message.size(); ++i) - { + for (std::size_t i = 16; i < slot.message.size(); ++i) { slot.message[i] = static_cast((slot.sequence + i * 17 + std::uint64_t(id) * 31) & 255); } slot.sent = clock_type::now(); ++inflight; - if (send(client, slot.message, phase)) - { + if (send(client, slot.message, phase)) { ++phase->sent; - } - else - { + } else { slot.message.clear(); --inflight; stopped = true; @@ -495,26 +409,20 @@ struct client_state phase = current; stopped = finished = false; expected_sequence = next_sequence; - try - { - if (!client.is_started()) - { + try { + if (!client.is_started()) { ++phase->disconnects; stopped = true; finish(true); return; } - for (std::size_t i = 0; i < slots.size(); ++i) - { + for (std::size_t i = 0; i < slots.size(); ++i) { transmit(i); } - if (stopped) - { + if (stopped) { finish(); } - } - catch (...) - { + } catch (...) { ++phase->callback_exceptions; stopped = true; finish(true); @@ -522,31 +430,24 @@ struct client_state } void receive(std::string_view message) noexcept { - try - { + try { if (!phase || message.size() != settings.payload || message.substr(0, 3) != "ANR" || - static_cast(message[3]) != phase->tag || get_integer(message, 4, 4) != id) - { + static_cast(message[3]) != phase->tag || get_integer(message, 4, 4) != id) { ++(phase ? phase : owner->phases[0])->content_errors; return; } const auto sequence = get_integer(message, 8, 8); auto found = std::find_if(slots.begin(), slots.end(), [sequence](const pending& slot) { return !slot.message.empty() && slot.sequence == sequence; }); - if (finished || found == slots.end()) - { + if (finished || found == slots.end()) { ++phase->unexpected; return; } const auto now = clock_type::now(); - if (message != found->message) - { + if (message != found->message) { ++phase->content_errors; - } - else - { - if (sequence != expected_sequence) - { + } else { + if (sequence != expected_sequence) { ++phase->order_errors; } expected_sequence = sequence + 1; @@ -556,22 +457,18 @@ struct client_state const auto index = static_cast(found - slots.begin()); found->message.clear(); --inflight; - if (now < phase->deadline) - { + if (now < phase->deadline) { transmit(index); } finish(); - } - catch (...) - { + } catch (...) { ++(phase ? phase : owner->phases[0])->callback_exceptions; stopped = true; } } void disconnect() noexcept { - if (!owner->closing && phase) - { + if (!owner->closing && phase) { ++phase->disconnects; stopped = true; finish(true); @@ -581,8 +478,7 @@ struct client_state void run_phase(const std::vector>& clients, const std::shared_ptr& phase, double seconds, const std::function& sample = {}) { - if (seconds == 0) - { + if (seconds == 0) { return; } phase->remaining = clients.size(); @@ -590,20 +486,16 @@ void run_phase(const std::vector>& clients, const phase->deadline = phase->started + std::chrono::duration_cast(std::chrono::duration(seconds)); const auto cpu_start = process_cpu(); - for (const auto& state : clients) - { + for (const auto& state : clients) { state->client.post([state, phase] { state->begin(phase); }); } - while (clock_type::now() < phase->deadline) - { + while (clock_type::now() < phase->deadline) { std::this_thread::sleep_until(std::min(phase->deadline, clock_type::now() + std::chrono::milliseconds(100))); - if (sample) - { + if (sample) { sample(); } } - for (const auto& state : clients) - { + for (const auto& state : clients) { state->client.post([state] { state->stopped = true; state->finish(); @@ -612,14 +504,12 @@ void run_phase(const std::vector>& clients, const { std::unique_lock lock(phase->done_mutex); if (!phase->done.wait_until(lock, phase->deadline + drain_time, - [&] { return phase->remaining == 0 && phase->pending_completions == 0; })) - { + [&] { return phase->remaining == 0 && phase->pending_completions == 0; })) { ++phase->drain_timeouts; } } std::vector> barriers; - for (const auto& state : clients) - { + for (const auto& state : clients) { barriers.push_back(state->client.post( [state] { state->stopped = true; @@ -627,18 +517,15 @@ void run_phase(const std::vector>& clients, const }, asio::use_future)); } - for (auto& barrier : barriers) - { - if (barrier.wait_for(std::chrono::seconds(5)) != std::future_status::ready) - { + for (auto& barrier : barriers) { + if (barrier.wait_for(std::chrono::seconds(5)) != std::future_status::ready) { throw std::runtime_error("client IO barrier timed out"); } barrier.get(); } phase->elapsed = std::chrono::duration(clock_type::now() - phase->started).count(); const auto cpu_end = process_cpu(); - if (cpu_start && cpu_end && phase->elapsed > 0) - { + if (cpu_start && cpu_end && phase->elapsed > 0) { phase->cpu_percent = (*cpu_end - *cpu_start) / phase->elapsed * 100; } } @@ -646,15 +533,13 @@ void run_phase(const std::vector>& clients, const int configure(asio::ip::udp::socket& socket, const std::shared_ptr& owner) noexcept { arknet::error_code error; socket.set_option(asio::socket_base::receive_buffer_size(receive_buffer), error); - if (error) - { + if (error) { ++owner->phases[0]->callback_exceptions; return 0; } asio::socket_base::receive_buffer_size effective; socket.get_option(effective, error); - if (error) - { + if (error) { ++owner->phases[0]->callback_exceptions; return 0; } @@ -673,40 +558,34 @@ std::shared_ptr benchmark(const options& settings, std::size_t roun std::vector> states; const auto stop = [&] { owner->closing = true; - for (auto& client : clients) - { + for (auto& client : clients) { client->stop(); } server.stop(); relay.stop(); }; - try - { + try { server.set_rudp_options(arknet::arq::options{}); server.set_fec_options(fec_options(settings)); server.bind_init([&server, owner] { owner->server_receive_buffer = configure(server.acceptor(), owner); }); server.bind_recv([owner](auto& session, std::string_view message) noexcept { - try - { + try { const auto tag = message.size() >= 16 ? static_cast(message[3]) : 2; const auto state = owner->phases[tag < 2 ? tag : 0]; - if (tag > 1 || message.substr(0, 3) != "ANR") - { + if (tag > 1 || message.substr(0, 3) != "ANR") { ++state->content_errors; return; } ++state->server_received; send(*session, std::string(message), state); + } catch (...) { + ++owner->phases[0]->callback_exceptions; } - catch (...) - { ++owner->phases[0]->callback_exceptions; } }); - if (!server.start("127.0.0.1", 0)) - { + if (!server.start("127.0.0.1", 0)) { throw std::runtime_error("server start failed: " + arknet::get_last_error().message()); } - for (std::size_t i = 0; i < settings.clients; ++i) - { + for (std::size_t i = 0; i < settings.clients; ++i) { auto client = std::make_unique(1024, 128 * 1024, runtime.context_at(i)); auto state = std::make_shared(*client, owner, settings, static_cast(i)); client->set_rudp_options(arknet::arq::options{}); @@ -721,8 +600,7 @@ std::shared_ptr benchmark(const options& settings, std::size_t roun clients.push_back(std::move(client)); states.push_back(std::move(state)); const auto port = relay.add({asio::ip::make_address("127.0.0.1"), server.get_listen_port()}); - if (!clients.back()->start("127.0.0.1", port)) - { + if (!clients.back()->start("127.0.0.1", port)) { throw std::runtime_error("client start failed: " + arknet::get_last_error().message()); } } @@ -741,18 +619,17 @@ std::shared_ptr benchmark(const options& settings, std::size_t roun run_phase(states, owner->phases[1], settings.seconds, sample); sample(); owner->relay_after = relay.snapshot(); + } catch (const std::exception& error) { + owner->error = error.what(); + } catch (...) { + owner->error = "unknown benchmark failure"; } - catch (const std::exception& error) - { owner->error = error.what(); } - catch (...) - { owner->error = "unknown benchmark failure"; } stop(); return owner; } double percentile(std::vector values, double fraction) { - if (values.empty()) - { + if (values.empty()) { return 0; } std::sort(values.begin(), values.end()); @@ -760,8 +637,7 @@ double percentile(std::vector values, double fraction) { } double median(std::vector values) { - if (values.empty()) - { + if (values.empty()) { return 0; } std::sort(values.begin(), values.end()); @@ -771,18 +647,12 @@ double median(std::vector values) { void write_quoted(std::string_view value) { std::cout << '"'; - for (const unsigned char c : value) - { - if (c == '"' || c == '\\') - { + for (const unsigned char c : value) { + if (c == '"' || c == '\\') { std::cout << '\\' << static_cast(c); - } - else if (c < 32) - { + } else if (c < 32) { std::cout << "\\u00" << std::hex << std::setw(2) << std::setfill('0') << unsigned(c) << std::dec; - } - else - { + } else { std::cout << static_cast(c); } } @@ -790,12 +660,9 @@ void write_quoted(std::string_view value) { } void optional_number(const std::optional& value) { - if (value) - { + if (value) { std::cout << *value; - } - else - { + } else { std::cout << "null"; } } @@ -854,10 +721,8 @@ bool output(const options& settings, const std::vectorcpu_percent) - { + if (state->cpu_percent) { cpu.push_back(*state->cpu_percent); } wire_rates.push_back(wire_rate); @@ -957,12 +820,9 @@ bool output(const options& settings, const std::vectorpending_completions << ",\"warmup_failures\":" << round->phases[0]->failures() << ",\"ok\":" << (passed ? "true" : "false") << ",\"error\":"; - if (round->error.empty()) - { + if (round->error.empty()) { std::cout << (passed ? "null" : "\"benchmark validation failed\""); - } - else - { + } else { write_quoted(round->error); } std::cout << '}'; @@ -980,12 +840,9 @@ bool output(const options& settings, const std::vector> rounds; - for (std::size_t i = 0; i < settings.rounds; ++i) - { + for (std::size_t i = 0; i < settings.rounds; ++i) { rounds.push_back(benchmark(settings, i)); } return output(settings, rounds) ? 0 : 1; - } - catch (const std::exception& error) - { + } catch (const std::exception& error) { std::cerr << "RUDP benchmark: " << error.what() << '\n'; return 1; } diff --git a/benchmarks/runtime.hpp b/benchmarks/runtime.hpp index e86feef..9f3dc3d 100644 --- a/benchmarks/runtime.hpp +++ b/benchmarks/runtime.hpp @@ -7,25 +7,19 @@ #include -class benchmark_runtime -{ +class benchmark_runtime { public: benchmark_runtime(std::string_view model, std::size_t threads) { const auto count = model == "shared" ? 1 : threads; - for (std::size_t i = 0; i < count; ++i) - { + for (std::size_t i = 0; i < count; ++i) { contexts_.push_back(std::make_shared()); guards_.push_back(asio::make_work_guard(*contexts_.back())); } - try - { - for (std::size_t i = 0; i < threads; ++i) - { + try { + for (std::size_t i = 0; i < threads; ++i) { workers_.emplace_back([context = contexts_[i % count]] { context->run(); }); } - } - catch (...) - { + } catch (...) { stop(); throw; } @@ -44,8 +38,7 @@ class benchmark_runtime std::vector server_lanes(std::size_t clients) { std::vector result; - for (std::size_t i = 0; i <= clients; ++i) - { + for (std::size_t i = 0; i <= clients; ++i) { result.push_back(&context_at(i)); } return result; @@ -53,14 +46,11 @@ class benchmark_runtime void stop() { // Drain cross-context captures before any context can destroy its services. - for (auto& guard : guards_) - { + for (auto& guard : guards_) { guard.reset(); } - for (auto& worker : workers_) - { - if (worker.joinable()) - { + for (auto& worker : workers_) { + if (worker.joinable()) { worker.join(); } } diff --git a/benchmarks/work.hpp b/benchmarks/work.hpp index 194668b..025e768 100644 --- a/benchmarks/work.hpp +++ b/benchmarks/work.hpp @@ -6,8 +6,7 @@ inline void benchmark_work(std::size_t iterations, std::string_view data) { std::uint64_t value = data.empty() ? 1 : static_cast(data.back()) + 1; - for (std::size_t i = 0; i < iterations; ++i) - { + for (std::size_t i = 0; i < iterations; ++i) { value ^= value << 13; value ^= value >> 7; value ^= value << 17; diff --git a/examples/echo_server.cpp b/examples/echo_server.cpp index c5dc83a..f7f4306 100644 --- a/examples/echo_server.cpp +++ b/examples/echo_server.cpp @@ -7,8 +7,7 @@ int main(int argc, char** argv) { const std::string port = argc > 2 ? argv[2] : "7000"; arknet::tcp_server server; server.bind_recv([](auto& session, std::string_view data) { session->async_send(data); }); - if (!server.start(host, port)) - { + if (!server.start(host, port)) { std::cerr << arknet::get_last_error().message() << '\n'; return 1; } diff --git a/examples/http_echo_server.cpp b/examples/http_echo_server.cpp index b5cda3a..b80094c 100644 --- a/examples/http_echo_server.cpp +++ b/examples/http_echo_server.cpp @@ -14,8 +14,7 @@ int main(int argc, char** argv) { response.prepare_payload(); session->async_send(std::move(response)); }); - if (!server.start(host, port)) - { + if (!server.start(host, port)) { std::cerr << arknet::get_last_error().message() << '\n'; return 1; } diff --git a/examples/ipv6_echo.cpp b/examples/ipv6_echo.cpp index 3db3763..ee91c70 100644 --- a/examples/ipv6_echo.cpp +++ b/examples/ipv6_echo.cpp @@ -10,8 +10,7 @@ int main() { arknet::tcp_server server; server.bind_recv([](auto& session, std::string_view data) { session->async_send(data); }); - if (!server.start("::1", 0, arknet::use_dgram)) - { + if (!server.start("::1", 0, arknet::use_dgram)) { std::cerr << arknet::get_last_error().message() << '\n'; return 1; } @@ -21,14 +20,12 @@ int main() { client.set_auto_reconnect(false); bool received_once = false; client.bind_recv([&](std::string_view data) { - if (!received_once) - { + if (!received_once) { received_once = true; reply.set_value(std::string(data)); } }); - if (!client.start("::1", server.get_listen_port(), arknet::use_dgram) || !client.async_send("hello IPv6")) - { + if (!client.start("::1", server.get_listen_port(), arknet::use_dgram) || !client.async_send("hello IPv6")) { std::cerr << arknet::get_last_error().message() << '\n'; return 2; } diff --git a/examples/mysql_client.cpp b/examples/mysql_client.cpp index c5bdbd6..80ef6d6 100644 --- a/examples/mysql_client.cpp +++ b/examples/mysql_client.cpp @@ -22,8 +22,7 @@ Result wait_for(arknet::mysql::connection& client, Submit submit) { auto completion = completed->get_future(); submit([completed](Result result) { completed->set_value(std::move(result)); }); // Waiting is confined to main. The callback owns its promise if a deadline closes the connection. - if (completion.wait_for(std::chrono::seconds(15)) != std::future_status::ready) - { + if (completion.wait_for(std::chrono::seconds(15)) != std::future_status::ready) { client.close(); throw std::runtime_error("MySQL example operation deadline exceeded"); } @@ -31,8 +30,7 @@ Result wait_for(arknet::mysql::connection& client, Submit submit) { } void require(const arknet::mysql::connection_result& status, std::string_view operation) { - if (!status) - { + if (!status) { throw std::runtime_error(std::string(operation) + " failed: client_code=" + std::to_string(static_cast(status.code)) + ", server_code=" + std::to_string(status.server_code) + @@ -43,8 +41,7 @@ void require(const arknet::mysql::connection_result& status, std::string_view op std::uint16_t port_number(std::string_view text) { unsigned value = 0; const auto parsed = std::from_chars(text.data(), text.data() + text.size(), value); - if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size() || value == 0 || value > 65535) - { + if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size() || value == 0 || value > 65535) { throw std::invalid_argument("PORT must be between 1 and 65535"); } return static_cast(value); @@ -53,10 +50,8 @@ std::uint16_t port_number(std::string_view text) { } // namespace int main(int argc, char** argv) { - try - { - if (argc != 3) - { + try { + if (argc != 3) { std::cerr << "Usage: arknet_mysql_client HOST PORT\n" "Environment: ARKNET_MYSQL_USERNAME, ARKNET_MYSQL_PASSWORD, optional " "ARKNET_MYSQL_DATABASE, ARKNET_MYSQL_CA_FILE, ARKNET_MYSQL_SERVER_NAME\n"; @@ -66,25 +61,20 @@ int main(int argc, char** argv) { options.host = argv[1]; options.port = port_number(argv[2]); const auto* username = std::getenv("ARKNET_MYSQL_USERNAME"); - if (!username || !*username) - { + if (!username || !*username) { throw std::invalid_argument("Set ARKNET_MYSQL_USERNAME"); } options.username = username; - if (const auto* password = std::getenv("ARKNET_MYSQL_PASSWORD")) - { + if (const auto* password = std::getenv("ARKNET_MYSQL_PASSWORD")) { options.password = password; } - if (const auto* database = std::getenv("ARKNET_MYSQL_DATABASE")) - { + if (const auto* database = std::getenv("ARKNET_MYSQL_DATABASE")) { options.database = database; } - if (const auto* authority = std::getenv("ARKNET_MYSQL_CA_FILE")) - { + if (const auto* authority = std::getenv("ARKNET_MYSQL_CA_FILE")) { options.ca_file = authority; } - if (const auto* identity = std::getenv("ARKNET_MYSQL_SERVER_NAME")) - { + if (const auto* identity = std::getenv("ARKNET_MYSQL_SERVER_NAME")) { options.server_name = identity; } @@ -98,15 +88,13 @@ int main(int argc, char** argv) { require(query.status, "Query"); if (query.rows.size() != 1 || query.rows.front().size() != 3 || std::get(query.rows[0][0]) != 42 || std::get(query.rows[0][1]) != "arknet" || - !std::holds_alternative(query.rows[0][2])) - { + !std::holds_alternative(query.rows[0][2])) { throw std::runtime_error("Unexpected query result"); } auto prepared = wait_for(client, [&](auto completed) { client.async_prepare("SELECT ? AS number, ? AS payload", std::move(completed)); }); require(prepared.status, "Prepare"); - if (prepared.statement.parameter_count() != 2) - { + if (prepared.statement.parameter_count() != 2) { throw std::runtime_error("Unexpected prepared parameter count"); } auto began = wait_for( @@ -118,8 +106,8 @@ int main(int argc, char** argv) { }); require(executed.status, "Execute"); if (executed.rows.size() != 1 || executed.rows.front().size() != 2 || - std::get(executed.rows[0][0]) != 42 || std::get(executed.rows[0][1]) != payload) - { + std::get(executed.rows[0][0]) != 42 || + std::get(executed.rows[0][1]) != payload) { throw std::runtime_error("Unexpected prepared result"); } auto committed = wait_for( @@ -131,9 +119,7 @@ int main(int argc, char** argv) { client.close(); std::cout << "Query, typed prepared parameters and transaction verified over TLS\n"; return 0; - } - catch (const std::exception& error) - { + } catch (const std::exception& error) { std::cerr << "MySQL example: " << error.what() << '\n'; return 1; } diff --git a/examples/mysql_pool.cpp b/examples/mysql_pool.cpp index 6c29a8f..7d27418 100644 --- a/examples/mysql_pool.cpp +++ b/examples/mysql_pool.cpp @@ -21,8 +21,7 @@ Result wait_for(arknet::mysql::pool& pool, Submit submit) { auto completed = std::make_shared>(); auto completion = completed->get_future(); submit([completed](Result response) { completed->set_value(std::move(response)); }); - if (completion.wait_for(std::chrono::seconds(15)) != std::future_status::ready) - { + if (completion.wait_for(std::chrono::seconds(15)) != std::future_status::ready) { pool.close(); throw std::runtime_error("MySQL pool example operation deadline exceeded"); } @@ -30,8 +29,7 @@ Result wait_for(arknet::mysql::pool& pool, Submit submit) { } void require(const arknet::mysql::connection_result& status, std::string_view operation) { - if (!status) - { + if (!status) { throw std::runtime_error(std::string(operation) + " failed: client_code=" + std::to_string(static_cast(status.code)) + ", server_code=" + std::to_string(status.server_code) + @@ -42,8 +40,7 @@ void require(const arknet::mysql::connection_result& status, std::string_view op std::uint16_t port_number(std::string_view text) { unsigned value = 0; const auto parsed = std::from_chars(text.data(), text.data() + text.size(), value); - if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size() || value == 0 || value > 65535) - { + if (parsed.ec != std::errc{} || parsed.ptr != text.data() + text.size() || value == 0 || value > 65535) { throw std::invalid_argument("PORT must be between 1 and 65535"); } return static_cast(value); @@ -52,10 +49,8 @@ std::uint16_t port_number(std::string_view text) { } // namespace int main(int argc, char** argv) { - try - { - if (argc != 3) - { + try { + if (argc != 3) { std::cerr << "Usage: arknet_mysql_pool HOST PORT\n" "Environment: ARKNET_MYSQL_USERNAME, ARKNET_MYSQL_PASSWORD, optional " "ARKNET_MYSQL_DATABASE, ARKNET_MYSQL_CA_FILE, ARKNET_MYSQL_SERVER_NAME\n"; @@ -68,33 +63,27 @@ int main(int argc, char** argv) { options.connection.host = argv[1]; options.connection.port = port_number(argv[2]); const auto* username = std::getenv("ARKNET_MYSQL_USERNAME"); - if (!username || !*username) - { + if (!username || !*username) { throw std::invalid_argument("Set ARKNET_MYSQL_USERNAME"); } options.connection.username = username; - if (const auto* password = std::getenv("ARKNET_MYSQL_PASSWORD")) - { + if (const auto* password = std::getenv("ARKNET_MYSQL_PASSWORD")) { options.connection.password = password; } - if (const auto* database = std::getenv("ARKNET_MYSQL_DATABASE")) - { + if (const auto* database = std::getenv("ARKNET_MYSQL_DATABASE")) { options.connection.database = database; } - if (const auto* authority = std::getenv("ARKNET_MYSQL_CA_FILE")) - { + if (const auto* authority = std::getenv("ARKNET_MYSQL_CA_FILE")) { options.connection.ca_file = authority; } - if (const auto* identity = std::getenv("ARKNET_MYSQL_SERVER_NAME")) - { + if (const auto* identity = std::getenv("ARKNET_MYSQL_SERVER_NAME")) { options.connection.server_name = identity; } arknet::mysql::pool pool(options); auto acquired = wait_for( pool, [&](auto completed) { pool.async_acquire(std::move(completed)); }); require(acquired.status, "Acquire connection"); - if (!acquired) - { + if (!acquired) { throw std::runtime_error("MySQL pool lease is unavailable"); } auto connection = std::move(acquired.connection); @@ -105,8 +94,7 @@ int main(int argc, char** argv) { pool, [&](auto completed) { connection.async_query("SELECT 42 AS number", std::move(completed)); }); require(query.status, "Query"); if (query.rows.size() != 1 || query.rows.front().size() != 1 || - std::get(query.rows.front().front()) != 42) - { + std::get(query.rows.front().front()) != 42) { throw std::runtime_error("Unexpected MySQL pooled query result"); } auto committed = wait_for( @@ -117,9 +105,7 @@ int main(int argc, char** argv) { pool.close(); std::cout << "Pooled query and exclusive transaction verified over TLS\n"; return 0; - } - catch (const std::exception& error) - { + } catch (const std::exception& error) { std::cerr << "MySQL pool example: " << error.what() << '\n'; return 1; } diff --git a/examples/redis_client.cpp b/examples/redis_client.cpp index c36d576..c88b3b7 100644 --- a/examples/redis_client.cpp +++ b/examples/redis_client.cpp @@ -20,8 +20,7 @@ int run(const arknet::redis::connect_options& target, const arknet::redis::clien auto connection = connected->get_future(); client.async_connect( target, [connected](arknet::redis::result response) { connected->set_value(std::move(response)); }); - if (connection.wait_for(std::chrono::seconds(10)) != std::future_status::ready || !connection.get()) - { + if (connection.wait_for(std::chrono::seconds(10)) != std::future_status::ready || !connection.get()) { client.close(); std::cerr << "Redis connection or authentication failed\n"; return 1; @@ -32,23 +31,20 @@ int run(const arknet::redis::connect_options& target, const arknet::redis::clien auto completion = completed->get_future(); client.async_pipeline({{"PING"}, {"ECHO", payload}}, [completed](arknet::redis::result response) { completed->set_value(std::move(response)); }); - if (completion.wait_for(std::chrono::seconds(10)) != std::future_status::ready) - { + if (completion.wait_for(std::chrono::seconds(10)) != std::future_status::ready) { client.close(); std::cerr << "Redis request deadline exceeded\n"; return 1; } auto response = completion.get(); client.close(); - if (!response) - { + if (!response) { std::cerr << "Redis request failed; command_may_have_run=" << response.command_may_have_run << '\n'; return 1; } if (response.replies.size() != 2 || response.replies[0].type != arknet::redis::value_type::simple_string || response.replies[0].text != "PONG" || response.replies[1].type != arknet::redis::value_type::bulk_string || - response.replies[1].text != payload) - { + response.replies[1].text != payload) { std::cerr << "Unexpected Redis reply\n"; return 1; } @@ -59,10 +55,8 @@ int run(const arknet::redis::connect_options& target, const arknet::redis::clien } // namespace int main(int argc, char** argv) { - try - { - if (argc < 3 || argc > 5 || (argc >= 4 && std::string_view(argv[3]) != "--tls")) - { + try { + if (argc < 3 || argc > 5 || (argc >= 4 && std::string_view(argv[3]) != "--tls")) { std::cerr << "Usage: arknet_redis_client HOST PORT [--tls [CA_FILE]]\n" "Optional environment: ARKNET_REDIS_USERNAME, ARKNET_REDIS_PASSWORD\n"; return 1; @@ -70,21 +64,17 @@ int main(int argc, char** argv) { arknet::redis::connect_options target; target.host = argv[1]; target.port = argv[2]; - if (const auto* username = std::getenv("ARKNET_REDIS_USERNAME")) - { + if (const auto* username = std::getenv("ARKNET_REDIS_USERNAME")) { target.username = username; } - if (const auto* password = std::getenv("ARKNET_REDIS_PASSWORD")) - { + if (const auto* password = std::getenv("ARKNET_REDIS_PASSWORD")) { target.password = password; } arknet::redis::client_options options; - if (argc == 5) - { + if (argc == 5) { options.tls_certificate_authority = argv[4]; } - if (argc >= 4) - { + if (argc >= 4) { #if defined(ARKNET_ENABLE_SSL) return run(target, options); #else @@ -92,9 +82,7 @@ int main(int argc, char** argv) { #endif } return run(target, options); - } - catch (const std::exception& error) - { + } catch (const std::exception& error) { std::cerr << "Redis example: " << error.what() << '\n'; return 1; } diff --git a/examples/redis_pool.cpp b/examples/redis_pool.cpp index 2bfc31a..0626d02 100644 --- a/examples/redis_pool.cpp +++ b/examples/redis_pool.cpp @@ -19,15 +19,13 @@ int run(const arknet::redis::pool_options& options) { auto acquisition = acquired->get_future(); pool.async_acquire( [acquired](typename Pool::acquire_result response) { acquired->set_value(std::move(response)); }); - if (acquisition.wait_for(std::chrono::seconds(10)) != std::future_status::ready) - { + if (acquisition.wait_for(std::chrono::seconds(10)) != std::future_status::ready) { pool.close(); std::cerr << "Redis pool acquisition deadline exceeded\n"; return 1; } auto response = acquisition.get(); - if (!response) - { + if (!response) { pool.close(); std::cerr << "Redis pool acquisition failed\n"; return 1; @@ -37,8 +35,7 @@ int run(const arknet::redis::pool_options& options) { auto completion = completed->get_future(); connection.async_execute( {"PING"}, [completed](arknet::redis::result reply) { completed->set_value(std::move(reply)); }); - if (completion.wait_for(std::chrono::seconds(10)) != std::future_status::ready) - { + if (completion.wait_for(std::chrono::seconds(10)) != std::future_status::ready) { connection.discard(); pool.close(); std::cerr << "Redis pooled request deadline exceeded\n"; @@ -49,8 +46,7 @@ int run(const arknet::redis::pool_options& options) { connection.release(); pool.close(); if (!reply || reply.replies.size() != 1 || reply.replies.front().type != arknet::redis::value_type::simple_string || - reply.replies.front().text != "PONG") - { + reply.replies.front().text != "PONG") { std::cerr << "Redis pooled PING failed; command_may_have_run=" << reply.command_may_have_run << '\n'; return 1; } @@ -61,10 +57,8 @@ int run(const arknet::redis::pool_options& options) { } // namespace int main(int argc, char** argv) { - try - { - if (argc < 3 || argc > 5 || (argc >= 4 && std::string_view(argv[3]) != "--tls")) - { + try { + if (argc < 3 || argc > 5 || (argc >= 4 && std::string_view(argv[3]) != "--tls")) { std::cerr << "Usage: arknet_redis_pool HOST PORT [--tls [CA_FILE]]\n" "Optional environment: ARKNET_REDIS_USERNAME, ARKNET_REDIS_PASSWORD\n"; return 1; @@ -74,20 +68,16 @@ int main(int argc, char** argv) { options.max_waiters = 64; options.connection.host = argv[1]; options.connection.port = argv[2]; - if (const auto* username = std::getenv("ARKNET_REDIS_USERNAME")) - { + if (const auto* username = std::getenv("ARKNET_REDIS_USERNAME")) { options.connection.username = username; } - if (const auto* password = std::getenv("ARKNET_REDIS_PASSWORD")) - { + if (const auto* password = std::getenv("ARKNET_REDIS_PASSWORD")) { options.connection.password = password; } - if (argc == 5) - { + if (argc == 5) { options.certificate_authority = argv[4]; } - if (argc >= 4) - { + if (argc >= 4) { #if defined(ARKNET_ENABLE_SSL) return run(options); #else @@ -95,9 +85,7 @@ int main(int argc, char** argv) { #endif } return run(options); - } - catch (const std::exception& error) - { + } catch (const std::exception& error) { std::cerr << "Redis pool example: " << error.what() << '\n'; return 1; } diff --git a/include/arknet/arq/core.hpp b/include/arknet/arq/core.hpp index debbda0..2b85f84 100644 --- a/include/arknet/arq/core.hpp +++ b/include/arknet/arq/core.hpp @@ -16,8 +16,7 @@ namespace arknet::arq { -struct options -{ +struct options { std::size_t mtu = 1200; std::uint16_t send_window = 128; std::uint16_t receive_window = 512; @@ -32,16 +31,14 @@ struct options // KCP-compatible message ARQ. Serialize calls; output must not reenter and its views last only for the callback // duration. -class control_block -{ +class control_block { public: explicit control_block(std::uint32_t conversation, const options& settings = {}) : conversation_(conversation) , options_(settings) { if (settings.mtu <= header_size || settings.mtu > 65507 || settings.send_window == 0 || settings.receive_window == 0 || settings.interval == 0 || settings.interval > 5000 || - settings.max_retransmits == 0 || settings.max_pending_bytes < header_size) - { + settings.max_retransmits == 0 || settings.max_pending_bytes < header_size) { throw std::invalid_argument("invalid ARQ options"); } congestion_bytes_ = payload_size(); @@ -52,25 +49,21 @@ class control_block } int send(std::string_view message) { - if (failed_) - { + if (failed_) { return -1; } const auto count = message.empty() ? 1 : 1 + (message.size() - 1) / payload_size(); - if (count > 256 || count > options_.receive_window) - { + if (count > 256 || count > options_.receive_window) { return -1; } // Charge headers as well, so empty messages cannot bypass the queue bound. const auto occupied = pending_bytes_ + pending_segments() * header_size; if (occupied > options_.max_pending_bytes || count * header_size > options_.max_pending_bytes - occupied || - message.size() > options_.max_pending_bytes - occupied - count * header_size) - { + message.size() > options_.max_pending_bytes - occupied - count * header_size) { return -2; } std::list fragments; - for (std::size_t index = 0; index < count; ++index) - { + for (std::size_t index = 0; index < count; ++index) { segment fragment; fragment.fragment = static_cast(count - index - 1); const auto length = std::min(payload_size(), message.size()); @@ -78,8 +71,7 @@ class control_block message.remove_prefix(length); fragments.push_back(std::move(fragment)); } - for (const auto& fragment : fragments) - { + for (const auto& fragment : fragments) { pending_bytes_ += fragment.data.size(); } send_queue_.splice(send_queue_.end(), fragments); @@ -88,16 +80,13 @@ class control_block // -5: receive wire-byte budget exhausted; the rejected PUSH is not acknowledged. int input(std::string_view datagram) { - if (failed_ || datagram.size() < header_size || datagram.size() > 65507) - { + if (failed_ || datagram.size() < header_size || datagram.size() > 65507) { return -1; } // Validate every frame before acknowledging or accepting any part of a datagram. std::vector frames; - while (!datagram.empty()) - { - if (datagram.size() < header_size || read32(datagram, 0) != conversation_) - { + while (!datagram.empty()) { + if (datagram.size() < header_size || read32(datagram, 0) != conversation_) { return -1; } frame value; @@ -109,18 +98,15 @@ class control_block value.unacknowledged = read32(datagram, 16); const auto length = read32(datagram, 20); datagram.remove_prefix(header_size); - if (length > datagram.size()) - { + if (length > datagram.size()) { return -2; } - if (value.command < push_command || value.command > window_command) - { + if (value.command < push_command || value.command > window_command) { return -3; } if ((value.command != push_command && (length != 0 || value.fragment != 0)) || (value.command == push_command && value.fragment >= options_.receive_window) || - difference(value.unacknowledged, next_send_) > 0) - { + difference(value.unacknowledged, next_send_) > 0) { return -4; } value.data = datagram.substr(0, length); @@ -131,30 +117,24 @@ class control_block const auto previous_oldest = oldest_send(); bool saw_ack = false; std::uint32_t highest_ack = 0; - for (const auto& value : frames) - { + for (const auto& value : frames) { remote_window_ = value.window; acknowledge_before(value.unacknowledged); - switch (value.command) - { + switch (value.command) { case ack_command: - if (difference(value.sequence, previous_oldest) >= 0 && difference(value.sequence, next_send_) < 0) - { - if (difference(current_, value.timestamp) >= 0) - { + if (difference(value.sequence, previous_oldest) >= 0 && difference(value.sequence, next_send_) < 0) { + if (difference(current_, value.timestamp) >= 0) { update_timeout(static_cast(difference(current_, value.timestamp))); } acknowledge(value.sequence); - if (!saw_ack || difference(value.sequence, highest_ack) > 0) - { + if (!saw_ack || difference(value.sequence, highest_ack) > 0) { highest_ack = value.sequence; } saw_ack = true; } break; case push_command: - if (!accept(value)) - { + if (!accept(value)) { return -5; } break; @@ -165,18 +145,14 @@ class control_block break; } } - if (saw_ack) - { - for (auto& fragment : send_buffer_) - { - if (difference(highest_ack, fragment.sequence) > 0 && fragment.fast_acks < options_.fast_resend) - { + if (saw_ack) { + for (auto& fragment : send_buffer_) { + if (difference(highest_ack, fragment.sequence) > 0 && fragment.fast_acks < options_.fast_resend) { ++fragment.fast_acks; } } } - if (difference(oldest_send(), previous_oldest) > 0) - { + if (difference(oldest_send(), previous_oldest) > 0) { grow_window(); } return 0; @@ -184,21 +160,17 @@ class control_block void update(std::uint32_t milliseconds) { current_ = milliseconds; - if (!updated_) - { + if (!updated_) { updated_ = true; next_flush_ = current_; } const auto elapsed = difference(current_, next_flush_); - if (elapsed > 10000 || elapsed < -10000) - { + if (elapsed > 10000 || elapsed < -10000) { next_flush_ = current_; } - if (difference(current_, next_flush_) >= 0) - { + if (difference(current_, next_flush_) >= 0) { next_flush_ += options_.interval; - if (difference(current_, next_flush_) >= 0) - { + if (difference(current_, next_flush_) >= 0) { next_flush_ = current_ + options_.interval; } flush(); @@ -206,16 +178,14 @@ class control_block } void flush() { - if (!updated_ || !output_ || failed_) - { + if (!updated_ || !output_ || failed_) { return; } std::string datagram; datagram.reserve(options_.mtu); const auto append = [&](std::uint8_t command, std::uint8_t fragment, std::uint32_t timestamp, std::uint32_t sequence, std::string_view data) { - if (datagram.size() + header_size + data.size() > options_.mtu) - { + if (datagram.size() + header_size + data.size() > options_.mtu) { output_(datagram); datagram.clear(); } @@ -229,104 +199,81 @@ class control_block write32(datagram, static_cast(data.size())); datagram.append(data); }; - for (const auto& [sequence, timestamp] : acknowledgements_) - { + for (const auto& [sequence, timestamp] : acknowledgements_) { append(ack_command, 0, timestamp, sequence, {}); } acknowledgements_.clear(); - if (remote_window_ == 0) - { - if (probe_delay_ == 0) - { + if (remote_window_ == 0) { + if (probe_delay_ == 0) { probe_delay_ = 7000; next_probe_ = current_ + probe_delay_; - } - else if (difference(current_, next_probe_) >= 0) - { + } else if (difference(current_, next_probe_) >= 0) { append(probe_command, 0, 0, 0, {}); probe_delay_ = std::min(120000, probe_delay_ + probe_delay_ / 2); next_probe_ = current_ + probe_delay_; } - } - else - { + } else { probe_delay_ = 0; } - if (announce_window_) - { + if (announce_window_) { append(window_command, 0, 0, 0, {}); announce_window_ = false; } auto window = std::min(options_.send_window, remote_window_); - if (options_.congestion_control) - { + if (options_.congestion_control) { window = std::min(window, congestion_window_); } - while (!send_queue_.empty() && next_send_ - oldest_send() < window) - { + while (!send_queue_.empty() && next_send_ - oldest_send() < window) { auto& fragment = send_queue_.front(); fragment.sequence = next_send_++; send_buffer_.splice(send_buffer_.end(), send_queue_, send_queue_.begin()); } bool timeout_loss = false; bool fast_loss = false; - for (auto& fragment : send_buffer_) - { + for (auto& fragment : send_buffer_) { const bool initial = fragment.transmissions == 0; const bool timeout = !initial && difference(current_, fragment.deadline) >= 0; // Bound ACK-driven retries as KCP does; delayed recovery must still have time to arrive. const bool fast = !initial && fragment.transmissions <= 5 && options_.fast_resend != 0 && fragment.fast_acks >= options_.fast_resend; - if (!initial && !timeout && !fast) - { + if (!initial && !timeout && !fast) { continue; } - if (fragment.transmissions > options_.max_retransmits) - { + if (fragment.transmissions > options_.max_retransmits) { failed_ = true; break; } - if (initial) - { + if (initial) { fragment.timeout = retransmit_timeout_; - } - else if (timeout) - { + } else if (timeout) { fragment.timeout = std::min( 60000, fragment.timeout + (options_.nodelay ? retransmit_timeout_ / 2 : retransmit_timeout_)); timeout_loss = true; - } - else - { + } else { fast_loss = true; } ++fragment.transmissions; ++transmissions_; - if (!initial) - { + if (!initial) { ++retransmissions_; } fragment.fast_acks = 0; fragment.deadline = current_ + fragment.timeout + (initial && !options_.nodelay ? fragment.timeout / 8 : 0); append(push_command, fragment.fragment, current_, fragment.sequence, fragment.data); } - if (!datagram.empty()) - { + if (!datagram.empty()) { output_(datagram); } - if (options_.congestion_control) - { - if (fast_loss) - { + if (options_.congestion_control) { + if (fast_loss) { slow_start_threshold_ = std::max(2, (next_send_ - oldest_send()) / 2); congestion_window_ = static_cast(std::min( remote_window_, static_cast(slow_start_threshold_) + options_.fast_resend)); congestion_bytes_ = static_cast(congestion_window_) * payload_size(); } - if (timeout_loss) - { + if (timeout_loss) { slow_start_threshold_ = std::max(2, window / 2); congestion_window_ = 1; congestion_bytes_ = payload_size(); @@ -335,39 +282,33 @@ class control_block } bool receive(std::string& message) { - if (receive_queue_.empty() || failed_) - { + if (receive_queue_.empty() || failed_) { return false; } const auto count = static_cast(receive_queue_.front().fragment) + 1; std::size_t length = 0; auto position = receive_queue_.begin(); - for (std::size_t index = 0; index < std::min(count, receive_queue_.size()); ++index, ++position) - { - if (position->fragment != count - index - 1) - { + for (std::size_t index = 0; index < std::min(count, receive_queue_.size()); ++index, ++position) { + if (position->fragment != count - index - 1) { failed_ = true; return false; } length += position->data.size(); } - if (receive_queue_.size() < count) - { + if (receive_queue_.size() < count) { return false; } std::string assembled; assembled.reserve(length); position = receive_queue_.begin(); - for (std::size_t index = 0; index < count; ++index, ++position) - { + for (std::size_t index = 0; index < count; ++index, ++position) { assembled.append(position->data); } const bool full = available_window() == 0; receive_bytes_ -= length + count * header_size; receive_queue_.erase(receive_queue_.begin(), position); promote_received(); - if (full && available_window() != 0) - { + if (full && available_window() != 0) { announce_window_ = true; } message.swap(assembled); @@ -409,8 +350,7 @@ class control_block static constexpr std::uint8_t probe_command = 83; static constexpr std::uint8_t window_command = 84; - struct segment - { + struct segment { std::string data; std::uint32_t sequence = 0; std::uint32_t deadline = 0; @@ -420,8 +360,7 @@ class control_block std::uint8_t fragment = 0; }; - struct frame - { + struct frame { std::string_view data; std::uint32_t timestamp = 0; std::uint32_t sequence = 0; @@ -446,8 +385,7 @@ class control_block static std::uint32_t read32(std::string_view value, std::size_t offset) { std::uint32_t result = 0; - for (std::size_t index = 0; index < 4; ++index) - { + for (std::size_t index = 0; index < 4; ++index) { result |= static_cast(byte(value, offset + index)) << (index * 8); } return result; @@ -459,8 +397,7 @@ class control_block } static void write32(std::string& target, std::uint32_t value) { - for (std::size_t index = 0; index < 4; ++index) - { + for (std::size_t index = 0; index < 4; ++index) { target.push_back(static_cast((value >> (index * 8)) & 0xff)); } } @@ -478,18 +415,15 @@ class control_block } void acknowledge_before(std::uint32_t sequence) { - while (!send_buffer_.empty() && difference(sequence, send_buffer_.front().sequence) > 0) - { + while (!send_buffer_.empty() && difference(sequence, send_buffer_.front().sequence) > 0) { pending_bytes_ -= send_buffer_.front().data.size(); send_buffer_.pop_front(); } } void acknowledge(std::uint32_t sequence) { - for (auto position = send_buffer_.begin(); position != send_buffer_.end(); ++position) - { - if (position->sequence == sequence) - { + for (auto position = send_buffer_.begin(); position != send_buffer_.end(); ++position) { + if (position->sequence == sequence) { pending_bytes_ -= position->data.size(); send_buffer_.erase(position); break; @@ -499,13 +433,10 @@ class control_block void update_timeout(std::uint32_t elapsed) { const auto sample = static_cast(std::min(elapsed, 60000)); - if (smoothed_rtt_ == 0) - { + if (smoothed_rtt_ == 0) { smoothed_rtt_ = std::max(1, sample); rtt_variance_ = sample / 2; - } - else - { + } else { const auto deviation = sample > smoothed_rtt_ ? sample - smoothed_rtt_ : smoothed_rtt_ - sample; rtt_variance_ = (3 * rtt_variance_ + deviation) / 4; smoothed_rtt_ = std::max(1, (7 * smoothed_rtt_ + sample) / 8); @@ -516,22 +447,17 @@ class control_block } bool accept(const frame& value) { - if (difference(value.sequence, next_receive_ + options_.receive_window) >= 0) - { + if (difference(value.sequence, next_receive_ + options_.receive_window) >= 0) { return true; } - if (difference(value.sequence, next_receive_) >= 0) - { + if (difference(value.sequence, next_receive_) >= 0) { auto position = receive_buffer_.begin(); - while (position != receive_buffer_.end() && difference(value.sequence, position->sequence) > 0) - { + while (position != receive_buffer_.end() && difference(value.sequence, position->sequence) > 0) { ++position; } - if (position == receive_buffer_.end() || position->sequence != value.sequence) - { + if (position == receive_buffer_.end() || position->sequence != value.sequence) { const auto cost = value.data.size() + header_size; - if (cost > options_.max_pending_bytes - receive_bytes_) - { + if (cost > options_.max_pending_bytes - receive_bytes_) { return false; } segment fragment; @@ -543,8 +469,7 @@ class control_block promote_received(); } } - if (acknowledgements_.size() >= options_.receive_window && !acknowledgements_.contains(value.sequence)) - { + if (acknowledgements_.size() >= options_.receive_window && !acknowledgements_.contains(value.sequence)) { acknowledgements_.erase(acknowledgements_.begin()); } acknowledgements_[value.sequence] = value.timestamp; @@ -553,30 +478,24 @@ class control_block void promote_received() { while (!receive_buffer_.empty() && receive_buffer_.front().sequence == next_receive_ && - receive_queue_.size() < options_.receive_window) - { + receive_queue_.size() < options_.receive_window) { receive_queue_.splice(receive_queue_.end(), receive_buffer_, receive_buffer_.begin()); ++next_receive_; } } void grow_window() { - if (!options_.congestion_control || congestion_window_ >= remote_window_) - { + if (!options_.congestion_control || congestion_window_ >= remote_window_) { return; } - if (congestion_window_ < slow_start_threshold_) - { + if (congestion_window_ < slow_start_threshold_) { ++congestion_window_; congestion_bytes_ += payload_size(); - } - else - { + } else { const auto payload = static_cast(payload_size()); congestion_bytes_ = std::max(congestion_bytes_, payload); congestion_bytes_ += payload * payload / congestion_bytes_ + payload / 16; - if ((static_cast(congestion_window_) + 1) * payload <= congestion_bytes_) - { + if ((static_cast(congestion_window_) + 1) * payload <= congestion_bytes_) { ++congestion_window_; } } diff --git a/include/arknet/base/basic_stream.hpp b/include/arknet/base/basic_stream.hpp index 8ebc077..1eb329b 100644 --- a/include/arknet/base/basic_stream.hpp +++ b/include/arknet/base/basic_stream.hpp @@ -13,8 +13,7 @@ using unlimited_rate_policy = beast::unlimited_rate_policy; using simple_rate_policy = beast::simple_rate_policy; template -class basic_stream : public beast::basic_stream -{ +class basic_stream : public beast::basic_stream { public: using super = beast::basic_stream; using super::super; diff --git a/include/arknet/base/client.hpp b/include/arknet/base/client.hpp index d549643..f83c7ed 100644 --- a/include/arknet/base/client.hpp +++ b/include/arknet/base/client.hpp @@ -31,8 +31,7 @@ namespace arknet { -class client -{ +class client { public: static constexpr bool is_client() noexcept { return true; @@ -71,8 +70,7 @@ class client_impl_t , public send_cp , public post_cp , public condition_event_cp - , public reconnect_timer_cp -{ + , public reconnect_timer_cp { ARKNET_CLASS_FRIEND_DECLARE_BASE; public: @@ -134,15 +132,13 @@ class client_impl_t } void request_stop() { - if (!this->derived().is_stopped()) - { + if (!this->derived().is_stopped()) { this->post([this] { this->derived().stop(); }); } } bool wait_stopped() { - if (this->iopool().running_in_threads() || this->io_->context().get_executor().running_in_this_thread()) - { + if (this->iopool().running_in_threads() || this->io_->context().get_executor().running_in_this_thread()) { set_last_error(asio::error::operation_not_supported); return false; } diff --git a/include/arknet/base/detail/allocator.hpp b/include/arknet/base/detail/allocator.hpp index c6b05ee..d5567f8 100644 --- a/include/arknet/base/detail/allocator.hpp +++ b/include/arknet/base/detail/allocator.hpp @@ -24,31 +24,23 @@ constexpr std::size_t calc_allocator_storage_size() noexcept { } template -struct allocator_size_op -{ +struct allocator_size_op { static constexpr std::size_t size = Size; }; -struct allocator_fixed_size_tag -{ -}; +struct allocator_fixed_size_tag {}; template -struct allocator_fixed_size_op : allocator_fixed_size_tag -{ +struct allocator_fixed_size_op : allocator_fixed_size_tag { static constexpr std::size_t size = Size; }; template -struct allocator_size_traits -{ +struct allocator_size_traits { static constexpr std::size_t value = [] { - if constexpr (requires { Args::allocator_storage_size; }) - { + if constexpr (requires { Args::allocator_storage_size; }) { return std::size_t(Args::allocator_storage_size); - } - else - { + } else { return std::size_t(0); } }(); @@ -59,30 +51,24 @@ using assizer = allocator_size_op::value>; template constexpr std::size_t get_allocator_storage_size() noexcept { - if constexpr (std::is_base_of_v) - { + if constexpr (std::is_base_of_v) { return Size::size; } #ifdef ARKNET_ALLOCATOR_STORAGE_SIZE - else - { + else { return ARKNET_ALLOCATOR_STORAGE_SIZE; } #else - else if constexpr (Size::size >= 64 && Size::size <= 1024 * 1024) - { + else if constexpr (Size::size >= 64 && Size::size <= 1024 * 1024) { return Size::size; - } - else - { + } else { return calc_allocator_storage_size(); } #endif } template()>> -class handler_memory -{ +class handler_memory { public: static constexpr std::size_t storage_size = get_allocator_storage_size(); handler_memory() = default; @@ -91,28 +77,21 @@ class handler_memory void* allocate(std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) { if (bytes <= storage_size && alignment <= alignof(std::max_align_t) && - !occupied_.test_and_set(std::memory_order_acquire)) - { + !occupied_.test_and_set(std::memory_order_acquire)) { return storage_.data(); } - if (alignment > alignof(std::max_align_t)) - { + if (alignment > alignof(std::max_align_t)) { return ::operator new(bytes, std::align_val_t(alignment)); } return ::operator new(bytes); } void deallocate(void* pointer, std::size_t alignment = alignof(std::max_align_t)) noexcept { - if (pointer == storage_.data()) - { + if (pointer == storage_.data()) { occupied_.clear(std::memory_order_release); - } - else if (alignment > alignof(std::max_align_t)) - { + } else if (alignment > alignof(std::max_align_t)) { ::operator delete(pointer, std::align_val_t(alignment)); - } - else - { + } else { ::operator delete(pointer); } } @@ -123,8 +102,7 @@ class handler_memory }; template -class handler_allocator -{ +class handler_allocator { template friend class handler_allocator; @@ -132,8 +110,7 @@ class handler_allocator using value_type = T; template - struct rebind - { + struct rebind { using other = handler_allocator; }; @@ -150,8 +127,7 @@ class handler_allocator } T* allocate(std::size_t count) const { - if (count > std::numeric_limits::max() / sizeof(T)) - { + if (count > std::numeric_limits::max() / sizeof(T)) { throw std::bad_array_new_length(); } return static_cast(memory_->allocate(count * sizeof(T), alignof(T))); @@ -166,8 +142,7 @@ class handler_allocator }; template -class custom_alloc_handler -{ +class custom_alloc_handler { public: using allocator_type = handler_allocator; diff --git a/include/arknet/base/detail/buffer_wrap.hpp b/include/arknet/base/detail/buffer_wrap.hpp index fd0a9d1..a8f2a27 100644 --- a/include/arknet/base/detail/buffer_wrap.hpp +++ b/include/arknet/base/detail/buffer_wrap.hpp @@ -14,33 +14,25 @@ namespace arknet::detail { inline constexpr std::size_t min_size = 512; template -struct buffer_has_limit : std::bool_constant> -{ -}; +struct buffer_has_limit : std::bool_constant> {}; template - struct buffer_has_max_size : std::bool_constant < requires(const T& buffer) -{ + struct buffer_has_max_size : std::bool_constant < requires(const T& buffer) { buffer.max_size(); } > {}; -struct callback_helper -{ +struct callback_helper { template static void call(Callback& callback, std::size_t count) { - if constexpr (std::is_invocable_v) - { + if constexpr (std::is_invocable_v) { std::invoke(callback, count); - } - else - { + } else { std::invoke(callback); } } }; -struct empty_buffer -{ +struct empty_buffer { using size_type = std::size_t; constexpr size_type size() const noexcept { @@ -69,8 +61,7 @@ struct empty_buffer }; template -struct proxy_buffer -{ +struct proxy_buffer { using size_type = std::size_t; proxy_buffer() = default; @@ -112,21 +103,16 @@ struct proxy_buffer }; template -struct buffer_identity : std::type_identity -{ -}; +struct buffer_identity : std::type_identity {}; template -struct buffer_identity> : std::type_identity -{ -}; +struct buffer_identity> : std::type_identity {}; } // namespace arknet::detail namespace arknet { template::value> -class buffer_wrap : public Buffer -{ +class buffer_wrap : public Buffer { public: using buffer_type = typename detail::buffer_identity::type; using size_type = std::size_t; @@ -148,8 +134,7 @@ class buffer_wrap : public Buffer : Buffer(maximum) , pre_(initial) , max_(maximum) { - if constexpr (!requires { this->b_; }) - { + if constexpr (!requires { this->b_; }) { Buffer::prepare(std::min(initial, maximum)); } } @@ -159,52 +144,39 @@ class buffer_wrap : public Buffer : Buffer() , pre_(initial) , max_(maximum) { - if constexpr (!requires { this->b_; }) - { + if constexpr (!requires { this->b_; }) { Buffer::prepare(std::min(initial, maximum)); } } buffer_type& base() noexcept { - if constexpr (requires { this->b_; }) - { + if constexpr (requires { this->b_; }) { return *this->b_; - } - else - { + } else { return *this; } } const buffer_type& base() const noexcept { - if constexpr (requires { this->b_; }) - { + if constexpr (requires { this->b_; }) { return *this->b_; - } - else - { + } else { return *this; } } size_type pre_size() const noexcept { - if constexpr (std::is_same_v) - { + if constexpr (std::is_same_v) { return 0; - } - else - { + } else { return pre_; } } size_type max_size() const noexcept { - if constexpr (detail::buffer_has_max_size::value) - { + if constexpr (detail::buffer_has_max_size::value) { return Buffer::max_size(); - } - else - { + } else { return max_; } } @@ -215,8 +187,7 @@ class buffer_wrap : public Buffer } buffer_wrap& max_size(size_type size) noexcept { - if constexpr (!Limited) - { + if constexpr (!Limited) { max_ = size; } return *this; diff --git a/include/arknet/base/detail/deadline.hpp b/include/arknet/base/detail/deadline.hpp index 7d8661f..59ea55c 100644 --- a/include/arknet/base/detail/deadline.hpp +++ b/include/arknet/base/detail/deadline.hpp @@ -14,20 +14,17 @@ inline std::optional checked_deadline( using clock = std::chrono::steady_clock; using duration = clock::duration; using rep = duration::rep; - if (timeout <= std::chrono::milliseconds::zero()) - { + if (timeout <= std::chrono::milliseconds::zero()) { return std::nullopt; } const auto ticks = std::chrono::duration(timeout).count(); // The floating representation of max may round upward. Rejecting equality // keeps the following float-to-integer conversion within range on that ABI. - if (ticks >= static_cast((std::numeric_limits::max)())) - { + if (ticks >= static_cast((std::numeric_limits::max)())) { return std::nullopt; } const duration delta(static_cast(ticks)); - if (base.time_since_epoch() > duration::max() - delta) - { + if (base.time_since_epoch() > duration::max() - delta) { return std::nullopt; } return base + delta; diff --git a/include/arknet/base/detail/ecs.hpp b/include/arknet/base/detail/ecs.hpp index 1d9e214..01ae6ee 100644 --- a/include/arknet/base/detail/ecs.hpp +++ b/include/arknet/base/detail/ecs.hpp @@ -11,17 +11,12 @@ namespace arknet::detail { -struct component_tag -{ -}; +struct component_tag {}; -struct component_dummy -{ -}; +struct component_dummy {}; template -class component_t : public component_tag -{ +class component_t : public component_tag { public: static constexpr std::size_t componentc = sizeof...(Options); using components_type = std::tuple()))...>; @@ -29,8 +24,7 @@ class component_t : public component_tag using condition_lowest_type = typename condition_type::type; template - struct components - { + struct components { using type = std::tuple_element_t; using raw_type = typename type::element_type; }; @@ -72,14 +66,12 @@ class component_t : public component_tag template component_t(Match, Options...) -> component_t; -struct ecs_base -{ +struct ecs_base { virtual ~ecs_base() = default; }; template -class ecs_t : public ecs_base -{ +class ecs_t : public ecs_base { public: using component_type = component_dummy; using condition_type = condition_t; @@ -98,12 +90,9 @@ class ecs_t : public ecs_base ecs_t& operator=(ecs_t&&) = default; ecs_t clone() { - if constexpr (std::is_void_v) - { + if constexpr (std::is_void_v) { return {}; - } - else - { + } else { return ecs_t(condition_.clone()); } } @@ -125,8 +114,7 @@ template ecs_t(Match) -> ecs_t>; template -class ecs_t> : public ecs_base -{ +class ecs_t> : public ecs_base { public: using component_type = component_t; using condition_type = typename component_type::condition_type; @@ -154,16 +142,12 @@ class ecs_t> : public ecs_base component_type component_; }; -struct ecs_helper -{ +struct ecs_helper { template static constexpr bool is_component() noexcept { - if constexpr (is_template_instance_of_v) - { + if constexpr (is_template_instance_of_v) { return is_component::element_type>(); - } - else - { + } else { return std::is_base_of_v>; } } @@ -175,23 +159,15 @@ struct ecs_helper template static auto make_ecs(Default default_match, Args... args) { - if constexpr (args_has_match_condition()) - { - if constexpr (sizeof...(Args) == 1) - { + if constexpr (args_has_match_condition()) { + if constexpr (sizeof...(Args) == 1) { return to_shared_ptr(ecs_t(std::move(args)...)); - } - else - { + } else { return to_shared_ptr(ecs_t(component_t(std::move(args)...))); } - } - else if constexpr (sizeof...(Args) == 0) - { + } else if constexpr (sizeof...(Args) == 0) { return to_shared_ptr(ecs_t(std::move(default_match))); - } - else - { + } else { return to_shared_ptr(ecs_t(component_t(std::move(default_match), std::move(args)...))); } } diff --git a/include/arknet/base/detail/function.hpp b/include/arknet/base/detail/function.hpp index d130ea9..2f527d7 100644 --- a/include/arknet/base/detail/function.hpp +++ b/include/arknet/base/detail/function.hpp @@ -11,8 +11,7 @@ namespace arknet::detail { template -struct function_size_traits -{ +struct function_size_traits { static constexpr std::size_t value = 0; }; @@ -21,8 +20,7 @@ class function; // C++20 std::function owns a copyable handle to a potentially move-only handler. template -class function -{ +class function { public: using signature = R(Args...); diff --git a/include/arknet/base/detail/function_traits.hpp b/include/arknet/base/detail/function_traits.hpp index 150df95..99ecce1 100644 --- a/include/arknet/base/detail/function_traits.hpp +++ b/include/arknet/base/detail/function_traits.hpp @@ -9,35 +9,27 @@ namespace arknet::detail { template -struct lambda_dummy_t -{ -}; +struct lambda_dummy_t {}; template -struct lambda_dummy_t -{ +struct lambda_dummy_t { Return operator()(const Args&...) { - if constexpr (std::is_reference_v) - { + if constexpr (std::is_reference_v) { static std::remove_reference_t value{}; return value; - } - else if constexpr (!std::is_void_v) - { + } else if constexpr (!std::is_void_v) { return {}; } } }; template -struct function_traits -{ +struct function_traits { static constexpr bool is_callable = false; }; template -struct function_traits -{ +struct function_traits { static constexpr bool is_callable = true; static constexpr std::size_t argc = sizeof...(Args); using function_type = Return(Args...); @@ -50,157 +42,100 @@ struct function_traits using pod_tuple_type = std::tuple...>; template - struct args : std::type_identity> - { - }; + struct args : std::type_identity> {}; }; template -struct function_traits : function_traits -{ +struct function_traits : function_traits { using class_type = Class; }; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits + : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template -struct function_traits : function_traits -{ -}; +struct function_traits + : function_traits {}; template requires requires { &Callable::operator(); } -struct function_traits : function_traits -{ -}; +struct function_traits : function_traits {}; template inline constexpr bool is_callable_v = function_traits>::is_callable; @@ -208,9 +143,7 @@ template inline constexpr bool is_template_callable_v = std::is_invocable_v; template -struct is_template_callable : std::bool_constant> -{ -}; +struct is_template_callable : std::bool_constant> {}; template auto to_function(Callable&& callable) { diff --git a/include/arknet/base/detail/future.hpp b/include/arknet/base/detail/future.hpp index b2e7c51..1969a10 100644 --- a/include/arknet/base/detail/future.hpp +++ b/include/arknet/base/detail/future.hpp @@ -16,20 +16,16 @@ auto make_future_task(Function&& callback) { auto future = promise.get_future(); // The future owns only the result state, so abandoning a task releases its callable. auto task = [callback = std::forward(callback), promise = std::move(promise)]() mutable { - try - { - if constexpr (std::is_void_v) - { + try { + if constexpr (std::is_void_v) { std::invoke(callback); promise.set_value(); - } - else - { + } else { promise.set_value(std::invoke(callback)); } + } catch (...) { + promise.set_exception(std::current_exception()); } - catch (...) - { promise.set_exception(std::current_exception()); } }; return std::make_pair(std::move(future), std::move(task)); } diff --git a/include/arknet/base/detail/keepalive_options.hpp b/include/arknet/base/detail/keepalive_options.hpp index 4283e65..b8d0655 100644 --- a/include/arknet/base/detail/keepalive_options.hpp +++ b/include/arknet/base/detail/keepalive_options.hpp @@ -22,40 +22,32 @@ template bool set_keepalive_options( Socket& socket, bool enabled = true, unsigned idle = 60, unsigned interval = 3, unsigned count = 3) noexcept { clear_last_error(); - if constexpr (!std::same_as) - { + if constexpr (!std::same_as) { return true; - } - else - { - if (!socket.is_open()) - { + } else { + if (!socket.is_open()) { set_last_error(asio::error::not_connected); return false; } constexpr auto limit = static_cast((std::numeric_limits::max)()); - if (enabled && (!idle || !interval || !count || idle > limit || interval > limit || count > limit)) - { + if (enabled && (!idle || !interval || !count || idle > limit || interval > limit || count > limit)) { set_last_error(asio::error::invalid_argument); return false; } #if defined(_WIN32) constexpr auto millisecond_limit = (std::numeric_limits::max)() / 1000; - if (enabled && (idle > millisecond_limit || interval > millisecond_limit)) - { + if (enabled && (idle > millisecond_limit || interval > millisecond_limit)) { set_last_error(asio::error::invalid_argument); return false; } #endif error_code ec; socket.set_option(asio::socket_base::keep_alive(enabled), ec); - if (ec) - { + if (ec) { set_last_error(ec); return false; } - if (!enabled) - { + if (!enabled) { return true; } auto fd = socket.native_handle(); @@ -76,8 +68,8 @@ bool set_keepalive_options( #if defined(_WIN32) tcp_keepalive options{1, idle * 1000UL, interval * 1000UL}; DWORD written = 0; - if (::WSAIoctl(fd, SIO_KEEPALIVE_VALS, &options, sizeof(options), nullptr, 0, &written, nullptr, nullptr) != 0) - { + if (::WSAIoctl(fd, SIO_KEEPALIVE_VALS, &options, sizeof(options), nullptr, 0, &written, nullptr, nullptr) != + 0) { set_last_error(::WSAGetLastError(), asio::error::get_system_category()); return false; } diff --git a/include/arknet/base/detail/linear_buffer.hpp b/include/arknet/base/detail/linear_buffer.hpp index 46589ef..a938fad 100644 --- a/include/arknet/base/detail/linear_buffer.hpp +++ b/include/arknet/base/detail/linear_buffer.hpp @@ -12,8 +12,7 @@ namespace arknet { template -class basic_linear_buffer -{ +class basic_linear_buffer { public: using allocator_type = typename Container::allocator_type; using size_type = typename Container::size_type; @@ -42,12 +41,10 @@ class basic_linear_buffer mutable_buffers_type prepare(size_type count) { const auto available = size(); - if (count > limit_ - available) - { + if (count > limit_ - available) { throw std::length_error("arknet buffer limit exceeded"); } - if (consumed_ && count > bytes_.capacity() - committed_) - { + if (consumed_ && count > bytes_.capacity() - committed_) { std::memmove(bytes_.data(), bytes_.data() + consumed_, available); consumed_ = 0; committed_ = available; @@ -62,8 +59,7 @@ class basic_linear_buffer void consume(size_type count) noexcept { consumed_ += std::min(count, size()); - if (consumed_ == committed_) - { + if (consumed_ == committed_) { consumed_ = committed_ = 0; bytes_.clear(); } @@ -71,8 +67,7 @@ class basic_linear_buffer void shrink_to_fit() { const auto remaining = size(); - if (consumed_) - { + if (consumed_) { std::memmove(bytes_.data(), bytes_.data() + consumed_, remaining); } consumed_ = 0; diff --git a/include/arknet/base/detail/match_condition.hpp b/include/arknet/base/detail/match_condition.hpp index baff0c6..4a97b1a 100644 --- a/include/arknet/base/detail/match_condition.hpp +++ b/include/arknet/base/detail/match_condition.hpp @@ -14,49 +14,38 @@ using iterator = asio::buffers_iterator; using diff_type = iterator::difference_type; inline std::pair dgram_match_role(iterator first, iterator last) noexcept { - if (first == last) - { + if (first == last) { return {first, false}; } const auto marker = static_cast(*first); const auto prefix = marker < 254 ? 1 : marker == 254 ? 3 : 9; - if (last - first < prefix) - { + if (last - first < prefix) { return {first, false}; } std::uint64_t length = marker; - if (prefix != 1) - { + if (prefix != 1) { length = 0; - for (int index = 1; index < prefix; ++index) - { + for (int index = 1; index < prefix; ++index) { length |= std::uint64_t(static_cast(first[index])) << (8 * (index - 1)); } const auto minimum = prefix == 3 ? 254u : 65536u; - if (length < minimum || length > std::uint64_t(std::numeric_limits::max())) - { + if (length < minimum || length > std::uint64_t(std::numeric_limits::max())) { return {first, true}; } } const auto payload = first + prefix; - if (std::uint64_t(last - payload) < length) - { + if (std::uint64_t(last - payload) < length) { return {first, false}; } return {payload + static_cast(length), true}; } -struct use_dgram_t -{ -}; +struct use_dgram_t {}; -struct hook_buffer_t -{ -}; +struct hook_buffer_t {}; template -class condition_t -{ +class condition_t { public: using type = T; @@ -79,16 +68,11 @@ class condition_t } decltype(auto) lowest() noexcept { - if constexpr (std::is_same_v) - { + if constexpr (std::is_same_v) { return &dgram_match_role; - } - else if constexpr (std::is_same_v) - { + } else if constexpr (std::is_same_v) { return asio::transfer_at_least(1); - } - else - { + } else { return (value_); } } @@ -101,8 +85,7 @@ template condition_t(T) -> condition_t>; template<> -class condition_t -{ +class condition_t { public: using type = void; diff --git a/include/arknet/base/detail/object.hpp b/include/arknet/base/detail/object.hpp index 5248c6a..efb1c63 100644 --- a/include/arknet/base/detail/object.hpp +++ b/include/arknet/base/detail/object.hpp @@ -8,9 +8,7 @@ namespace arknet { -class object -{ -}; +class object {}; } // namespace arknet namespace arknet::detail { @@ -18,8 +16,7 @@ namespace arknet::detail { template class object_t : public arknet::object - , public std::conditional_t, std::type_identity> -{ + , public std::conditional_t, std::type_identity> { protected: Derived& derived() noexcept { return static_cast(*this); @@ -30,12 +27,9 @@ class object_t } std::shared_ptr selfptr() noexcept { - if constexpr (Shared) - { + if constexpr (Shared) { return this->weak_from_this().lock(); - } - else - { + } else { return {}; } } diff --git a/include/arknet/base/detail/shared_mutex.hpp b/include/arknet/base/detail/shared_mutex.hpp index e6ec2e8..6c31b30 100644 --- a/include/arknet/base/detail/shared_mutex.hpp +++ b/include/arknet/base/detail/shared_mutex.hpp @@ -5,8 +5,7 @@ namespace arknet { -class shared_mutexer : public std::shared_mutex -{ +class shared_mutexer : public std::shared_mutex { public: std::shared_mutex& native_handle() noexcept { return *this; diff --git a/include/arknet/base/detail/type_traits.hpp b/include/arknet/base/detail/type_traits.hpp index f67f065..dbe9da9 100644 --- a/include/arknet/base/detail/type_traits.hpp +++ b/include/arknet/base/detail/type_traits.hpp @@ -13,9 +13,7 @@ namespace arknet::detail { template -struct remove_cvref : std::type_identity> -{ -}; +struct remove_cvref : std::type_identity> {}; template using remove_cvref_t = std::remove_cvref_t; @@ -37,22 +35,17 @@ void for_each_tuple(Tuple&& tuple, Function&& function) { } template class Template, class, class... Args> - struct is_template_instantiable : std::bool_constant < requires -{ + struct is_template_instantiable : std::bool_constant < requires { typename Template; } > {}; template class Template, class... Args> inline constexpr bool is_template_instantiable_v = is_template_instantiable::value; template class Template, class T> -struct is_template_instance_of : std::false_type -{ -}; +struct is_template_instance_of : std::false_type {}; template class Template, class... Args> -struct is_template_instance_of> : std::true_type -{ -}; +struct is_template_instance_of> : std::true_type {}; template class Template, class T> inline constexpr bool is_template_instance_of_v = is_template_instance_of>::value; @@ -64,25 +57,19 @@ struct is_char : std::bool_constant, char> || std::same_as, wchar_t> || std::same_as, char8_t> || std::same_as, char16_t> || - std::same_as, char32_t>> -{ -}; + std::same_as, char32_t>> {}; template inline constexpr bool is_char_v = is_char::value; template -struct is_string : is_template_instance_of> -{ -}; +struct is_string : is_template_instance_of> {}; template inline constexpr bool is_string_v = is_string::value; template -struct is_string_view : is_template_instance_of> -{ -}; +struct is_string_view : is_template_instance_of> {}; template inline constexpr bool is_string_view_v = is_string_view::value; @@ -90,9 +77,7 @@ inline constexpr bool is_string_view_v = is_string_view::value; template struct is_char_pointer : std::bool_constant> && - is_char_v>>> -{ -}; + is_char_v>>> {}; template inline constexpr bool is_char_pointer_v = is_char_pointer::value; @@ -100,105 +85,75 @@ inline constexpr bool is_char_pointer_v = is_char_pointer::value; template struct is_char_array : std::bool_constant> && - is_char_v>>> -{ -}; + is_char_v>>> {}; template inline constexpr bool is_char_array_v = is_char_array::value; template || is_char_pointer_v || is_char_array_v> -struct char_type : std::type_identity -{ -}; +struct char_type : std::type_identity {}; template struct char_type - : std::type_identity>>>> -{ -}; + : std::type_identity>>>> {}; template inline constexpr bool is_character_string_v = is_string_v || is_string_view_v || is_char_pointer_v || is_char_array_v; template - struct has_stream_operator : std::bool_constant < requires(Stream& stream, Value& value) -{ + struct has_stream_operator : std::bool_constant < requires(Stream& stream, Value& value) { stream << value; } > {}; template -struct has_equal_operator : std::bool_constant> -{ -}; +struct has_equal_operator : std::bool_constant> {}; template - struct has_bool_operator : std::bool_constant < requires(T& value) -{ + struct has_bool_operator : std::bool_constant < requires(T& value) { value.operator bool(); } > {}; template struct can_convert_to_string - : std::bool_constant && std::is_assignable_v> -{ -}; + : std::bool_constant && std::is_assignable_v> {}; template inline constexpr bool can_convert_to_string_v = can_convert_to_string>::value; template -struct shared_ptr_adapter : std::type_identity>> -{ -}; +struct shared_ptr_adapter : std::type_identity>> {}; template -struct shared_ptr_adapter> : std::type_identity> -{ -}; +struct shared_ptr_adapter> : std::type_identity> {}; template auto to_shared_ptr(T&& value) { - if constexpr (is_template_instance_of_v) - { + if constexpr (is_template_instance_of_v) { return std::forward(value); - } - else - { + } else { return std::make_shared>(std::forward(value)); } } template -struct element_type_adapter : std::type_identity> -{ -}; +struct element_type_adapter : std::type_identity> {}; template -struct element_type_adapter> : std::type_identity -{ -}; +struct element_type_adapter> : std::type_identity {}; template -struct element_type_adapter> : std::type_identity -{ -}; +struct element_type_adapter> : std::type_identity {}; template -struct element_type_adapter : std::type_identity -{ -}; +struct element_type_adapter : std::type_identity {}; template decltype(auto) to_element_ref(T& value) noexcept { if constexpr (std::is_pointer_v || is_template_instance_of_v || - is_template_instance_of_v) - { + is_template_instance_of_v) { return *value; - } - else - { + } else { return (value); } } diff --git a/include/arknet/base/detail/util.hpp b/include/arknet/base/detail/util.hpp index 6a81240..a62ec8d 100644 --- a/include/arknet/base/detail/util.hpp +++ b/include/arknet/base/detail/util.hpp @@ -52,8 +52,7 @@ Endpoint to_endpoint(Host&& host, Service&& service) { set_last_error(error ? error : results.empty() ? error_code(asio::error::host_not_found) : error_code{}); return get_last_error() ? Endpoint{} : results.begin()->endpoint(); } -enum class net_protocol : std::uint8_t -{ +enum class net_protocol : std::uint8_t { none, udp, tcp, @@ -65,8 +64,7 @@ enum class net_protocol : std::uint8_t }; template -struct variant_overloaded : Functions... -{ +struct variant_overloaded : Functions... { using Functions::operator()...; }; } // namespace arknet @@ -77,31 +75,23 @@ using arknet::to_endpoint; using arknet::to_string; using arknet::to_string_view; -struct tcp_tag -{ +struct tcp_tag { using tl_tag_type = tcp_tag; }; -struct udp_tag -{ +struct udp_tag { using tl_tag_type = udp_tag; }; -struct cast_tag -{ +struct cast_tag { using tl_tag_type = cast_tag; }; -struct ssl_stream_tag -{ -}; +struct ssl_stream_tag {}; -struct ws_stream_tag -{ -}; +struct ws_stream_tag {}; -enum class state_t : std::int8_t -{ +enum class state_t : std::int8_t { stopped, stopping, starting, @@ -131,8 +121,7 @@ std::size_t default_concurrency() noexcept { template std::size_t bkdr_hash(const unsigned char* bytes, std::size_t size) noexcept { std::size_t value = 0; - for (auto byte : std::span(bytes, size)) - { + for (auto byte : std::span(bytes, size)) { value = value * 131 + byte; } return value; @@ -141,8 +130,7 @@ std::size_t bkdr_hash(const unsigned char* bytes, std::size_t size) noexcept { template Integer fnv1a_hash(Integer value, const unsigned char* bytes, Integer count) noexcept { constexpr Integer multiplier = sizeof(Integer) == 4 ? Integer(16777619u) : Integer(1099511628211ull); - for (auto byte : std::span(bytes, std::size_t(count))) - { + for (auto byte : std::span(bytes, std::size_t(count))) { value = (value ^ byte) * multiplier; } return value; @@ -155,8 +143,7 @@ Integer fnv1a_hash(const unsigned char* bytes, Integer count) noexcept { } template -class copyable_wrapper -{ +class copyable_wrapper { public: using value_type = Value; @@ -173,18 +160,16 @@ class copyable_wrapper }; template -struct is_copyable_wrapper : is_template_instance_of -{ -}; +struct is_copyable_wrapper : is_template_instance_of {}; template inline constexpr bool is_copyable_wrapper_v = is_copyable_wrapper::value; inline void cancel_timer(asio::steady_timer& timer) noexcept { - try - { timer.cancel(); } - catch (const system_error&) - {} + try { + timer.cancel(); + } catch (const system_error&) { + } } template @@ -194,19 +179,16 @@ asio::steady_timer::duration to_steady_duration(std::chrono::duration= maximum) - { + if (ticks >= maximum) { return Duration::max(); } - if (ticks <= minimum) - { + if (ticks <= minimum) { return Duration::min(); } return Duration(static_cast(ticks)); } -struct safe_timer -{ +struct safe_timer { explicit safe_timer(asio::io_context& context) : timer(context) {} @@ -223,8 +205,7 @@ struct safe_timer }; template -struct repeating_timer_state : std::enable_shared_from_this> -{ +struct repeating_timer_state : std::enable_shared_from_this> { std::shared_ptr timer; Duration interval; Function callback; @@ -237,8 +218,7 @@ struct repeating_timer_state : std::enable_shared_from_thistimer.expires_after(interval); timer->timer.async_wait([self = this->shared_from_this()](const error_code& error) { - if (!self->timer->canceled.test(std::memory_order_acquire) && self->callback(error)) - { + if (!self->timer->canceled.test(std::memory_order_acquire) && self->callback(error)) { self->arm(); } }); @@ -255,18 +235,15 @@ auto mktimer(asio::io_context& context, std::chrono::duration inter } template -class id_maker -{ +class id_maker { public: explicit id_maker(Integer first = 1) noexcept : next_(first) {} Integer mkid() noexcept { auto value = next_.fetch_add(1, std::memory_order_relaxed); - if constexpr (SkipZero) - { - while (value == 0) - { + if constexpr (SkipZero) { + while (value == 0) { value = next_.fetch_add(1, std::memory_order_relaxed); } } @@ -294,8 +271,7 @@ void swap_bytes(Value& value) noexcept { template Value host_to_network(Value value) noexcept { - if constexpr (std::endian::native == std::endian::little) - { + if constexpr (std::endian::native == std::endian::little) { swap_bytes(value); } return value; @@ -337,8 +313,7 @@ void disable_sso(String& value) { } template -struct integer_add_sub_guard -{ +struct integer_add_sub_guard { explicit integer_add_sub_guard(Integer& value) noexcept : value_(value) { ++value_; @@ -354,10 +329,8 @@ struct integer_add_sub_guard template bool is_subpath_of(const std::filesystem::path& base, const std::filesystem::path& path) noexcept { auto position = path.begin(); - for (const auto& component : base) - { - if (position == path.end() || *position++ != component) - { + for (const auto& component : base) { + if (position == path.end() || *position++ != component) { return false; } } @@ -368,8 +341,7 @@ template std::filesystem::path make_filepath(const std::filesystem::path& base, const std::filesystem::path& path) noexcept { std::error_code error; const auto root = std::filesystem::canonical(base, error); - if (error) - { + if (error) { return {}; } const auto result = std::filesystem::canonical(root / path.relative_path(), error); @@ -377,31 +349,23 @@ std::filesystem::path make_filepath(const std::filesystem::path& base, const std } template -struct current_object_result_t : std::type_identity -{ -}; +struct current_object_result_t : std::type_identity {}; template -struct current_object_result_t> : std::type_identity&> -{ -}; +struct current_object_result_t> : std::type_identity&> {}; template typename current_object_result_t::type get_current_object() noexcept { - if constexpr (is_template_instance_of_v) - { + if constexpr (is_template_instance_of_v) { thread_local std::weak_ptr current; return current; - } - else - { + } else { thread_local Value current{}; return current; } } -struct wait_timer_op -{ +struct wait_timer_op { asio::steady_timer& timer_; bool waiting_ = false; @@ -410,51 +374,39 @@ struct wait_timer_op template void operator()(Operation& operation, error_code = {}) { - if (std::exchange(waiting_, true)) - { + if (std::exchange(waiting_, true)) { operation.complete(get_last_error()); - } - else - { + } else { timer_.async_wait(std::move(operation)); } } }; -struct data_filter_before_helper -{ +struct data_filter_before_helper { template - struct has_member_data_filter_before_recv : std::bool_constant < requires(T& object, std::string_view bytes) - { + struct has_member_data_filter_before_recv : std::bool_constant < requires(T& object, std::string_view bytes) { object.data_filter_before_recv(bytes); } > {}; template - struct has_member_data_filter_before_send : std::bool_constant < requires(T& object, std::string_view bytes) - { + struct has_member_data_filter_before_send : std::bool_constant < requires(T& object, std::string_view bytes) { object.data_filter_before_send(bytes); } > {}; template static std::string_view call_data_filter_before_recv(Object& object, std::string_view bytes) noexcept { - if constexpr (requires { object.data_filter_before_recv(bytes); }) - { + if constexpr (requires { object.data_filter_before_recv(bytes); }) { return object.data_filter_before_recv(bytes); - } - else - { + } else { return bytes; } } template static auto call_data_filter_before_send(Object& object, Data&& data) { - if constexpr (requires { object.data_filter_before_send(std::forward(data)); }) - { + if constexpr (requires { object.data_filter_before_send(std::forward(data)); }) { return object.data_filter_before_send(std::forward(data)); - } - else - { + } else { return std::forward(data); } } @@ -471,8 +423,7 @@ auto call_data_filter_before_send(Object& object, Data&& data) { } template - struct has_member_insert : std::bool_constant < requires(T& container, std::string_view value) -{ + struct has_member_insert : std::bool_constant < requires(T& container, std::string_view value) { container.insert(container.begin(), value.begin(), value.end()); } > {}; } // namespace arknet::detail @@ -481,16 +432,11 @@ namespace arknet { template T get_current_caller() noexcept { - if constexpr (detail::is_template_instance_of_v) - { + if constexpr (detail::is_template_instance_of_v) { return detail::get_current_object().lock(); - } - else if constexpr (std::is_reference_v) - { + } else if constexpr (std::is_reference_v) { return *detail::get_current_object*>(); - } - else - { + } else { return detail::get_current_object(); } } diff --git a/include/arknet/base/error.hpp b/include/arknet/base/error.hpp index 97b08d4..891ae75 100644 --- a/include/arknet/base/error.hpp +++ b/include/arknet/base/error.hpp @@ -31,21 +31,16 @@ void set_last_error(int value, const Category& category) noexcept { template void set_last_error(Error value) noexcept { - if constexpr (std::is_integral_v) - { + if constexpr (std::is_integral_v) { get_last_error().assign(static_cast(value), asio::error::get_system_category()); - } - else if constexpr (std::is_same_v || std::is_same_v || - std::is_same_v) - { + } else if constexpr (std::is_same_v || std::is_same_v || + std::is_same_v) { #ifdef ASIO_STANDALONE get_last_error() = std::make_error_code(value); #else get_last_error().assign(static_cast(value), asio::error::get_system_category()); #endif - } - else - { + } else { get_last_error() = value; } } diff --git a/include/arknet/base/impl/alive_time_cp.hpp b/include/arknet/base/impl/alive_time_cp.hpp index b7c9961..a593b06 100644 --- a/include/arknet/base/impl/alive_time_cp.hpp +++ b/include/arknet/base/impl/alive_time_cp.hpp @@ -6,8 +6,7 @@ namespace arknet::detail { template -class alive_time_cp -{ +class alive_time_cp { public: auto last_alive_time() const noexcept { return get_last_alive_time(); diff --git a/include/arknet/base/impl/close_cp.hpp b/include/arknet/base/impl/close_cp.hpp index ec1f2e8..277ab0b 100644 --- a/include/arknet/base/impl/close_cp.hpp +++ b/include/arknet/base/impl/close_cp.hpp @@ -11,8 +11,7 @@ namespace arknet::detail { template -class close_cp -{ +class close_cp { public: using self = close_cp; @@ -26,10 +25,8 @@ class close_cp defer_event next(std::move(completion), std::move(guard)); set_last_error(error); auto prior = object.state_.load(); - while (prior == state_t::started || prior == state_t::starting) - { - if (object.state_.compare_exchange_weak(prior, state_t::stopping)) - { + while (prior == state_t::started || prior == state_t::starting) { + if (object.state_.compare_exchange_weak(prior, state_t::stopping)) { object._post_close(error, std::move(owner), prior, std::move(next)); return; } @@ -42,8 +39,7 @@ class close_cp template void _post_close(const error_code& error, std::shared_ptr owner, state_t prior, Chain chain) { auto& object = static_cast(*this); - if constexpr (Args::is_session) - { + if constexpr (Args::is_session) { // Session map mutation and disconnect notification belong to the acceptor lane. object.post([&object, error, prior, owner = std::move(owner), chain = std::move(chain)]() mutable { object.sessions_.erase( @@ -51,8 +47,7 @@ class close_cp set_last_error(error); auto expected = state_t::stopping; if (object.state_.compare_exchange_strong(expected, state_t::stopped) && erased && - prior == state_t::started) - { + prior == state_t::started) { object._fire_disconnect(owner); } object.dispatch([&object, error, owner = std::move(owner), chain = std::move(chain)]() mutable { @@ -60,33 +55,24 @@ class close_cp }); }); }); - } - else - { + } else { set_last_error(error); auto expected = state_t::stopping; - if (object.state_.compare_exchange_strong(expected, state_t::stopped) && prior == state_t::started) - { + if (object.state_.compare_exchange_strong(expected, state_t::stopped) && prior == state_t::started) { object._fire_disconnect(owner); } - if (!chain.empty()) - { + if (!chain.empty()) { object._handle_close(error, std::move(owner), std::move(chain)); - } - else - { + } else { auto guard = chain.move_guard(); object._handle_close(error, owner, defer_event( [&object, owner](event_queue_guard guard) mutable { object.disp_event( [&object, owner = std::move(owner)](event_queue_guard) mutable { - if (object.reconnect_enable_) - { + if (object.reconnect_enable_) { object._wake_reconnect_timer(); - } - else - { + } else { object._stop_reconnect_timer(); } }, diff --git a/include/arknet/base/impl/condition_event_cp.hpp b/include/arknet/base/impl/condition_event_cp.hpp index 539c2c3..dd257a9 100644 --- a/include/arknet/base/impl/condition_event_cp.hpp +++ b/include/arknet/base/impl/condition_event_cp.hpp @@ -19,12 +19,9 @@ class condition_event_cp; namespace arknet { -class async_event : public detail::object_t -{ -}; +class async_event : public detail::object_t {}; -class condition_event : public detail::object_t -{ +class condition_event : public detail::object_t { template friend class detail::condition_event_cp; @@ -34,11 +31,9 @@ class condition_event : public detail::object_t void notify() { notified_.store(true, std::memory_order_release); - if (auto lane = event_timer_io_.lock()) - { + if (auto lane = event_timer_io_.lock()) { asio::dispatch(lane->executor(), [self = this->selfptr()] { - if (self && self->event_timer_) - { + if (self && self->event_timer_) { detail::cancel_timer(*self->event_timer_); } }); @@ -49,8 +44,7 @@ class condition_event : public detail::object_t template void async_wait(Handler&& callback) { auto lane = event_timer_io_.lock(); - if (!lane) - { + if (!lane) { return; } event_timer_ = std::make_unique(lane->executor()); @@ -61,8 +55,7 @@ class condition_event : public detail::object_t lane->timers().erase(self->event_timer_.get()); callback(); }); - if (notified_.load(std::memory_order_acquire)) - { + if (notified_.load(std::memory_order_acquire)) { detail::cancel_timer(*event_timer_); } } @@ -78,10 +71,8 @@ class condition_event : public detail::object_t namespace arknet::detail { template -class condition_event_cp -{ - struct operation_state - { +class condition_event_cp { + struct operation_state { std::atomic_bool alive{true}; std::atomic_bool accepting{true}; std::shared_ptr pending = std::make_shared(0); @@ -99,15 +90,12 @@ class condition_event_cp auto post_condition_event(Function&& callback) { auto& object = static_cast(*this); auto event = std::make_shared(object.io_); - if (!events_->accepting.load(std::memory_order_acquire)) - { + if (!events_->accepting.load(std::memory_order_acquire)) { set_last_error(asio::error::operation_aborted); return event; } - if constexpr (requires { object._accepts_async_work(); }) - { - if (!object._accepts_async_work()) - { + if constexpr (requires { object._accepts_async_work(); }) { + if (!object._accepts_async_work()) { set_last_error(asio::error::operation_aborted); return event; } @@ -115,8 +103,8 @@ class condition_event_cp asio::dispatch(object.io_->executor(), pending_handler(events_->pending, [state = events_, owner = object.selfptr(), event, callback = std::forward(callback)]() mutable { - if (!state->alive.load(std::memory_order_acquire) || !state->accepting.load(std::memory_order_acquire)) - { + if (!state->alive.load(std::memory_order_acquire) || + !state->accepting.load(std::memory_order_acquire)) { return; } state->events.emplace(event.get(), event); @@ -127,8 +115,7 @@ class condition_event_cp state->events.erase(key); auto current = std::move(callback); callback.reset(); - if (state->alive.load(std::memory_order_acquire)) - { + if (state->alive.load(std::memory_order_acquire)) { (*current)(); } }); @@ -140,8 +127,7 @@ class condition_event_cp auto& object = static_cast(*this); asio::dispatch(object.io_->executor(), [state = events_, owner = object.selfptr(), pending = pending_operation(events_->pending)] { - for (auto& [key, event] : state->events) - { + for (auto& [key, event] : state->events) { event->notify(); } }); diff --git a/include/arknet/base/impl/connect_cp.hpp b/include/arknet/base/impl/connect_cp.hpp index a047ee1..cd8f7da 100644 --- a/include/arknet/base/impl/connect_cp.hpp +++ b/include/arknet/base/impl/connect_cp.hpp @@ -14,8 +14,7 @@ namespace arknet::detail { template -class run_connect_op -{ +class run_connect_op { public: using resolver_type = asio::ip::basic_resolver::protocol_type>; using endpoints_type = typename resolver_type::results_type; @@ -28,34 +27,26 @@ class run_connect_op template void operator()(Operation& operation, error_code error = {}, endpoints_type endpoints = {}) { - if (phase_ == phase::initial) - { + if (phase_ == phase::initial) { phase_ = phase::resolving; resolver_->async_resolve(host_, service_, std::move(operation)); return; } - if (phase_ == phase::resolving) - { - if (error) - { + if (phase_ == phase::resolving) { + if (error) { operation.complete(error); return; } endpoints_ = std::move(endpoints); position_ = endpoints_.begin(); phase_ = phase::connecting; - } - else if (!error || error == asio::error::operation_aborted) - { + } else if (!error || error == asio::error::operation_aborted) { operation.complete(error); return; - } - else - { + } else { ++position_; } - if (position_ == endpoints_.end()) - { + if (position_ == endpoints_.end()) { operation.complete(error ? error : error_code(asio::error::host_unreachable)); return; } @@ -65,8 +56,7 @@ class run_connect_op } private: - enum class phase - { + enum class phase { initial, resolving, connecting @@ -92,13 +82,10 @@ auto async_connect(std::string host, std::string service, Socket& socket, Token& namespace arknet::detail { template -class connect_cp_member_variables -{ -}; +class connect_cp_member_variables {}; template -class connect_cp_member_variables -{ +class connect_cp_member_variables { public: template Derived& set_host(Host&& host) { @@ -125,8 +112,7 @@ class connect_cp_member_variables }; template -class connect_cp : public connect_cp_member_variables -{ +class connect_cp : public connect_cp_member_variables { public: using socket_t = typename Args::socket_t; using decay_socket_t = std::remove_cvref_t; @@ -147,8 +133,7 @@ class connect_cp : public connect_cp_member_variables(std::move(endpoints)); const auto first = owned->begin(); object._post_connect( @@ -203,40 +184,33 @@ class connect_cp : public connect_cp_member_variables endpoints, endpoints_iterator position, std::shared_ptr owner, std::shared_ptr> options, Chain chain) { auto& object = static_cast(*this); - if (object.is_iopool_stop_requested() || object.state_.load() != state_t::starting) - { + if (object.is_iopool_stop_requested() || object.state_.load() != state_t::starting) { object._handle_connect( asio::error::operation_aborted, std::move(owner), std::move(options), std::move(chain)); return; } - if (position == endpoints->end()) - { + if (position == endpoints->end()) { object._handle_connect(error ? error : error_code(asio::error::host_unreachable), std::move(owner), std::move(options), std::move(chain)); return; } auto& socket = object.socket(); error_code ignored; - if (socket.is_open()) - { + if (socket.is_open()) { const auto local = socket.local_endpoint(ignored); - if (ignored || local.protocol() != position->endpoint().protocol()) - { + if (ignored || local.protocol() != position->endpoint().protocol()) { socket.cancel(ignored); socket.close(ignored); } } - if (!socket.is_open()) - { + if (!socket.is_open()) { socket.open(position->endpoint().protocol(), error); - if (error) - { + if (error) { object._handle_connect(error, std::move(owner), std::move(options), std::move(chain)); return; } // UDP clients need exclusive source ports for unambiguous reply routing. - if constexpr (std::is_same_v) - { + if constexpr (std::is_same_v) { socket.set_option(asio::socket_base::reuse_address(true), ignored); } detail::set_keepalive_options(socket); @@ -244,8 +218,7 @@ class connect_cp : public connect_cp_member_variables(*this); error_code ignored; const auto endpoint = object.socket().lowest_layer().remote_endpoint(ignored); - if (!ignored) - { + if (!ignored) { object.remote_endpoint_ = endpoint; } // A stop queued after init must fence protocol handshakes too. @@ -296,12 +265,10 @@ class connect_cp : public connect_cp_member_variables owner, std::shared_ptr> options, Chain chain) { auto& object = static_cast(*this); - if constexpr (Args::is_session) - { + if constexpr (Args::is_session) { object.dispatch([&object, error, owner = std::move(owner), options = std::move(options), chain = std::move(chain)]() mutable { - if (!object.socket().is_open()) - { + if (!object.socket().is_open()) { error = asio::error::timed_out; } object.sessions_.dispatch(object._track_async_operation( @@ -310,15 +277,10 @@ class connect_cp : public connect_cp_member_variables owner, std::shared_ptr> options, Chain chain) { auto& object = static_cast(*this); - if constexpr (Args::is_session) - { - if (!error && object.stop_requested_) - { + if constexpr (Args::is_session) { + if (!error && object.stop_requested_) { error = asio::error::operation_aborted; } } - if (!error) - { - if constexpr (!Args::is_session) - { + if (!error) { + if constexpr (!Args::is_session) { object.reset_life_id(); } auto expected = state_t::starting; - if (!object.state_.compare_exchange_strong(expected, state_t::started)) - { + if (!object.state_.compare_exchange_strong(expected, state_t::started)) { error = asio::error::operation_aborted; } } set_last_error(error); - if constexpr (Args::is_session) - { - if (!error && object.state_.load() == state_t::started) - { + if constexpr (Args::is_session) { + if (!error && object.state_.load() == state_t::started) { object._fire_connect(owner, options); } - } - else if (object.state_.load() != state_t::stopped) - { + } else if (object.state_.load() != state_t::stopped) { object._fire_connect(owner, options); } - if (!error && object.state_.load() != state_t::started) - { + if (!error && object.state_.load() != state_t::started) { error = asio::error::operation_aborted; } object._stop_connect_timeout_timer(); set_last_error(error); - if (error) - { + if (error) { // Publish the failed connect completion before disconnect can change last_error. { auto completion = chain.move_event(); } object._do_disconnect(error, std::move(owner), defer_event(chain.move_guard())); - } - else - { + } else { object._do_start(std::move(owner), std::move(options), std::move(chain)); } } diff --git a/include/arknet/base/impl/connect_time_cp.hpp b/include/arknet/base/impl/connect_time_cp.hpp index 39969b7..2f7adc2 100644 --- a/include/arknet/base/impl/connect_time_cp.hpp +++ b/include/arknet/base/impl/connect_time_cp.hpp @@ -6,8 +6,7 @@ namespace arknet::detail { template -class connect_time_cp -{ +class connect_time_cp { public: auto get_connect_time() const noexcept { return connect_time_; diff --git a/include/arknet/base/impl/connect_timeout_cp.hpp b/include/arknet/base/impl/connect_timeout_cp.hpp index 857791d..8566637 100644 --- a/include/arknet/base/impl/connect_timeout_cp.hpp +++ b/include/arknet/base/impl/connect_timeout_cp.hpp @@ -11,8 +11,7 @@ namespace arknet::detail { template -class connect_timeout_cp -{ +class connect_timeout_cp { public: auto get_connect_timeout() const noexcept { return connect_timeout_; @@ -29,8 +28,7 @@ class connect_timeout_cp void _make_connect_timeout_timer(std::shared_ptr owner, std::chrono::duration duration) { auto& object = static_cast(*this); object.dispatch([this, owner = std::move(owner), duration]() mutable { - if (connect_timeout_timer_) - { + if (connect_timeout_timer_) { connect_timeout_timer_->cancel(); } connect_timeout_timer_ = std::make_shared(static_cast(*this).io_->executor()); @@ -41,8 +39,7 @@ class connect_timeout_cp template void _post_connect_timeout_timer(std::shared_ptr owner, std::shared_ptr timer, std::chrono::duration duration) { - if (timer != connect_timeout_timer_) - { + if (timer != connect_timeout_timer_) { return; } timer->timer.expires_after(to_steady_duration(duration)); @@ -56,22 +53,17 @@ class connect_timeout_cp template void _handle_connect_timeout_timer( const error_code& error, std::shared_ptr owner, std::shared_ptr timer) { - if (timer != connect_timeout_timer_) - { + if (timer != connect_timeout_timer_) { return; } connect_timeout_timer_.reset(); - if (error || timer->canceled.test(std::memory_order_acquire)) - { + if (error || timer->canceled.test(std::memory_order_acquire)) { return; } auto& object = static_cast(*this); - if constexpr (Object::is_session()) - { + if constexpr (Object::is_session()) { object._do_disconnect(asio::error::timed_out, std::move(owner)); - } - else - { + } else { error_code ignored; object.socket().shutdown(asio::socket_base::shutdown_both, ignored); object.socket().cancel(ignored); diff --git a/include/arknet/base/impl/data_persistence_cp.hpp b/include/arknet/base/impl/data_persistence_cp.hpp index 01c83b8..ddae784 100644 --- a/include/arknet/base/impl/data_persistence_cp.hpp +++ b/include/arknet/base/impl/data_persistence_cp.hpp @@ -14,21 +14,15 @@ namespace arknet::detail { template -struct is_span : std::false_type -{ -}; +struct is_span : std::false_type {}; template -struct is_span> : std::true_type -{ -}; +struct is_span> : std::true_type {}; template -class data_persistence_cp -{ +class data_persistence_cp { static std::string copy_bytes(asio::const_buffer bytes) { - if (!bytes.size()) - { + if (!bytes.size()) { return {}; } return {static_cast(bytes.data()), bytes.size()}; @@ -38,32 +32,21 @@ class data_persistence_cp template static auto _own_send_data(Data&& data) { using Type = std::remove_cvref_t; - if constexpr (is_string_view_v) - { + if constexpr (is_string_view_v) { using String = std::basic_string; return data.empty() ? String{} : String(data.begin(), data.end()); - } - else if constexpr (is_char_array_v) - { + } else if constexpr (is_char_array_v) { using Character = typename char_type::type; const auto view = std::span(data); return std::basic_string(view.begin(), std::find(view.begin(), view.end(), Character{})); - } - else if constexpr (is_char_pointer_v) - { + } else if constexpr (is_char_pointer_v) { using Character = typename char_type::type; return data ? std::basic_string(data) : std::basic_string{}; - } - else if constexpr (is_span::value) - { + } else if constexpr (is_span::value) { return copy_bytes(asio::const_buffer(data.data(), data.size_bytes())); - } - else if constexpr (std::is_convertible_v || std::is_array_v) - { + } else if constexpr (std::is_convertible_v || std::is_array_v) { return copy_bytes(asio::buffer(data)); - } - else - { + } else { return std::forward(data); } } diff --git a/include/arknet/base/impl/disconnect_cp.hpp b/include/arknet/base/impl/disconnect_cp.hpp index b1c20e6..3740a0e 100644 --- a/include/arknet/base/impl/disconnect_cp.hpp +++ b/include/arknet/base/impl/disconnect_cp.hpp @@ -12,8 +12,7 @@ namespace arknet::detail { template -class disconnect_cp -{ +class disconnect_cp { public: using self = disconnect_cp; @@ -44,12 +43,9 @@ class disconnect_cp template void _handle_disconnect(const error_code& error, std::shared_ptr owner, Chain chain) { auto& object = static_cast(*this); - if (object.reading_) - { + if (object.reading_) { object._make_readend_timer(error, std::move(owner), std::move(chain)); - } - else - { + } else { object._handle_readend(error, std::move(owner), std::move(chain)); } } @@ -58,16 +54,14 @@ class disconnect_cp void _handle_readend(const error_code& error, std::shared_ptr owner, Chain chain) { auto& object = static_cast(*this); object.disconnecting_ = false; - if constexpr (Args::is_session) - { + if constexpr (Args::is_session) { object._do_stop(error, std::move(owner), std::move(chain)); } } template void _make_readend_timer(const error_code& error, std::shared_ptr owner, Chain chain) { - if (readend_timer_) - { + if (readend_timer_) { readend_timer_->cancel(); } auto& object = static_cast(*this); @@ -78,8 +72,7 @@ class disconnect_cp template void _post_readend_timer( const error_code& error, std::shared_ptr owner, Chain chain, std::shared_ptr timer) { - if (timer != readend_timer_) - { + if (timer != readend_timer_) { return; } auto& object = static_cast(*this); @@ -95,8 +88,7 @@ class disconnect_cp template void _handle_readend_timer(const error_code&, const error_code& error, std::shared_ptr owner, Chain chain, std::shared_ptr timer) { - if (timer != readend_timer_) - { + if (timer != readend_timer_) { return; } readend_timer_.reset(); @@ -105,8 +97,7 @@ class disconnect_cp void _stop_readend_timer(std::shared_ptr owner) { static_cast(*this).dispatch([this, owner = std::move(owner)] { - if (readend_timer_) - { + if (readend_timer_) { readend_timer_->cancel(); } }); diff --git a/include/arknet/base/impl/event_queue_cp.hpp b/include/arknet/base/impl/event_queue_cp.hpp index 5e470c9..b1f9659 100644 --- a/include/arknet/base/impl/event_queue_cp.hpp +++ b/include/arknet/base/impl/event_queue_cp.hpp @@ -20,8 +20,7 @@ template class defer_event; template -class event_queue_guard -{ +class event_queue_guard { template friend class event_queue_cp; template @@ -41,8 +40,7 @@ class event_queue_guard event_queue_guard& operator=(const event_queue_guard&) = delete; ~event_queue_guard() noexcept { - if (valid_) - { + if (valid_) { derive->next_event(std::move(*this)); } } @@ -67,8 +65,7 @@ class event_queue_guard }; template -class defer_event -{ +class defer_event { public: template explicit defer_event(Callable&& callable) @@ -81,8 +78,7 @@ class defer_event defer_event(const defer_event&) = delete; ~defer_event() { - if (armed_) - { + if (armed_) { function_(); } } @@ -105,8 +101,7 @@ class defer_event }; template<> -class defer_event -{ +class defer_event { public: defer_event() = default; @@ -126,14 +121,12 @@ class defer_event }; template -struct defer_eqg_dummy -{ +struct defer_eqg_dummy { void operator()(event_queue_guard) {} }; template -class defer_event -{ +class defer_event { template friend class defer_event; @@ -149,8 +142,7 @@ class defer_event defer_event(const defer_event&) = delete; ~defer_event() { - if (armed_) - { + if (armed_) { function_(event_queue_guard{}); } } @@ -181,8 +173,7 @@ class defer_event }; template -class defer_event -{ +class defer_event { public: template defer_event(Callable&& callable, event_queue_guard guard) @@ -202,8 +193,7 @@ class defer_event defer_event(const defer_event&) = delete; ~defer_event() { - if (armed_) - { + if (armed_) { function_(std::move(guard_)); } } @@ -237,8 +227,7 @@ class defer_event }; template -class defer_event -{ +class defer_event { public: defer_event() = default; @@ -285,12 +274,10 @@ template defer_event(event_queue_guard) -> defer_event; template -class event_queue_cp -{ +class event_queue_cp { public: std::size_t get_pending_event_count() const noexcept { - if (!static_cast(*this).io_->running_in_this_thread()) - { + if (!static_cast(*this).io_->running_in_this_thread()) { set_last_error(asio::error::operation_not_supported); return 0; } @@ -315,8 +302,7 @@ class event_queue_cp Derived& push_event(Callable&& callable) { auto& object = static_cast(*this); #ifndef ARKNET_STRONG_EVENT_ORDER - if (object.io_->running_in_this_thread()) - { + if (object.io_->running_in_this_thread()) { enqueue(object._track_async_operation(std::forward(callable))); return object; } @@ -338,8 +324,7 @@ class event_queue_cp template Derived& disp_event(Callable&& callable, event_queue_guard guard) { auto& object = static_cast(*this); - if (guard.empty()) - { + if (guard.empty()) { return push_event(std::forward(callable)); } asio::dispatch( @@ -356,8 +341,7 @@ class event_queue_cp // Popping the event may release its last token while pump still uses // the endpoint, including when an external guard returns on this lane. auto pending = object._track_async_operation([] {}); - if (!object.io_->running_in_this_thread()) - { + if (!object.io_->running_in_this_thread()) { asio::post(object.io_->executor(), object._track_async_operation( [this, guard = std::move(guard)]() mutable { next_event(std::move(guard)); })); @@ -378,24 +362,21 @@ class event_queue_cp void enqueue(Callable&& callable) { const bool first = events_.empty(); events_.emplace(std::forward(callable)); - if (first) - { + if (first) { advance_pending_ = true; pump(); } } void pump() { - if (pumping_) - { + if (pumping_) { return; } // A synchronous completion can release the event's final session owner. auto lifetime = static_cast(*this).selfptr(); pumping_ = true; // A synchronous completion requests another iteration rather than recursing. - while (std::exchange(advance_pending_, false) && !events_.empty()) - { + while (std::exchange(advance_pending_, false) && !events_.empty()) { events_.front()(event_queue_guard(static_cast(*this))); } pumping_ = false; diff --git a/include/arknet/base/impl/io_context_cp.hpp b/include/arknet/base/impl/io_context_cp.hpp index 63edf90..7f174b0 100644 --- a/include/arknet/base/impl/io_context_cp.hpp +++ b/include/arknet/base/impl/io_context_cp.hpp @@ -6,8 +6,7 @@ namespace arknet::detail { template -class io_context_cp -{ +class io_context_cp { public: explicit io_context_cp(std::shared_ptr context) : io_(std::move(context)) {} diff --git a/include/arknet/base/impl/post_cp.hpp b/include/arknet/base/impl/post_cp.hpp index 6ec41b4..716052c 100644 --- a/include/arknet/base/impl/post_cp.hpp +++ b/include/arknet/base/impl/post_cp.hpp @@ -17,8 +17,7 @@ namespace arknet::detail { -class pending_operation -{ +class pending_operation { public: explicit pending_operation(std::shared_ptr counter) : counter_(std::move(counter)) { @@ -31,8 +30,7 @@ class pending_operation pending_operation(const pending_operation&) = delete; ~pending_operation() { - if (counter_) - { + if (counter_) { counter_->fetch_sub(1, std::memory_order_release); counter_->notify_all(); } @@ -43,15 +41,14 @@ class pending_operation }; inline void wait_pending_operations(const std::shared_ptr& counter) { - for (auto count = counter->load(std::memory_order_acquire); count; count = counter->load(std::memory_order_acquire)) - { + for (auto count = counter->load(std::memory_order_acquire); count; + count = counter->load(std::memory_order_acquire)) { counter->wait(count, std::memory_order_acquire); } } template -class pending_handler -{ +class pending_handler { // Retire the callable and its captures before publishing completion. Asio // releases associated operation storage before invoking this handler. pending_operation pending_; @@ -73,10 +70,8 @@ class pending_handler }; template -class post_cp -{ - struct operation_state - { +class post_cp { + struct operation_state { std::atomic_bool alive{true}; std::atomic_bool accepting{true}; std::atomic_bool accepting_timed{true}; @@ -105,15 +100,13 @@ class post_cp template Derived& post(Function&& callback, std::chrono::duration delay) { auto& object = static_cast(*this); - if (!tasks_->accepting_timed.load(std::memory_order_acquire)) - { + if (!tasks_->accepting_timed.load(std::memory_order_acquire)) { set_last_error(asio::error::operation_aborted); return object; } post([state = tasks_, lane = object.io_, owner = object.selfptr(), callback = std::forward(callback), delay]() mutable { - if (!state->accepting_timed.load(std::memory_order_acquire)) - { + if (!state->accepting_timed.load(std::memory_order_acquire)) { return; } auto timer = std::make_unique(lane->executor()); @@ -130,8 +123,7 @@ class post_cp set_last_error(error); auto current = std::move(callback); callback.reset(); - if (!state->alive.load(std::memory_order_acquire)) - { + if (!state->alive.load(std::memory_order_acquire)) { return; } #ifdef ARKNET_ENABLE_TIMER_CALLBACK_WHEN_ERROR @@ -211,17 +203,13 @@ class post_cp auto& object = static_cast(*this); auto handler = make_allocator(tasks_->allocator, [state = tasks_, owner = object.selfptr(), pending = pending_operation(tasks_->pending)] { - for (auto* timer : state->timers) - { + for (auto* timer : state->timers) { cancel_timer(*timer); } }); - if constexpr (Inline) - { + if constexpr (Inline) { asio::dispatch(object.io_->executor(), std::move(handler)); - } - else - { + } else { asio::post(object.io_->executor(), std::move(handler)); } return object; @@ -230,10 +218,8 @@ class post_cp template Derived& submit(Function&& callback) { auto& object = static_cast(*this); - if constexpr (requires { object._accepts_async_work(); }) - { - if (!object._accepts_async_work()) - { + if constexpr (requires { object._accepts_async_work(); }) { + if (!object._accepts_async_work()) { set_last_error(asio::error::operation_aborted); return object; } @@ -244,17 +230,13 @@ class post_cp std::optional>(std::in_place, std::forward(callback))]() mutable { auto current = std::move(callback); callback.reset(); - if (state->alive.load(std::memory_order_acquire) && state->accepting.load(std::memory_order_acquire)) - { + if (state->alive.load(std::memory_order_acquire) && state->accepting.load(std::memory_order_acquire)) { (*current)(); } }); - if constexpr (Inline) - { + if constexpr (Inline) { asio::dispatch(object.io_->executor(), std::move(handler)); - } - else - { + } else { asio::post(object.io_->executor(), std::move(handler)); } return object; @@ -277,8 +259,7 @@ namespace asio { template class Associator, class Handler, class DefaultCandidate> struct associator, DefaultCandidate> - : Associator -{ + : Associator { using base = Associator; static decltype(auto) get(const arknet::detail::pending_handler& handler) noexcept diff --git a/include/arknet/base/impl/reconnect_timer_cp.hpp b/include/arknet/base/impl/reconnect_timer_cp.hpp index e20dac7..47be9f4 100644 --- a/include/arknet/base/impl/reconnect_timer_cp.hpp +++ b/include/arknet/base/impl/reconnect_timer_cp.hpp @@ -12,8 +12,7 @@ namespace arknet::detail { template -class reconnect_timer_cp -{ +class reconnect_timer_cp { public: template Derived& set_auto_reconnect(bool enabled) noexcept { @@ -51,8 +50,7 @@ class reconnect_timer_cp template void _make_reconnect_timer(std::shared_ptr owner, std::shared_ptr> options) { static_cast(*this).dispatch([this, owner = std::move(owner), options = std::move(options)]() mutable { - if (reconnect_timer_) - { + if (reconnect_timer_) { reconnect_timer_->cancel(); } reconnect_timer_ = std::make_shared(static_cast(*this).io_->executor()); @@ -64,19 +62,15 @@ class reconnect_timer_cp template void _post_reconnect_timer(std::shared_ptr owner, std::shared_ptr> options, std::shared_ptr timer, std::chrono::duration delay) { - if (timer != reconnect_timer_ || timer->canceled.test(std::memory_order_acquire)) - { + if (timer != reconnect_timer_ || timer->canceled.test(std::memory_order_acquire)) { return; } #ifndef NDEBUG is_post_reconnect_timer_called_ = true; #endif - if (delay == delay.max()) - { + if (delay == delay.max()) { timer->timer.expires_at(asio::steady_timer::time_point::max()); - } - else - { + } else { timer->timer.expires_after(to_steady_duration(delay)); } auto* operation = &timer->timer; @@ -90,31 +84,25 @@ class reconnect_timer_cp template void _handle_reconnect_timer(const error_code& error, std::shared_ptr owner, std::shared_ptr> options, std::shared_ptr timer) { - if (timer != reconnect_timer_) - { + if (timer != reconnect_timer_) { return; } - if (timer->canceled.test(std::memory_order_acquire)) - { + if (timer->canceled.test(std::memory_order_acquire)) { reconnect_timer_.reset(); return; } auto& object = static_cast(*this); - if (error == asio::error::operation_aborted) - { + if (error == asio::error::operation_aborted) { _post_reconnect_timer(std::move(owner), std::move(options), std::move(timer), reconnect_delay_); return; } - if (!error && reconnect_enable_) - { + if (!error && reconnect_enable_) { object.push_event([&object, owner, options, timer](event_queue_guard guard) mutable { - if (timer->canceled.test(std::memory_order_acquire)) - { + if (timer->canceled.test(std::memory_order_acquire)) { return; } state_t expected = state_t::stopped; - if (object.state_.compare_exchange_strong(expected, state_t::starting)) - { + if (object.state_.compare_exchange_strong(expected, state_t::starting)) { object.template _start_connect( std::move(owner), std::move(options), defer_event(std::move(guard))); } @@ -135,8 +123,7 @@ class reconnect_timer_cp void _wake_reconnect_timer() { static_cast(*this).dispatch([this] { - if (reconnect_enable_ && reconnect_timer_) - { + if (reconnect_enable_ && reconnect_timer_) { cancel_timer(reconnect_timer_->timer); } }); diff --git a/include/arknet/base/impl/send_cp.hpp b/include/arknet/base/impl/send_cp.hpp index 32e0f6c..44c0154 100644 --- a/include/arknet/base/impl/send_cp.hpp +++ b/include/arknet/base/impl/send_cp.hpp @@ -23,8 +23,7 @@ ARKNET_CLASS_FORWARD_DECLARE_BASE; template class send_cp : public data_persistence_cp - , public send_queue_cp -{ + , public send_queue_cp { ARKNET_CLASS_FRIEND_DECLARE_BASE; using queue_type = send_queue_cp; using queue_type::_call_completion; @@ -80,22 +79,17 @@ class send_cp requires send_completion_handler bool async_send(Element* data, Count count, Callback&& callback) { if (!data || (std::is_signed_v && count < 0) || - static_cast(count) > std::numeric_limits::max() / sizeof(Element)) - { + static_cast(count) > std::numeric_limits::max() / sizeof(Element)) { return reject(callback, asio::error::invalid_argument); } const auto bytes = static_cast(count) * sizeof(Element); - if (!this->_can_send_buffer_size(bytes)) - { + if (!this->_can_send_buffer_size(bytes)) { return reject(callback, asio::error::no_buffer_space); } - if constexpr (is_char_v) - { + if constexpr (is_char_v) { return async_send(std::basic_string_view>(data, static_cast(count)), std::forward(callback)); - } - else - { + } else { return async_send(asio::const_buffer(data, bytes), std::forward(callback)); } } @@ -135,8 +129,7 @@ class send_cp } private: - struct ignore_completion - { + struct ignore_completion { void operator()(const error_code&, std::size_t) const noexcept {} }; @@ -173,16 +166,11 @@ class send_cp template std::size_t payload_size(const Data& data) { - if constexpr (requires { sender()._send_data_size(data); }) - { + if constexpr (requires { sender()._send_data_size(data); }) { return sender()._send_data_size(data); - } - else if constexpr (is_span>::value) - { + } else if constexpr (is_span>::value) { return data.size_bytes(); - } - else - { + } else { return asio::buffer_size(asio::buffer(data)); } } @@ -199,19 +187,16 @@ class send_cp callback(std::move(owner), error, 0, std::move(guard)); return false; }; - if (object.state_ != state_t::started) - { + if (object.state_ != state_t::started) { return fail(asio::error::not_connected); } const auto input_bytes = payload_size(input); - if (!this->_reserve_send_buffer(input_bytes)) - { + if (!this->_reserve_send_buffer(input_bytes)) { return fail(asio::error::no_buffer_space); } send_buffer_guard reservation(*this, input_bytes); auto data = object._data_persistence(std::forward(input)); - if (!reservation.resize(asio::buffer_size(asio::buffer(data)))) - { + if (!reservation.resize(asio::buffer_size(asio::buffer(data)))) { reservation.release(); return fail(asio::error::no_buffer_space); } @@ -221,16 +206,12 @@ class send_cp payload = std::move(payload), reservation = std::move(reservation)](event_queue_guard current) mutable { error_code error; - if (!object.is_started()) - { + if (!object.is_started()) { error = asio::error::not_connected; - } - else if (generation != object.life_id()) - { + } else if (generation != object.life_id()) { error = asio::error::operation_aborted; } - if (error) - { + if (error) { set_last_error(error); reservation.release(); callback(std::move(owner), error, 0, std::move(current)); @@ -253,8 +234,7 @@ class send_cp std::size_t wait_send(std::future future) { if (sender().io_->context().get_executor().running_in_this_thread() && - future.wait_for(std::chrono::nanoseconds::zero()) != std::future_status::ready) - { + future.wait_for(std::chrono::nanoseconds::zero()) != std::future_status::ready) { set_last_error(asio::error::in_progress); return 0; } diff --git a/include/arknet/base/impl/send_queue_cp.hpp b/include/arknet/base/impl/send_queue_cp.hpp index c6aa762..9b95d04 100644 --- a/include/arknet/base/impl/send_queue_cp.hpp +++ b/include/arknet/base/impl/send_queue_cp.hpp @@ -16,8 +16,7 @@ concept send_completion_handler = std::invocable || std::invocable; template -class send_queue_cp -{ +class send_queue_cp { public: Derived& set_max_send_buffer_size(std::size_t bytes) noexcept { limit_.store(bytes, std::memory_order_relaxed); @@ -33,8 +32,7 @@ class send_queue_cp } protected: - class send_buffer_guard - { + class send_buffer_guard { public: send_buffer_guard(send_queue_cp& queue, std::size_t bytes) noexcept : queue_(&queue) @@ -51,8 +49,7 @@ class send_queue_cp } bool resize(std::size_t bytes) noexcept { - if (!queue_ || !queue_->adjust(bytes_, bytes)) - { + if (!queue_ || !queue_->adjust(bytes_, bytes)) { return false; } bytes_ = bytes; @@ -60,8 +57,7 @@ class send_queue_cp } void release() noexcept { - if (auto* queue = std::exchange(queue_, nullptr)) - { + if (auto* queue = std::exchange(queue_, nullptr)) { queue->reserved_.fetch_sub(bytes_, std::memory_order_relaxed); queue->operations_.fetch_sub(1, std::memory_order_relaxed); } @@ -74,16 +70,11 @@ class send_queue_cp template static void _call_completion(Callback& callback, const error_code& error, std::size_t bytes) { - if constexpr (std::invocable) - { + if constexpr (std::invocable) { std::invoke(callback, error, bytes); - } - else if constexpr (std::invocable) - { + } else if constexpr (std::invocable) { std::invoke(callback, bytes); - } - else - { + } else { std::invoke(callback); } } @@ -96,8 +87,7 @@ class send_queue_cp bool _reserve_send_buffer(std::size_t bytes) noexcept { // Empty payloads still consume operation storage and must be bounded. const auto previous = operations_.fetch_add(1, std::memory_order_relaxed); - if (previous < operation_limit && adjust(0, bytes)) - { + if (previous < operation_limit && adjust(0, bytes)) { return true; } operations_.fetch_sub(1, std::memory_order_relaxed); @@ -113,17 +103,14 @@ class send_queue_cp } bool adjust(std::size_t previous, std::size_t next) noexcept { - if (next <= previous) - { + if (next <= previous) { reserved_.fetch_sub(previous - next, std::memory_order_relaxed); return true; } const auto additional = next - previous; auto used = reserved_.load(std::memory_order_relaxed); - do - { - if (!fits(used, additional)) - { + do { + if (!fits(used, additional)) { return false; } } while (!reserved_.compare_exchange_weak(used, used + additional, std::memory_order_relaxed)); diff --git a/include/arknet/base/impl/shutdown_cp.hpp b/include/arknet/base/impl/shutdown_cp.hpp index 539ceea..060bdd1 100644 --- a/include/arknet/base/impl/shutdown_cp.hpp +++ b/include/arknet/base/impl/shutdown_cp.hpp @@ -12,8 +12,7 @@ namespace arknet::detail { template -class shutdown_cp -{ +class shutdown_cp { public: using self = shutdown_cp; @@ -22,8 +21,7 @@ class shutdown_cp auto& object = static_cast(*this); const auto state = object.state_.load(); // A stop queued before close must not arm a fresh timer after shutdown. - if (stop_deadline_timer_ || (state != state_t::starting && state != state_t::started)) - { + if (stop_deadline_timer_ || (state != state_t::starting && state != state_t::started)) { return; } auto timer = std::make_shared(object.io_->executor()); @@ -31,8 +29,7 @@ class shutdown_cp timer->timer.expires_after(object.get_disconnect_timeout()); timer->timer.async_wait( object._track_async_operation([timer, socket = object.socket_](const error_code& error) { - if (error || timer->canceled.test(std::memory_order_acquire)) - { + if (error || timer->canceled.test(std::memory_order_acquire)) { return; } error_code ignored; @@ -44,8 +41,7 @@ class shutdown_cp template void _do_shutdown(const error_code& error, std::shared_ptr owner, Chain chain) { auto& object = static_cast(*this); - if (shutdown_timer_ || object.disconnecting_) - { + if (shutdown_timer_ || object.disconnecting_) { object._stop_shutdown_timer(std::move(owner)); return; } @@ -55,13 +51,10 @@ class shutdown_cp defer_event next(std::move(completion), std::move(guard)); set_last_error(error); const auto state = object.state_.load(); - if (state == state_t::started || state == state_t::starting) - { + if (state == state_t::started || state == state_t::starting) { object.disconnecting_ = true; object._post_shutdown(error, std::move(owner), std::move(next)); - } - else - { + } else { object.post([owner = std::move(owner), next = std::move(next)]() mutable {}); } }, @@ -71,12 +64,9 @@ class shutdown_cp template void _post_shutdown(const error_code& error, std::shared_ptr owner, Chain chain) { auto& object = static_cast(*this); - if constexpr (std::is_same_v) - { + if constexpr (std::is_same_v) { object._post_shutdown_tcp(error, std::move(owner), std::move(chain)); - } - else - { + } else { object._handle_shutdown(error, std::move(owner), std::move(chain)); } } @@ -84,17 +74,14 @@ class shutdown_cp template void _post_shutdown_tcp(const error_code& error, std::shared_ptr owner, Chain chain) { auto& object = static_cast(*this); - if (object.socket().is_open()) - { + if (object.socket().is_open()) { error_code option_error; asio::socket_base::linger linger; object.socket().lowest_layer().get_option(linger, option_error); - if (!option_error && !(linger.enabled() && linger.timeout() == 0)) - { + if (!option_error && !(linger.enabled() && linger.timeout() == 0)) { error_code shutdown_error; object.socket().shutdown(asio::socket_base::shutdown_send, shutdown_error); - if (!shutdown_error && object.reading_) - { + if (!shutdown_error && object.reading_) { object._make_shutdown_timer(error, std::move(owner), std::move(chain)); return; } @@ -106,8 +93,7 @@ class shutdown_cp template void _handle_shutdown(const error_code& error, std::shared_ptr owner, Chain chain) { shutdown_timer_.reset(); - if (stop_deadline_timer_) - { + if (stop_deadline_timer_) { stop_deadline_timer_->cancel(); stop_deadline_timer_.reset(); } @@ -117,8 +103,7 @@ class shutdown_cp template void _make_shutdown_timer(const error_code& error, std::shared_ptr owner, Chain chain) { auto& object = static_cast(*this); - if (shutdown_timer_) - { + if (shutdown_timer_) { shutdown_timer_->cancel(); } shutdown_timer_ = std::make_shared(object.io_->executor()); @@ -129,8 +114,7 @@ class shutdown_cp template void _post_shutdown_timer(const error_code& error, std::shared_ptr owner, Chain chain, std::chrono::duration delay, std::shared_ptr timer) { - if (timer != shutdown_timer_) - { + if (timer != shutdown_timer_) { return; } timer->timer.expires_after(to_steady_duration(delay)); @@ -146,17 +130,14 @@ class shutdown_cp template void _handle_shutdown_timer(const error_code& failure, const error_code& error, std::shared_ptr owner, Chain chain, std::shared_ptr timer) { - if (timer != shutdown_timer_) - { + if (timer != shutdown_timer_) { return; } auto& object = static_cast(*this); #ifdef ARKNET_WAIT_FOR_READING_END - if (!failure && !timer->canceled.test(std::memory_order_acquire)) - { + if (!failure && !timer->canceled.test(std::memory_order_acquire)) { const auto silent = object.get_silence_duration(); - if (silent < object.get_disconnect_timeout()) - { + if (silent < object.get_disconnect_timeout()) { object._post_shutdown_timer(error, std::move(owner), std::move(chain), object.get_disconnect_timeout() - silent, std::move(timer)); return; @@ -168,8 +149,7 @@ class shutdown_cp void _stop_shutdown_timer(std::shared_ptr owner) { static_cast(*this).dispatch([this, owner = std::move(owner)] { - if (shutdown_timer_) - { + if (shutdown_timer_) { shutdown_timer_->cancel(); } }); diff --git a/include/arknet/base/impl/silence_timer_cp.hpp b/include/arknet/base/impl/silence_timer_cp.hpp index 3166227..1ec452a 100644 --- a/include/arknet/base/impl/silence_timer_cp.hpp +++ b/include/arknet/base/impl/silence_timer_cp.hpp @@ -10,8 +10,7 @@ namespace arknet::detail { template -class silence_timer_cp -{ +class silence_timer_cp { public: auto get_silence_timeout() const noexcept { return silence_timeout_; @@ -27,12 +26,10 @@ class silence_timer_cp template void _post_silence_timer(std::chrono::duration delay, std::shared_ptr owner) { static_cast(*this).dispatch([this, owner = std::move(owner), delay]() mutable { - if (delay <= delay.zero()) - { + if (delay <= delay.zero()) { return; } - if (silence_timer_) - { + if (silence_timer_) { silence_timer_->cancel(); } auto timer = std::make_shared(static_cast(*this).io_->executor()); @@ -40,8 +37,7 @@ class silence_timer_cp timer->timer.expires_after(to_steady_duration(delay)); timer->timer.async_wait(static_cast(*this)._track_async_operation( [this, owner = std::move(owner), timer](const error_code& error) mutable { - if (timer != silence_timer_ || timer->canceled.test(std::memory_order_acquire)) - { + if (timer != silence_timer_ || timer->canceled.test(std::memory_order_acquire)) { return; } _handle_silence_timer(error, std::move(owner)); @@ -50,19 +46,15 @@ class silence_timer_cp } void _handle_silence_timer(const error_code& error, std::shared_ptr owner) { - if (error) - { + if (error) { silence_timer_.reset(); return; } auto& object = static_cast(*this); const auto silent = object.get_silence_duration(); - if (silent < silence_timeout_) - { + if (silent < silence_timeout_) { _post_silence_timer(silence_timeout_ - silent, std::move(owner)); - } - else - { + } else { silence_timer_.reset(); object._do_disconnect(asio::error::timed_out, std::move(owner)); } @@ -73,8 +65,7 @@ class silence_timer_cp #ifndef NDEBUG is_stop_silence_timer_called_ = true; #endif - if (silence_timer_) - { + if (silence_timer_) { silence_timer_->cancel(); silence_timer_.reset(); } diff --git a/include/arknet/base/impl/socket_cp.hpp b/include/arknet/base/impl/socket_cp.hpp index 871f975..ed8cc13 100644 --- a/include/arknet/base/impl/socket_cp.hpp +++ b/include/arknet/base/impl/socket_cp.hpp @@ -10,8 +10,7 @@ namespace arknet::detail { template -class socket_cp -{ +class socket_cp { public: using socket_type = std::remove_cvref_t; @@ -141,8 +140,7 @@ class socket_cp private: auto remote() const noexcept { auto endpoint = socket_->lowest_layer().remote_endpoint(get_last_error()); - if (get_last_error() && !remote_endpoint_.address().is_unspecified()) - { + if (get_last_error() && !remote_endpoint_.address().is_unspecified()) { clear_last_error(); return remote_endpoint_; } diff --git a/include/arknet/base/impl/thread_id_cp.hpp b/include/arknet/base/impl/thread_id_cp.hpp index 5c094cc..0af5ca8 100644 --- a/include/arknet/base/impl/thread_id_cp.hpp +++ b/include/arknet/base/impl/thread_id_cp.hpp @@ -6,8 +6,7 @@ namespace arknet::detail { template -class thread_id_cp -{ +class thread_id_cp { public: bool running_in_this_thread() const noexcept { return static_cast(*this).io_->running_in_this_thread(); diff --git a/include/arknet/base/impl/user_data_cp.hpp b/include/arknet/base/impl/user_data_cp.hpp index 494acee..07bfcba 100644 --- a/include/arknet/base/impl/user_data_cp.hpp +++ b/include/arknet/base/impl/user_data_cp.hpp @@ -10,8 +10,7 @@ namespace arknet::detail { template -class user_data_cp -{ +class user_data_cp { public: template Derived& set_user_data(T&& value) { @@ -31,27 +30,19 @@ class user_data_cp template T get_user_data() noexcept { - if constexpr (std::is_pointer_v) - { - if (auto* pointer = std::any_cast(&user_data_)) - { + if constexpr (std::is_pointer_v) { + if (auto* pointer = std::any_cast(&user_data_)) { return *pointer; } return std::any_cast>(&user_data_); - } - else - { - if (auto* value = std::any_cast>(&user_data_)) - { + } else { + if (auto* value = std::any_cast>(&user_data_)) { return *value; } - if constexpr (std::is_reference_v) - { + if constexpr (std::is_reference_v) { static std::remove_reference_t empty{}; return empty; - } - else - { + } else { return {}; } } diff --git a/include/arknet/base/impl/user_timer_cp.hpp b/include/arknet/base/impl/user_timer_cp.hpp index 6936229..1e2f8b6 100644 --- a/include/arknet/base/impl/user_timer_cp.hpp +++ b/include/arknet/base/impl/user_timer_cp.hpp @@ -17,8 +17,7 @@ namespace arknet::detail { -struct user_timer_handle -{ +struct user_timer_handle { template requires(!std::is_same_v, user_timer_handle>) user_timer_handle(T&& value) { @@ -40,31 +39,20 @@ struct user_timer_handle template void bind(T&& value) { using Type = std::remove_cvref_t; - if constexpr (std::is_integral_v) - { + if constexpr (std::is_integral_v) { handle = std::to_string(value); - } - else if constexpr (std::is_floating_point_v) - { + } else if constexpr (std::is_floating_point_v) { handle.assign(reinterpret_cast(std::addressof(value)), sizeof(Type)); - } - else if constexpr (is_string_v || is_string_view_v) - { + } else if constexpr (is_string_v || is_string_view_v) { handle.assign( reinterpret_cast(value.data()), value.size() * sizeof(typename Type::value_type)); - } - else if constexpr (is_char_pointer_v || is_char_array_v) - { + } else if constexpr (is_char_pointer_v || is_char_array_v) { using Character = typename char_type::type; const auto count = value ? std::char_traits::length(value) : 0; handle.assign(reinterpret_cast(value), count * sizeof(Character)); - } - else if constexpr (std::is_pointer_v) - { + } else if constexpr (std::is_pointer_v) { handle = std::to_string(reinterpret_cast(value)); - } - else - { + } else { static_assert(always_false_v, "Timer keys require a scalar, pointer, string or string view"); } } @@ -72,22 +60,19 @@ struct user_timer_handle std::string handle; }; -struct user_timer_handle_hash -{ +struct user_timer_handle_hash { std::size_t operator()(const user_timer_handle& key) const noexcept { return std::hash{}(key.handle); } }; -struct user_timer_handle_equal -{ +struct user_timer_handle_equal { bool operator()(const user_timer_handle& left, const user_timer_handle& right) const noexcept { return left == right; } }; -struct user_timer_obj -{ +struct user_timer_obj { user_timer_obj(user_timer_handle key, asio::any_io_executor executor) : id(std::move(key)) , timer(executor) {} @@ -101,15 +86,13 @@ struct user_timer_obj }; template -class user_timer_cp -{ +class user_timer_cp { public: using user_timer_map = std::unordered_map, user_timer_handle_hash, user_timer_handle_equal>; private: - struct operation_state - { + struct operation_state { std::atomic_bool alive{true}; std::atomic_bool accepting{true}; std::shared_ptr pending = std::make_shared(0); @@ -164,14 +147,12 @@ class user_timer_cp requires(std::is_integral_v && is_callable_v) void start_timer(Key&& key, std::chrono::duration interval, Repeat repeat, std::chrono::duration first, Function&& function, Bound&&... bound) { - if (!repeat) - { + if (!repeat) { set_last_error(asio::error::invalid_argument); return; } auto& object = static_cast(*this); - if (!timers_->accepting.load(std::memory_order_acquire)) - { + if (!timers_->accepting.load(std::memory_order_acquire)) { set_last_error(asio::error::operation_aborted); return; } @@ -180,8 +161,7 @@ class user_timer_cp key = user_timer_handle(std::forward(key)), period = to_steady_duration(interval), first = to_steady_duration(first), count = std::size_t(repeat), callback = std::bind_front(std::forward(function), std::forward(bound)...)]() mutable { - if (!state->accepting.load(std::memory_order_acquire)) - { + if (!state->accepting.load(std::memory_order_acquire)) { return; } cancel(state, key); @@ -225,8 +205,7 @@ class user_timer_cp void set_timer_interval(Key&& key, std::chrono::duration duration) { static_cast(*this).dispatch([state = timers_, key = user_timer_handle(std::forward(key)), duration = to_steady_duration(duration)] { - if (auto found = state->timers.find(key); found != state->timers.end()) - { + if (auto found = state->timers.find(key); found != state->timers.end()) { found->second->interval = duration; } }); @@ -267,19 +246,15 @@ class user_timer_cp void cancel_all() { auto& object = static_cast(*this); auto handler = [state = timers_, owner = object.selfptr(), pending = pending_operation(timers_->pending)] { - for (auto& [key, timer] : state->timers) - { + for (auto& [key, timer] : state->timers) { timer->exited = true; cancel_timer(timer->timer); } state->timers.clear(); }; - if constexpr (Inline) - { + if constexpr (Inline) { asio::dispatch(object.io_->executor(), std::move(handler)); - } - else - { + } else { asio::post(object.io_->executor(), std::move(handler)); } } @@ -290,13 +265,11 @@ class user_timer_cp auto* operation = &timer->timer; operation->async_wait([pending = pending_operation(state->pending), state, lane, owner = std::move(owner), timer = std::move(timer)](error_code error) mutable { - if (timer->exited || !state->alive.load(std::memory_order_acquire)) - { + if (timer->exited || !state->alive.load(std::memory_order_acquire)) { error = asio::error::operation_aborted; } set_last_error(error); - if (state->alive.load(std::memory_order_acquire)) - { + if (state->alive.load(std::memory_order_acquire)) { #ifdef ARKNET_ENABLE_TIMER_CALLBACK_WHEN_ERROR timer->callback(); #else @@ -306,22 +279,19 @@ class user_timer_cp } if (timer->repeat != std::size_t(-1)) --timer->repeat; - if (error || timer->exited || !timer->repeat || !state->alive.load(std::memory_order_acquire)) - { + if (error || timer->exited || !timer->repeat || !state->alive.load(std::memory_order_acquire)) { lane->timers().erase(&timer->timer); const auto found = state->timers.find(timer->id); if (found != state->timers.end() && found->second == timer) state->timers.erase(found); timer.reset(); - } - else + } else arm(state, lane, std::move(owner), timer, timer->interval); }); } static void cancel(const std::shared_ptr& state, const user_timer_handle& key) { - if (auto found = state->timers.find(key); found != state->timers.end()) - { + if (auto found = state->timers.find(key); found != state->timers.end()) { found->second->exited = true; cancel_timer(found->second->timer); state->timers.erase(found); @@ -332,27 +302,21 @@ class user_timer_cp auto query(Function&& function) { auto& object = static_cast(*this); using Result = std::invoke_result_t; - if (object.io_->running_in_this_thread()) - { + if (object.io_->running_in_this_thread()) { return function(); } - if (object.io_->context().get_executor().running_in_this_thread()) - { + if (object.io_->context().get_executor().running_in_this_thread()) { set_last_error(asio::error::operation_not_supported); return Result{}; } const bool stopped = object.io_->context().stopped() || [&] { - if constexpr (requires { object.iopool(); }) - { + if constexpr (requires { object.iopool(); }) { return !object.iopool().started(); - } - else - { + } else { return false; } }(); - if (stopped) - { + if (stopped) { set_last_error(asio::error::operation_aborted); return Result{}; } diff --git a/include/arknet/base/io_pool.hpp b/include/arknet/base/io_pool.hpp index 89a559f..71f3bd8 100644 --- a/include/arknet/base/io_pool.hpp +++ b/include/arknet/base/io_pool.hpp @@ -28,18 +28,14 @@ namespace arknet::detail { using io_context_work_guard = asio::executor_work_guard; -class io_t -{ - struct lane_state - { +class io_t { + struct lane_state { explicit lane_state(std::shared_ptr context, bool serialize) : context(std::move(context)) { - if (!this->context) - { + if (!this->context) { throw std::invalid_argument("null IO context"); } - if (serialize) - { + if (serialize) { strand.emplace(this->context->get_executor()); } executor = strand ? asio::any_io_executor(*strand) : this->context->get_executor(); @@ -112,8 +108,7 @@ class io_t } std::thread::id get_thread_id() const noexcept { - if (lane_->context->get_executor().running_in_this_thread()) - { + if (lane_->context->get_executor().running_in_this_thread()) { return std::this_thread::get_id(); } std::lock_guard lock(lane_->identity_mutex); @@ -122,8 +117,7 @@ class io_t template void regobj(Object* object) { - if (!object) - { + if (!object) { return; } asio::dispatch(executor(), [lane = lane_, object, owner = object->derived().selfptr()]() mutable { @@ -135,8 +129,7 @@ class io_t template void unregobj(Object* object) { - if (!object) - { + if (!object) { return; } asio::post( @@ -147,31 +140,27 @@ class io_t // A stopped context may never run this callback; do not retain its owner. asio::post(executor(), [weak = std::weak_ptr(lane_)] { auto lane = weak.lock(); - if (!lane) - { + if (!lane) { return; } - for (auto* timer : lane->timers) - { - try - { timer->cancel(); } - catch (const system_error&) - {} + for (auto* timer : lane->timers) { + try { + timer->cancel(); + } catch (const system_error&) { + } } lane->timers.clear(); // Detach owners before callbacks can unregister or stop another owner. auto owners = std::move(lane->owners); lane->owners.clear(); - for (auto& [key, stop] : owners) - { + for (auto& [key, stop] : owners) { stop(); } }); } }; -class iopool_base -{ +class iopool_base { public: virtual ~iopool_base() = default; virtual bool start() = 0; @@ -196,8 +185,7 @@ inline constinit std::atomic owned_pool_lifetimes{}; // A callback can release the last endpoint owner on one of its own workers. // Keep accepting deferred deletion until every owned pool has fully drained. -class owned_pool_cleanup -{ +class owned_pool_cleanup { std::mutex executor_mutex_; std::unique_ptr executor_; @@ -206,37 +194,31 @@ class owned_pool_cleanup #if defined(ARKNET_ENABLE_SSL) // Register OpenSSL's normal atexit cleanup before this singleton's // destructor, including when the first TLS context is created later. - if (!OPENSSL_init_ssl(0, nullptr)) - { + if (!OPENSSL_init_ssl(0, nullptr)) { throw std::runtime_error("could not initialize OpenSSL for owned IO runtime"); } #endif } ~owned_pool_cleanup() { - for (;;) - { + for (;;) { auto live = owned_pool_lifetimes.load(std::memory_order_acquire); - if (!live) - { - if (owned_pool_lifetimes.compare_exchange_weak(live, owned_pool_closed, std::memory_order_acq_rel)) - { + if (!live) { + if (owned_pool_lifetimes.compare_exchange_weak(live, owned_pool_closed, std::memory_order_acq_rel)) { break; } continue; } owned_pool_lifetimes.wait(live, std::memory_order_acquire); } - if (executor_) - { + if (executor_) { executor_->join(); } } void ensure_executor() { std::lock_guard lock(executor_mutex_); - if (!executor_) - { + if (!executor_) { executor_ = std::make_unique(1); } } @@ -264,8 +246,7 @@ namespace { } // namespace -class owned_pool_membership -{ +class owned_pool_membership { static void unregister() noexcept { owned_pool_lifetimes.fetch_sub(1, std::memory_order_acq_rel); owned_pool_lifetimes.notify_all(); @@ -274,29 +255,22 @@ class owned_pool_membership public: owned_pool_membership() { auto live = owned_pool_lifetimes.load(std::memory_order_acquire); - for (;;) - { - if (live & owned_pool_closed) - { + for (;;) { + if (live & owned_pool_closed) { throw std::logic_error("owned IO runtime has already shut down"); } - if (live == owned_pool_closed - 1) - { + if (live == owned_pool_closed - 1) { throw std::length_error("too many owned IO pools"); } - if (owned_pool_lifetimes.compare_exchange_weak(live, live + 1, std::memory_order_acq_rel)) - { + if (owned_pool_lifetimes.compare_exchange_weak(live, live + 1, std::memory_order_acq_rel)) { break; } } - try - { + try { // Fail worker initialization from pool construction, before its // callbacks can release their last owner on an IO worker. pool_cleanup().ensure_executor(); - } - catch (...) - { + } catch (...) { unregister(); throw; } @@ -310,10 +284,8 @@ class owned_pool_membership } }; -class io_pool : public iopool_base -{ - enum class phase - { +class io_pool : public iopool_base { + enum class phase { idle, running, draining @@ -330,8 +302,7 @@ class io_pool : public iopool_base void drain_until_stopped(const std::shared_ptr& target) { std::unique_lock lock(lifecycle_mutex_); - while (!target->context().stopped()) - { + while (!target->context().stopped()) { lock.unlock(); cancel(); lock.lock(); @@ -348,8 +319,7 @@ class io_pool : public iopool_base return std::apply(std::move(function), std::move(arguments)); }); std::lock_guard lock(lifecycle_mutex_); - if (phase_ != phase::running) - { + if (phase_ != phase::running) { std::promise rejected; result = rejected.get_future(); rejected.set_exception(std::make_exception_ptr(system_error(asio::error::operation_aborted))); @@ -357,16 +327,13 @@ class io_pool : public iopool_base } auto lane = get(index); ++lane->pending(); - try - { + try { asio::post(lane->executor(), [lane, task = std::move(task)]() mutable { task(); --lane->pending(); lane->pending().notify_all(); }); - } - catch (...) - { + } catch (...) { --lane->pending(); lane->pending().notify_all(); throw; @@ -376,13 +343,11 @@ class io_pool : public iopool_base public: explicit io_pool(std::size_t count = 0) { - if (!count) - { + if (!count) { count = (std::max)(1u, std::thread::hardware_concurrency()); } lanes_.reserve(count); - for (std::size_t index = 0; index < count; ++index) - { + for (std::size_t index = 0; index < count; ++index) { lanes_.push_back(std::make_shared(std::make_shared(1), false)); } } @@ -391,8 +356,7 @@ class io_pool : public iopool_base io_pool& operator=(const io_pool&) = delete; ~io_pool() override { - if (running_in_threads()) - { + if (running_in_threads()) { std::terminate(); } stop(); @@ -401,20 +365,16 @@ class io_pool : public iopool_base bool start() override { clear_last_error(); std::unique_lock lock(lifecycle_mutex_); - if (phase_ == phase::running) - { + if (phase_ == phase::running) { return true; } - if (phase_ != phase::idle || lanes_.empty()) - { + if (phase_ != phase::idle || lanes_.empty()) { set_last_error(asio::error::operation_aborted); return false; } std::latch ready(lanes_.size()); - try - { - for (auto& lane : lanes_) - { + try { + for (auto& lane : lanes_) { lane->context().restart(); work_.push_back(asio::make_work_guard(lane->context())); workers_.emplace_back([this, lane, &ready] { @@ -431,15 +391,11 @@ class io_pool : public iopool_base ready.wait(); phase_ = phase::running; return true; - } - catch (...) - { - for (auto& lane : lanes_) - { + } catch (...) { + for (auto& lane : lanes_) { lane->context().stop(); } - for (auto& worker : workers_) - { + for (auto& worker : workers_) { worker.join(); } workers_.clear(); @@ -449,41 +405,34 @@ class io_pool : public iopool_base } void stop() override { - if (running_in_threads()) - { + if (running_in_threads()) { cancel(); return; } { std::unique_lock lock(lifecycle_mutex_); lifecycle_changed_.wait(lock, [&] { return phase_ != phase::draining; }); - if (phase_ == phase::idle) - { + if (phase_ == phase::idle) { return; } phase_ = phase::draining; } cancel(); - for (auto& lane : lanes_) - { - while (lane->pending().load()) - { + for (auto& lane : lanes_) { + while (lane->pending().load()) { std::this_thread::yield(); } } // The listener remains runnable until its registered sessions release it. work_.front().reset(); drain_until_stopped(lanes_.front()); - for (auto& guard : work_) - { + for (auto& guard : work_) { guard.reset(); } - for (auto& lane : lanes_) - { + for (auto& lane : lanes_) { drain_until_stopped(lane); } - for (auto& worker : workers_) - { + for (auto& worker : workers_) { worker.join(); } { @@ -540,8 +489,7 @@ class io_pool : public iopool_base std::thread::native_handle_type get_thread_handle(std::size_t index) { std::lock_guard lock(lifecycle_mutex_); - if (workers_.empty()) - { + if (workers_.empty()) { throw std::logic_error("IO pool is stopped"); } return workers_[index % workers_.size()].native_handle(); @@ -552,10 +500,8 @@ class io_pool : public iopool_base } void cancel() { - for (auto& lane : lanes_) - { - if (!lane->context().stopped()) - { + for (auto& lane : lanes_) { + if (!lane->context().stopped()) { lane->cancel(); } } @@ -585,8 +531,7 @@ class io_pool : public iopool_base template void wait_for(std::chrono::duration duration) { - if (running_in_threads()) - { + if (running_in_threads()) { set_last_error(asio::error::operation_not_supported); return; } @@ -601,8 +546,7 @@ class io_pool : public iopool_base template int wait_signal(I... numbers) { - if (running_in_threads()) - { + if (running_in_threads()) { set_last_error(asio::error::operation_not_supported); return 0; } @@ -622,8 +566,7 @@ class io_pool : public iopool_base using iopool = io_pool; using default_iopool = io_pool; -class user_iopool final : public iopool_base -{ +class user_iopool final : public iopool_base { std::vector> lanes_; std::atomic active_{}; std::atomic cursor_{}; @@ -632,8 +575,7 @@ class user_iopool final : public iopool_base public: explicit user_iopool(std::vector> lanes) : lanes_(std::move(lanes)) { - if (lanes_.empty()) - { + if (lanes_.empty()) { throw std::invalid_argument("empty IO scheduler"); } } @@ -645,23 +587,18 @@ class user_iopool final : public iopool_base } void stop() override { - if (running_in_threads()) - { + if (running_in_threads()) { return; } std::lock_guard lock(stop_mutex_); - if (!active_.exchange(false)) - { + if (!active_.exchange(false)) { return; } - for (auto& lane : lanes_) - { - while (lane->pending().load()) - { + for (auto& lane : lanes_) { + while (lane->pending().load()) { std::this_thread::yield(); } - if (lane->context().stopped()) - { + if (lane->context().stopped()) { continue; } std::promise barrier; @@ -702,23 +639,18 @@ class user_iopool final : public iopool_base } }; -struct iopool_deleter -{ +struct iopool_deleter { void operator()(iopool_base* pool) const { - if (pool->owns_context() && pool->running_in_threads()) - { + if (pool->owns_context() && pool->running_in_threads()) { pool_cleanup().release(pool); - } - else - { + } else { delete pool; } } }; template -class iopool_cp -{ +class iopool_cp { std::mutex wait_mutex_; std::condition_variable wait_changed_; bool stop_requested_ = false; @@ -727,63 +659,41 @@ class iopool_cp template static std::vector> to_iots(T&& scheduler) { using type = std::remove_cvref_t; - if constexpr (std::derived_from) - { + if constexpr (std::derived_from) { std::vector> lanes; - for (std::size_t index = 0; index < scheduler.size(); ++index) - { + for (std::size_t index = 0; index < scheduler.size(); ++index) { lanes.push_back(scheduler.get(index)); } return lanes; - } - else if constexpr (std::same_as>) - { + } else if constexpr (std::same_as>) { return {std::make_shared(scheduler, true)}; - } - else if constexpr (std::same_as) - { + } else if constexpr (std::same_as) { return {std::make_shared(std::shared_ptr(scheduler, [](auto*) {}), true)}; - } - else if constexpr (std::same_as) - { + } else if constexpr (std::same_as) { static_assert(std::is_lvalue_reference_v, "external IO contexts must be lvalues"); return to_iots(std::addressof(scheduler)); - } - else if constexpr (std::same_as>) - { - if (!scheduler) - { + } else if constexpr (std::same_as>) { + if (!scheduler) { throw std::invalid_argument("null IO lane"); } return {scheduler}; - } - else if constexpr (std::same_as) - { - if (!scheduler) - { + } else if constexpr (std::same_as) { + if (!scheduler) { throw std::invalid_argument("null IO lane"); } return {std::shared_ptr(scheduler, [](auto*) {})}; - } - else if constexpr (std::same_as) - { + } else if constexpr (std::same_as) { static_assert(std::is_lvalue_reference_v, "external IO lanes must be lvalues"); return to_iots(std::addressof(scheduler)); - } - else if constexpr (std::ranges::range) - { + } else if constexpr (std::ranges::range) { std::vector> lanes; - for (auto&& item : scheduler) - { - for (auto& lane : to_iots(item)) - { + for (auto&& item : scheduler) { + for (auto& lane : to_iots(item)) { lanes.push_back(std::move(lane)); } } return lanes; - } - else - { + } else { static_assert(sizeof(T) == 0, "unsupported IO scheduler"); } } @@ -818,12 +728,10 @@ class iopool_cp std::lock_guard lock(wait_mutex_); stop_requested_ = false; } - if (!iopool_->start()) - { + if (!iopool_->start()) { return false; } - for (auto& lane : iots_) - { + for (auto& lane : iots_) { asio::dispatch(lane->executor(), [lane] { lane->init_thread_id(); }); } return true; @@ -841,12 +749,9 @@ class iopool_cp template void stop_before_destroy(Endpoint& endpoint) { // A completed close must not publish new callbacks against a dying object. - if (endpoint.is_stopped() && iopool_->running_in_threads()) - { + if (endpoint.is_stopped() && iopool_->running_in_threads()) { stop_iopool(); - } - else - { + } else { endpoint.stop(); } } @@ -859,16 +764,12 @@ class iopool_cp public: template explicit iopool_cp(T&& scheduler) { - if constexpr (std::integral>) - { + if constexpr (std::integral>) { iopool_.reset(new io_pool(static_cast(scheduler))); - } - else - { + } else { iopool_.reset(new user_iopool(to_iots(std::forward(scheduler)))); } - for (std::size_t index = 0; index < iopool_->size(); ++index) - { + for (std::size_t index = 0; index < iopool_->size(); ++index) { iots_.push_back(iopool_->get(index)); } } @@ -882,8 +783,7 @@ class iopool_cp } void wait_stop() { - if (iopool_->running_in_threads()) - { + if (iopool_->running_in_threads()) { set_last_error(asio::error::operation_not_supported); return; } @@ -894,8 +794,7 @@ class iopool_cp template void wait_for(std::chrono::duration duration) { - if (iopool_->running_in_threads()) - { + if (iopool_->running_in_threads()) { set_last_error(asio::error::operation_not_supported); return; } @@ -910,8 +809,7 @@ class iopool_cp template int wait_signal(I... numbers) { - if (iopool_->running_in_threads()) - { + if (iopool_->running_in_threads()) { set_last_error(asio::error::operation_not_supported); return 0; } diff --git a/include/arknet/base/listener.hpp b/include/arknet/base/listener.hpp index 954495e..9184397 100644 --- a/include/arknet/base/listener.hpp +++ b/include/arknet/base/listener.hpp @@ -15,8 +15,7 @@ namespace arknet::detail { -enum class event_type : std::int8_t -{ +enum class event_type : std::int8_t { recv, send, connect, @@ -38,14 +37,12 @@ constexpr std::string_view to_string(event_type event) { return index < names.size() ? names[index] : "none"; } -struct observer_base -{ +struct observer_base { virtual ~observer_base() = default; }; template -class observer_t : public observer_base -{ +class observer_t : public observer_base { public: using func_type = std::function; using args_type = std::tuple; @@ -85,8 +82,7 @@ class observer_t : public observer_base void operator()(Args... args) { // Retain the same callback instance while it replaces or clears its listener. auto current = fn_; - if (current && *current) - { + if (current && *current) { (*current)(std::forward(args)...); } } @@ -100,8 +96,7 @@ class observer_t : public observer_base std::shared_ptr fn_; }; -class listener_t -{ +class listener_t { public: template void bind(event_type event, Observer&& observer) { @@ -112,17 +107,13 @@ class listener_t void notify(event_type event, Args&&... args) { using Observer = observer_t< std::conditional_t, std::string_view>, std::string_view, Args>...>; - if (auto* observer = static_cast(find(event).get())) - { - try - { (*observer)(std::forward(args)...); } - catch (const std::exception& exception) - { + if (auto* observer = static_cast(find(event).get())) { + try { + (*observer)(std::forward(args)...); + } catch (const std::exception& exception) { ARKNET_LOG_ERROR("Callback {} failed: {}", to_string(event), exception.what()); ARKNET_ASSERT(false); - } - catch (...) - { + } catch (...) { ARKNET_LOG_ERROR("Callback {} failed", to_string(event)); ARKNET_ASSERT(false); } @@ -138,8 +129,7 @@ class listener_t } void clear() noexcept { - for (auto& observer : observers_) - { + for (auto& observer : observers_) { observer.reset(); } } diff --git a/include/arknet/base/manual_runtime.hpp b/include/arknet/base/manual_runtime.hpp index f58c0d0..2486d5c 100644 --- a/include/arknet/base/manual_runtime.hpp +++ b/include/arknet/base/manual_runtime.hpp @@ -13,10 +13,8 @@ namespace arknet { // A caller-driven execution context. It creates no worker to run user handlers: // poll() dispatches them on the constructing thread. Asio platform services may // still use internal helper threads, such as Windows timers or DNS resolution. -class manual_runtime -{ - struct state - { +class manual_runtime { + struct state { const std::thread::id owner = std::this_thread::get_id(); asio::io_context context{1}; asio::executor_work_guard work{context.get_executor()}; @@ -47,17 +45,14 @@ class manual_runtime // A handler may release the façade. Keep its execution context and // polling flag alive until this invocation has completely returned. auto runtime = state_; - if (std::this_thread::get_id() != runtime->owner) - { + if (std::this_thread::get_id() != runtime->owner) { throw std::logic_error("manual_runtime::poll requires its constructing thread"); } - if (runtime->polling) - { + if (runtime->polling) { throw std::logic_error("manual_runtime::poll does not permit reentrant calls"); } - struct poll_guard - { + struct poll_guard { bool& active; explicit poll_guard(bool& flag) noexcept @@ -71,10 +66,8 @@ class manual_runtime } guard(runtime->polling); std::size_t completed{}; - while (completed < max_handlers) - { - if (!runtime->context.poll_one()) - { + while (completed < max_handlers) { + if (!runtime->context.poll_one()) { break; } ++completed; diff --git a/include/arknet/base/server.hpp b/include/arknet/base/server.hpp index 1bd9789..59ba2a7 100644 --- a/include/arknet/base/server.hpp +++ b/include/arknet/base/server.hpp @@ -23,8 +23,7 @@ namespace arknet { -class server -{ +class server { public: static constexpr bool is_client() noexcept { return false; @@ -53,8 +52,7 @@ class server_impl_t , public user_data_cp , public user_timer_cp , public post_cp - , public condition_event_cp -{ + , public condition_event_cp { ARKNET_CLASS_FRIEND_DECLARE_BASE; public: @@ -101,15 +99,13 @@ class server_impl_t } void request_stop() { - if (!this->derived().is_stopped()) - { + if (!this->derived().is_stopped()) { this->post([this] { this->derived().stop(); }); } } bool wait_stopped() { - if (this->iopool().running_in_threads() || this->io_->context().get_executor().running_in_this_thread()) - { + if (this->iopool().running_in_threads() || this->io_->context().get_executor().running_in_this_thread()) { set_last_error(asio::error::operation_not_supported); return false; } @@ -126,8 +122,7 @@ class server_impl_t template requires std::is_integral_v Derived& async_send(Character* data, Count count) { - if (data) - { + if (data) { sessions_.for_each([&](auto& session) { session->async_send(data, count); }); } return this->derived(); @@ -239,8 +234,7 @@ class server_impl_t #endif private: - struct listen_metadata - { + struct listen_metadata { asio::ip::address address; unsigned short port = 0; error_code error = asio::error::bad_descriptor; diff --git a/include/arknet/base/session.hpp b/include/arknet/base/session.hpp index 5f9dc21..d3f81ab 100644 --- a/include/arknet/base/session.hpp +++ b/include/arknet/base/session.hpp @@ -32,8 +32,7 @@ namespace arknet { -class session -{ +class session { public: static constexpr bool is_client() noexcept { return false; @@ -71,8 +70,7 @@ class session_impl_t , public send_cp , public post_cp , public condition_event_cp - , public silence_timer_cp -{ + , public silence_timer_cp { ARKNET_CLASS_FRIEND_DECLARE_BASE; public: diff --git a/include/arknet/base/session_mgr.hpp b/include/arknet/base/session_mgr.hpp index eee739f..6b9ddda 100644 --- a/include/arknet/base/session_mgr.hpp +++ b/include/arknet/base/session_mgr.hpp @@ -15,8 +15,7 @@ namespace arknet::detail { template -class session_mgr_t -{ +class session_mgr_t { friend Session; ARKNET_CLASS_FRIEND_DECLARE_BASE; ARKNET_CLASS_FRIEND_DECLARE_TCP_BASE; @@ -39,16 +38,14 @@ class session_mgr_t template void emplace(std::shared_ptr session, Callback&& callback) { - if (!session) - { + if (!session) { return; } dispatch([this, session = std::move(session), callback = std::forward(callback)]() mutable { bool inserted = false; { arknet::unique_locker lock(mutex_); - if (state_.load() == state_t::started) - { + if (state_.load() == state_t::started) { inserted = sessions_.try_emplace(session->hash_key(), session).second; } } @@ -58,8 +55,7 @@ class session_mgr_t template void erase(std::shared_ptr session, Callback&& callback) { - if (!session) - { + if (!session) { return; } dispatch([this, session = std::move(session), callback = std::forward(callback)]() mutable { @@ -68,8 +64,7 @@ class session_mgr_t arknet::unique_locker lock(mutex_); const auto found = sessions_.find(session->hash_key()); erased = found != sessions_.end() && found->second == session; - if (erased) - { + if (erased) { sessions_.erase(found); } } @@ -89,8 +84,7 @@ class session_mgr_t template void for_each(Callable&& callback) { - for (auto& session : snapshot()) - { + for (auto& session : snapshot()) { callback(session); } } @@ -98,8 +92,7 @@ class session_mgr_t template void quick_for_each(Callable&& callback) { arknet::shared_locker lock(mutex_); - for (auto& [key, session] : sessions_) - { + for (auto& [key, session] : sessions_) { callback(session); } } @@ -112,10 +105,8 @@ class session_mgr_t template std::shared_ptr find_if(Predicate&& predicate) { - for (auto& session : snapshot()) - { - if (predicate(session)) - { + for (auto& session : snapshot()) { + if (predicate(session)) { return session; } } @@ -153,8 +144,7 @@ class session_mgr_t std::vector> result; arknet::shared_locker lock(mutex_); result.reserve(sessions_.size()); - for (const auto& [key, session] : sessions_) - { + for (const auto& [key, session] : sessions_) { result.push_back(session); } return result; diff --git a/include/arknet/base/timer.hpp b/include/arknet/base/timer.hpp index c415fe5..bdb90bd 100644 --- a/include/arknet/base/timer.hpp +++ b/include/arknet/base/timer.hpp @@ -14,8 +14,7 @@ namespace arknet::detail { -struct template_args_timer -{ +struct template_args_timer { static constexpr std::size_t allocator_storage_size = 256; }; @@ -26,8 +25,7 @@ class timer_impl_t , public thread_id_cp , public user_timer_cp , public post_cp - , public condition_event_cp -{ + , public condition_event_cp { ARKNET_CLASS_FRIEND_DECLARE_BASE; public: @@ -55,8 +53,7 @@ class timer_impl_t } bool start() { - if (!this->start_iopool()) - { + if (!this->start_iopool()) { return false; } accepting_.store(true, std::memory_order_release); @@ -68,23 +65,20 @@ class timer_impl_t } void stop() { - if (!io_) - { + if (!io_) { return; } accepting_.store(false, std::memory_order_release); this->_set_post_acceptance(false); this->_set_condition_acceptance(false); - if (!this->is_iopool_stopped()) - { + if (!this->is_iopool_stopped()) { io_->unregobj(&this->derived()); this->_dispatch_stop_all_timers(); this->_dispatch_stop_all_timed_events(); this->notify_all_condition_events(); this->stop_iopool(); } - if (io_->context().get_executor().running_in_this_thread() || io_->context().stopped()) - { + if (io_->context().get_executor().running_in_this_thread() || io_->context().stopped()) { return; } // A strand barrier can run before canceled waits; drain their actual completions. @@ -130,8 +124,7 @@ class timer_impl_t namespace arknet { -class timer : public detail::timer_impl_t -{ +class timer : public detail::timer_impl_t { public: using detail::timer_impl_t::timer_impl_t; }; diff --git a/include/arknet/fec/transport.hpp b/include/arknet/fec/transport.hpp index 943cde4..0a5db91 100644 --- a/include/arknet/fec/transport.hpp +++ b/include/arknet/fec/transport.hpp @@ -20,15 +20,13 @@ namespace arknet::fec { -enum class policy -{ +enum class policy { disabled, fixed, adaptive }; -enum class change_reason -{ +enum class change_reason { configured, loss, healthy, @@ -36,8 +34,7 @@ enum class change_reason }; inline const char* reason_name(change_reason reason) noexcept { - switch (reason) - { + switch (reason) { case change_reason::loss: return "loss"; case change_reason::healthy: @@ -49,8 +46,7 @@ inline const char* reason_name(change_reason reason) noexcept { } } -struct options -{ +struct options { policy mode = policy::disabled; std::uint8_t data_shards = 8; std::uint8_t parity_shards = 2; @@ -89,15 +85,13 @@ struct options max_buffer_bytes < 4096 || max_buffer_bytes > 64 * 1024 * 1024 || max_redundancy_percent > 100 || !sample_window_ms || sample_window_ms > 60000 || cooldown_ms > 60000 || !min_samples || !enable_loss_percent || enable_loss_percent > 100 || disable_loss_percent >= enable_loss_percent || - !stable_windows || !overload_queue_percent || overload_queue_percent > 100) - { + !stable_windows || !overload_queue_percent || overload_queue_percent > 100) { throw std::invalid_argument("invalid FEC options"); } } }; -struct statistics -{ +struct statistics { std::uint64_t data_packets_tx{}, parity_packets_tx{}, wire_bytes_tx{}, parity_bytes_tx{}, wire_bytes_rx{}; std::uint64_t groups_rx{}, data_shards_rx{}, missing_shards{}, recovered_shards{}, duplicates{}; std::uint64_t invalid_packets{}, expired_groups{}, evicted_groups{}, policy_changes{}; @@ -110,16 +104,14 @@ namespace detail { inline std::uint64_t read(std::string_view bytes, std::size_t offset, unsigned count) noexcept { std::uint64_t value{}; - for (unsigned i = 0; i < count; ++i) - { + for (unsigned i = 0; i < count; ++i) { value |= std::uint64_t(static_cast(bytes[offset + i])) << (8 * i); } return value; } inline void write(std::string& bytes, std::size_t offset, std::uint64_t value, unsigned count) noexcept { - for (unsigned i = 0; i < count; ++i) - { + for (unsigned i = 0; i < count; ++i) { bytes[offset + i] = static_cast(value >> (8 * i)); } } @@ -131,11 +123,9 @@ inline std::int32_t difference(std::uint32_t a, std::uint32_t b) noexcept { inline std::uint32_t checksum(std::string_view bytes) noexcept { static constexpr auto table = [] { std::array values{}; - for (std::uint32_t i = 0; i < 256; ++i) - { + for (std::uint32_t i = 0; i < 256; ++i) { auto value = i; - for (unsigned bit = 0; bit < 8; ++bit) - { + for (unsigned bit = 0; bit < 8; ++bit) { value = (value >> 1) ^ ((value & 1) ? 0xedb88320U : 0U); } values[i] = value; @@ -143,8 +133,7 @@ inline std::uint32_t checksum(std::string_view bytes) noexcept { return values; }(); std::uint32_t value = 0xffffffffU; - for (unsigned char c : bytes) - { + for (unsigned char c : bytes) { value = table[(value ^ c) & 255] ^ (value >> 8); } return value ^ 0xffffffffU; @@ -152,8 +141,7 @@ inline std::uint32_t checksum(std::string_view bytes) noexcept { } // namespace detail // Serialize calls. CRC detects corruption; it does not authenticate a peer. -class transport -{ +class transport { public: static constexpr std::size_t header_size = 20, payload_prefix = 6, overhead = header_size + payload_prefix; using output = std::function; @@ -166,77 +154,62 @@ class transport , emit_(std::move(emit)) , deliver_(std::move(deliver)) { options_.validate(); - if (mtu <= overhead + 24 || mtu > 65507 || !emit_ || !deliver_) - { + if (mtu <= overhead + 24 || mtu > 65507 || !emit_ || !deliver_) { throw std::invalid_argument("invalid FEC transport"); } stats_.current_parity_shards = options_.mode == policy::fixed ? options_.parity_shards : 0; } bool send(std::string_view bytes, std::uint32_t now, bool control = false) { - if (stopped_ || bytes.size() < 24 || bytes.size() > mtu_ - overhead) - { + if (stopped_ || bytes.size() < 24 || bytes.size() > mtu_ - overhead) { return false; } - if (control || !stats_.current_parity_shards) - { + if (control || !stats_.current_parity_shards) { emit_group({std::string(bytes)}, 0, control ? 3 : 0); return !stopped_; } - if (retained_bytes_ + pending_bytes_ + bytes.size() + payload_prefix > options_.max_buffer_bytes) - { + if (retained_bytes_ + pending_bytes_ + bytes.size() + payload_prefix > options_.max_buffer_bytes) { flush(); - if (stopped_) - { + if (stopped_) { return false; } emit_group({std::string(bytes)}, 0); return !stopped_; } - if (pending_.empty()) - { + if (pending_.empty()) { pending_since_ = now; } pending_.emplace_back(bytes); pending_bytes_ += bytes.size() + payload_prefix; - if (pending_.size() >= options_.data_shards) - { + if (pending_.size() >= options_.data_shards) { flush(); } return !stopped_; } void update(std::uint32_t now) { - if (stopped_) - { + if (stopped_) { return; } if (!pending_.empty() && - detail::difference(now, pending_since_) >= static_cast(options_.group_wait_ms)) - { + detail::difference(now, pending_since_) >= static_cast(options_.group_wait_ms)) { flush(); } - if (stopped_) - { + if (stopped_) { return; } - for (auto it = groups_.begin(); it != groups_.end();) - { - if (detail::difference(now, it->second.created) >= static_cast(options_.group_lifetime_ms)) - { + for (auto it = groups_.begin(); it != groups_.end();) { + if (detail::difference(now, it->second.created) >= static_cast(options_.group_lifetime_ms)) { finalize(it->second); retire(it->first); retained_bytes_ -= group_cost + it->second.bytes; it = groups_.erase(it); - } - else - { + } else { ++it; } } if (!feedback_started_ || - detail::difference(now, feedback_at_) >= static_cast(options_.sample_window_ms)) - { + detail::difference(now, feedback_at_) >= static_cast(options_.sample_window_ms)) { feedback_started_ = true; feedback_at_ = now; auto bytes = header(2, 0, 0, 0, 24, ++feedback_sequence_); @@ -250,20 +223,17 @@ class transport void adapt(std::uint32_t now, std::uint64_t transmissions, std::uint64_t retransmissions, std::uint32_t rtt_ms, std::size_t queued_bytes, std::size_t queue_limit) { - if (stopped_ || options_.mode != policy::adaptive) - { + if (stopped_ || options_.mode != policy::adaptive) { return; } - if (!sampling_) - { + if (!sampling_) { sampling_ = true; sample_at_ = now; last_transmissions_ = transmissions; last_retransmissions_ = retransmissions; return; } - if (detail::difference(now, sample_at_) < static_cast(options_.sample_window_ms)) - { + if (detail::difference(now, sample_at_) < static_cast(options_.sample_window_ms)) { return; } sample_at_ = now; @@ -275,23 +245,20 @@ class transport last_retransmissions_ = retransmissions; last_peer_data_ = peer_data_; last_peer_missing_ = peer_missing_; - if (rtt_ms && (!minimum_rtt_ || rtt_ms < minimum_rtt_)) - { + if (rtt_ms && (!minimum_rtt_ || rtt_ms < minimum_rtt_)) { minimum_rtt_ = rtt_ms; } const bool overloaded = (queue_limit && static_cast(queued_bytes) / queue_limit * 100 >= options_.overload_queue_percent) || (minimum_rtt_ && rtt_ms > std::uint64_t(minimum_rtt_) * 2 && queued_bytes > previous_queued_bytes_); previous_queued_bytes_ = queued_bytes; - if (overloaded) - { + if (overloaded) { change(0, change_reason::overload, now); stable_ = 0; return; } if ((local_samples < options_.min_samples && peer_samples < options_.min_samples) || - (changed_ && detail::difference(now, changed_at_) < static_cast(options_.cooldown_ms))) - { + (changed_ && detail::difference(now, changed_at_) < static_cast(options_.cooldown_ms))) { return; } const auto ratio = [&](std::uint64_t lost, std::uint64_t samples) { @@ -300,33 +267,25 @@ class transport : std::min(100.0, 100.0 * static_cast(lost) / static_cast(samples)); }; const auto percent = std::max(ratio(local_lost, local_samples), ratio(peer_lost, peer_samples)); - if (percent >= options_.enable_loss_percent) - { + if (percent >= options_.enable_loss_percent) { stable_ = 0; const auto desired = static_cast(std::clamp(std::ceil(percent * options_.data_shards / 50.0), 1.0, static_cast(options_.max_parity_shards))); change(std::max(stats_.current_parity_shards, desired), change_reason::loss, now); - } - else if (percent <= options_.disable_loss_percent) - { - if (++stable_ >= options_.stable_windows) - { - if (stats_.current_parity_shards) - { + } else if (percent <= options_.disable_loss_percent) { + if (++stable_ >= options_.stable_windows) { + if (stats_.current_parity_shards) { change(stats_.current_parity_shards - 1, change_reason::healthy, now); } stable_ = 0; } - } - else - { + } else { stable_ = 0; } } bool input(std::string_view bytes, std::uint32_t now) { - if (stopped_) - { + if (stopped_) { return false; } stats_.wire_bytes_rx += bytes.size(); @@ -335,8 +294,7 @@ class transport return false; }; if (bytes.size() < header_size || bytes.size() > mtu_ || bytes.substr(0, 4) != "AFEC" || - static_cast(bytes[4]) != 1 || bytes[9] != 0 || detail::read(bytes, 12, 4) != conversation_) - { + static_cast(bytes[4]) != 1 || bytes[9] != 0 || detail::read(bytes, 12, 4) != conversation_) { return invalid(); } const auto kind = static_cast(bytes[5]); @@ -344,18 +302,15 @@ class transport index = static_cast(bytes[8]); const auto size = static_cast(detail::read(bytes, 10, 2)); const auto id = static_cast(detail::read(bytes, 16, 4)); - if (kind == 2) - { - if (k || p || index || size != 24 || bytes.size() != header_size + 24) - { + if (kind == 2) { + if (k || p || index || size != 24 || bytes.size() != header_size + 24) { return invalid(); } const auto data = detail::read(bytes, header_size, 8), missing = detail::read(bytes, header_size + 8, 8), recovered = detail::read(bytes, header_size + 16, 8); if (missing > data || recovered > missing || data > stats_.data_packets_tx || (have_peer_ && (detail::difference(id, peer_sequence_) <= 0 || data < peer_data_ || - missing < peer_missing_ || recovered < peer_recovered_))) - { + missing < peer_missing_ || recovered < peer_recovered_))) { return false; } have_peer_ = true; @@ -367,62 +322,49 @@ class transport } if (kind > 3 || !k || k > 32 || p > 16 || index >= k + p || size < payload_prefix + 24 || size > mtu_ - header_size || ((kind == 0 || kind == 3) && index >= k) || (kind == 1 && index < k) || - (kind == 3 && (k != 1 || p || index)) || (!p && (k != 1 || index || kind == 1))) - { + (kind == 3 && (k != 1 || p || index)) || (!p && (k != 1 || index || kind == 1))) { return invalid(); } const auto payload = bytes.substr(header_size); - if (kind == 1 && payload.size() != size) - { + if (kind == 1 && payload.size() != size) { return invalid(); } std::string_view original; - if (kind != 1) - { - if (payload.size() < payload_prefix || payload.size() > size) - { + if (kind != 1) { + if (payload.size() < payload_prefix || payload.size() > size) { return invalid(); } original = unpack(payload); - if (original.empty() || payload.size() != payload_prefix + original.size()) - { + if (original.empty() || payload.size() != payload_prefix + original.size()) { return invalid(); } } auto it = groups_.find(id); - if (it != groups_.end() && (it->second.k != k || it->second.p != p || it->second.size != size)) - { + if (it != groups_.end() && (it->second.k != k || it->second.p != p || it->second.size != size)) { return invalid(); } - if (have_highest_ && detail::difference(id, highest_) < -4095) - { + if (have_highest_ && detail::difference(id, highest_) < -4095) { return invalid(); } - if (retired_valid_[id % retired_.size()] && retired_[id % retired_.size()] == id) - { + if (retired_valid_[id % retired_.size()] && retired_[id % retired_.size()] == id) { ++stats_.duplicates; return false; } - if (!have_highest_ || detail::difference(id, highest_) > 0) - { + if (!have_highest_ || detail::difference(id, highest_) > 0) { highest_ = id; have_highest_ = true; } - if (kind == 3 || !p) - { + if (kind == 3 || !p) { retire(id); - if (kind == 0) - { + if (kind == 0) { ++stats_.groups_rx; ++stats_.data_shards_rx; } return deliver_packet(original); } - if (it == groups_.end()) - { + if (it == groups_.end()) { if (groups_.size() >= options_.max_groups || - retained_bytes_ + pending_bytes_ + group_cost + size > options_.max_buffer_bytes) - { + retained_bytes_ + pending_bytes_ + group_cost + size > options_.max_buffer_bytes) { ++stats_.evicted_groups; return kind != 1 && deliver_packet(original); } @@ -436,13 +378,11 @@ class transport ++stats_.groups_rx; } auto& value = it->second; - if (value.present[index]) - { + if (value.present[index]) { ++stats_.duplicates; return false; } - if (retained_bytes_ + pending_bytes_ + size > options_.max_buffer_bytes) - { + if (retained_bytes_ + pending_bytes_ + size > options_.max_buffer_bytes) { retained_bytes_ -= group_cost + value.bytes; groups_.erase(it); ++stats_.evicted_groups; @@ -452,8 +392,7 @@ class transport value.present[index] = true; ++value.count; std::vector deliveries; - if (kind != 1 && !value.delivered[index]) - { + if (kind != 1 && !value.delivered[index]) { deliveries.emplace_back(original); value.delivered[index] = true; } @@ -461,54 +400,42 @@ class transport std::copy(payload.begin(), payload.end(), value.shards[index].begin()); value.bytes += size; retained_bytes_ += size; - if (value.count >= k) - { + if (value.count >= k) { std::map shares; - for (unsigned i = 0; i < k + p; ++i) - { - if (value.present[i]) - { + for (unsigned i = 0; i < k + p; ++i) { + if (value.present[i]) { shares.emplace(i, reinterpret_cast(value.shards[i].data())); } } std::vector> recovered; - try - { - if (std::count(value.delivered.begin(), value.delivered.begin() + k, true) != k) - { + try { + if (std::count(value.delivered.begin(), value.delivered.begin() + k, true) != k) { arknet::detail::fecpp::fec_code code(k, k + p); code.decode( shares, size, [&](std::size_t i, std::size_t, const std::uint8_t* data, std::size_t count) { - if (value.delivered[i]) - { + if (value.delivered[i]) { return; } const auto message = unpack({reinterpret_cast(data), count}); - if (message.empty()) - { + if (message.empty()) { throw std::invalid_argument("corrupt recovered FEC shard"); } recovered.emplace_back(static_cast(i), message); }); } - } - catch (const std::exception&) - { + } catch (const std::exception&) { retained_bytes_ -= group_cost + value.bytes; groups_.erase(it); ++stats_.invalid_packets; guard.release(); - for (const auto& message : deliveries) - { - if (!deliver_packet(message)) - { + for (const auto& message : deliveries) { + if (!deliver_packet(message)) { return false; } } return !deliveries.empty(); } - for (const auto& [i, message] : recovered) - { + for (const auto& [i, message] : recovered) { deliveries.push_back(message); value.delivered[i] = true; ++stats_.recovered_shards; @@ -522,10 +449,8 @@ class transport } guard.release(); // All group references are released before callbacks can stop the channel. - for (const auto& message : deliveries) - { - if (!deliver_packet(message)) - { + for (const auto& message : deliveries) { + if (!deliver_packet(message)) { return false; } } @@ -552,8 +477,7 @@ class transport return !stopped_ && deliver_(bytes) && !stopped_; } - struct group - { + struct group { unsigned k{}, p{}, count{}, recovered{}; std::size_t size{}, bytes{}; std::uint32_t created{}; @@ -564,8 +488,7 @@ class transport }; // Drop partially updated shards during unwinding; subsequent input may retry the group. - struct input_guard - { + struct input_guard { transport& owner; std::uint32_t id; int exceptions = std::uncaught_exceptions(); @@ -576,11 +499,9 @@ class transport } ~input_guard() { - if (active && std::uncaught_exceptions() > exceptions) - { + if (active && std::uncaught_exceptions() > exceptions) { const auto it = owner.groups_.find(id); - if (it != owner.groups_.end()) - { + if (it != owner.groups_.end()) { owner.retained_bytes_ -= group_cost + it->second.bytes; owner.groups_.erase(it); } @@ -591,13 +512,11 @@ class transport static constexpr std::size_t group_cost = sizeof(group) + 64; static std::string_view unpack(std::string_view bytes) { - if (bytes.size() < payload_prefix) - { + if (bytes.size() < payload_prefix) { return {}; } const auto length = static_cast(detail::read(bytes, 0, 2)); - if (length < 24 || length > bytes.size() - payload_prefix) - { + if (length < 24 || length > bytes.size() - payload_prefix) { return {}; } const auto message = bytes.substr(payload_prefix, length); @@ -620,17 +539,14 @@ class transport } void emit_wire(std::string_view bytes, bool data, bool parity) { - if (stopped_) - { + if (stopped_) { return; } stats_.wire_bytes_tx += bytes.size(); - if (data) - { + if (data) { ++stats_.data_packets_tx; } - if (parity) - { + if (parity) { ++stats_.parity_packets_tx; stats_.parity_bytes_tx += bytes.size(); } @@ -638,28 +554,22 @@ class transport } void emit_group(const std::vector& messages, unsigned parity, unsigned kind = 0) { - if (stopped_ || messages.empty()) - { + if (stopped_ || messages.empty()) { return; } std::size_t size{}, wire_size{}; - for (const auto& bytes : messages) - { + for (const auto& bytes : messages) { size = std::max(size, bytes.size() + payload_prefix); wire_size += overhead + bytes.size(); } parity = std::min(parity, wire_size * options_.max_redundancy_percent / 100 / (header_size + size)); - if (messages.size() < options_.min_data_shards) - { + if (messages.size() < options_.min_data_shards) { parity = 0; } - if (!parity) - { - for (const auto& message : messages) - { - if (stopped_) - { + if (!parity) { + for (const auto& message : messages) { + if (stopped_) { break; } std::string payload(payload_prefix, '\0'); @@ -673,10 +583,8 @@ class transport const auto k = messages.size(); const auto id = next_group_++; std::vector data(k * size); - for (std::size_t i = 0; i < k; ++i) - { - if (stopped_) - { + for (std::size_t i = 0; i < k; ++i) { + if (stopped_) { return; } std::string payload(payload_prefix, '\0'); @@ -687,15 +595,13 @@ class transport auto bytes = header(kind, k, parity, i, size, id) + payload; emit_wire(bytes, kind == 0, false); } - if (stopped_) - { + if (stopped_) { return; } arknet::detail::fecpp::fec_code code(k, k + parity); code.encode( data.data(), data.size(), [&](std::size_t i, std::size_t, const std::uint8_t* data, std::size_t count) { - if (i < k) - { + if (i < k) { return; } auto bytes = header(1, k, parity, i, size, id); @@ -705,8 +611,7 @@ class transport } void flush() { - if (stopped_ || pending_.empty()) - { + if (stopped_ || pending_.empty()) { return; } auto messages = std::move(pending_); @@ -717,16 +622,13 @@ class transport void finalize(const group& value) { stats_.data_shards_rx += value.k; - for (unsigned i = 0; i < value.k; ++i) - { - if (!value.present[i]) - { + for (unsigned i = 0; i < value.k; ++i) { + if (!value.present[i]) { ++stats_.missing_shards; } } finalized_recovered_ += value.recovered; - if (!value.decoded) - { + if (!value.decoded) { ++stats_.expired_groups; } } @@ -737,13 +639,11 @@ class transport } void change(std::uint8_t parity, change_reason reason, std::uint32_t now) { - if (parity == stats_.current_parity_shards) - { + if (parity == stats_.current_parity_shards) { return; } flush(); - if (stopped_) - { + if (stopped_) { return; } stats_.current_parity_shards = parity; diff --git a/include/arknet/http/http_client.hpp b/include/arknet/http/http_client.hpp index a12cf95..22fb5c3 100644 --- a/include/arknet/http/http_client.hpp +++ b/include/arknet/http/http_client.hpp @@ -9,8 +9,7 @@ template using http_client_impl_t = detail::http_endpoint_impl_t; template -class http_client_t : public http_client_impl_t -{ +class http_client_t : public http_client_impl_t { public: using http_client_impl_t::http_client_impl_t; using http_client_impl_t::start; @@ -27,8 +26,7 @@ class http_client_t : public http_client_impl_t } }; -class http_client : public http_client_t -{ +class http_client : public http_client_t { public: using http_client_t::http_client_t; }; diff --git a/include/arknet/http/http_server.hpp b/include/arknet/http/http_server.hpp index 64f0633..e3f6211 100644 --- a/include/arknet/http/http_server.hpp +++ b/include/arknet/http/http_server.hpp @@ -9,8 +9,7 @@ template using http_server_impl_t = detail::http_server_impl_t; template -class http_server_t : public http_server_impl_t, Session> -{ +class http_server_t : public http_server_impl_t, Session> { public: using http_server_impl_t, Session>::http_server_impl_t; }; diff --git a/include/arknet/http/http_session.hpp b/include/arknet/http/http_session.hpp index 9148034..f139936 100644 --- a/include/arknet/http/http_session.hpp +++ b/include/arknet/http/http_session.hpp @@ -9,14 +9,12 @@ template using http_session_impl_t = detail::http_endpoint_impl_t; template -class http_session_t : public http_session_impl_t -{ +class http_session_t : public http_session_impl_t { public: using http_session_impl_t::http_session_impl_t; }; -class http_session : public http_session_t -{ +class http_session : public http_session_t { public: using http_session_t::http_session_t; }; diff --git a/include/arknet/http/https_client.hpp b/include/arknet/http/https_client.hpp index 310a38b..b72aa5f 100644 --- a/include/arknet/http/https_client.hpp +++ b/include/arknet/http/https_client.hpp @@ -12,8 +12,7 @@ template using https_client_impl_t = detail::http_endpoint_impl_t; template -class https_client_t : public https_client_impl_t -{ +class https_client_t : public https_client_impl_t { public: using https_client_impl_t::https_client_impl_t; using https_client_impl_t::start; @@ -30,8 +29,7 @@ class https_client_t : public https_client_impl_t } }; -class https_client : public https_client_t -{ +class https_client : public https_client_t { public: using https_client_t::https_client_t; }; diff --git a/include/arknet/http/https_server.hpp b/include/arknet/http/https_server.hpp index 54c86a4..1651ca9 100644 --- a/include/arknet/http/https_server.hpp +++ b/include/arknet/http/https_server.hpp @@ -12,8 +12,7 @@ template using https_server_impl_t = detail::http_server_impl_t; template -class https_server_t : public https_server_impl_t, Session> -{ +class https_server_t : public https_server_impl_t, Session> { public: using https_server_impl_t, Session>::https_server_impl_t; }; diff --git a/include/arknet/http/https_session.hpp b/include/arknet/http/https_session.hpp index b72374a..a5345fb 100644 --- a/include/arknet/http/https_session.hpp +++ b/include/arknet/http/https_session.hpp @@ -12,14 +12,12 @@ template using https_session_impl_t = detail::http_endpoint_impl_t; template -class https_session_t : public https_session_impl_t -{ +class https_session_t : public https_session_impl_t { public: using https_session_impl_t::https_session_impl_t; }; -class https_session : public https_session_t -{ +class https_session : public https_session_t { public: using https_session_t::https_session_t; }; diff --git a/include/arknet/http/impl/http_endpoint.hpp b/include/arknet/http/impl/http_endpoint.hpp index 920a147..fc896b2 100644 --- a/include/arknet/http/impl/http_endpoint.hpp +++ b/include/arknet/http/impl/http_endpoint.hpp @@ -16,8 +16,7 @@ namespace arknet::detail { template -class http_limits_cp -{ +class http_limits_cp { public: Derived& set_http_body_limit(std::uint64_t bytes) noexcept { body_limit_ = bytes; @@ -42,8 +41,7 @@ class http_limits_cp std::atomic header_limit_{8192}; }; -struct http_send_data -{ +struct http_send_data { std::string wire; http::verb method = http::verb::unknown; bool close = false; @@ -56,8 +54,7 @@ struct http_send_data template class Transport, class Args> class http_endpoint_impl_t : public Transport - , public http_limits_cp -{ + , public http_limits_cp { ARKNET_CLASS_FRIEND_DECLARE_BASE; ARKNET_CLASS_FRIEND_DECLARE_TCP_BASE; ARKNET_CLASS_FRIEND_DECLARE_TCP_CLIENT; @@ -78,21 +75,17 @@ class http_endpoint_impl_t using recv_message_type = http::message; ~http_endpoint_impl_t() { - if constexpr (Args::is_client) - { + if constexpr (Args::is_client) { this->stop_before_destroy(*this); } } template Derived& bind_recv(F&& fun, C&&... obj) { - if constexpr (request_side) - { + if constexpr (request_side) { this->listener_.bind(event_type::recv, observer_t&, recv_message_type&>( std::forward(fun), std::forward(obj)...)); - } - else - { + } else { this->listener_.bind( event_type::recv, observer_t(std::forward(fun), std::forward(obj)...)); } @@ -115,29 +108,21 @@ class http_endpoint_impl_t "HTTP sends support string_body and empty_body"); std::size_t bytes = 32; auto add = [&](std::size_t count) { - if (count > (std::numeric_limits::max)() - bytes) - { + if (count > (std::numeric_limits::max)() - bytes) { bytes = (std::numeric_limits::max)(); - } - else - { + } else { bytes += count; } }; - if constexpr (std::is_same_v) - { + if constexpr (std::is_same_v) { add(message.body().size()); } - if constexpr (Request) - { + if constexpr (Request) { add(message.target().size()); - } - else - { + } else { add(message.reason().size()); } - for (const auto& field : message) - { + for (const auto& field : message) { add(field.name_string().size()); add(field.value().size()); add(4); @@ -149,28 +134,22 @@ class http_endpoint_impl_t http_send_data _data_persistence(http::message message) { auto owned = detail::call_data_filter_before_send(this->derived(), std::move(message)); http_send_data data; - if constexpr (Request) - { + if constexpr (Request) { data.method = owned.method(); - } - else - { + } else { data.close = owned.need_eof(); } http::serializer serializer(owned); error_code ec; - while (!serializer.is_done()) - { + while (!serializer.is_done()) { std::size_t bytes = 0; serializer.next(ec, [&](error_code&, const auto& buffers) { - for (auto buffer : beast::buffers_range_ref(buffers)) - { + for (auto buffer : beast::buffers_range_ref(buffers)) { data.wire.append(static_cast(buffer.data()), buffer.size()); bytes += buffer.size(); } }); - if (ec) - { + if (ec) { throw system_error(ec); } serializer.consume(bytes); @@ -180,17 +159,14 @@ class http_endpoint_impl_t template bool _do_send(http_send_data& data, Callback&& callback) { - if (closing_) - { + if (closing_) { set_last_error(asio::error::operation_aborted); callback(get_last_error(), 0); return false; } - if constexpr (Args::is_client) - { + if constexpr (Args::is_client) { // A HEAD parser must skip the body before any response bytes are parsed. - if (methods_.empty() && parser_ && !parser_->got_some()) - { + if (methods_.empty() && parser_ && !parser_->got_some()) { parser_->skip(data.method == http::verb::head); } methods_.push_back(data.method); @@ -201,8 +177,7 @@ class http_endpoint_impl_t const error_code& ec, std::size_t bytes) mutable { // Schedule shutdown before releasing the queue guard to block // later writes. - if (close && !ec) - { + if (close && !ec) { this->derived()._do_disconnect(asio::error::eof, this->selfptr()); } fn(ec, bytes); @@ -211,15 +186,13 @@ class http_endpoint_impl_t template void _post_recv(std::shared_ptr self, std::shared_ptr> ecs) { - if (!this->is_started() || closing_) - { + if (!this->is_started() || closing_) { return; } parser_.emplace(); parser_->body_limit(this->get_http_body_limit()); parser_->header_limit(this->get_http_header_limit()); - if constexpr (Args::is_client) - { + if constexpr (Args::is_client) { parser_->skip(!methods_.empty() && methods_.front() == http::verb::head); } _read_http(std::move(self), std::move(ecs), true); @@ -240,8 +213,7 @@ class http_endpoint_impl_t #ifndef NDEBUG this->post_recv_counter_--; #endif - if (ec || !this->is_started()) - { + if (ec || !this->is_started()) { if (ec == http::error::end_of_stream) ec = asio::error::eof; if (!ec) @@ -249,23 +221,18 @@ class http_endpoint_impl_t super::_handle_recv(ec, 0, std::move(self), std::move(ecs)); return; } - if constexpr (request_side) - { - if (header) - { + if constexpr (request_side) { + if (header) { auto& request = parser_->get(); if (request.version() == 11 && - (request.count(http::field::host) != 1 || request[http::field::host].empty())) - { + (request.count(http::field::host) != 1 || request[http::field::host].empty())) { _reject_http(http::status::bad_request); return; } const auto expect = request[http::field::expect]; - if (!expect.empty()) - { + if (!expect.empty()) { if (request.version() != 11 || request.count(http::field::expect) != 1 || - !beast::iequals(expect, "100-continue")) - { + !beast::iequals(expect, "100-continue")) { _reject_http(http::status::expectation_failed); return; } @@ -275,8 +242,7 @@ class http_endpoint_impl_t } } } - if (!parser_->is_done()) - { + if (!parser_->is_done()) { _read_http(std::move(self), std::move(ecs), false); return; } @@ -284,37 +250,29 @@ class http_endpoint_impl_t clear_last_error(); auto message = parser_->release(); const bool keep_alive = message.keep_alive(); - if constexpr (Args::is_client) - { + if constexpr (Args::is_client) { if (message.result_int() >= 200 && !methods_.empty()) methods_.pop_front(); - if (message.result_int() == 101) - { + if (message.result_int() == 101) { super::_handle_recv(asio::error::operation_not_supported, 0, std::move(self), std::move(ecs)); return; } this->listener_.notify(event_type::recv, message); - if (!keep_alive && message.result_int() >= 200) - { + if (!keep_alive && message.result_int() >= 200) { super::_handle_recv(asio::error::eof, 0, std::move(self), std::move(ecs)); return; } - } - else + } else this->listener_.notify(event_type::recv, self, message); - if constexpr (request_side) - { + if constexpr (request_side) { if (!keep_alive) return; } _post_recv(std::move(self), std::move(ecs)); })); - if (header) - { + if (header) { http::async_read_header(this->stream(), this->buffer().base(), *parser_, std::move(completion)); - } - else - { + } else { http::async_read(this->stream(), this->buffer().base(), *parser_, std::move(completion)); } } @@ -334,8 +292,7 @@ class http_endpoint_impl_t template class Transport> class http_server_impl_t : public Transport - , public http_limits_cp -{ + , public http_limits_cp { ARKNET_CLASS_FRIEND_DECLARE_BASE; ARKNET_CLASS_FRIEND_DECLARE_TCP_SERVER; using super = Transport; diff --git a/include/arknet/http/impl/websocket_endpoint.hpp b/include/arknet/http/impl/websocket_endpoint.hpp index f426556..fc2569a 100644 --- a/include/arknet/http/impl/websocket_endpoint.hpp +++ b/include/arknet/http/impl/websocket_endpoint.hpp @@ -12,8 +12,7 @@ template class websocket_endpoint_impl : public Transport , public ws_stream_cp - , public ws_send_op -{ + , public ws_send_op { static constexpr bool secure = std::is_base_of_v; ARKNET_CLASS_FRIEND_DECLARE_BASE; ARKNET_CLASS_FRIEND_DECLARE_TCP_BASE; @@ -36,8 +35,7 @@ class websocket_endpoint_impl websocket_endpoint_impl& operator=(const websocket_endpoint_impl&) = delete; ~websocket_endpoint_impl() { - if constexpr (Args::is_client) - { + if constexpr (Args::is_client) { this->stop_before_destroy(*this); } } @@ -156,19 +154,13 @@ class websocket_endpoint_impl template void _handle_connect(const error_code& ec, std::shared_ptr lifetime, std::shared_ptr> policy, Chain chain) { - if constexpr (secure) - { + if constexpr (secure) { Transport::_handle_connect(ec, std::move(lifetime), std::move(policy), std::move(chain)); - } - else - { + } else { set_last_error(ec); - if (ec) - { + if (ec) { this->derived()._done_connect(ec, std::move(lifetime), std::move(policy), std::move(chain)); - } - else - { + } else { begin_upgrade(std::move(lifetime), std::move(policy), std::move(chain)); } } @@ -183,28 +175,19 @@ class websocket_endpoint_impl chain = std::move(chain)]() mutable { set_last_error(ec); this->derived()._fire_handshake(lifetime); - if (ec) - { - if constexpr (Args::is_session) - { + if (ec) { + if constexpr (Args::is_session) { this->derived()._do_disconnect(ec, std::move(lifetime), std::move(chain)); - } - else - { + } else { this->derived()._done_connect(ec, std::move(lifetime), std::move(policy), std::move(chain)); } - } - else - { + } else { begin_upgrade(std::move(lifetime), std::move(policy), std::move(chain)); } }; - if constexpr (Args::is_session) - { + if constexpr (Args::is_session) { this->sessions_.dispatch(std::move(notify)); - } - else - { + } else { notify(); } } @@ -226,13 +209,10 @@ class websocket_endpoint_impl } void _fire_upgrade(std::shared_ptr& lifetime) { - if constexpr (Args::is_session) - { + if constexpr (Args::is_session) { ARKNET_ASSERT(this->sessions_.io().running_in_this_thread()); this->listener_.notify(event_type::upgrade, lifetime); - } - else - { + } else { ARKNET_ASSERT(this->io_->running_in_this_thread()); this->listener_.notify(event_type::upgrade); } @@ -240,23 +220,17 @@ class websocket_endpoint_impl private: auto& transport() noexcept { - if constexpr (secure) - { + if constexpr (secure) { return this->ssl_stream(); - } - else - { + } else { return this->socket(); } } const auto& transport() const noexcept { - if constexpr (secure) - { + if constexpr (secure) { return this->ssl_stream(); - } - else - { + } else { return this->socket(); } } @@ -269,14 +243,11 @@ class websocket_endpoint_impl template bool connect(Host&& host, Port&& port, First&& first, Options&&... options) { - if constexpr (detail::can_convert_to_string>::value) - { + if constexpr (detail::can_convert_to_string>::value) { this->derived().set_upgrade_target(std::forward(first)); return this->derived().template _do_connect(std::forward(host), std::forward(port), ecs_helper::make_ecs('0', std::forward(options)...)); - } - else - { + } else { return this->derived().template _do_connect(std::forward(host), std::forward(port), ecs_helper::make_ecs('0', std::forward(first), std::forward(options)...)); } @@ -287,12 +258,9 @@ class websocket_endpoint_impl this->derived().dispatch([this, lifetime = std::move(lifetime), policy = std::move(policy), chain = std::move(chain)]() mutable { this->_ws_start(lifetime, policy, transport()); - if constexpr (Args::is_session) - { + if constexpr (Args::is_session) { this->derived()._post_read_upgrade_request(std::move(lifetime), std::move(policy), std::move(chain)); - } - else - { + } else { this->derived()._post_control_callback(lifetime, policy); this->derived()._post_upgrade( std::move(lifetime), std::move(policy), upgrade_message_, std::move(chain)); @@ -305,8 +273,7 @@ class websocket_endpoint_impl }; template -class websocket_server_impl : public Transport -{ +class websocket_server_impl : public Transport { public: template explicit websocket_server_impl(Construction&&... construction) diff --git a/include/arknet/http/impl/ws_send_op.hpp b/include/arknet/http/impl/ws_send_op.hpp index 0d9e267..3c1b859 100644 --- a/include/arknet/http/impl/ws_send_op.hpp +++ b/include/arknet/http/impl/ws_send_op.hpp @@ -9,8 +9,7 @@ namespace arknet::detail { template -class ws_send_op -{ +class ws_send_op { protected: template bool _ws_send(Data& payload, Completion&& completion) { diff --git a/include/arknet/http/impl/ws_stream_cp.hpp b/include/arknet/http/impl/ws_stream_cp.hpp index 6933f8e..9dfa488 100644 --- a/include/arknet/http/impl/ws_stream_cp.hpp +++ b/include/arknet/http/impl/ws_stream_cp.hpp @@ -11,20 +11,17 @@ namespace arknet::detail { template - struct is_websocket_client : std::bool_constant < requires(T& client) -{ + struct is_websocket_client : std::bool_constant < requires(T& client) { client.ws_stream(); } > {}; template - struct is_websocket_server : std::bool_constant < requires(typename T::session_type& session) -{ + struct is_websocket_server : std::bool_constant < requires(typename T::session_type& session) { session.ws_stream(); } > {}; template -class ws_stream_cp : public ws_stream_tag -{ +class ws_stream_cp : public ws_stream_tag { public: using ws_stream_type = typename Args::stream_t; @@ -59,8 +56,7 @@ class ws_stream_cp : public ws_stream_tag void _ws_stop(std::shared_ptr lifetime, Chain chain) { auto& owner = static_cast(*this); ARKNET_ASSERT(owner.io_->running_in_this_thread()); - if (!ws_stream_) - { + if (!ws_stream_) { return; } owner.disp_event( @@ -73,16 +69,14 @@ class ws_stream_cp : public ws_stream_tag ws_stream_->set_option(limits); ws_stream_->set_option(websocket::stream_base::decorator{[](websocket::request_type&) {}}); ws_stream_->set_option(websocket::stream_base::decorator{[](websocket::response_type&) {}}); - if (!ws_stream_->is_open()) - { + if (!ws_stream_->is_open()) { ws_stream_->control_callback(); return; } auto timer = std::make_shared(owner.io_->executor()); timer->expires_after(owner.get_disconnect_timeout()); timer->async_wait(owner._track_async_operation([&owner, lifetime](error_code ec) { - if (!ec) - { + if (!ec) { error_code ignored; owner.socket().cancel(ignored); owner.socket().close(ignored); @@ -113,10 +107,8 @@ class ws_stream_cp : public ws_stream_tag void _ws_post_recv(std::shared_ptr lifetime, std::shared_ptr> policy) { auto& owner = static_cast(*this); ARKNET_ASSERT(owner.io_->running_in_this_thread()); - if (!owner.is_started()) - { - if (owner.state_ == state_t::started) - { + if (!owner.is_started()) { + if (owner.state_ == state_t::started) { owner._do_disconnect(get_last_error(), std::move(lifetime)); } return; @@ -153,8 +145,7 @@ class ws_stream_cp : public ws_stream_tag ws_stream_->control_callback( [&owner, weak, tracked, policy](websocket::frame_type type, beast::string_view payload) mutable { auto locked = weak.lock(); - if (tracked && !locked) - { + if (tracked && !locked) { return; } owner._handle_control_callback(type, payload, std::move(locked), policy); @@ -167,8 +158,7 @@ class ws_stream_cp : public ws_stream_tag std::shared_ptr lifetime, std::shared_ptr> policy) { auto& owner = static_cast(*this); owner.update_alive_time(); - switch (type) - { + switch (type) { case websocket::frame_type::ping: owner._handle_control_ping(payload, std::move(lifetime), std::move(policy)); break; @@ -191,8 +181,7 @@ class ws_stream_cp : public ws_stream_tag void _handle_control_close( beast::string_view, std::shared_ptr lifetime, std::shared_ptr>) { auto& owner = static_cast(*this); - if (owner.state_ == state_t::started) - { + if (owner.state_ == state_t::started) { owner._do_disconnect(websocket::error::closed, std::move(lifetime)); } } @@ -224,12 +213,9 @@ class ws_stream_cp : public ws_stream_tag std::shared_ptr> policy, Chain chain) { auto& owner = static_cast(*this); set_last_error(ec); - if (ec) - { + if (ec) { owner._handle_upgrade(ec, std::move(lifetime), std::move(policy), std::move(chain)); - } - else - { + } else { owner.update_alive_time(); owner._post_control_callback(lifetime, policy); owner._post_upgrade(std::move(lifetime), std::move(policy), owner.get_upgrade_request(), std::move(chain)); @@ -251,12 +237,9 @@ class ws_stream_cp : public ws_stream_tag #endif owner._handle_upgrade(ec, std::move(lifetime), std::move(policy), std::move(chain)); })); - if constexpr (Args::is_session) - { + if constexpr (Args::is_session) { ws_stream_->async_accept(message, std::move(completion)); - } - else - { + } else { error_code parse_error; const auto address = asio::ip::make_address(owner.host_, parse_error); auto authority = !parse_error && address.is_v6() ? "[" + owner.host_ + "]" : owner.host_; @@ -285,12 +268,9 @@ class ws_stream_cp : public ws_stream_tag chain = std::move(chain)]() mutable { set_last_error(ec); owner._fire_upgrade(lifetime); - if (ec) - { + if (ec) { owner._do_disconnect(ec, std::move(lifetime), std::move(chain)); - } - else - { + } else { owner._done_connect(ec, std::move(lifetime), std::move(policy), std::move(chain)); } }); @@ -308,13 +288,10 @@ class ws_stream_cp : public ws_stream_tag template void _handle_upgrade(const error_code& ec, std::shared_ptr lifetime, std::shared_ptr> policy, Chain chain) { - if constexpr (Args::is_session) - { + if constexpr (Args::is_session) { static_cast(*this)._session_handle_upgrade( ec, std::move(lifetime), std::move(policy), std::move(chain)); - } - else - { + } else { static_cast(*this)._client_handle_upgrade( ec, std::move(lifetime), std::move(policy), std::move(chain)); } diff --git a/include/arknet/http/request.hpp b/include/arknet/http/request.hpp index a8520c9..ac9494d 100644 --- a/include/arknet/http/request.hpp +++ b/include/arknet/http/request.hpp @@ -17,8 +17,7 @@ namespace arknet { -struct http_request_options -{ +struct http_request_options { std::chrono::steady_clock::duration connect_timeout = std::chrono::seconds(5); std::chrono::steady_clock::duration io_timeout = std::chrono::seconds(20); std::uint32_t header_limit = 8192; @@ -26,8 +25,7 @@ struct http_request_options }; // A single HTTP transaction. Keep the caller's context running until completion. -class http_request -{ +class http_request { public: using request_type = http::request; using response_type = http::response; @@ -97,8 +95,7 @@ class http_request } private: - struct state : std::enable_shared_from_this - { + struct state : std::enable_shared_from_this { explicit state(asio::io_context& context) : strand(asio::make_strand(context)) , resolver(strand) @@ -107,8 +104,7 @@ class http_request void start(std::string host, std::string service, request_type message, http_request_options limits, std::function completion) { - if (started) - { + if (started) { completion(asio::error::already_started, {}); return; } @@ -116,23 +112,19 @@ class http_request callback = std::move(completion); options = limits; request = std::move(message); - if (canceled) - { + if (canceled) { finish(asio::error::operation_aborted); return; } if (!valid_request(host, service) || options.connect_timeout <= options.connect_timeout.zero() || - options.io_timeout <= options.io_timeout.zero() || !options.header_limit) - { + options.io_timeout <= options.io_timeout.zero() || !options.header_limit) { finish(asio::error::invalid_argument); return; } - if (!request.count(http::field::host)) - { + if (!request.count(http::field::host)) { auto authority = host.find(':') == std::string::npos ? host : "[" + host + "]"; if (service != "80" && - std::all_of(service.begin(), service.end(), [](unsigned char c) { return c >= '0' && c <= '9'; })) - { + std::all_of(service.begin(), service.end(), [](unsigned char c) { return c >= '0' && c <= '9'; })) { authority += ":" + service; } request.set(http::field::host, authority); @@ -142,14 +134,12 @@ class http_request arm_timeout(options.connect_timeout); resolver.async_resolve(host, service, [self = shared_from_this()](error_code ec, asio::ip::tcp::resolver::results_type endpoints) { - if (self->done || self->fail(ec)) - { + if (self->done || self->fail(ec)) { return; } asio::async_connect( self->socket, endpoints, [self](error_code error, const asio::ip::tcp::endpoint&) { - if (self->done || self->fail(error)) - { + if (self->done || self->fail(error)) { return; } self->arm_timeout(self->options.io_timeout); @@ -160,16 +150,14 @@ class http_request void cancel() { canceled = true; - if (started) - { + if (started) { finish(asio::error::operation_aborted); } } void write() { http::async_write(socket, request, [self = shared_from_this()](error_code ec, std::size_t) { - if (!self->done && !self->fail(ec)) - { + if (!self->done && !self->fail(ec)) { self->read(); } }); @@ -181,29 +169,21 @@ class http_request parser->header_limit(options.header_limit); parser->skip(request.method() == http::verb::head); http::async_read(socket, buffer, *parser, [self = shared_from_this()](error_code ec, std::size_t) { - if (self->done || self->fail(ec)) - { + if (self->done || self->fail(ec)) { return; } auto response = self->parser->release(); - if (response.count(http::field::content_length) > 1) - { + if (response.count(http::field::content_length) > 1) { self->finish(http::error::bad_content_length); return; } - if (response.result_int() < 200) - { - if (response.result() == http::status::switching_protocols) - { + if (response.result_int() < 200) { + if (response.result() == http::status::switching_protocols) { self->finish(asio::error::operation_not_supported); - } - else - { + } else { self->read(); } - } - else - { + } else { self->finish({}, std::move(response)); } }); @@ -211,12 +191,10 @@ class http_request bool fail(error_code ec) { // Ready IO handlers may run before the timer handler after the deadline. - if (std::chrono::steady_clock::now() >= deadline.expiry()) - { + if (std::chrono::steady_clock::now() >= deadline.expiry()) { ec = asio::error::timed_out; } - if (ec) - { + if (ec) { finish(ec); } return bool(ec); @@ -226,16 +204,14 @@ class http_request const auto generation = ++timer_generation; deadline.expires_after(duration); deadline.async_wait([self = shared_from_this(), generation](error_code ec) { - if (!ec && !self->done && generation == self->timer_generation) - { + if (!ec && !self->done && generation == self->timer_generation) { self->finish(asio::error::timed_out); } }); } void finish(error_code ec, response_type response = {}) { - if (done) - { + if (done) { return; } done = true; @@ -270,15 +246,12 @@ class http_request !std::all_of(request.method_string().begin(), request.method_string().end(), token_character) || request.target().empty() || std::any_of(request.target().begin(), request.target().end(), - [](unsigned char c) { return c <= 32 || c >= 127 || c == '#'; })) - { + [](unsigned char c) { return c <= 32 || c >= 127 || c == '#'; })) { return false; } - for (const auto& field : request) - { + for (const auto& field : request) { if (field.name_string().empty() || !valid_header_value(field.value()) || - !std::all_of(field.name_string().begin(), field.name_string().end(), token_character)) - { + !std::all_of(field.name_string().begin(), field.name_string().end(), token_character)) { return false; } } diff --git a/include/arknet/http/router.hpp b/include/arknet/http/router.hpp index 50ed97e..3bbcf28 100644 --- a/include/arknet/http/router.hpp +++ b/include/arknet/http/router.hpp @@ -19,18 +19,15 @@ namespace arknet { using http_route_request = http::request; using http_route_response = http::response; -struct http_route_context -{ +struct http_route_context { const http_route_request& request; std::string_view path; std::string_view query; std::vector> parameters; std::string_view param(std::string_view name) const noexcept { - for (const auto& [key, value] : parameters) - { - if (key == name) - { + for (const auto& [key, value] : parameters) { + if (key == name) { return value; } } @@ -38,23 +35,19 @@ struct http_route_context } }; -class http_router -{ - enum class segment_kind - { +class http_router { + enum class segment_kind { wildcard, parameter, literal }; - struct segment - { + struct segment { segment_kind kind; std::string value; }; - struct route - { + struct route { http::verb method; std::vector segments; detail::function handler; @@ -73,15 +66,12 @@ class http_router template http_router& add(http::verb method, std::string_view pattern, Handler&& handler) { - if (method == http::verb::unknown || http::to_string(method).empty()) - { + if (method == http::verb::unknown || http::to_string(method).empty()) { throw std::invalid_argument("HTTP route requires a known method"); } auto segments = parse_pattern(pattern); - for (const auto& existing : routes_) - { - if (existing.method == method && same_resource(existing.segments, segments)) - { + for (const auto& existing : routes_) { + if (existing.method == method && same_resource(existing.segments, segments)) { throw std::invalid_argument("duplicate HTTP method and route pattern"); } } @@ -107,99 +97,75 @@ class http_router const std::string_view target(request.target().data(), request.target().size()); std::string path; std::string_view query; - if (!parse_target(target, request.method(), path, query)) - { + if (!parse_target(target, request.method(), path, query)) { response.result(http::status::bad_request); response.body() = "Bad Request"; return finish(request, std::move(response)); } const auto parts = split_path(path); const route* resource = nullptr; - for (const auto& candidate : routes_) - { + for (const auto& candidate : routes_) { if (matches(candidate.segments, parts) && - (!resource || more_specific(candidate.segments, resource->segments))) - { + (!resource || more_specific(candidate.segments, resource->segments))) { resource = &candidate; } } const route* selected = nullptr; std::vector allowed; - for (const auto& candidate : routes_) - { - if (path != "*" && (!resource || !same_resource(candidate.segments, resource->segments))) - { + for (const auto& candidate : routes_) { + if (path != "*" && (!resource || !same_resource(candidate.segments, resource->segments))) { continue; } allowed.push_back(candidate.method); - if (candidate.method == request.method()) - { + if (candidate.method == request.method()) { selected = &candidate; } } - if (request.method() == http::verb::head && !selected && resource) - { - for (const auto& candidate : routes_) - { - if (candidate.method == http::verb::get && same_resource(candidate.segments, resource->segments)) - { + if (request.method() == http::verb::head && !selected && resource) { + for (const auto& candidate : routes_) { + if (candidate.method == http::verb::get && same_resource(candidate.segments, resource->segments)) { selected = &candidate; } } } const std::string allow = make_allow(std::move(allowed)); http_route_context context{request, path, query, {}}; - if (selected && path != "*") - { + if (selected && path != "*") { capture(*selected, parts, path, context); } - try - { + try { bool proceed = true; - for (const auto& middleware : middleware_) - { - if (!middleware(context, response)) - { + for (const auto& middleware : middleware_) { + if (!middleware(context, response)) { proceed = false; break; } } - if (proceed) - { - if (path == "*" || (resource && request.method() == http::verb::options && !selected)) - { + if (proceed) { + if (path == "*" || (resource && request.method() == http::verb::options && !selected)) { response.result(http::status::no_content); response.set(http::field::allow, allow); - } - else if (!resource) - { + } else if (!resource) { response.result(http::status::not_found); response.body() = "Not Found"; - } - else if (!selected) - { + } else if (!selected) { response.result(http::status::method_not_allowed); response.set(http::field::allow, allow); response.body() = "Method Not Allowed"; - } - else - { + } else { selected->handler(context, response); } } return finish(request, std::move(response)); - } - catch (...) - { + } catch (...) { const auto exception = std::current_exception(); - if (error_handler_) - { - try - { error_handler_(exception); } - catch (...) - {} + if (error_handler_) { + try { + error_handler_(exception); + } catch (...) { + } } http_route_response failure(http::status::internal_server_error, request.version()); failure.keep_alive(request.keep_alive()); @@ -210,29 +176,24 @@ class http_router private: static bool name_valid(std::string_view name) noexcept { - if (name.empty()) - { + if (name.empty()) { return false; } const auto letter = [](char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; }; - if (!letter(name.front())) - { + if (!letter(name.front())) { return false; } return std::all_of(name.begin(), name.end(), [&](char c) { return letter(c) || (c >= '0' && c <= '9'); }); } static int hex(char c) noexcept { - if (c >= '0' && c <= '9') - { + if (c >= '0' && c <= '9') { return c - '0'; } - if (c >= 'a' && c <= 'f') - { + if (c >= 'a' && c <= 'f') { return c - 'a' + 10; } - if (c >= 'A' && c <= 'F') - { + if (c >= 'A' && c <= 'F') { return c - 'A' + 10; } return -1; @@ -244,33 +205,25 @@ class http_router } static bool decode(std::string_view input, std::string* output, bool query = false) { - for (std::size_t index = 0; index < input.size(); ++index) - { + for (std::size_t index = 0; index < input.size(); ++index) { auto value = static_cast(input[index]); - if (value == '%') - { - if (index + 2 >= input.size()) - { + if (value == '%') { + if (index + 2 >= input.size()) { return false; } const int high = hex(input[index + 1]), low = hex(input[index + 2]); - if (high < 0 || low < 0) - { + if (high < 0 || low < 0) { return false; } value = static_cast((high << 4) | low); index += 2; - if (value < 0x20 || value == 0x7f || (!query && (value == '/' || value == '\\'))) - { + if (value < 0x20 || value == 0x7f || (!query && (value == '/' || value == '\\'))) { return false; } - } - else if (!uri_character(value) && !(query && value == '?')) - { + } else if (!uri_character(value) && !(query && value == '?')) { return false; } - if (output) - { + if (output) { output->push_back(static_cast(value)); } } @@ -279,17 +232,14 @@ class http_router static std::vector split_path(std::string_view path) { std::vector result; - if (path == "/" || path == "*") - { + if (path == "/" || path == "*") { return result; } path.remove_prefix(1); - while (true) - { + while (true) { const auto slash = path.find('/'); result.push_back(path.substr(0, slash)); - if (slash == std::string_view::npos) - { + if (slash == std::string_view::npos) { return result; } path.remove_prefix(slash + 1); @@ -297,32 +247,25 @@ class http_router } static bool parse_target(std::string_view target, http::verb method, std::string& path, std::string_view& query) { - if (target == "*" && method == http::verb::options) - { + if (target == "*" && method == http::verb::options) { path = "*"; return true; } - if (target.empty() || target.front() != '/') - { + if (target.empty() || target.front() != '/') { return false; } const auto separator = target.find('?'); - if (!decode(target.substr(0, separator), &path)) - { + if (!decode(target.substr(0, separator), &path)) { return false; } - if (separator != std::string_view::npos) - { + if (separator != std::string_view::npos) { query = target.substr(separator + 1); - if (!decode(query, nullptr, true)) - { + if (!decode(query, nullptr, true)) { return false; } } - for (auto part : split_path(path)) - { - if (part == "." || part == "..") - { + for (auto part : split_path(path)) { + if (part == "." || part == "..") { return false; } } @@ -330,33 +273,26 @@ class http_router } static std::vector parse_pattern(std::string_view pattern) { - if (pattern.empty() || pattern.front() != '/' || pattern.find_first_of("?#") != std::string_view::npos) - { + if (pattern.empty() || pattern.front() != '/' || pattern.find_first_of("?#") != std::string_view::npos) { throw std::invalid_argument("HTTP route patterns require an absolute path without query or fragment"); } const auto parts = split_path(pattern); std::vector result; std::vector names; - for (std::size_t index = 0; index < parts.size(); ++index) - { + for (std::size_t index = 0; index < parts.size(); ++index) { const auto part = parts[index]; - if (!part.empty() && (part.front() == ':' || part.front() == '*')) - { + if (!part.empty() && (part.front() == ':' || part.front() == '*')) { const auto name = part.substr(1); if (!name_valid(name) || std::find(names.begin(), names.end(), name) != names.end() || - (part.front() == '*' && index + 1 != parts.size())) - { + (part.front() == '*' && index + 1 != parts.size())) { throw std::invalid_argument("HTTP parameter names must be unique; wildcards must end the path"); } names.push_back(name); result.push_back( {part.front() == ':' ? segment_kind::parameter : segment_kind::wildcard, std::string(name)}); - } - else - { + } else { std::string literal; - if (!decode(part, &literal) || literal == "." || literal == "..") - { + if (!decode(part, &literal) || literal == "." || literal == "..") { throw std::invalid_argument("invalid HTTP route path segment"); } result.push_back({segment_kind::literal, std::move(literal)}); @@ -366,15 +302,12 @@ class http_router } static bool same_resource(const std::vector& left, const std::vector& right) noexcept { - if (left.size() != right.size()) - { + if (left.size() != right.size()) { return false; } - for (std::size_t index = 0; index < left.size(); ++index) - { + for (std::size_t index = 0; index < left.size(); ++index) { if (left[index].kind != right[index].kind || - (left[index].kind == segment_kind::literal && left[index].value != right[index].value)) - { + (left[index].kind == segment_kind::literal && left[index].value != right[index].value)) { return false; } } @@ -382,22 +315,17 @@ class http_router } static bool matches(const std::vector& pattern, const std::vector& parts) noexcept { - for (std::size_t index = 0; index < pattern.size(); ++index) - { - if (pattern[index].kind == segment_kind::wildcard) - { + for (std::size_t index = 0; index < pattern.size(); ++index) { + if (pattern[index].kind == segment_kind::wildcard) { return true; } - if (index >= parts.size()) - { + if (index >= parts.size()) { return false; } - if (pattern[index].kind == segment_kind::literal && pattern[index].value != parts[index]) - { + if (pattern[index].kind == segment_kind::literal && pattern[index].value != parts[index]) { return false; } - if (pattern[index].kind == segment_kind::parameter && parts[index].empty()) - { + if (pattern[index].kind == segment_kind::parameter && parts[index].empty()) { return false; } } @@ -405,10 +333,8 @@ class http_router } static bool more_specific(const std::vector& left, const std::vector& right) noexcept { - for (std::size_t index = 0; index < (std::min)(left.size(), right.size()); ++index) - { - if (left[index].kind != right[index].kind) - { + for (std::size_t index = 0; index < (std::min)(left.size(), right.size()); ++index) { + if (left[index].kind != right[index].kind) { return left[index].kind > right[index].kind; } } @@ -418,15 +344,11 @@ class http_router static void capture(const route& selected, const std::vector& parts, std::string_view path, http_route_context& context) { - for (std::size_t index = 0; index < selected.segments.size(); ++index) - { + for (std::size_t index = 0; index < selected.segments.size(); ++index) { const auto& part = selected.segments[index]; - if (part.kind == segment_kind::parameter) - { + if (part.kind == segment_kind::parameter) { context.parameters.emplace_back(part.value, parts[index]); - } - else if (part.kind == segment_kind::wildcard) - { + } else if (part.kind == segment_kind::wildcard) { const auto value = index < parts.size() ? path.substr(static_cast(parts[index].data() - path.data())) : std::string_view{}; @@ -436,8 +358,7 @@ class http_router } static std::string make_allow(std::vector methods) { - if (std::find(methods.begin(), methods.end(), http::verb::get) != methods.end()) - { + if (std::find(methods.begin(), methods.end(), http::verb::get) != methods.end()) { methods.push_back(http::verb::head); } methods.push_back(http::verb::options); @@ -445,10 +366,8 @@ class http_router [](auto left, auto right) { return http::to_string(left) < http::to_string(right); }); methods.erase(std::unique(methods.begin(), methods.end()), methods.end()); std::string result; - for (auto method : methods) - { - if (!result.empty()) - { + for (auto method : methods) { + if (!result.empty()) { result += ", "; } const auto name = http::to_string(method); @@ -460,30 +379,22 @@ class http_router static http_route_response finish(const http_route_request& request, http_route_response response) { response.version(request.version()); const auto status = response.result_int(); - if (status < 200 || status == 204 || status == 205 || status == 304) - { + if (status < 200 || status == 204 || status == 205 || status == 304) { response.body().clear(); response.erase(http::field::transfer_encoding); - if (status != 304) - { + if (status != 304) { response.erase(http::field::content_length); } - if (status == 205) - { + if (status == 205) { response.content_length(0); } - } - else if (request.method() == http::verb::head) - { - if (!response.count(http::field::content_length)) - { + } else if (request.method() == http::verb::head) { + if (!response.count(http::field::content_length)) { response.content_length(response.body().size()); } response.body().clear(); response.erase(http::field::transfer_encoding); - } - else - { + } else { response.prepare_payload(); } return response; @@ -492,8 +403,7 @@ class http_router template Server& bind_router(Server& server, std::shared_ptr router) { - if (!router) - { + if (!router) { throw std::invalid_argument("null HTTP router"); } server.bind_recv([router = std::move(router)]( diff --git a/include/arknet/http/ws_client.hpp b/include/arknet/http/ws_client.hpp index 6fde94b..c683730 100644 --- a/include/arknet/http/ws_client.hpp +++ b/include/arknet/http/ws_client.hpp @@ -6,16 +6,14 @@ namespace arknet::detail { -struct template_args_ws_client : template_args_tcp_client -{ +struct template_args_ws_client : template_args_tcp_client { using stream_t = websocket::stream; using body_t = http::string_body; using buffer_t = beast::flat_buffer; }; template -class ws_client_impl_t : public websocket_endpoint_impl, Args> -{ +class ws_client_impl_t : public websocket_endpoint_impl, Args> { public: template explicit ws_client_impl_t(Construction&&... construction) @@ -31,14 +29,12 @@ template using ws_client_impl_t = detail::ws_client_impl_t; template -class ws_client_t : public detail::ws_client_impl_t -{ +class ws_client_t : public detail::ws_client_impl_t { public: using detail::ws_client_impl_t::ws_client_impl_t; }; -class ws_client : public ws_client_t -{ +class ws_client : public ws_client_t { public: using ws_client_t::ws_client_t; }; diff --git a/include/arknet/http/ws_server.hpp b/include/arknet/http/ws_server.hpp index c119ca4..40a30d5 100644 --- a/include/arknet/http/ws_server.hpp +++ b/include/arknet/http/ws_server.hpp @@ -7,8 +7,7 @@ namespace arknet::detail { template -class ws_server_impl_t : public websocket_server_impl, Session> -{ +class ws_server_impl_t : public websocket_server_impl, Session> { public: template explicit ws_server_impl_t(Construction&&... construction) @@ -23,8 +22,7 @@ template using ws_server_impl_t = detail::ws_server_impl_t; template -class ws_server_t : public detail::ws_server_impl_t, Session> -{ +class ws_server_t : public detail::ws_server_impl_t, Session> { public: using detail::ws_server_impl_t, Session>::ws_server_impl_t; }; diff --git a/include/arknet/http/ws_session.hpp b/include/arknet/http/ws_session.hpp index 40d2af0..1610ab6 100644 --- a/include/arknet/http/ws_session.hpp +++ b/include/arknet/http/ws_session.hpp @@ -6,16 +6,14 @@ namespace arknet::detail { -struct template_args_ws_session : template_args_tcp_session -{ +struct template_args_ws_session : template_args_tcp_session { using stream_t = websocket::stream; using body_t = http::string_body; using buffer_t = beast::flat_buffer; }; template -class ws_session_impl_t : public websocket_endpoint_impl, Args> -{ +class ws_session_impl_t : public websocket_endpoint_impl, Args> { public: template explicit ws_session_impl_t(Construction&&... construction) @@ -31,14 +29,12 @@ template using ws_session_impl_t = detail::ws_session_impl_t; template -class ws_session_t : public detail::ws_session_impl_t -{ +class ws_session_t : public detail::ws_session_impl_t { public: using detail::ws_session_impl_t::ws_session_impl_t; }; -class ws_session : public ws_session_t -{ +class ws_session : public ws_session_t { public: using ws_session_t::ws_session_t; }; diff --git a/include/arknet/http/wss_client.hpp b/include/arknet/http/wss_client.hpp index db439e8..04c51fc 100644 --- a/include/arknet/http/wss_client.hpp +++ b/include/arknet/http/wss_client.hpp @@ -7,14 +7,12 @@ namespace arknet::detail { -struct template_args_wss_client : template_args_ws_client -{ +struct template_args_wss_client : template_args_ws_client { using stream_t = websocket::stream&>; }; template -class wss_client_impl_t : public websocket_endpoint_impl, Args> -{ +class wss_client_impl_t : public websocket_endpoint_impl, Args> { public: template explicit wss_client_impl_t(Construction&&... construction) @@ -30,14 +28,12 @@ template using wss_client_impl_t = detail::wss_client_impl_t; template -class wss_client_t : public detail::wss_client_impl_t -{ +class wss_client_t : public detail::wss_client_impl_t { public: using detail::wss_client_impl_t::wss_client_impl_t; }; -class wss_client : public wss_client_t -{ +class wss_client : public wss_client_t { public: using wss_client_t::wss_client_t; }; diff --git a/include/arknet/http/wss_server.hpp b/include/arknet/http/wss_server.hpp index dd14f91..dd91219 100644 --- a/include/arknet/http/wss_server.hpp +++ b/include/arknet/http/wss_server.hpp @@ -8,8 +8,7 @@ namespace arknet::detail { template -class wss_server_impl_t : public websocket_server_impl, Session> -{ +class wss_server_impl_t : public websocket_server_impl, Session> { public: template explicit wss_server_impl_t(Construction&&... construction) @@ -24,8 +23,7 @@ template using wss_server_impl_t = detail::wss_server_impl_t; template -class wss_server_t : public detail::wss_server_impl_t, Session> -{ +class wss_server_t : public detail::wss_server_impl_t, Session> { public: using detail::wss_server_impl_t, Session>::wss_server_impl_t; }; diff --git a/include/arknet/http/wss_session.hpp b/include/arknet/http/wss_session.hpp index b5c0536..9afaac9 100644 --- a/include/arknet/http/wss_session.hpp +++ b/include/arknet/http/wss_session.hpp @@ -7,14 +7,12 @@ namespace arknet::detail { -struct template_args_wss_session : template_args_ws_session -{ +struct template_args_wss_session : template_args_ws_session { using stream_t = websocket::stream&>; }; template -class wss_session_impl_t : public websocket_endpoint_impl, Args> -{ +class wss_session_impl_t : public websocket_endpoint_impl, Args> { public: template explicit wss_session_impl_t(Construction&&... construction) @@ -30,14 +28,12 @@ template using wss_session_impl_t = detail::wss_session_impl_t; template -class wss_session_t : public detail::wss_session_impl_t -{ +class wss_session_t : public detail::wss_session_impl_t { public: using detail::wss_session_impl_t::wss_session_impl_t; }; -class wss_session : public wss_session_t -{ +class wss_session : public wss_session_t { public: using wss_session_t::wss_session_t; }; diff --git a/include/arknet/mysql/connection.hpp b/include/arknet/mysql/connection.hpp index c6c7131..25d2223 100644 --- a/include/arknet/mysql/connection.hpp +++ b/include/arknet/mysql/connection.hpp @@ -35,14 +35,12 @@ namespace arknet::mysql { -enum class tls_mode -{ +enum class tls_mode { required, disabled }; -enum class connection_errc -{ +enum class connection_errc { none, invalid_options, already_started, @@ -62,8 +60,7 @@ enum class connection_errc server }; -struct connection_result -{ +struct connection_result { connection_errc code = connection_errc::none; error_code transport_error; std::uint16_t server_code = 0; @@ -76,8 +73,7 @@ struct connection_result } }; -struct connect_options -{ +struct connect_options { std::string host = "localhost"; std::uint16_t port = 3306; std::string username; @@ -106,16 +102,14 @@ using datetime_value = detail::datetime_value; using time_value = detail::time_value; using request_id = std::uint64_t; -struct query_options -{ +struct query_options { std::chrono::milliseconds timeout{10000}; std::size_t max_rows = 100000; std::size_t max_columns = 4096; std::size_t max_result_bytes = detail::default_message_limit; }; -struct query_result -{ +struct query_result { connection_result status; std::vector columns; std::vector rows; @@ -132,12 +126,10 @@ struct query_result class connection; class pool; -class prepared_statement -{ +class prepared_statement { friend class connection; - struct control - { + struct control { std::weak_ptr owner; std::uint64_t generation = 0; std::uint64_t session_version = 0; @@ -148,8 +140,7 @@ class prepared_statement std::function cleanup; ~control() { - if (!closed.exchange(true) && cleanup) - { + if (!closed.exchange(true) && cleanup) { cleanup(); } } @@ -174,8 +165,7 @@ class prepared_statement } }; -struct prepare_result -{ +struct prepare_result { connection_result status; prepared_statement statement; @@ -189,8 +179,7 @@ struct prepare_result // synchronously on their caller. close() cancels a pending connect and can be // called in its handler. // The connection itself may also be destroyed in a completion handler. -class connection -{ +class connection { friend class pool; public: @@ -200,8 +189,7 @@ class connection using result_handler = std::function; private: - struct attempt - { + struct attempt { connect_options options; connect_handler completion; std::uint64_t generation; @@ -246,16 +234,14 @@ class connection } }; - enum class command_kind - { + enum class command_kind { query, prepare, execute, close_statement, reset }; - enum class command_phase - { + enum class command_phase { first, columns, column_end, @@ -266,8 +252,7 @@ class connection prepare_column_end }; - struct command - { + struct command { request_id id = 0; command_kind kind = command_kind::query; command_phase phase = command_phase::first; @@ -303,8 +288,7 @@ class connection using statement_lifetime_list = std::vector>; - struct state : std::enable_shared_from_this - { + struct state : std::enable_shared_from_this { // Destroyed last, after the resolver, timer and SSL stream. std::shared_ptr pool; asio::any_io_executor executor; @@ -339,8 +323,7 @@ class connection , executor(pool->get()->executor()) , owns_runtime(owned) {} - class publication - { + class publication { inline static thread_local publication* current_{}; state* owner_{}; publication* previous_{}; @@ -348,8 +331,7 @@ class connection public: explicit publication(state& owner) { std::lock_guard lock(owner.publishing_mutex); - if (!owner.terminating) - { + if (!owner.terminating) { ++owner.publishers; owner_ = &owner; previous_ = current_; @@ -361,8 +343,7 @@ class connection publication& operator=(const publication&) = delete; ~publication() { - if (owner_) - { + if (owner_) { current_ = previous_; { std::lock_guard lock(owner_->publishing_mutex); @@ -377,10 +358,8 @@ class connection } static bool active_on_this_thread(const state& owner) noexcept { - for (auto* current = current_; current; current = current->previous_) - { - if (current->owner_ == &owner) - { + for (auto* current = current_; current; current = current->previous_) { + if (current->owner_ == &owner) { return true; } } @@ -397,28 +376,22 @@ class connection } void finish(const std::shared_ptr& operation, connection_result result) { - if (operation->completed) - { + if (operation->completed) { return; } - if (result && operation->generation != generation.load()) - { + if (result && operation->generation != generation.load()) { result = failure(connection_errc::cancelled, "MySQL connection cancelled", asio::error::operation_aborted); } operation->completed = true; operation->deadline.cancel(); - if (!result) - { + if (!result) { operation->cancel(); - if (current == operation) - { + if (current == operation) { open = false; current.reset(); } - } - else - { + } else { { std::lock_guard lock(admission_mutex); admission_ready = operation->generation == generation.load(); @@ -436,8 +409,7 @@ class connection operation->outgoing.clear(); } auto handler = std::move(operation->completion); - if (handler) - { + if (handler) { handler(std::move(result)); } } @@ -452,19 +424,15 @@ class connection } void start(connect_options options, connect_handler handler, std::uint64_t epoch) { - if (epoch != generation.load()) - { - if (handler) - { + if (epoch != generation.load()) { + if (handler) { handler(failure( connection_errc::cancelled, "MySQL connection cancelled", asio::error::operation_aborted)); } return; } - if (current) - { - if (handler) - { + if (current) { + if (handler) { handler(failure(connection_errc::already_started, "MySQL connection already active")); } return; @@ -473,10 +441,8 @@ class connection if (options.host.empty() || options.port == 0 || !deadline || options.max_message_size == 0 || options.max_message_size > detail::default_message_limit || contains_null(options.host) || contains_null(options.username) || contains_null(options.password) || contains_null(options.database) || - contains_null(options.server_name) || contains_null(options.ca_file)) - { - if (handler) - { + contains_null(options.server_name) || contains_null(options.ca_file)) { + if (handler) { handler(failure(connection_errc::invalid_options, "Invalid MySQL connection options")); } return; @@ -485,8 +451,7 @@ class connection current = operation; operation->deadline.expires_at(*deadline); operation->deadline.async_wait([self = shared_from_this(), operation](error_code error) { - if (!error && self->active(operation)) - { + if (!error && self->active(operation)) { self->fail(operation, connection_errc::timeout, "MySQL connection deadline exceeded", asio::error::timed_out); } @@ -494,23 +459,19 @@ class connection operation->resolver.async_resolve(operation->options.host, std::to_string(operation->options.port), [self = shared_from_this(), operation]( error_code error, asio::ip::tcp::resolver::results_type endpoints) { - if (!self->active(operation)) - { + if (!self->active(operation)) { return; } - if (error) - { + if (error) { self->fail(operation, connection_errc::transport, "MySQL name resolution failed", error); return; } asio::async_connect(operation->stream.next_layer(), endpoints, [self, operation](error_code connect_error, const asio::ip::tcp::endpoint&) { - if (!self->active(operation)) - { + if (!self->active(operation)) { return; } - if (connect_error) - { + if (connect_error) { self->fail(operation, connection_errc::transport, "MySQL TCP connection failed", connect_error); return; @@ -521,8 +482,7 @@ class connection } void read_message(const std::shared_ptr& operation, bool greeting) { - if (!active(operation)) - { + if (!active(operation)) { return; } std::string payload; @@ -530,53 +490,39 @@ class connection const auto before = input.size(); const auto status = operation->decoder.consume(input, payload); operation->incoming.erase(0, before - input.size()); - if (status == detail::packet_status::complete) - { + if (status == detail::packet_status::complete) { operation->sequence = operation->decoder.expected_sequence(); - if (greeting) - { + if (greeting) { accept_greeting(operation, payload); - } - else - { + } else { accept_auth_reply(operation, payload); } return; } - if (status != detail::packet_status::incomplete) - { + if (status != detail::packet_status::incomplete) { fail(operation, connection_errc::protocol, status == detail::packet_status::sequence_mismatch ? "MySQL packet sequence mismatch" : "MySQL packet exceeds message limit"); return; } auto handler = [self = shared_from_this(), operation, greeting](error_code error, std::size_t size) { - if (!self->active(operation)) - { + if (!self->active(operation)) { return; } // Asio may report both bytes and EOF; consume any delivered // bytes before a subsequent read reports the terminal error. - if (size) - { + if (size) { operation->incoming.append(operation->read_buffer.data(), size); self->read_message(operation, greeting); - } - else if (error) - { + } else if (error) { self->fail(operation, connection_errc::transport, "MySQL packet read failed", error); - } - else - { + } else { self->fail(operation, connection_errc::protocol, "Empty MySQL transport read"); } }; - if (operation->encrypted) - { + if (operation->encrypted) { operation->stream.async_read_some(asio::buffer(operation->read_buffer), std::move(handler)); - } - else - { + } else { operation->stream.next_layer().async_read_some( asio::buffer(operation->read_buffer), std::move(handler)); } @@ -584,37 +530,30 @@ class connection void send_message( const std::shared_ptr& operation, std::string_view payload, std::function continuation) { - if (!active(operation)) - { + if (!active(operation)) { return; } operation->outgoing.clear(); if (!detail::encode_message( - payload, operation->sequence, operation->outgoing, operation->options.max_message_size)) - { + payload, operation->sequence, operation->outgoing, operation->options.max_message_size)) { fail(operation, connection_errc::protocol, "MySQL outgoing message exceeds limit"); return; } operation->decoder.reset(operation->sequence); auto handler = [self = shared_from_this(), operation, continuation = std::move(continuation)]( error_code error, std::size_t) mutable { - if (!self->active(operation)) - { + if (!self->active(operation)) { return; } - if (error) - { + if (error) { self->fail(operation, connection_errc::transport, "MySQL packet write failed", error); return; } continuation(); }; - if (operation->encrypted) - { + if (operation->encrypted) { asio::async_write(operation->stream, asio::buffer(operation->outgoing), std::move(handler)); - } - else - { + } else { asio::async_write( operation->stream.next_layer(), asio::buffer(operation->outgoing), std::move(handler)); } @@ -622,17 +561,13 @@ class connection bool accept_error( const std::shared_ptr& operation, std::string_view payload, std::uint32_t capabilities) { - if (payload.empty() || static_cast(payload.front()) != 0xff) - { + if (payload.empty() || static_cast(payload.front()) != 0xff) { return false; } detail::error_packet error; - if (detail::parse_error(payload, capabilities, error) != detail::handshake_status::complete) - { + if (detail::parse_error(payload, capabilities, error) != detail::handshake_status::complete) { fail(operation, connection_errc::protocol, "Malformed MySQL error packet"); - } - else - { + } else { finish(operation, {capabilities ? connection_errc::authentication : connection_errc::server, {}, error.code, std::move(error.sql_state), std::move(error.message)}); } @@ -640,105 +575,85 @@ class connection } void accept_greeting(const std::shared_ptr& operation, std::string_view payload) { - if (accept_error(operation, payload, 0)) - { + if (accept_error(operation, payload, 0)) { return; } - if (detail::parse_greeting(payload, operation->greeting) != detail::handshake_status::complete) - { + if (detail::parse_greeting(payload, operation->greeting) != detail::handshake_status::complete) { fail(operation, connection_errc::protocol, "Unsupported or malformed MySQL greeting"); return; } constexpr auto required = detail::capability::protocol_41; constexpr auto auth_capabilities = detail::capability::secure_connection | detail::capability::plugin_auth; if ((operation->greeting.capabilities & required) != required || - !(operation->greeting.capabilities & auth_capabilities)) - { + !(operation->greeting.capabilities & auth_capabilities)) { fail(operation, connection_errc::protocol, "MySQL server lacks required protocol capabilities"); return; } operation->capabilities = required | (operation->greeting.capabilities & (auth_capabilities | detail::capability::transactions)); operation->auth_plugin = operation->greeting.auth_plugin_name; - if (!operation->options.database.empty()) - { - if (!(operation->greeting.capabilities & detail::capability::connect_with_db)) - { + if (!operation->options.database.empty()) { + if (!(operation->greeting.capabilities & detail::capability::connect_with_db)) { fail(operation, connection_errc::protocol, "MySQL server cannot select initial database"); return; } operation->capabilities |= detail::capability::connect_with_db; } - if (operation->options.tls == tls_mode::disabled) - { + if (operation->options.tls == tls_mode::disabled) { send_handshake_response(operation); return; } - if (!(operation->greeting.capabilities & detail::capability::ssl)) - { + if (!(operation->greeting.capabilities & detail::capability::ssl)) { fail(operation, connection_errc::tls_required, "MySQL server does not offer required TLS"); return; } operation->capabilities |= detail::capability::ssl; error_code error; operation->stream.set_verify_mode(asio::ssl::verify_peer, error); - if (!error) - { - if (operation->options.ca_file.empty()) - { + if (!error) { + if (operation->options.ca_file.empty()) { operation->tls_context.set_default_verify_paths(error); - } - else - { + } else { operation->tls_context.load_verify_file(operation->options.ca_file, error); } } const auto& identity = operation->options.server_name.empty() ? operation->options.host : operation->options.server_name; - if (!error) - { + if (!error) { operation->stream.set_verify_callback(asio::ssl::host_name_verification(identity), error); } - if (!error && !SSL_set_min_proto_version(operation->stream.native_handle(), TLS1_2_VERSION)) - { + if (!error && !SSL_set_min_proto_version(operation->stream.native_handle(), TLS1_2_VERSION)) { error = error_code(static_cast(ERR_get_error()), asio::error::get_ssl_category()); - if (!error) - { + if (!error) { error = asio::error::operation_not_supported; } } error_code address_error; asio::ip::make_address(identity, address_error); if (!error && address_error && - !SSL_set_tlsext_host_name(operation->stream.native_handle(), identity.c_str())) - { + !SSL_set_tlsext_host_name(operation->stream.native_handle(), identity.c_str())) { error = error_code(static_cast(ERR_get_error()), asio::error::get_ssl_category()); - if (!error) - { + if (!error) { error = asio::error::operation_not_supported; } } - if (error) - { + if (error) { fail(operation, connection_errc::tls, "MySQL TLS configuration failed", error); return; } std::string request; if (detail::encode_ssl_request(operation->capabilities, static_cast(operation->options.max_message_size), 45, - request) != detail::handshake_status::complete) - { + request) != detail::handshake_status::complete) { fail(operation, connection_errc::protocol, "Invalid MySQL SSLRequest"); return; } send_message(operation, request, [self = shared_from_this(), operation] { operation->stream.async_handshake(asio::ssl::stream_base::client, [self, operation](error_code error) { - if (!self->active(operation)) - { + if (!self->active(operation)) { return; } - if (error) - { + if (error) { self->fail( operation, connection_errc::tls, "MySQL TLS verification or handshake failed", error); return; @@ -753,20 +668,17 @@ class connection const std::shared_ptr& operation, std::string_view challenge, std::string& response) { std::string_view scramble; const auto normalized = detail::password_scramble(operation->auth_plugin, challenge, scramble); - if (normalized == detail::handshake_status::unsupported) - { + if (normalized == detail::handshake_status::unsupported) { fail(operation, connection_errc::unsupported_auth, "Unsupported MySQL authentication plugin"); return false; } - if (normalized != detail::handshake_status::complete) - { + if (normalized != detail::handshake_status::complete) { fail(operation, connection_errc::protocol, "Invalid MySQL authentication challenge"); return false; } const auto status = detail::make_auth_response(operation->auth_plugin, operation->options.password, scramble, response); - if (status != detail::auth_status::complete) - { + if (status != detail::auth_status::complete) { fail(operation, status == detail::auth_status::unsupported_plugin ? connection_errc::unsupported_auth : connection_errc::authentication, @@ -778,8 +690,7 @@ class connection void send_handshake_response(const std::shared_ptr& operation) { std::string response; - if (!make_response(operation, operation->greeting.auth_plugin_data, response)) - { + if (!make_response(operation, operation->greeting.auth_plugin_data, response)) { return; } detail::handshake_response settings; @@ -792,8 +703,7 @@ class connection ? operation->auth_plugin : std::string_view{}; std::string payload; - if (detail::encode_handshake_response(settings, payload) != detail::handshake_status::complete) - { + if (detail::encode_handshake_response(settings, payload) != detail::handshake_status::complete) { fail(operation, connection_errc::protocol, "Invalid MySQL HandshakeResponse41"); return; } @@ -802,38 +712,31 @@ class connection } void accept_auth_reply(const std::shared_ptr& operation, std::string_view payload) { - if (++operation->auth_rounds > 16 || payload.empty()) - { + if (++operation->auth_rounds > 16 || payload.empty()) { fail(operation, connection_errc::protocol, "Invalid MySQL authentication exchange"); return; } - if (accept_error(operation, payload, operation->capabilities)) - { + if (accept_error(operation, payload, operation->capabilities)) { return; } const auto marker = static_cast(payload.front()); - if (marker == 0) - { + if (marker == 0) { detail::ok_packet packet; - if (detail::parse_ok(payload, operation->capabilities, packet) != detail::handshake_status::complete) - { + if (detail::parse_ok(payload, operation->capabilities, packet) != detail::handshake_status::complete) { fail(operation, connection_errc::protocol, "Malformed MySQL authentication OK packet"); return; } finish(operation, {}); return; } - if (operation->fast_auth_complete) - { + if (operation->fast_auth_complete) { fail(operation, connection_errc::protocol, "Expected final MySQL authentication OK packet"); return; } - if (marker == 0xfe) - { + if (marker == 0xfe) { detail::auth_switch_request request; const auto status = detail::parse_auth_switch(payload, request); - if (status != detail::handshake_status::complete) - { + if (status != detail::handshake_status::complete) { fail(operation, status == detail::handshake_status::unsupported ? connection_errc::unsupported_auth : connection_errc::protocol, @@ -842,26 +745,21 @@ class connection } operation->auth_plugin = std::move(request.auth_plugin_name); std::string response; - if (!make_response(operation, request.auth_plugin_data, response)) - { + if (!make_response(operation, request.auth_plugin_data, response)) { return; } send_message(operation, response, [self = shared_from_this(), operation] { self->read_message(operation, false); }); return; } - if (marker == 1 && payload.size() == 2 && operation->auth_plugin == "caching_sha2_password") - { - if (payload[1] == 3) - { + if (marker == 1 && payload.size() == 2 && operation->auth_plugin == "caching_sha2_password") { + if (payload[1] == 3) { operation->fast_auth_complete = true; read_message(operation, false); return; } - if (payload[1] == 4) - { - if (!operation->encrypted) - { + if (payload[1] == 4) { + if (!operation->encrypted) { fail(operation, connection_errc::tls_required, "MySQL full password authentication requires verified TLS"); return; @@ -882,43 +780,35 @@ class connection connection_result reserve_command(const std::shared_ptr& operation) { std::size_t bytes = operation->request.capacity(); const auto add = [&](std::size_t amount) { - if (amount > detail::default_message_limit - bytes) - { + if (amount > detail::default_message_limit - bytes) { return false; } bytes += amount; return true; }; - if (bytes > detail::default_message_limit) - { + if (bytes > detail::default_message_limit) { return failure(connection_errc::queue_full, "MySQL request exceeds admission byte limit"); } if (operation->parameters.capacity() > (detail::default_message_limit - bytes) / sizeof(field_value) || - !add(operation->parameters.capacity() * sizeof(field_value))) - { + !add(operation->parameters.capacity() * sizeof(field_value))) { return failure(connection_errc::queue_full, "MySQL parameter storage exceeds admission byte limit"); } - for (const auto& value : operation->parameters) - { - if (const auto* text = std::get_if(&value); text && !add(text->capacity())) - { + for (const auto& value : operation->parameters) { + if (const auto* text = std::get_if(&value); text && !add(text->capacity())) { return failure(connection_errc::queue_full, "MySQL parameter storage exceeds admission byte limit"); } } // EXECUTE/CLOSE headers are generated later on the owned lane. if ((operation->kind == command_kind::execute || operation->kind == command_kind::close_statement) && - !add(16)) - { + !add(16)) { return failure(connection_errc::queue_full, "MySQL request exceeds admission byte limit"); } std::lock_guard lock(admission_mutex); - if (!admission_ready || operation->generation != generation.load()) - { + if (!admission_ready || operation->generation != generation.load()) { return failure(connection_errc::not_connected, "MySQL connection is not authenticated"); } if (admitted_count >= admission_count_limit || admitted_bytes > admission_byte_limit || - bytes > admission_byte_limit - admitted_bytes) - { + bytes > admission_byte_limit - admitted_bytes) { return failure(connection_errc::queue_full, "MySQL outstanding request admission limit exceeded"); } ++admitted_count; @@ -930,8 +820,7 @@ class connection void release_command(const std::shared_ptr& operation) { std::lock_guard lock(admission_mutex); - if (operation->admitted) - { + if (operation->admitted) { --admitted_count; admitted_bytes -= operation->admission_bytes; operation->admitted = false; @@ -956,10 +845,8 @@ class connection } void invalidate_statements() { - for (auto& [id, weak] : statements) - { - if (auto statement = weak.lock()) - { + for (auto& [id, weak] : statements) { + if (auto statement = weak.lock()) { statement->closed = true; } } @@ -969,8 +856,7 @@ class connection bool draining_commands = false; void complete_command(const std::shared_ptr& operation, connection_result result) { - if (operation->completed) - { + if (operation->completed) { return; } operation->completed = true; @@ -981,67 +867,51 @@ class connection std::vector{}.swap(operation->parameters); release_command(operation); operation->deadline.cancel(); - if (active_command == operation) - { + if (active_command == operation) { active_command.reset(); - } - else - { + } else { const auto position = std::find(pending.begin(), pending.end(), operation); - if (position != pending.end()) - { + if (position != pending.end()) { pending_bytes -= operation->queue_bytes; pending.erase(position); } } - if (operation->kind == command_kind::prepare) - { + if (operation->kind == command_kind::prepare) { prepare_result prepared{std::move(result), std::move(operation->prepared)}; auto handler = std::move(operation->prepare_completion); - if (handler) - { + if (handler) { handler(std::move(prepared)); } - } - else if (operation->kind == command_kind::close_statement || operation->kind == command_kind::reset) - { + } else if (operation->kind == command_kind::close_statement || operation->kind == command_kind::reset) { auto handler = std::move(operation->result_completion); - if (handler) - { + if (handler) { handler(std::move(result)); } - } - else - { + } else { operation->result.status = std::move(result); auto handler = std::move(operation->query_completion); - if (handler) - { + if (handler) { handler(std::move(operation->result)); } } - if (!draining_commands) - { + if (!draining_commands) { start_next_command(); } } void abort_commands(connection_result reason, bool advance_generation = true) { stop_admission(); - if (advance_generation) - { + if (advance_generation) { ++generation; } open = false; - if (current) - { + if (current) { current->cancel(); current.reset(); } invalidate_statements(); draining_commands = true; - if (auto operation = active_command) - { + if (auto operation = active_command) { auto result = reason; result.outcome_unknown = operation->sent; complete_command(operation, std::move(result)); @@ -1049,8 +919,7 @@ class connection auto queued = std::move(pending); pending.clear(); pending_bytes = 0; - for (auto& operation : queued) - { + for (auto& operation : queued) { complete_command( operation, failure(connection_errc::cancelled, "MySQL connection closed before command was sent", asio::error::operation_aborted)); @@ -1059,8 +928,7 @@ class connection } bool charge_result(const std::shared_ptr& operation, std::size_t amount) { - if (amount > operation->options.max_result_bytes - operation->result_bytes) - { + if (amount > operation->options.max_result_bytes - operation->result_bytes) { abort_commands(failure(connection_errc::result_limit, "MySQL accumulated result exceeds memory limit")); return false; } @@ -1069,12 +937,9 @@ class connection } void codec_failure(detail::command_status status, std::string message) { - if (status == detail::command_status::limit_exceeded) - { + if (status == detail::command_status::limit_exceeded) { abort_commands(failure(connection_errc::result_limit, std::move(message))); - } - else - { + } else { abort_commands(failure(status == detail::command_status::unsupported ? connection_errc::unsupported : connection_errc::protocol, std::move(message))); @@ -1082,26 +947,19 @@ class connection } bool command_error(const std::shared_ptr& operation, std::string_view payload) { - if (payload.empty() || static_cast(payload.front()) != 0xff) - { + if (payload.empty() || static_cast(payload.front()) != 0xff) { return false; } detail::error_packet error; if (detail::parse_error(payload, operation->session->capabilities, error) != - detail::handshake_status::complete) - { + detail::handshake_status::complete) { abort_commands(failure(connection_errc::protocol, "Malformed MySQL command error packet")); - } - else - { + } else { connection_result result{ connection_errc::server, {}, error.code, std::move(error.sql_state), std::move(error.message)}; - if (operation->kind == command_kind::prepare && operation->phase != command_phase::first) - { + if (operation->kind == command_kind::prepare && operation->phase != command_phase::first) { abort_commands(std::move(result)); - } - else - { + } else { complete_command(operation, std::move(result)); } } @@ -1110,8 +968,7 @@ class connection bool accept_command_status(const std::shared_ptr& operation, const detail::ok_packet& packet) { // MULTI_RESULTS and MULTI_STATEMENTS are deliberately not negotiated. - if (packet.status_flags & 8) - { + if (packet.status_flags & 8) { abort_commands(failure(connection_errc::unsupported, "Unexpected MySQL multiple-result response")); return false; } @@ -1123,8 +980,7 @@ class connection } void read_command(const std::shared_ptr& operation) { - if (!command_active(operation)) - { + if (!command_active(operation)) { return; } auto& session = *operation->session; @@ -1133,11 +989,9 @@ class connection const auto before = input.size(); const auto status = session.decoder.consume(input, payload); session.incoming.erase(0, before - input.size()); - if (status == detail::packet_status::complete) - { + if (status == detail::packet_status::complete) { session.sequence = session.decoder.expected_sequence(); - if (payload.size() > operation->options.max_result_bytes - operation->wire_bytes) - { + if (payload.size() > operation->options.max_result_bytes - operation->wire_bytes) { abort_commands( failure(connection_errc::result_limit, "MySQL accumulated response exceeds byte limit")); return; @@ -1146,8 +1000,7 @@ class connection accept_command_packet(operation, payload); return; } - if (status != detail::packet_status::incomplete) - { + if (status != detail::packet_status::incomplete) { abort_commands( failure(status == detail::packet_status::message_too_large ? connection_errc::result_limit : connection_errc::protocol, @@ -1155,27 +1008,20 @@ class connection return; } auto handler = [self = shared_from_this(), operation](error_code error, std::size_t count) { - if (!self->command_active(operation)) - { + if (!self->command_active(operation)) { return; } - if (count) - { + if (count) { operation->session->incoming.append(operation->session->read_buffer.data(), count); self->read_command(operation); - } - else - { + } else { self->abort_commands(failure(connection_errc::transport, "MySQL command read failed", error ? error : error_code(asio::error::eof))); } }; - if (session.encrypted) - { + if (session.encrypted) { session.stream.async_read_some(asio::buffer(session.read_buffer), std::move(handler)); - } - else - { + } else { session.stream.next_layer().async_read_some(asio::buffer(session.read_buffer), std::move(handler)); } } @@ -1186,11 +1032,9 @@ class connection const auto epoch = record->generation; const auto version = record->session_version; record->cleanup = [weak = weak_from_this(), id, epoch, version] { - if (auto self = weak.lock()) - { + if (auto self = weak.lock()) { publication publishing(*self); - if (publishing) - { + if (publishing) { asio::post(self->executor, [self, id, epoch, version] { self->automatic_statement_close(id, epoch, version); }); } @@ -1202,13 +1046,11 @@ class connection } void accept_prepare_packet(const std::shared_ptr& operation, std::string_view payload) { - if (operation->phase == command_phase::first) - { + if (operation->phase == command_phase::first) { detail::prepared_statement_ok response; const auto status = detail::parse_prepare_ok(payload, response, limits(operation), operation->session->capabilities); - if (status != detail::command_status::complete) - { + if (status != detail::command_status::complete) { codec_failure(status, "Invalid MySQL prepare response"); return; } @@ -1219,8 +1061,7 @@ class connection record->id = response.statement_id; record->parameter_count = response.parameter_count; if (const auto previous = statements.find(record->id); - previous != statements.end() && !previous->second.expired()) - { + previous != statements.end() && !previous->second.expired()) { abort_commands(failure(connection_errc::protocol, "MySQL reused an active prepared statement ID")); return; } @@ -1228,69 +1069,48 @@ class connection statements[record->id] = record; operation->remaining_parameters = response.parameter_count; operation->remaining_columns = response.column_count; - if (!charge_result(operation, sizeof(prepared_statement::control))) - { + if (!charge_result(operation, sizeof(prepared_statement::control))) { return; } - if (operation->remaining_parameters) - { + if (operation->remaining_parameters) { operation->phase = command_phase::prepare_parameters; - } - else if (operation->remaining_columns) - { + } else if (operation->remaining_columns) { operation->phase = command_phase::prepare_columns; - } - else - { + } else { finish_preparation(operation); return; } - } - else if (operation->phase == command_phase::prepare_parameters || - operation->phase == command_phase::prepare_columns) - { + } else if (operation->phase == command_phase::prepare_parameters || + operation->phase == command_phase::prepare_columns) { column_definition column; const auto status = detail::parse_column(payload, column, limits(operation)); - if (status != detail::command_status::complete) - { + if (status != detail::command_status::complete) { codec_failure(status, "Invalid MySQL prepared metadata"); return; } - if (operation->phase == command_phase::prepare_parameters) - { - if (!--operation->remaining_parameters) - { + if (operation->phase == command_phase::prepare_parameters) { + if (!--operation->remaining_parameters) { operation->phase = command_phase::prepare_parameter_end; } - } - else - { - if (!charge_result(operation, sizeof(column_definition) + payload.size())) - { + } else { + if (!charge_result(operation, sizeof(column_definition) + payload.size())) { return; } operation->prepared.control_->columns.push_back(std::move(column)); - if (!--operation->remaining_columns) - { + if (!--operation->remaining_columns) { operation->phase = command_phase::prepare_column_end; } } - } - else - { + } else { detail::ok_packet eof; const auto status = detail::parse_eof(payload, eof, limits(operation)); - if (status != detail::command_status::complete) - { + if (status != detail::command_status::complete) { codec_failure(status, "Invalid MySQL prepared metadata terminator"); return; } - if (operation->phase == command_phase::prepare_parameter_end && operation->remaining_columns) - { + if (operation->phase == command_phase::prepare_parameter_end && operation->remaining_columns) { operation->phase = command_phase::prepare_columns; - } - else - { + } else { finish_preparation(operation); return; } @@ -1299,106 +1119,82 @@ class connection } void accept_command_packet(const std::shared_ptr& operation, std::string_view payload) { - if (command_error(operation, payload)) - { + if (command_error(operation, payload)) { return; } - if (operation->kind == command_kind::prepare) - { + if (operation->kind == command_kind::prepare) { accept_prepare_packet(operation, payload); return; } - if (operation->phase == command_phase::first) - { - if (!payload.empty() && payload.front() == 0) - { + if (operation->phase == command_phase::first) { + if (!payload.empty() && payload.front() == 0) { detail::ok_packet packet; if (detail::parse_ok(payload, operation->session->capabilities, packet) != - detail::handshake_status::complete) - { + detail::handshake_status::complete) { abort_commands(failure(connection_errc::protocol, "Malformed MySQL command OK packet")); return; } - if (!accept_command_status(operation, packet)) - { + if (!accept_command_status(operation, packet)) { return; } - if (operation->kind == command_kind::reset) - { + if (operation->kind == command_kind::reset) { ++operation->session->session_version; invalidate_statements(); } complete_command(operation, {}); return; } - if (operation->kind == command_kind::reset) - { + if (operation->kind == command_kind::reset) { abort_commands(failure(connection_errc::protocol, "Expected MySQL reset OK packet")); return; } - if (!payload.empty() && static_cast(payload.front()) == 0xfb) - { + if (!payload.empty() && static_cast(payload.front()) == 0xfb) { abort_commands(failure(connection_errc::unsupported, "MySQL LOCAL INFILE is disabled")); return; } const auto status = detail::parse_column_count( payload, operation->remaining_columns, limits(operation), operation->session->capabilities); - if (status != detail::command_status::complete) - { + if (status != detail::command_status::complete) { codec_failure(status, "Invalid MySQL result column count"); return; } operation->phase = command_phase::columns; - } - else if (operation->phase == command_phase::columns) - { + } else if (operation->phase == command_phase::columns) { column_definition column; const auto status = detail::parse_column(payload, column, limits(operation)); - if (status != detail::command_status::complete) - { + if (status != detail::command_status::complete) { codec_failure(status, "Invalid MySQL column definition"); return; } - if (!charge_result(operation, sizeof(column_definition) + payload.size())) - { + if (!charge_result(operation, sizeof(column_definition) + payload.size())) { return; } operation->result.columns.push_back(std::move(column)); - if (!--operation->remaining_columns) - { + if (!--operation->remaining_columns) { operation->phase = command_phase::column_end; } - } - else if (operation->phase == command_phase::column_end || - (operation->phase == command_phase::rows && !payload.empty() && - static_cast(payload.front()) == 0xfe && payload.size() < 9)) - { + } else if (operation->phase == command_phase::column_end || + (operation->phase == command_phase::rows && !payload.empty() && + static_cast(payload.front()) == 0xfe && payload.size() < 9)) { detail::ok_packet packet; const auto status = detail::parse_eof(payload, packet, limits(operation)); - if (status != detail::command_status::complete) - { + if (status != detail::command_status::complete) { codec_failure(status, "Invalid MySQL result terminator"); return; } - if (!accept_command_status(operation, packet)) - { + if (!accept_command_status(operation, packet)) { return; } - if (operation->phase == command_phase::rows) - { + if (operation->phase == command_phase::rows) { complete_command(operation, {}); return; } operation->phase = command_phase::rows; - } - else - { + } else { if (operation->result.rows.size() >= operation->options.max_rows || !charge_result(operation, - sizeof(row_values) + operation->result.columns.size() * sizeof(field_value) + payload.size())) - { - if (command_active(operation)) - { + sizeof(row_values) + operation->result.columns.size() * sizeof(field_value) + payload.size())) { + if (command_active(operation)) { abort_commands(failure(connection_errc::result_limit, "MySQL result exceeds row limit")); } return; @@ -1408,8 +1204,7 @@ class connection operation->kind == command_kind::execute ? detail::parse_binary_row(payload, operation->result.columns, row, limits(operation)) : detail::parse_text_row(payload, operation->result.columns, row, limits(operation)); - if (status != detail::command_status::complete) - { + if (status != detail::command_status::complete) { codec_failure(status, "Invalid MySQL result row"); return; } @@ -1426,82 +1221,68 @@ class connection } void start_next_command() { - if (draining_commands || active_command || pending.empty()) - { + if (draining_commands || active_command || pending.empty()) { return; } auto operation = pending.front(); pending.pop_front(); pending_bytes -= operation->queue_bytes; active_command = operation; - if (!current || !open || operation->generation != generation.load() || current != operation->session) - { + if (!current || !open || operation->generation != generation.load() || current != operation->session) { complete_command(operation, failure(connection_errc::not_connected, "MySQL connection is not ready")); return; } - if (std::chrono::steady_clock::now() >= *operation->expires) - { + if (std::chrono::steady_clock::now() >= *operation->expires) { complete_command(operation, failure(connection_errc::timeout, "MySQL command expired before sending", asio::error::timed_out)); return; } if ((operation->kind == command_kind::execute || operation->kind == command_kind::close_statement) && - !operation->automatic_close) - { - if (!valid_statement(operation)) - { + !operation->automatic_close) { + if (!valid_statement(operation)) { complete_command(operation, failure(connection_errc::invalid_statement, "MySQL statement belongs to a closed or different session")); return; } if (operation->kind == command_kind::execute && - operation->parameters.size() != operation->statement->parameter_count) - { + operation->parameters.size() != operation->statement->parameter_count) { complete_command(operation, failure(connection_errc::invalid_options, "MySQL statement parameter count mismatch")); return; } } - if (operation->automatic_close && operation->automatic_close_version != current->session_version) - { + if (operation->automatic_close && operation->automatic_close_version != current->session_version) { complete_command(operation, {}); return; } auto request_limits = limits(operation); request_limits.max_bytes = current->options.max_message_size; detail::command_status encoded = detail::command_status::complete; - if (operation->kind == command_kind::execute) - { + if (operation->kind == command_kind::execute) { encoded = detail::encode_execute( operation->statement->id, operation->parameters, operation->request, request_limits); - } - else if (operation->kind == command_kind::close_statement) - { + } else if (operation->kind == command_kind::close_statement) { const auto id = operation->automatic_close ? operation->automatic_close_id : operation->statement->id; encoded = detail::encode_close(id, operation->request, request_limits); - if (operation->statement) - { + if (operation->statement) { operation->statement->closed = true; } statements.erase(id); } - if (encoded != detail::command_status::complete) - { + if (encoded != detail::command_status::complete) { complete_command(operation, failure(connection_errc::invalid_options, "Invalid MySQL statement parameters or request size")); return; } current->sequence = 0; current->outgoing.clear(); - if (!current->incoming.empty() || current->decoder.buffered_size() != 0) - { + if (!current->incoming.empty() || current->decoder.buffered_size() != 0) { abort_commands( failure(connection_errc::protocol, "Unexpected buffered MySQL bytes at command boundary")); return; } if (!detail::encode_message( - operation->request, current->sequence, current->outgoing, current->options.max_message_size)) - { + operation->request, current->sequence, current->outgoing, current->options.max_message_size)) { complete_command( operation, failure(connection_errc::invalid_options, "MySQL command exceeds message size limit")); return; @@ -1509,44 +1290,33 @@ class connection current->decoder = detail::packet_decoder( (std::min)(current->options.max_message_size, operation->options.max_result_bytes), current->sequence); auto handler = [self = shared_from_this(), operation](error_code error, std::size_t) { - if (!self->command_active(operation)) - { + if (!self->command_active(operation)) { return; } - if (error) - { + if (error) { self->abort_commands(failure(connection_errc::transport, "MySQL command write failed", error)); - } - else if (operation->kind == command_kind::close_statement) - { + } else if (operation->kind == command_kind::close_statement) { self->complete_command(operation, {}); - } - else - { + } else { self->read_command(operation); } }; operation->sent = true; - if (current->encrypted) - { + if (current->encrypted) { asio::async_write(current->stream, asio::buffer(current->outgoing), std::move(handler)); - } - else - { + } else { asio::async_write(current->stream.next_layer(), asio::buffer(current->outgoing), std::move(handler)); } } void enqueue_command(const std::shared_ptr& operation) { - if (operation->generation != generation.load()) - { + if (operation->generation != generation.load()) { complete_command( operation, failure(connection_errc::cancelled, "MySQL command cancelled before submission", asio::error::operation_aborted)); return; } - if (!current || !open || !current->completed) - { + if (!current || !open || !current->completed) { complete_command( operation, failure(connection_errc::not_connected, "MySQL connection is not authenticated")); return; @@ -1554,27 +1324,22 @@ class connection operation->session = current; if (!operation->expires || !operation->options.max_columns || operation->options.max_columns > 65535 || !operation->options.max_result_bytes || - operation->options.max_result_bytes > detail::default_message_limit) - { + operation->options.max_result_bytes > detail::default_message_limit) { complete_command(operation, failure(connection_errc::invalid_options, "Invalid MySQL query options")); return; } operation->queue_bytes = operation->request.size(); - for (const auto& value : operation->parameters) - { + for (const auto& value : operation->parameters) { if (operation->queue_bytes > current->options.max_pending_bytes || - sizeof(field_value) > current->options.max_pending_bytes - operation->queue_bytes) - { + sizeof(field_value) > current->options.max_pending_bytes - operation->queue_bytes) { operation->queue_bytes = current->options.max_pending_bytes; complete_command(operation, failure(connection_errc::queue_full, "MySQL command parameters exceed queue byte limit")); return; } operation->queue_bytes += sizeof(field_value); - if (const auto* text = std::get_if(&value)) - { - if (text->size() > current->options.max_pending_bytes - operation->queue_bytes) - { + if (const auto* text = std::get_if(&value)) { + if (text->size() > current->options.max_pending_bytes - operation->queue_bytes) { complete_command(operation, failure(connection_errc::queue_full, "MySQL command parameters exceed queue byte limit")); return; @@ -1583,25 +1348,20 @@ class connection } } if ((active_command && pending.size() >= current->options.max_pending_commands) || - operation->queue_bytes > current->options.max_pending_bytes - pending_bytes) - { + operation->queue_bytes > current->options.max_pending_bytes - pending_bytes) { complete_command( operation, failure(connection_errc::queue_full, "MySQL pending command queue limit exceeded")); return; } operation->deadline.expires_at(*operation->expires); operation->deadline.async_wait([self = shared_from_this(), operation](error_code error) { - if (error || operation->completed) - { + if (error || operation->completed) { return; } - if (self->active_command == operation) - { + if (self->active_command == operation) { self->abort_commands( failure(connection_errc::timeout, "MySQL command deadline exceeded", asio::error::timed_out)); - } - else - { + } else { self->complete_command(operation, failure(connection_errc::timeout, "MySQL command expired in queue", asio::error::timed_out)); } @@ -1612,8 +1372,7 @@ class connection } void automatic_statement_close(std::uint32_t id, std::uint64_t epoch, std::uint64_t version) { - if (!current || !open || epoch != generation.load() || version != current->session_version) - { + if (!current || !open || epoch != generation.load() || version != current->session_version) { return; } auto operation = std::make_shared(executor); @@ -1626,37 +1385,30 @@ class connection operation->automatic_close_id = id; operation->automatic_close_version = version; operation->result_completion = [weak = weak_from_this()](connection_result result) { - if (!result && result.code == connection_errc::queue_full) - { - if (auto self = weak.lock()) - { + if (!result && result.code == connection_errc::queue_full) { + if (auto self = weak.lock()) { self->abort_commands(failure( connection_errc::queue_full, "MySQL automatic statement cleanup exceeded queue limit")); } } }; const auto admission = reserve_command(operation); - if (!admission) - { + if (!admission) { complete_command(operation, admission); - } - else - { + } else { enqueue_command(operation); } } void cancel_command(request_id id) { - if (active_command && active_command->id == id) - { + if (active_command && active_command->id == id) { abort_commands(failure(connection_errc::cancelled, "MySQL command cancelled after sending", asio::error::operation_aborted)); return; } const auto position = std::find_if( pending.begin(), pending.end(), [id](const auto& operation) { return operation->id == id; }); - if (position != pending.end()) - { + if (position != pending.end()) { auto operation = *position; complete_command(operation, failure(connection_errc::cancelled, "MySQL queued command cancelled", asio::error::operation_aborted)); @@ -1669,36 +1421,29 @@ class connection stop_admission(); open = false; asio::post(executor, [self, cutoff] { - if (!self->current || self->current->generation >= cutoff) - { + if (!self->current || self->current->generation >= cutoff) { return; } auto operation = self->current; - if (self->active_command || !self->pending.empty()) - { + if (self->active_command || !self->pending.empty()) { self->abort_commands( failure(connection_errc::cancelled, "MySQL connection closed", asio::error::operation_aborted), false); } self->invalidate_statements(); operation->cancel(); - if (!operation->completed) - { + if (!operation->completed) { self->fail(operation, connection_errc::cancelled, "MySQL connection cancelled", asio::error::operation_aborted); - } - else - { + } else { self->current.reset(); } }); } static void retire_statements(statement_lifetime_list& lifetimes) { - for (auto& weak : lifetimes) - { - if (auto statement = weak.lock()) - { + for (auto& weak : lifetimes) { + if (auto statement = weak.lock()) { statement->closed = true; statement->cleanup = {}; } @@ -1723,16 +1468,13 @@ class connection { std::unique_lock lock(publishing_mutex); if (!owns_runtime || (pool && pool->running_in_threads()) || - publication::active_on_this_thread(*this) || finalizer == std::this_thread::get_id()) - { + publication::active_on_this_thread(*this) || finalizer == std::this_thread::get_id()) { arknet::set_last_error(asio::error::operation_not_supported); return false; } - if (terminating) - { + if (terminating) { publishing_changed.wait(lock, [&] { return terminated || shutdown_failure; }); - if (shutdown_failure) - { + if (shutdown_failure) { std::rethrow_exception(shutdown_failure); } arknet::clear_last_error(); @@ -1742,8 +1484,7 @@ class connection finalizer = std::this_thread::get_id(); publishing_changed.wait(lock, [&] { return publishers == 0; }); } - try - { + try { // All API publications preceding the gate are now enqueued. // The actor cancels protocol IO; worker join retires the outer // Asio handlers and their captures, beyond the actor callback. @@ -1763,9 +1504,7 @@ class connection publishing_changed.notify_all(); arknet::clear_last_error(); return true; - } - catch (...) - { + } catch (...) { { std::lock_guard lock(publishing_mutex); shutdown_failure = std::current_exception(); @@ -1781,16 +1520,12 @@ class connection template static request_id reject_terminated(Handler handler) { - if (handler) - { + if (handler) { Result result; auto error = state::failure(connection_errc::not_connected, "MySQL connection permanently closed"); - if constexpr (std::is_same_v) - { + if constexpr (std::is_same_v) { result = std::move(error); - } - else - { + } else { result.status = std::move(error); } handler(std::move(result)); @@ -1820,27 +1555,20 @@ class connection : encoded == detail::command_status::complete ? self->reserve_command(operation) : state::failure(connection_errc::invalid_options, "Invalid MySQL command payload or size"); - if (!admission) - { + if (!admission) { std::string{}.swap(operation->request); std::vector{}.swap(operation->parameters); operation->statement.reset(); } - try - { + try { asio::post(self->executor, [self, operation, admission = std::move(admission)]() mutable { - if (!admission) - { + if (!admission) { self->complete_command(operation, std::move(admission)); - } - else - { + } else { self->enqueue_command(operation); } }); - } - catch (...) - { + } catch (...) { self->release_command(operation); throw; } @@ -1849,8 +1577,7 @@ class connection explicit connection(std::shared_ptr runtime, bool owned = false) : state_(std::make_shared(std::move(runtime), owned)) { - if (!state_->pool->start()) - { + if (!state_->pool->start()) { throw std::runtime_error("Cannot start MySQL IO runtime"); } } @@ -1874,8 +1601,7 @@ class connection // A completion on another thread may destroy the facade before post returns. const auto self = state_; state::publication publishing(*self); - if (!publishing) - { + if (!publishing) { reject_terminated(std::move(handler)); return; } @@ -1891,8 +1617,7 @@ class connection request_id async_query(std::string sql, query_handler handler, query_options options = {}) { const auto self = state_; state::publication publishing(*self); - if (!publishing) - { + if (!publishing) { return reject_terminated(std::move(handler)); } auto operation = make_command(command_kind::query, options); @@ -1904,8 +1629,7 @@ class connection request_id async_prepare(std::string sql, prepare_handler handler, query_options options = {}) { const auto self = state_; state::publication publishing(*self); - if (!publishing) - { + if (!publishing) { return reject_terminated(std::move(handler)); } auto operation = make_command(command_kind::prepare, options); @@ -1918,8 +1642,7 @@ class connection query_handler handler, query_options options = {}) { const auto self = state_; state::publication publishing(*self); - if (!publishing) - { + if (!publishing) { return reject_terminated(std::move(handler)); } auto operation = make_command(command_kind::execute, options); @@ -1933,8 +1656,7 @@ class connection const prepared_statement& statement, result_handler handler = {}, query_options options = {}) { const auto self = state_; state::publication publishing(*self); - if (!publishing) - { + if (!publishing) { return reject_terminated(std::move(handler)); } auto operation = make_command(command_kind::close_statement, options); @@ -1962,8 +1684,7 @@ class connection request_id async_reset(result_handler handler, query_options options = {}) { const auto self = state_; state::publication publishing(*self); - if (!publishing) - { + if (!publishing) { return reject_terminated(std::move(handler)); } auto operation = make_command(command_kind::reset, options); @@ -1975,8 +1696,7 @@ class connection void cancel(request_id id) { const auto self = state_; state::publication publishing(*self); - if (!publishing) - { + if (!publishing) { return; } asio::post(self->executor, [self, id] { self->cancel_command(id); }); @@ -1985,8 +1705,7 @@ class connection void close() { const auto self = state_; state::publication publishing(*self); - if (publishing) - { + if (publishing) { self->request_close(); } } diff --git a/include/arknet/mysql/detail/auth.hpp b/include/arknet/mysql/detail/auth.hpp index 0a05a90..7a2be52 100644 --- a/include/arknet/mysql/detail/auth.hpp +++ b/include/arknet/mysql/detail/auth.hpp @@ -14,8 +14,7 @@ namespace arknet::mysql::detail { -enum class auth_status -{ +enum class auth_status { complete, unsupported_plugin, invalid_challenge, @@ -27,14 +26,11 @@ namespace auth_detail { inline bool digest(const EVP_MD* algorithm, std::initializer_list parts, unsigned char* output, unsigned int expected_size) { const auto context = std::unique_ptr(EVP_MD_CTX_new(), &EVP_MD_CTX_free); - if (!context || !algorithm || EVP_DigestInit_ex(context.get(), algorithm, nullptr) != 1) - { + if (!context || !algorithm || EVP_DigestInit_ex(context.get(), algorithm, nullptr) != 1) { return false; } - for (auto part : parts) - { - if (!part.empty() && EVP_DigestUpdate(context.get(), part.data(), part.size()) != 1) - { + for (auto part : parts) { + if (!part.empty() && EVP_DigestUpdate(context.get(), part.data(), part.size()) != 1) { return false; } } @@ -42,8 +38,7 @@ inline bool digest(const EVP_MD* algorithm, std::initializer_list first{}; std::array second{}; std::array third{}; @@ -62,17 +57,14 @@ struct hash_storage inline auth_status make_auth_response( std::string_view plugin, std::string_view password, std::string_view challenge, std::string& output) { const bool native = plugin == "mysql_native_password"; - if (!native && plugin != "caching_sha2_password") - { + if (!native && plugin != "caching_sha2_password") { return auth_status::unsupported_plugin; } - if (password.empty()) - { + if (password.empty()) { output.clear(); return auth_status::complete; } - if (challenge.size() != 20) - { + if (challenge.size() != 20) { return auth_status::invalid_challenge; } const EVP_MD* algorithm = native ? EVP_sha1() : EVP_sha256(); @@ -82,8 +74,7 @@ inline auth_status make_auth_response( return std::string_view(reinterpret_cast(hash.data()), size); }; if (!auth_detail::digest(algorithm, {password}, hashes.first.data(), size) || - !auth_detail::digest(algorithm, {bytes(hashes.first)}, hashes.second.data(), size)) - { + !auth_detail::digest(algorithm, {bytes(hashes.first)}, hashes.second.data(), size)) { return auth_status::crypto_error; } // Native: SHA1(challenge || SHA1(SHA1(password))). @@ -91,13 +82,11 @@ inline auth_status make_auth_response( if (!auth_detail::digest(algorithm, native ? std::initializer_list{challenge, bytes(hashes.second)} : std::initializer_list{bytes(hashes.second), challenge}, - hashes.third.data(), size)) - { + hashes.third.data(), size)) { return auth_status::crypto_error; } std::string response(size, '\0'); - for (unsigned int i = 0; i < size; ++i) - { + for (unsigned int i = 0; i < size; ++i) { response[i] = static_cast(hashes.first[i] ^ hashes.third[i]); } output = std::move(response); diff --git a/include/arknet/mysql/detail/command.hpp b/include/arknet/mysql/detail/command.hpp index 926caf1..ff7fcb5 100644 --- a/include/arknet/mysql/detail/command.hpp +++ b/include/arknet/mysql/detail/command.hpp @@ -22,8 +22,7 @@ namespace arknet::mysql::detail { // Classic command/result payloads, without QUERY_ATTRIBUTES or optional metadata. // https://dev.mysql.com/doc/dev/mysql-server/9.7.2/page_protocol_binary_resultset.html -enum class command_status -{ +enum class command_status { complete, incomplete, invalid, @@ -31,8 +30,7 @@ enum class command_status limit_exceeded }; -enum class result_phase -{ +enum class result_phase { initial, columns, metadata_end, @@ -41,8 +39,7 @@ enum class result_phase failed }; -enum class field_type : std::uint8_t -{ +enum class field_type : std::uint8_t { decimal = 0, tiny = 1, short_integer = 2, @@ -75,15 +72,13 @@ enum class field_type : std::uint8_t inline constexpr std::uint16_t unsigned_column_flag = 0x20; inline constexpr std::uint32_t optional_metadata_capability = 1u << 25; -struct codec_limits -{ +struct codec_limits { std::size_t max_columns = 4096; std::size_t max_parameters = 65535; std::size_t max_bytes = default_message_limit; }; -struct date_value -{ +struct date_value { std::uint16_t year = 0; std::uint8_t month = 0; std::uint8_t day = 0; @@ -91,8 +86,7 @@ struct date_value bool operator==(const date_value&) const = default; }; -struct datetime_value -{ +struct datetime_value { date_value date; std::uint8_t hour = 0; std::uint8_t minute = 0; @@ -102,8 +96,7 @@ struct datetime_value bool operator==(const datetime_value&) const = default; }; -struct time_value -{ +struct time_value { bool negative = false; std::uint32_t days = 0; std::uint8_t hour = 0; @@ -120,8 +113,7 @@ using field_value = std::variant; using row_values = std::vector; -struct column_definition -{ +struct column_definition { std::string catalog; std::string schema; std::string table; @@ -135,8 +127,7 @@ struct column_definition std::uint8_t decimals = 0; }; -struct prepared_statement_ok -{ +struct prepared_statement_ok { std::uint32_t statement_id = 0; std::uint16_t column_count = 0; std::uint16_t parameter_count = 0; @@ -150,8 +141,7 @@ inline command_status command_read_status(read_status status) { } inline bool string_field_type(field_type type) { - switch (type) - { + switch (type) { case field_type::decimal: case field_type::varchar: case field_type::bit: @@ -173,8 +163,7 @@ inline bool string_field_type(field_type type) { } inline unsigned integer_field_bits(field_type type) { - switch (type) - { + switch (type) { case field_type::tiny: return 8; case field_type::short_integer: @@ -199,8 +188,7 @@ inline bool supported_field_type(field_type type) { inline command_status encode_sql_command( std::uint8_t command, std::string_view sql, std::string& output, const codec_limits& limits = {}) { - if (limits.max_bytes == 0 || sql.size() > limits.max_bytes - 1) - { + if (limits.max_bytes == 0 || sql.size() > limits.max_bytes - 1) { return command_status::limit_exceeded; } std::string encoded(1, static_cast(command)); @@ -218,8 +206,7 @@ inline command_status encode_prepare(std::string_view sql, std::string& output, } inline command_status encode_close(std::uint32_t statement_id, std::string& output, const codec_limits& limits = {}) { - if (limits.max_bytes < 5) - { + if (limits.max_bytes < 5) { return command_status::limit_exceeded; } std::string encoded(1, '\x19'); @@ -230,59 +217,47 @@ inline command_status encode_close(std::uint32_t statement_id, std::string& outp inline command_status parse_prepare_ok(std::string_view payload, prepared_statement_ok& output, const codec_limits& limits = {}, std::uint32_t capabilities = capability::protocol_41) { - if ((capabilities & capability::protocol_41) == 0 || (capabilities & optional_metadata_capability) != 0) - { + if ((capabilities & capability::protocol_41) == 0 || (capabilities & optional_metadata_capability) != 0) { return command_status::unsupported; } - if (payload.size() > limits.max_bytes) - { + if (payload.size() > limits.max_bytes) { return command_status::limit_exceeded; } wire_reader reader(payload); std::uint64_t value; - if (reader.read_integer(1, value) != read_status::complete) - { + if (reader.read_integer(1, value) != read_status::complete) { return command_status::incomplete; } - if (value != 0) - { + if (value != 0) { return command_status::invalid; } prepared_statement_ok result; - if (reader.read_integer(4, value) != read_status::complete) - { + if (reader.read_integer(4, value) != read_status::complete) { return command_status::incomplete; } result.statement_id = static_cast(value); - if (reader.read_integer(2, value) != read_status::complete) - { + if (reader.read_integer(2, value) != read_status::complete) { return command_status::incomplete; } result.column_count = static_cast(value); - if (reader.read_integer(2, value) != read_status::complete) - { + if (reader.read_integer(2, value) != read_status::complete) { return command_status::incomplete; } result.parameter_count = static_cast(value); - if (reader.read_integer(1, value) != read_status::complete) - { + if (reader.read_integer(1, value) != read_status::complete) { return command_status::incomplete; } - if (value != 0) - { + if (value != 0) { return command_status::invalid; } - if (reader.read_integer(2, value) != read_status::complete) - { + if (reader.read_integer(2, value) != read_status::complete) { return command_status::incomplete; } result.warnings = static_cast(value); - if (reader.remaining() != 0) - { + if (reader.remaining() != 0) { return command_status::invalid; } - if (result.column_count > limits.max_columns || result.parameter_count > limits.max_parameters) - { + if (result.column_count > limits.max_columns || result.parameter_count > limits.max_parameters) { return command_status::limit_exceeded; } output = result; @@ -291,27 +266,22 @@ inline command_status parse_prepare_ok(std::string_view payload, prepared_statem inline command_status parse_column_count(std::string_view payload, std::size_t& output, const codec_limits& limits = {}, std::uint32_t capabilities = capability::protocol_41) { - if ((capabilities & capability::protocol_41) == 0 || (capabilities & optional_metadata_capability) != 0) - { + if ((capabilities & capability::protocol_41) == 0 || (capabilities & optional_metadata_capability) != 0) { return command_status::unsupported; } - if (payload.size() > limits.max_bytes) - { + if (payload.size() > limits.max_bytes) { return command_status::limit_exceeded; } wire_reader reader(payload); std::optional count; const auto status = reader.read_length(count); - if (status != read_status::complete) - { + if (status != read_status::complete) { return command_read_status(status); } - if (!count || *count == 0 || reader.remaining() != 0) - { + if (!count || *count == 0 || reader.remaining() != 0) { return command_status::invalid; } - if (*count > limits.max_columns) - { + if (*count > limits.max_columns) { return command_status::limit_exceeded; } output = static_cast(*count); @@ -319,33 +289,27 @@ inline command_status parse_column_count(std::string_view payload, std::size_t& } inline command_status parse_eof(std::string_view payload, ok_packet& output, const codec_limits& limits = {}) { - if (payload.size() > limits.max_bytes) - { + if (payload.size() > limits.max_bytes) { return command_status::limit_exceeded; } wire_reader reader(payload); std::uint64_t value; - if (reader.read_integer(1, value) != read_status::complete) - { + if (reader.read_integer(1, value) != read_status::complete) { return command_status::incomplete; } - if (value != 0xfe) - { + if (value != 0xfe) { return command_status::invalid; } ok_packet result; - if (reader.read_integer(2, value) != read_status::complete) - { + if (reader.read_integer(2, value) != read_status::complete) { return command_status::incomplete; } result.warnings = static_cast(value); - if (reader.read_integer(2, value) != read_status::complete) - { + if (reader.read_integer(2, value) != read_status::complete) { return command_status::incomplete; } result.status_flags = static_cast(value); - if (reader.remaining() != 0) - { + if (reader.remaining() != 0) { return command_status::invalid; } output = std::move(result); @@ -354,66 +318,53 @@ inline command_status parse_eof(std::string_view payload, ok_packet& output, con inline command_status parse_column( std::string_view payload, column_definition& output, const codec_limits& limits = {}) { - if (payload.size() > limits.max_bytes) - { + if (payload.size() > limits.max_bytes) { return command_status::limit_exceeded; } wire_reader reader(payload); column_definition result; for (auto* target : - {&result.catalog, &result.schema, &result.table, &result.original_table, &result.name, &result.original_name}) - { + {&result.catalog, &result.schema, &result.table, &result.original_table, &result.name, &result.original_name}) { const auto status = required_string(reader, *target); - if (status != handshake_status::complete) - { + if (status != handshake_status::complete) { return status == handshake_status::incomplete ? command_status::incomplete : command_status::invalid; } } std::uint64_t value; const auto status = required_length(reader, value); - if (status != handshake_status::complete) - { + if (status != handshake_status::complete) { return status == handshake_status::incomplete ? command_status::incomplete : command_status::invalid; } - if (value != 12 || result.catalog != "def") - { + if (value != 12 || result.catalog != "def") { return command_status::invalid; } - if (reader.read_integer(2, value) != read_status::complete) - { + if (reader.read_integer(2, value) != read_status::complete) { return command_status::incomplete; } result.character_set = static_cast(value); - if (reader.read_integer(4, value) != read_status::complete) - { + if (reader.read_integer(4, value) != read_status::complete) { return command_status::incomplete; } result.column_length = static_cast(value); - if (reader.read_integer(1, value) != read_status::complete) - { + if (reader.read_integer(1, value) != read_status::complete) { return command_status::incomplete; } result.type = static_cast(value); - if (reader.read_integer(2, value) != read_status::complete) - { + if (reader.read_integer(2, value) != read_status::complete) { return command_status::incomplete; } result.flags = static_cast(value); - if (reader.read_integer(1, value) != read_status::complete) - { + if (reader.read_integer(1, value) != read_status::complete) { return command_status::incomplete; } result.decimals = static_cast(value); - if (reader.read_integer(2, value) != read_status::complete) - { + if (reader.read_integer(2, value) != read_status::complete) { return command_status::incomplete; } - if (value != 0 || reader.remaining() != 0) - { + if (value != 0 || reader.remaining() != 0) { return command_status::invalid; } - if (!supported_field_type(result.type)) - { + if (!supported_field_type(result.type)) { return command_status::unsupported; } output = std::move(result); @@ -430,8 +381,7 @@ inline bool valid_time_parts(unsigned hour, unsigned minute, unsigned second, st } inline bool parse_digits(std::string_view text, std::uint64_t& output) { - if (text.empty()) - { + if (text.empty()) { return false; } const auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), output); @@ -439,14 +389,12 @@ inline bool parse_digits(std::string_view text, std::uint64_t& output) { } inline bool parse_text_date(std::string_view text, date_value& output) { - if (text.size() != 10 || text[4] != '-' || text[7] != '-') - { + if (text.size() != 10 || text[4] != '-' || text[7] != '-') { return false; } std::uint64_t year, month, day; if (!parse_digits(text.substr(0, 4), year) || !parse_digits(text.substr(5, 2), month) || - !parse_digits(text.substr(8, 2), day) || year > 9999 || month > 12 || day > 31) - { + !parse_digits(text.substr(8, 2), day) || year > 9999 || month > 12 || day > 31) { return false; } output = {static_cast(year), static_cast(month), static_cast(day)}; @@ -455,21 +403,17 @@ inline bool parse_text_date(std::string_view text, date_value& output) { inline bool parse_text_microseconds(std::string_view text, std::uint32_t& output) { output = 0; - if (text.empty()) - { + if (text.empty()) { return true; } - if (text.size() < 2 || text.size() > 7 || text.front() != '.') - { + if (text.size() < 2 || text.size() > 7 || text.front() != '.') { return false; } std::uint64_t microsecond; - if (!parse_digits(text.substr(1), microsecond)) - { + if (!parse_digits(text.substr(1), microsecond)) { return false; } - for (std::size_t width = text.size() - 1; width < 6; ++width) - { + for (std::size_t width = text.size() - 1; width < 6; ++width) { microsecond *= 10; } output = static_cast(microsecond); @@ -477,8 +421,7 @@ inline bool parse_text_microseconds(std::string_view text, std::uint32_t& output } inline bool parse_text_datetime(std::string_view text, datetime_value& output) { - if (text.size() < 19 || text[10] != ' ' || text[13] != ':' || text[16] != ':') - { + if (text.size() < 19 || text[10] != ' ' || text[13] != ':' || text[16] != ':') { return false; } datetime_value value; @@ -486,8 +429,7 @@ inline bool parse_text_datetime(std::string_view text, datetime_value& output) { if (!parse_text_date(text.substr(0, 10), value.date) || !parse_digits(text.substr(11, 2), hour) || !parse_digits(text.substr(14, 2), minute) || !parse_digits(text.substr(17, 2), second) || !parse_text_microseconds(text.substr(19), value.microsecond) || - !valid_time_parts(hour, minute, second, value.microsecond)) - { + !valid_time_parts(hour, minute, second, value.microsecond)) { return false; } value.hour = static_cast(hour); @@ -499,14 +441,12 @@ inline bool parse_text_datetime(std::string_view text, datetime_value& output) { inline bool parse_text_time(std::string_view text, time_value& output) { time_value value; - if (!text.empty() && text.front() == '-') - { + if (!text.empty() && text.front() == '-') { value.negative = true; text.remove_prefix(1); } const auto colon = text.find(':'); - if (colon == std::string_view::npos || colon < 2 || text.size() < colon + 6 || text[colon + 3] != ':') - { + if (colon == std::string_view::npos || colon < 2 || text.size() < colon + 6 || text[colon + 3] != ':') { return false; } std::uint64_t hours, minute, second; @@ -514,8 +454,7 @@ inline bool parse_text_time(std::string_view text, time_value& output) { !parse_digits(text.substr(colon + 4, 2), second) || !parse_text_microseconds(text.substr(colon + 6), value.microsecond) || hours / 24 > std::numeric_limits::max() || - !valid_time_parts(hours % 24, minute, second, value.microsecond)) - { + !valid_time_parts(hours % 24, minute, second, value.microsecond)) { return false; } value.days = static_cast(hours / 24); @@ -540,80 +479,56 @@ inline bool integer_in_range(std::int64_t value, unsigned bits) { inline command_status text_field(std::string_view text, const column_definition& column, field_value& output) { const auto bits = integer_field_bits(column.type); - if (bits != 0) - { - if (unsigned_field(column)) - { + if (bits != 0) { + if (unsigned_field(column)) { std::uint64_t value; - if (!parse_digits(text, value) || !integer_in_range(value, bits)) - { + if (!parse_digits(text, value) || !integer_in_range(value, bits)) { return command_status::invalid; } output = value; - } - else - { - if (text.empty()) - { + } else { + if (text.empty()) { return command_status::invalid; } std::int64_t value; const auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), value); - if (error != std::errc() || end != text.data() + text.size() || !integer_in_range(value, bits)) - { + if (error != std::errc() || end != text.data() + text.size() || !integer_in_range(value, bits)) { return command_status::invalid; } output = value; } - } - else if (column.type == field_type::double_number || column.type == field_type::float_number) - { - if (text.empty()) - { + } else if (column.type == field_type::double_number || column.type == field_type::float_number) { + if (text.empty()) { return command_status::invalid; } double value; const auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), value); if (error != std::errc() || end != text.data() + text.size() || !std::isfinite(value) || - (column.type == field_type::float_number && std::abs(value) > std::numeric_limits::max())) - { + (column.type == field_type::float_number && std::abs(value) > std::numeric_limits::max())) { return command_status::invalid; } output = value; - } - else if (string_field_type(column.type)) - { + } else if (string_field_type(column.type)) { output = std::string(text); - } - else if (column.type == field_type::date) - { + } else if (column.type == field_type::date) { date_value value; - if (!parse_text_date(text, value)) - { + if (!parse_text_date(text, value)) { return command_status::invalid; } output = value; - } - else if (column.type == field_type::datetime || column.type == field_type::timestamp) - { + } else if (column.type == field_type::datetime || column.type == field_type::timestamp) { datetime_value value; - if (!parse_text_datetime(text, value)) - { + if (!parse_text_datetime(text, value)) { return command_status::invalid; } output = value; - } - else if (column.type == field_type::time) - { + } else if (column.type == field_type::time) { time_value value; - if (!parse_text_time(text, value)) - { + if (!parse_text_time(text, value)) { return command_status::invalid; } output = value; - } - else - { + } else { return column.type == field_type::null ? command_status::invalid : command_status::unsupported; } return command_status::complete; @@ -621,42 +536,34 @@ inline command_status text_field(std::string_view text, const column_definition& inline command_status parse_text_row(std::string_view payload, std::span columns, row_values& output, const codec_limits& limits = {}) { - if (columns.size() > limits.max_columns || payload.size() > limits.max_bytes) - { + if (columns.size() > limits.max_columns || payload.size() > limits.max_bytes) { return command_status::limit_exceeded; } - if (columns.empty()) - { + if (columns.empty()) { return command_status::invalid; } wire_reader reader(payload); row_values row; row.reserve(columns.size()); - for (const auto& column : columns) - { + for (const auto& column : columns) { std::optional text; const auto status = reader.read_string(text); - if (status != read_status::complete) - { + if (status != read_status::complete) { return command_read_status(status); } - if (!supported_field_type(column.type)) - { + if (!supported_field_type(column.type)) { return command_status::unsupported; } field_value value; - if (text) - { + if (text) { const auto field_status = text_field(*text, column, value); - if (field_status != command_status::complete) - { + if (field_status != command_status::complete) { return field_status; } } row.push_back(std::move(value)); } - if (reader.remaining() != 0) - { + if (reader.remaining() != 0) { return command_status::invalid; } output = std::move(row); @@ -665,32 +572,26 @@ inline command_status parse_text_row(std::string_view payload, std::span(length), bytes) != read_status::complete) - { + if (reader.read_bytes(static_cast(length), bytes) != read_status::complete) { return command_status::incomplete; } wire_reader parts(bytes); std::uint64_t value = 0; - if (is_time) - { + if (is_time) { time_value time; - if (length != 0) - { + if (length != 0) { parts.read_integer(1, value); - if (value > 1) - { + if (value > 1) { return command_status::invalid; } time.negative = value == 1; @@ -702,23 +603,18 @@ inline command_status binary_temporal(wire_reader& reader, field_type type, fiel time.minute = static_cast(value); parts.read_integer(1, value); time.second = static_cast(value); - if (length == 12) - { + if (length == 12) { parts.read_integer(4, value); time.microsecond = static_cast(value); } } - if (!valid_time_parts(time.hour, time.minute, time.second, time.microsecond)) - { + if (!valid_time_parts(time.hour, time.minute, time.second, time.microsecond)) { return command_status::invalid; } output = time; - } - else - { + } else { datetime_value datetime; - if (length >= 4) - { + if (length >= 4) { parts.read_integer(2, value); datetime.date.year = static_cast(value); parts.read_integer(1, value); @@ -726,8 +622,7 @@ inline command_status binary_temporal(wire_reader& reader, field_type type, fiel parts.read_integer(1, value); datetime.date.day = static_cast(value); } - if (length >= 7) - { + if (length >= 7) { parts.read_integer(1, value); datetime.hour = static_cast(value); parts.read_integer(1, value); @@ -735,14 +630,12 @@ inline command_status binary_temporal(wire_reader& reader, field_type type, fiel parts.read_integer(1, value); datetime.second = static_cast(value); } - if (length == 11) - { + if (length == 11) { parts.read_integer(4, value); datetime.microsecond = static_cast(value); } if (!valid_date(datetime.date) || - !valid_time_parts(datetime.hour, datetime.minute, datetime.second, datetime.microsecond)) - { + !valid_time_parts(datetime.hour, datetime.minute, datetime.second, datetime.microsecond)) { return command_status::invalid; } output = type == field_type::date ? field_value(datetime.date) : field_value(datetime); @@ -752,72 +645,56 @@ inline command_status binary_temporal(wire_reader& reader, field_type type, fiel inline command_status binary_field(wire_reader& reader, const column_definition& column, field_value& output) { const auto bits = integer_field_bits(column.type); - if (bits != 0 || column.type == field_type::float_number || column.type == field_type::double_number) - { + if (bits != 0 || column.type == field_type::float_number || column.type == field_type::double_number) { const auto width = column.type == field_type::int24 || column.type == field_type::float_number ? 4 : column.type == field_type::double_number ? 8 : bits / 8; std::uint64_t value; const auto status = reader.read_integer(width, value); - if (status != read_status::complete) - { + if (status != read_status::complete) { return command_read_status(status); } - if (bits != 0) - { - if (unsigned_field(column)) - { - if (!integer_in_range(value, bits)) - { + if (bits != 0) { + if (unsigned_field(column)) { + if (!integer_in_range(value, bits)) { return command_status::invalid; } output = value; - } - else - { - if (width != 8 && (value & (std::uint64_t(1) << (width * 8 - 1))) != 0) - { + } else { + if (width != 8 && (value & (std::uint64_t(1) << (width * 8 - 1))) != 0) { value |= std::numeric_limits::max() << (width * 8); } const auto signed_value = std::bit_cast(value); - if (!integer_in_range(signed_value, bits)) - { + if (!integer_in_range(signed_value, bits)) { return command_status::invalid; } output = signed_value; } - } - else - { + } else { const double number = column.type == field_type::float_number ? static_cast(std::bit_cast(static_cast(value))) : std::bit_cast(value); - if (!std::isfinite(number)) - { + if (!std::isfinite(number)) { return command_status::invalid; } output = number; } return command_status::complete; } - if (string_field_type(column.type)) - { + if (string_field_type(column.type)) { std::optional text; const auto status = reader.read_string(text); - if (status != read_status::complete) - { + if (status != read_status::complete) { return command_read_status(status); } - if (!text) - { + if (!text) { return command_status::invalid; // Binary NULL belongs in the bitmap. } output = std::string(*text); return command_status::complete; } if (column.type == field_type::date || column.type == field_type::datetime || - column.type == field_type::timestamp || column.type == field_type::time) - { + column.type == field_type::timestamp || column.type == field_type::time) { return binary_temporal(reader, column.type, output); } return column.type == field_type::null ? command_status::invalid : command_status::unsupported; @@ -825,57 +702,46 @@ inline command_status binary_field(wire_reader& reader, const column_definition& inline command_status parse_binary_row(std::string_view payload, std::span columns, row_values& output, const codec_limits& limits = {}) { - if (columns.size() > limits.max_columns || payload.size() > limits.max_bytes) - { + if (columns.size() > limits.max_columns || payload.size() > limits.max_bytes) { return command_status::limit_exceeded; } - if (columns.empty()) - { + if (columns.empty()) { return command_status::invalid; } wire_reader reader(payload); std::uint64_t header; - if (reader.read_integer(1, header) != read_status::complete) - { + if (reader.read_integer(1, header) != read_status::complete) { return command_status::incomplete; } - if (header != 0) - { + if (header != 0) { return command_status::invalid; } // Divide before adding so user-selected limits cannot overflow size_t. const auto bitmap_size = columns.size() / 8 + (columns.size() % 8 + 9) / 8; std::string_view bitmap; - if (reader.read_bytes(bitmap_size, bitmap) != read_status::complete) - { + if (reader.read_bytes(bitmap_size, bitmap) != read_status::complete) { return command_status::incomplete; } - if ((static_cast(bitmap[0]) & 3) != 0) - { + if ((static_cast(bitmap[0]) & 3) != 0) { return command_status::invalid; } row_values row; row.reserve(columns.size()); - for (std::size_t index = 0; index < columns.size(); ++index) - { - if (!supported_field_type(columns[index].type)) - { + for (std::size_t index = 0; index < columns.size(); ++index) { + if (!supported_field_type(columns[index].type)) { return command_status::unsupported; } const auto bit = index + 2; field_value value; - if ((static_cast(bitmap[bit / 8]) & (1u << (bit % 8))) == 0) - { + if ((static_cast(bitmap[bit / 8]) & (1u << (bit % 8))) == 0) { const auto status = binary_field(reader, columns[index], value); - if (status != command_status::complete) - { + if (status != command_status::complete) { return status; } } row.push_back(std::move(value)); } - if (reader.remaining() != 0) - { + if (reader.remaining() != 0) { return command_status::invalid; } output = std::move(row); @@ -889,60 +755,43 @@ inline field_type parameter_type(const field_value& value) { } inline command_status append_parameter(std::string& encoded, const field_value& value) { - if (value.valueless_by_exception()) - { + if (value.valueless_by_exception()) { return command_status::invalid; } - if (const auto* integer = std::get_if(&value)) - { + if (const auto* integer = std::get_if(&value)) { append_integer(encoded, std::bit_cast(*integer), 8); - } - else if (const auto* integer = std::get_if(&value)) - { + } else if (const auto* integer = std::get_if(&value)) { append_integer(encoded, *integer, 8); - } - else if (const auto* number = std::get_if(&value)) - { - if (!std::isfinite(*number)) - { + } else if (const auto* number = std::get_if(&value)) { + if (!std::isfinite(*number)) { return command_status::invalid; } append_integer(encoded, std::bit_cast(*number), 8); - } - else if (const auto* text = std::get_if(&value)) - { + } else if (const auto* text = std::get_if(&value)) { append_string(encoded, *text); - } - else if (const auto* time = std::get_if(&value)) - { - if (!valid_time_parts(time->hour, time->minute, time->second, time->microsecond)) - { + } else if (const auto* time = std::get_if(&value)) { + if (!valid_time_parts(time->hour, time->minute, time->second, time->microsecond)) { return command_status::invalid; } const unsigned size = time->microsecond != 0 ? 12 : time->days != 0 || time->hour != 0 || time->minute != 0 || time->second != 0 ? 8 : 0; encoded.push_back(static_cast(size)); - if (size != 0) - { + if (size != 0) { encoded.push_back(time->negative ? 1 : 0); append_integer(encoded, time->days, 4); encoded.push_back(static_cast(time->hour)); encoded.push_back(static_cast(time->minute)); encoded.push_back(static_cast(time->second)); - if (size == 12) - { + if (size == 12) { append_integer(encoded, time->microsecond, 4); } } - } - else if (std::holds_alternative(value) || std::holds_alternative(value)) - { + } else if (std::holds_alternative(value) || std::holds_alternative(value)) { const auto date_only = std::holds_alternative(value); const auto datetime = date_only ? datetime_value{std::get(value)} : std::get(value); if (!valid_date(datetime.date) || - !valid_time_parts(datetime.hour, datetime.minute, datetime.second, datetime.microsecond)) - { + !valid_time_parts(datetime.hour, datetime.minute, datetime.second, datetime.microsecond)) { return command_status::invalid; } const unsigned size = datetime.microsecond != 0 ? 11 @@ -950,20 +799,17 @@ inline command_status append_parameter(std::string& encoded, const field_value& : datetime.date.year != 0 || datetime.date.month != 0 || datetime.date.day != 0 ? 4 : 0; encoded.push_back(static_cast(size)); - if (size >= 4) - { + if (size >= 4) { append_integer(encoded, datetime.date.year, 2); encoded.push_back(static_cast(datetime.date.month)); encoded.push_back(static_cast(datetime.date.day)); } - if (size >= 7) - { + if (size >= 7) { encoded.push_back(static_cast(datetime.hour)); encoded.push_back(static_cast(datetime.minute)); encoded.push_back(static_cast(datetime.second)); } - if (size == 11) - { + if (size == 11) { append_integer(encoded, datetime.microsecond, 4); } } @@ -972,53 +818,43 @@ inline command_status append_parameter(std::string& encoded, const field_value& inline command_status encode_execute(std::uint32_t statement_id, std::span parameters, std::string& output, const codec_limits& limits = {}) { - if (parameters.size() > limits.max_parameters || parameters.size() > 65535 || limits.max_bytes < 10) - { + if (parameters.size() > limits.max_parameters || parameters.size() > 65535 || limits.max_bytes < 10) { return command_status::limit_exceeded; } std::string encoded(1, '\x17'); append_integer(encoded, statement_id, 4); encoded.push_back('\0'); // No server-side cursor requested. append_integer(encoded, 1, 4); - if (!parameters.empty()) - { + if (!parameters.empty()) { const auto bitmap_size = (parameters.size() + 7) / 8; - if (bitmap_size + 1 + parameters.size() * 2 > limits.max_bytes - encoded.size()) - { + if (bitmap_size + 1 + parameters.size() * 2 > limits.max_bytes - encoded.size()) { return command_status::limit_exceeded; } const auto bitmap_offset = encoded.size(); encoded.append(bitmap_size, '\0'); encoded.push_back(1); // Always send current types; no hidden cached binding state. - for (std::size_t index = 0; index < parameters.size(); ++index) - { + for (std::size_t index = 0; index < parameters.size(); ++index) { const auto& parameter = parameters[index]; - if (parameter.valueless_by_exception()) - { + if (parameter.valueless_by_exception()) { return command_status::invalid; } - if (std::holds_alternative(parameter)) - { + if (std::holds_alternative(parameter)) { encoded[bitmap_offset + index / 8] = static_cast( static_cast(encoded[bitmap_offset + index / 8]) | (1u << (index % 8))); } encoded.push_back(static_cast(parameter_type(parameter))); encoded.push_back(std::holds_alternative(parameter) ? static_cast(0x80) : 0); } - for (const auto& parameter : parameters) - { + for (const auto& parameter : parameters) { const auto* text = std::get_if(¶meter); - if (text != nullptr && text->size() > limits.max_bytes - encoded.size()) - { + if (text != nullptr && text->size() > limits.max_bytes - encoded.size()) { return command_status::limit_exceeded; } const auto status = append_parameter(encoded, parameter); - if (status != command_status::complete) - { + if (status != command_status::complete) { return status; } - if (encoded.size() > limits.max_bytes) - { + if (encoded.size() > limits.max_bytes) { return command_status::limit_exceeded; } } diff --git a/include/arknet/mysql/detail/handshake.hpp b/include/arknet/mysql/detail/handshake.hpp index fcbb051..1272267 100644 --- a/include/arknet/mysql/detail/handshake.hpp +++ b/include/arknet/mysql/detail/handshake.hpp @@ -46,16 +46,14 @@ inline constexpr std::uint32_t multi_factor_authentication = 1u << 28; inline constexpr std::uint16_t server_session_state_changed = 0x4000; -enum class handshake_status -{ +enum class handshake_status { complete, incomplete, invalid, unsupported }; -struct server_greeting -{ +struct server_greeting { std::string server_version; std::uint32_t connection_id = 0; std::uint32_t capabilities = 0; @@ -68,111 +66,87 @@ struct server_greeting inline handshake_status parse_greeting(std::string_view payload, server_greeting& output) { wire_reader reader(payload); std::uint64_t value; - if (reader.read_integer(1, value) != read_status::complete) - { + if (reader.read_integer(1, value) != read_status::complete) { return handshake_status::incomplete; } - if (value != 10) - { + if (value != 10) { return handshake_status::unsupported; } server_greeting result; std::string_view text; - if (reader.read_null_terminated(text) != read_status::complete) - { + if (reader.read_null_terminated(text) != read_status::complete) { return handshake_status::incomplete; } result.server_version = text; - if (reader.read_integer(4, value) != read_status::complete) - { + if (reader.read_integer(4, value) != read_status::complete) { return handshake_status::incomplete; } result.connection_id = static_cast(value); - if (reader.read_bytes(8, text) != read_status::complete) - { + if (reader.read_bytes(8, text) != read_status::complete) { return handshake_status::incomplete; } result.auth_plugin_data = text; - if (reader.read_integer(1, value) != read_status::complete) - { + if (reader.read_integer(1, value) != read_status::complete) { return handshake_status::incomplete; } - if (value != 0) - { + if (value != 0) { return handshake_status::invalid; } - if (reader.read_integer(2, value) != read_status::complete) - { + if (reader.read_integer(2, value) != read_status::complete) { return handshake_status::incomplete; } result.capabilities = static_cast(value); - if ((result.capabilities & capability::protocol_41) == 0) - { + if ((result.capabilities & capability::protocol_41) == 0) { return handshake_status::unsupported; } - if (reader.read_integer(1, value) != read_status::complete) - { + if (reader.read_integer(1, value) != read_status::complete) { return handshake_status::incomplete; } result.character_set = static_cast(value); - if (reader.read_integer(2, value) != read_status::complete) - { + if (reader.read_integer(2, value) != read_status::complete) { return handshake_status::incomplete; } result.status_flags = static_cast(value); - if (reader.read_integer(2, value) != read_status::complete) - { + if (reader.read_integer(2, value) != read_status::complete) { return handshake_status::incomplete; } result.capabilities |= static_cast(value) << 16; - if (reader.read_integer(1, value) != read_status::complete) - { + if (reader.read_integer(1, value) != read_status::complete) { return handshake_status::incomplete; } const auto auth_size = static_cast(value); - if (reader.read_bytes(10, text) != read_status::complete) - { + if (reader.read_bytes(10, text) != read_status::complete) { return handshake_status::incomplete; } - if (std::any_of(text.begin(), text.end(), [](char byte) { return byte != 0; })) - { + if (std::any_of(text.begin(), text.end(), [](char byte) { return byte != 0; })) { return handshake_status::invalid; } - if ((result.capabilities & (capability::secure_connection | capability::plugin_auth)) == 0) - { + if ((result.capabilities & (capability::secure_connection | capability::plugin_auth)) == 0) { return handshake_status::unsupported; } const bool plugin_auth = (result.capabilities & capability::plugin_auth) != 0; - if ((!plugin_auth && auth_size != 0) || (plugin_auth && auth_size != 0 && auth_size < 8)) - { + if ((!plugin_auth && auth_size != 0) || (plugin_auth && auth_size != 0 && auth_size < 8)) { return handshake_status::invalid; } // The declared count includes all opaque bytes actually emitted by MySQL. // A zero count in the legacy format leaves the fixed 13-byte suffix. const auto suffix_size = auth_size == 0 ? 13 : auth_size - 8; - if (reader.read_bytes(suffix_size, text) != read_status::complete) - { + if (reader.read_bytes(suffix_size, text) != read_status::complete) { return handshake_status::incomplete; } result.auth_plugin_data.append(text); - if (plugin_auth) - { - if (reader.read_null_terminated(text) != read_status::complete) - { + if (plugin_auth) { + if (reader.read_null_terminated(text) != read_status::complete) { return handshake_status::incomplete; } - if (text.empty()) - { + if (text.empty()) { return handshake_status::invalid; } result.auth_plugin_name = text; - } - else - { + } else { result.auth_plugin_name = "mysql_native_password"; } - if (reader.remaining() != 0) - { + if (reader.remaining() != 0) { return handshake_status::invalid; } output = std::move(result); @@ -182,24 +156,20 @@ inline handshake_status parse_greeting(std::string_view payload, server_greeting // Known password methods use a 20-byte scramble, optionally followed by its // protocol NUL. Other plugins keep their opaque data and need their own method. inline handshake_status password_scramble(std::string_view plugin, std::string_view data, std::string_view& output) { - if (plugin != "mysql_native_password" && plugin != "caching_sha2_password" && plugin != "sha256_password") - { + if (plugin != "mysql_native_password" && plugin != "caching_sha2_password" && plugin != "sha256_password") { return handshake_status::unsupported; } - if (data.size() == 21 && data.back() == '\0') - { + if (data.size() == 21 && data.back() == '\0') { data.remove_suffix(1); } - if (data.size() != 20) - { + if (data.size() != 20) { return handshake_status::invalid; } output = data; return handshake_status::complete; } -struct handshake_response -{ +struct handshake_response { std::uint32_t capabilities = capability::protocol_41 | capability::secure_connection; std::uint32_t max_packet_size = static_cast(default_message_limit); std::uint8_t character_set = 45; @@ -219,12 +189,10 @@ inline handshake_status validate_handshake_capabilities(std::uint32_t flags) { capability::connect_attrs | capability::plugin_auth_lenenc_client_data | capability::session_track | capability::deprecate_eof; if ((flags & capability::protocol_41) == 0 || (flags & ~supported) != 0 || - (flags & (capability::secure_connection | capability::plugin_auth)) == 0) - { + (flags & (capability::secure_connection | capability::plugin_auth)) == 0) { return handshake_status::unsupported; } - if ((flags & capability::plugin_auth_lenenc_client_data) != 0 && (flags & capability::plugin_auth) == 0) - { + if ((flags & capability::plugin_auth_lenenc_client_data) != 0 && (flags & capability::plugin_auth) == 0) { return handshake_status::invalid; } return handshake_status::complete; @@ -234,12 +202,10 @@ inline handshake_status validate_handshake_capabilities(std::uint32_t flags) { inline handshake_status encode_ssl_request( std::uint32_t capabilities, std::uint32_t max_packet_size, std::uint8_t character_set, std::string& output) { const auto status = validate_handshake_capabilities(capabilities); - if (status != handshake_status::complete) - { + if (status != handshake_status::complete) { return status; } - if ((capabilities & capability::ssl) == 0 || max_packet_size == 0) - { + if ((capabilities & capability::ssl) == 0 || max_packet_size == 0) { return handshake_status::invalid; } std::string encoded; @@ -254,8 +220,7 @@ inline handshake_status encode_ssl_request( inline handshake_status encode_handshake_response(const handshake_response& response, std::string& output) { const auto flags = response.capabilities; const auto status = validate_handshake_capabilities(flags); - if (status != handshake_status::complete) - { + if (status != handshake_status::complete) { return status; } const bool lenenc_auth = (flags & capability::plugin_auth_lenenc_client_data) != 0; @@ -267,8 +232,7 @@ inline handshake_status encode_handshake_response(const handshake_response& resp response.auth_plugin_name.find('\0') != std::string_view::npos || (!has_database && !response.database.empty()) || (!has_plugin && !response.auth_plugin_name.empty()) || (has_plugin && response.auth_plugin_name.empty()) || (!has_attributes && !response.attributes.empty()) || - (!lenenc_auth && response.auth_response.size() > 255)) - { + (!lenenc_auth && response.auth_response.size() > 255)) { return handshake_status::invalid; } std::string encoded; @@ -278,30 +242,23 @@ inline handshake_status encode_handshake_response(const handshake_response& resp encoded.append(23, '\0'); encoded.append(response.username); encoded.push_back('\0'); - if (lenenc_auth) - { + if (lenenc_auth) { append_length(encoded, response.auth_response.size()); - } - else - { + } else { encoded.push_back(static_cast(response.auth_response.size())); } encoded.append(response.auth_response); - if (has_database) - { + if (has_database) { encoded.append(response.database); encoded.push_back('\0'); } - if (has_plugin) - { + if (has_plugin) { encoded.append(response.auth_plugin_name); encoded.push_back('\0'); } - if (has_attributes) - { + if (has_attributes) { std::string attributes; - for (const auto& [key, value] : response.attributes) - { + for (const auto& [key, value] : response.attributes) { append_string(attributes, key); append_string(attributes, value); } @@ -311,8 +268,7 @@ inline handshake_status encode_handshake_response(const handshake_response& resp return handshake_status::complete; } -struct ok_packet -{ +struct ok_packet { std::uint64_t affected_rows = 0; std::uint64_t last_insert_id = 0; std::uint16_t status_flags = 0; @@ -324,12 +280,10 @@ struct ok_packet inline handshake_status required_length(wire_reader& reader, std::uint64_t& output) { std::optional value; const auto status = reader.read_length(value); - if (status == read_status::incomplete) - { + if (status == read_status::incomplete) { return handshake_status::incomplete; } - if (status == read_status::invalid || !value) - { + if (status == read_status::invalid || !value) { return handshake_status::invalid; } output = *value; @@ -339,12 +293,10 @@ inline handshake_status required_length(wire_reader& reader, std::uint64_t& outp inline handshake_status required_string(wire_reader& reader, std::string& output) { std::optional value; const auto status = reader.read_string(value); - if (status == read_status::incomplete) - { + if (status == read_status::incomplete) { return handshake_status::incomplete; } - if (status == read_status::invalid || !value) - { + if (status == read_status::invalid || !value) { return handshake_status::invalid; } output.assign(*value); @@ -352,38 +304,31 @@ inline handshake_status required_string(wire_reader& reader, std::string& output } inline handshake_status parse_ok(std::string_view payload, std::uint32_t capabilities, ok_packet& output) { - if ((capabilities & capability::protocol_41) == 0) - { + if ((capabilities & capability::protocol_41) == 0) { return handshake_status::unsupported; } wire_reader reader(payload); std::uint64_t value; - if (reader.read_integer(1, value) != read_status::complete) - { + if (reader.read_integer(1, value) != read_status::complete) { return handshake_status::incomplete; } - if (value != 0 && (value != 0xfe || (capabilities & capability::deprecate_eof) == 0)) - { + if (value != 0 && (value != 0xfe || (capabilities & capability::deprecate_eof) == 0)) { return handshake_status::invalid; } ok_packet result; auto status = required_length(reader, result.affected_rows); - if (status != handshake_status::complete) - { + if (status != handshake_status::complete) { return status; } status = required_length(reader, result.last_insert_id); - if (status != handshake_status::complete) - { + if (status != handshake_status::complete) { return status; } - if (reader.read_integer(2, value) != read_status::complete) - { + if (reader.read_integer(2, value) != read_status::complete) { return handshake_status::incomplete; } result.status_flags = static_cast(value); - if (reader.read_integer(2, value) != read_status::complete) - { + if (reader.read_integer(2, value) != read_status::complete) { return handshake_status::incomplete; } result.warnings = static_cast(value); @@ -391,32 +336,26 @@ inline handshake_status parse_ok(std::string_view payload, std::uint32_t capabil (capabilities & capability::session_track) != 0 && (result.status_flags & server_session_state_changed) != 0; // MySQL 8/9 net_send_ok stores nonempty info with a length prefix even // without SESSION_TRACK; the published string table is inconsistent. - if (reader.remaining() != 0 || state_changed) - { + if (reader.remaining() != 0 || state_changed) { status = required_string(reader, result.info); - if (status != handshake_status::complete) - { + if (status != handshake_status::complete) { return status; } } - if (state_changed) - { + if (state_changed) { status = required_string(reader, result.session_state); - if (status != handshake_status::complete) - { + if (status != handshake_status::complete) { return status; } } - if (reader.remaining() != 0) - { + if (reader.remaining() != 0) { return handshake_status::invalid; } output = std::move(result); return handshake_status::complete; } -struct error_packet -{ +struct error_packet { std::uint16_t code = 0; std::string sql_state; std::string message; @@ -425,33 +364,26 @@ struct error_packet inline handshake_status parse_error(std::string_view payload, std::uint32_t capabilities, error_packet& output) { wire_reader reader(payload); std::uint64_t value; - if (reader.read_integer(1, value) != read_status::complete) - { + if (reader.read_integer(1, value) != read_status::complete) { return handshake_status::incomplete; } - if (value != 0xff) - { + if (value != 0xff) { return handshake_status::invalid; } - if (reader.read_integer(2, value) != read_status::complete) - { + if (reader.read_integer(2, value) != read_status::complete) { return handshake_status::incomplete; } error_packet result; result.code = static_cast(value); std::string_view text; - if ((capabilities & capability::protocol_41) != 0) - { - if (reader.read_integer(1, value) != read_status::complete) - { + if ((capabilities & capability::protocol_41) != 0) { + if (reader.read_integer(1, value) != read_status::complete) { return handshake_status::incomplete; } - if (value != '#') - { + if (value != '#') { return handshake_status::invalid; } - if (reader.read_bytes(5, text) != read_status::complete) - { + if (reader.read_bytes(5, text) != read_status::complete) { return handshake_status::incomplete; } result.sql_state = text; @@ -462,8 +394,7 @@ inline handshake_status parse_error(std::string_view payload, std::uint32_t capa return handshake_status::complete; } -struct auth_switch_request -{ +struct auth_switch_request { std::string auth_plugin_name; std::string auth_plugin_data; }; @@ -471,26 +402,21 @@ struct auth_switch_request inline handshake_status parse_auth_switch(std::string_view payload, auth_switch_request& output) { wire_reader reader(payload); std::uint64_t value; - if (reader.read_integer(1, value) != read_status::complete) - { + if (reader.read_integer(1, value) != read_status::complete) { return handshake_status::incomplete; } - if (value != 0xfe) - { + if (value != 0xfe) { return handshake_status::invalid; } - if (reader.remaining() == 0) - { + if (reader.remaining() == 0) { return handshake_status::unsupported; // Old Password Authentication request. } auth_switch_request result; std::string_view text; - if (reader.read_null_terminated(text) != read_status::complete) - { + if (reader.read_null_terminated(text) != read_status::complete) { return handshake_status::incomplete; } - if (text.empty()) - { + if (text.empty()) { return handshake_status::invalid; } result.auth_plugin_name = text; diff --git a/include/arknet/mysql/detail/wire.hpp b/include/arknet/mysql/detail/wire.hpp index 7c5c87f..6a5cbaf 100644 --- a/include/arknet/mysql/detail/wire.hpp +++ b/include/arknet/mysql/detail/wire.hpp @@ -16,15 +16,13 @@ namespace arknet::mysql::detail { inline constexpr std::size_t max_packet_payload = 0x00ffffff; inline constexpr std::size_t default_message_limit = 64 * 1024 * 1024; -enum class read_status -{ +enum class read_status { complete, incomplete, invalid }; -class wire_reader -{ +class wire_reader { public: explicit wire_reader(std::string_view input) : input_(input) {} @@ -38,17 +36,14 @@ class wire_reader } read_status read_integer(std::size_t width, std::uint64_t& output) { - if (width == 0 || width > sizeof(output)) - { + if (width == 0 || width > sizeof(output)) { return read_status::invalid; } - if (remaining() < width) - { + if (remaining() < width) { return read_status::incomplete; } std::uint64_t value = 0; - for (std::size_t i = 0; i < width; ++i) - { + for (std::size_t i = 0; i < width; ++i) { value |= std::uint64_t(static_cast(input_[position_ + i])) << (i * 8); } position_ += width; @@ -57,8 +52,7 @@ class wire_reader } read_status read_bytes(std::size_t count, std::string_view& output) { - if (count > remaining()) - { + if (count > remaining()) { return read_status::incomplete; } output = input_.substr(position_, count); @@ -68,8 +62,7 @@ class wire_reader read_status read_null_terminated(std::string_view& output) { const auto end = input_.find('\0', position_); - if (end == std::string_view::npos) - { + if (end == std::string_view::npos) { return read_status::incomplete; } output = input_.substr(position_, end - position_); @@ -79,30 +72,25 @@ class wire_reader // The null marker is distinct from the integer zero and an empty string. read_status read_length(std::optional& output) { - if (remaining() == 0) - { + if (remaining() == 0) { return read_status::incomplete; } const auto marker = static_cast(input_[position_]); - if (marker < 0xfb) - { + if (marker < 0xfb) { ++position_; output = marker; return read_status::complete; } - if (marker == 0xfb) - { + if (marker == 0xfb) { ++position_; output.reset(); return read_status::complete; } - if (marker == 0xff) - { + if (marker == 0xff) { return read_status::invalid; } const std::size_t width = marker == 0xfc ? 2 : marker == 0xfd ? 3 : 8; - if (remaining() - 1 < width) - { + if (remaining() - 1 < width) { return read_status::incomplete; } ++position_; @@ -116,17 +104,14 @@ class wire_reader const auto start = position_; std::optional length; const auto status = read_length(length); - if (status != read_status::complete) - { + if (status != read_status::complete) { return status; } - if (!length) - { + if (!length) { output.reset(); return read_status::complete; } - if (*length > remaining()) - { + if (*length > remaining()) { position_ = start; return read_status::incomplete; } @@ -141,24 +126,18 @@ class wire_reader }; inline void append_integer(std::string& output, std::uint64_t value, std::size_t width) { - for (std::size_t i = 0; i < width; ++i) - { + for (std::size_t i = 0; i < width; ++i) { output.push_back(static_cast(value & 0xff)); value >>= 8; } } inline void append_length(std::string& output, std::optional value) { - if (!value) - { + if (!value) { output.push_back(static_cast(0xfb)); - } - else if (*value < 0xfb) - { + } else if (*value < 0xfb) { output.push_back(static_cast(*value)); - } - else - { + } else { const std::size_t width = *value <= 0xffff ? 2 : *value <= 0xffffff ? 3 : 8; output.push_back(static_cast(width == 2 ? 0xfc : width == 3 ? 0xfd : 0xfe)); append_integer(output, *value, width); @@ -166,8 +145,7 @@ inline void append_length(std::string& output, std::optional valu } inline void append_string(std::string& output, std::optional value) { - if (!value) - { + if (!value) { append_length(output, std::nullopt); return; } @@ -175,8 +153,7 @@ inline void append_string(std::string& output, std::optional v output.append(*value); } -enum class packet_status -{ +enum class packet_status { complete, incomplete, sequence_mismatch, @@ -185,39 +162,32 @@ enum class packet_status // Consumes at most one logical message per call. The caller retains unconsumed // input and calls again for coalesced messages. Partial input never changes output. -class packet_decoder -{ +class packet_decoder { public: explicit packet_decoder(std::size_t message_limit = default_message_limit, std::uint8_t sequence = 0) : message_limit_(message_limit) , expected_sequence_(sequence) {} packet_status consume(std::string_view& input, std::string& output) { - if (failure_ != packet_status::incomplete) - { + if (failure_ != packet_status::incomplete) { return failure_; } - for (;;) - { - if (header_size_ < header_.size()) - { + for (;;) { + if (header_size_ < header_.size()) { const auto count = std::min(input.size(), header_.size() - header_size_); std::copy_n(input.begin(), count, header_.begin() + header_size_); header_size_ += count; input.remove_prefix(count); - if (header_size_ < header_.size()) - { + if (header_size_ < header_.size()) { return packet_status::incomplete; } - if (static_cast(header_[3]) != expected_sequence_) - { + if (static_cast(header_[3]) != expected_sequence_) { return fail(packet_status::sequence_mismatch); } packet_size_ = static_cast(header_[0]) | (std::size_t(static_cast(header_[1])) << 8) | (std::size_t(static_cast(header_[2])) << 16); - if (packet_size_ > message_limit_ - message_.size()) - { + if (packet_size_ > message_limit_ - message_.size()) { return fail(packet_status::message_too_large); } remaining_ = packet_size_; @@ -227,13 +197,11 @@ class packet_decoder message_.append(input.substr(0, count)); input.remove_prefix(count); remaining_ -= count; - if (remaining_ != 0) - { + if (remaining_ != 0) { return packet_status::incomplete; } header_size_ = 0; - if (packet_size_ < max_packet_payload) - { + if (packet_size_ < max_packet_payload) { output = std::move(message_); message_.clear(); return packet_status::complete; @@ -278,27 +246,23 @@ class packet_decoder // Sequence and output are committed only after the entire message is encoded. inline bool encode_message(std::string_view message, std::uint8_t& sequence, std::string& output, std::size_t message_limit = default_message_limit) { - if (message.size() > message_limit) - { + if (message.size() > message_limit) { return false; } const auto packet_count = message.size() / max_packet_payload + 1; std::string encoded; - if (message.size() > encoded.max_size() || packet_count > (encoded.max_size() - message.size()) / 4) - { + if (message.size() > encoded.max_size() || packet_count > (encoded.max_size() - message.size()) / 4) { return false; } encoded.reserve(message.size() + packet_count * 4); auto next_sequence = sequence; - for (;;) - { + for (;;) { const auto count = std::min(message.size(), max_packet_payload); append_integer(encoded, count, 3); encoded.push_back(static_cast(next_sequence++)); encoded.append(message.substr(0, count)); message.remove_prefix(count); - if (count < max_packet_payload) - { + if (count < max_packet_payload) { break; } } diff --git a/include/arknet/mysql/pool.hpp b/include/arknet/mysql/pool.hpp index 2756e9e..8e38f07 100644 --- a/include/arknet/mysql/pool.hpp +++ b/include/arknet/mysql/pool.hpp @@ -26,8 +26,7 @@ namespace arknet::mysql { -struct pool_options -{ +struct pool_options { std::size_t max_connections = 8; std::size_t max_waiters = 1024; std::size_t io_threads = 1; @@ -39,8 +38,7 @@ struct pool_options // Internal return-publication failures do not change the result already // delivered to the caller. A bounded snapshot retains only the last exception. -struct pool_diagnostics -{ +struct pool_diagnostics { std::uint64_t return_submission_failures{}; std::exception_ptr last_return_submission_exception; }; @@ -50,14 +48,12 @@ struct pool_diagnostics // connection. The configured initial database is restored with a quoted USE; // if no initial database is configured, returned connections are discarded. // No statement or transaction is replayed on another connection. -class pool -{ +class pool { struct state; struct waiter; struct lease_control; - struct publication_gate - { + struct publication_gate { std::mutex mutex; std::condition_variable changed; std::size_t publishers = 0; @@ -68,8 +64,7 @@ class pool pool_diagnostics diagnostics; }; - class publication - { + class publication { inline static thread_local publication* current_{}; publication_gate* owner_{}; publication* previous_{}; @@ -77,8 +72,7 @@ class pool public: explicit publication(publication_gate& gate) { std::lock_guard lock(gate.mutex); - if (!gate.terminating) - { + if (!gate.terminating) { ++gate.publishers; owner_ = &gate; previous_ = current_; @@ -90,8 +84,7 @@ class pool publication& operator=(const publication&) = delete; ~publication() { - if (owner_) - { + if (owner_) { current_ = previous_; { std::lock_guard lock(owner_->mutex); @@ -106,10 +99,8 @@ class pool } static bool active_on_this_thread(const publication_gate& gate) noexcept { - for (auto* current = current_; current; current = current->previous_) - { - if (current->owner_ == &gate) - { + for (auto* current = current_; current; current = current->previous_) { + if (current->owner_ == &gate) { return true; } } @@ -117,8 +108,7 @@ class pool } }; - enum class entry_phase - { + enum class entry_phase { connecting, idle, leased, @@ -126,8 +116,7 @@ class pool resetting }; - struct entry - { + struct entry { std::shared_ptr client; entry_phase phase = entry_phase::connecting; std::weak_ptr waiting; @@ -140,15 +129,13 @@ class pool const std::shared_ptr& gate, std::exception_ptr exception) { session->failed_return = true; std::lock_guard lock(gate->mutex); - if (gate->diagnostics.return_submission_failures != (std::numeric_limits::max)()) - { + if (gate->diagnostics.return_submission_failures != (std::numeric_limits::max)()) { ++gate->diagnostics.return_submission_failures; } gate->diagnostics.last_return_submission_exception = std::move(exception); } - struct lease_control - { + struct lease_control { std::shared_ptr session; std::shared_ptr gate; std::mutex mutex; @@ -170,15 +157,13 @@ class pool std::lock_guard lock(mutex); unhealthy = unhealthy || result.outcome_unknown || !session->client->is_open(); --pending; - if (publishing && released && !pending) - { + if (publishing && released && !pending) { returning = std::exchange(drained, {}); discard = unhealthy; deadline = return_time; } } - if (returning) - { + if (returning) { returning(session, discard, deadline); } } @@ -190,8 +175,7 @@ class pool std::chrono::steady_clock::time_point deadline; { std::lock_guard lock(mutex); - if (released) - { + if (released) { return; } released = true; @@ -201,32 +185,27 @@ class pool unhealthy = unhealthy || !expires; return_time = expires.value_or(now); deadline = return_time; - if (!publishing) - { + if (!publishing) { begin_return = {}; drained = {}; return; } starting = std::exchange(begin_return, {}); - if (!pending) - { + if (!pending) { returning = std::exchange(drained, {}); } } - if (starting) - { + if (starting) { starting(session, deadline); } - if (returning) - { + if (returning) { returning(session, unhealthy, deadline); } } }; public: - class lease - { + class lease { friend class pool; std::shared_ptr control_; @@ -236,18 +215,13 @@ class pool template request_id submit(Handler handler, Submit submitter) { auto control = control_; - if (!control) - { - if (handler) - { + if (!control) { + if (handler) { Result result; - if constexpr (std::is_same_v) - { + if constexpr (std::is_same_v) { result.code = connection_errc::not_connected; result.message = "Empty MySQL pool lease"; - } - else - { + } else { result.status.code = connection_errc::not_connected; result.status.message = "Empty MySQL pool lease"; } @@ -256,25 +230,19 @@ class pool return 0; } publication publishing(*control->gate); - if (!publishing) - { + if (!publishing) { return mysql::connection::reject_terminated(std::move(handler)); } std::unique_lock lock(control->mutex); - if (control->released || !control->available()) - { + if (control->released || !control->available()) { const auto id = control->session->client->state_->next_request.fetch_add(1); asio::post(control->session->client->state_->executor, [handler = std::move(handler)]() mutable { - if (handler) - { + if (handler) { Result result; - if constexpr (std::is_same_v) - { + if constexpr (std::is_same_v) { result.code = connection_errc::not_connected; result.message = "MySQL pool lease released or closed"; - } - else - { + } else { result.status.code = connection_errc::not_connected; result.status.message = "MySQL pool lease released or closed"; } @@ -284,28 +252,21 @@ class pool return id; } ++control->pending; - try - { + try { return submitter( *control->session->client, [control, handler = std::move(handler)](Result response) mutable { connection_result status; - if constexpr (std::is_same_v) - { + if constexpr (std::is_same_v) { status = response; - } - else - { + } else { status = response.status; } - if (handler) - { + if (handler) { handler(std::move(response)); } control->finish(status); }); - } - catch (...) - { + } catch (...) { --control->pending; throw; } @@ -318,8 +279,7 @@ class pool lease(lease&& other) noexcept = default; lease& operator=(lease&& other) noexcept { - if (this != &other) - { + if (this != &other) { release(); control_ = std::move(other.control_); } @@ -331,8 +291,7 @@ class pool } explicit operator bool() const noexcept { - if (!control_) - { + if (!control_) { return false; } std::lock_guard lock(control_->mutex); @@ -383,16 +342,13 @@ class pool void cancel(request_id id) { auto control = control_; - if (control) - { + if (control) { publication publishing(*control->gate); - if (!publishing) - { + if (!publishing) { return; } std::lock_guard lock(control->mutex); - if (!control->released && control->available()) - { + if (!control->released && control->available()) { control->session->client->cancel(id); } } @@ -400,23 +356,20 @@ class pool void release() { auto control = control_; - if (control) - { + if (control) { control->release(); } } void discard() { auto control = control_; - if (control) - { + if (control) { control->release(true); } } }; - struct acquire_result - { + struct acquire_result { connection_result status; lease connection; @@ -428,8 +381,7 @@ class pool using acquire_handler = std::function; private: - struct waiter - { + struct waiter { request_id id = 0; acquire_handler handler; std::shared_ptr timer; @@ -439,10 +391,8 @@ class pool bool completed = false; }; - struct state : std::enable_shared_from_this - { - struct child_record - { + struct state : std::enable_shared_from_this { + struct child_record { std::weak_ptr session; std::weak_ptr child; std::shared_ptr statements; @@ -468,15 +418,13 @@ class pool if (!options.max_connections || !options.max_waiters || !options.io_threads || !arknet::detail::checked_deadline(now, options.acquire_timeout) || !arknet::detail::checked_deadline(now, options.reset_timeout) || - !arknet::detail::checked_deadline(now, options.connection.timeout)) - { + !arknet::detail::checked_deadline(now, options.connection.timeout)) { throw std::invalid_argument("MySQL pool limits and deadlines must be positive and representable"); } runtime = std::shared_ptr( new arknet::detail::io_pool(options.io_threads), arknet::detail::iopool_deleter{}); executor = runtime->get(0)->executor(); - if (!runtime->start()) - { + if (!runtime->start()) { throw std::runtime_error("Cannot start MySQL pool IO runtime"); } } @@ -491,10 +439,8 @@ class pool bool reserve() { auto count = admitted.load(); - while (count < options.max_waiters) - { - if (admitted.compare_exchange_weak(count, count + 1)) - { + while (count < options.max_waiters) { + if (admitted.compare_exchange_weak(count, count + 1)) { return true; } } @@ -506,27 +452,21 @@ class pool } bool discard(std::shared_ptr session, bool retry_failed = false) { - if (!contains(session)) - { + if (!contains(session)) { return true; } - if (session->failed_return && !retry_failed) - { + if (session->failed_return && !retry_failed) { return false; } - try - { + try { // Preserve membership until close has actually been published. session->client->close(); - } - catch (...) - { + } catch (...) { record_return_failure(session, gate, std::current_exception()); return false; } std::erase(entries, session); - if (session->return_deadline) - { + if (session->return_deadline) { session->return_deadline->cancel(); } return true; @@ -536,11 +476,9 @@ class pool // entrance. A second allocation failure leaves it indexed and unusable; // another ordinary actor entrance, not a retry loop, may make progress. void recover_failed_returns() { - for (std::size_t index = 0; index != entries.size();) - { + for (std::size_t index = 0; index != entries.size();) { const auto session = entries[index]; - if (session->failed_return && discard(session, true)) - { + if (session->failed_return && discard(session, true)) { continue; } ++index; @@ -548,24 +486,20 @@ class pool } void complete(std::shared_ptr request, acquire_result response) { - if (request->completed) - { + if (request->completed) { return; } request->completed = true; - if (request->timer) - { + if (request->timer) { request->timer->cancel(); } pending.erase(request->id); std::erase(queue, request); - if (std::exchange(request->admitted, false)) - { + if (std::exchange(request->admitted, false)) { --admitted; } auto handler = std::move(request->handler); - if (handler) - { + if (handler) { handler(std::move(response)); } } @@ -584,50 +518,36 @@ class pool }; control->begin_return = [weak = weak_from_this(), gate = gate]( std::shared_ptr current, auto deadline) { - if (current->failed_return) - { + if (current->failed_return) { return; } const auto self = weak.lock(); - try - { - if (self) - { + try { + if (self) { asio::post( self->executor, [self, current, deadline] { self->begin_return(current, deadline); }); - } - else - { + } else { current->client->close(); } - } - catch (...) - { + } catch (...) { const auto failure = std::current_exception(); record_return_failure(current, gate, failure); } }; control->drained = [weak = weak_from_this(), gate = gate]( std::shared_ptr current, bool unhealthy, auto deadline) { - if (current->failed_return) - { + if (current->failed_return) { return; } const auto self = weak.lock(); - try - { - if (self) - { + try { + if (self) { asio::post(self->executor, [self, current, unhealthy, deadline] { self->returned(current, unhealthy, deadline); }); - } - else - { + } else { current->client->close(); } - } - catch (...) - { + } catch (...) { const auto failure = std::current_exception(); record_return_failure(current, gate, failure); } @@ -637,19 +557,16 @@ class pool void acquire(const std::shared_ptr& request) { recover_failed_returns(); - if (closed || close_requested) - { + if (closed || close_requested) { complete(request, {failure(connection_errc::cancelled, "MySQL pool closed"), {}}); return; } - if (!request->admitted) - { + if (!request->admitted) { complete(request, {failure(connection_errc::queue_full, "MySQL pool waiter limit exceeded"), {}}); pump(); return; } - if (std::chrono::steady_clock::now() >= request->deadline) - { + if (std::chrono::steady_clock::now() >= request->deadline) { complete(request, {failure(connection_errc::timeout, "MySQL pool acquire deadline exceeded", asio::error::timed_out), {}}); @@ -659,12 +576,9 @@ class pool request->timer = std::make_shared(executor); request->timer->expires_at(request->deadline); request->timer->async_wait([weak = weak_from_this(), waiting = std::weak_ptr(request)](error_code error) { - if (!error) - { - if (auto self = weak.lock()) - { - if (auto current = waiting.lock()) - { + if (!error) { + if (auto self = weak.lock()) { + if (auto current = waiting.lock()) { self->cancel(current, connection_errc::timeout); } } @@ -677,15 +591,12 @@ class pool void cancel(std::shared_ptr request, connection_errc reason) { recover_failed_returns(); - if (request->completed) - { + if (request->completed) { pump(); return; } - if (auto assigned = request->assigned.lock(); assigned && assigned->phase == entry_phase::connecting) - { - if (assigned->waiting.lock() == request) - { + if (auto assigned = request->assigned.lock(); assigned && assigned->phase == entry_phase::connecting) { + if (assigned->waiting.lock() == request) { discard(assigned); } } @@ -701,8 +612,7 @@ class pool void prune_tracking() { std::erase_if(controls, [](const auto& weak) { return weak.expired(); }); std::erase_if(children, [](const auto& record) { - if (!record.child.expired()) - { + if (!record.child.expired()) { return false; } return std::all_of(record.statements->begin(), record.statements->end(), @@ -712,14 +622,11 @@ class pool void pump() { prune_tracking(); - while (!closed && !close_requested && !queue.empty()) - { - if (std::chrono::steady_clock::now() >= queue.front()->deadline) - { + while (!closed && !close_requested && !queue.empty()) { + if (std::chrono::steady_clock::now() >= queue.front()->deadline) { auto request = queue.front(); if (auto assigned = request->assigned.lock(); - assigned && assigned->phase == entry_phase::connecting && assigned->waiting.lock() == request) - { + assigned && assigned->phase == entry_phase::connecting && assigned->waiting.lock() == request) { discard(assigned); } complete(request, {failure(connection_errc::timeout, "MySQL pool acquire deadline exceeded", @@ -729,13 +636,10 @@ class pool } const auto idle = std::find_if(entries.begin(), entries.end(), [](const auto& session) { return session->phase == entry_phase::idle && !session->failed_return; }); - if (idle != entries.end()) - { + if (idle != entries.end()) { auto session = *idle; - if (!session->client->is_open()) - { - if (!discard(session)) - { + if (!session->client->is_open()) { + if (!discard(session)) { return; } continue; @@ -744,16 +648,14 @@ class pool complete(request, {{}, make_lease(session)}); continue; } - if (entries.size() >= options.max_connections) - { + if (entries.size() >= options.max_connections) { return; } auto waiting = std::find_if(queue.begin(), queue.end(), [](const auto& request) { const auto assigned = request->assigned.lock(); return !assigned || assigned->phase != entry_phase::connecting; }); - if (waiting == queue.end()) - { + if (waiting == queue.end()) { return; } auto request = *waiting; @@ -765,28 +667,22 @@ class pool entries.push_back(session); session->client->async_connect( options.connection, [weak = weak_from_this(), session](connection_result result) mutable { - if (auto self = weak.lock()) - { + if (auto self = weak.lock()) { asio::post(self->executor, [self, session, result = std::move(result)]() mutable { - if (!self->contains(session)) - { + if (!self->contains(session)) { return; } - if (!result || self->closed || self->close_requested) - { + if (!result || self->closed || self->close_requested) { auto request = session->waiting.lock(); self->discard(session); - if (request && !request->completed) - { + if (request && !request->completed) { self->complete( request, {self->closed || self->close_requested ? failure(connection_errc::cancelled, "MySQL pool closed") : std::move(result), {}}); } - } - else - { + } else { session->phase = entry_phase::idle; } self->pump(); @@ -797,22 +693,18 @@ class pool } void begin_return(const std::shared_ptr& session, std::chrono::steady_clock::time_point deadline) { - if (session->failed_return) - { + if (session->failed_return) { recover_failed_returns(); pump(); return; } - if (!contains(session)) - { + if (!contains(session)) { return; } - if (session->phase != entry_phase::leased) - { + if (session->phase != entry_phase::leased) { return; } - if (closed || close_requested || std::chrono::steady_clock::now() >= deadline) - { + if (closed || close_requested || std::chrono::steady_clock::now() >= deadline) { discard(session); pump(); return; @@ -821,56 +713,45 @@ class pool const auto return_generation = ++session->return_generation; session->return_deadline = std::make_shared(executor); session->return_deadline->expires_at(deadline); - session->return_deadline->async_wait( - [weak = weak_from_this(), current = std::weak_ptr(session), return_generation](error_code error) { - if (!error) - { - if (auto self = weak.lock()) - { - if (auto session = current.lock(); - session && self->contains(session) && session->return_generation == return_generation && - (session->phase == entry_phase::returning || session->phase == entry_phase::resetting)) - { - if (session->failed_return) - { - self->recover_failed_returns(); - } - else - { - self->discard(session); - } - self->pump(); + session->return_deadline->async_wait([weak = weak_from_this(), current = std::weak_ptr(session), + return_generation](error_code error) { + if (!error) { + if (auto self = weak.lock()) { + if (auto session = current.lock(); + session && self->contains(session) && session->return_generation == return_generation && + (session->phase == entry_phase::returning || session->phase == entry_phase::resetting)) { + if (session->failed_return) { + self->recover_failed_returns(); + } else { + self->discard(session); } + self->pump(); } } - }); + } + }); } void returned( const std::shared_ptr& session, bool unhealthy, std::chrono::steady_clock::time_point deadline) { - if (session->failed_return) - { + if (session->failed_return) { recover_failed_returns(); pump(); return; } - if (!contains(session)) - { + if (!contains(session)) { return; } if (closed || close_requested || unhealthy || !session->client->is_open() || - options.connection.database.empty() || std::chrono::steady_clock::now() >= deadline) - { + options.connection.database.empty() || std::chrono::steady_clock::now() >= deadline) { discard(session); pump(); return; } - if (session->phase == entry_phase::leased) - { + if (session->phase == entry_phase::leased) { begin_return(session, deadline); } - if (!contains(session) || session->phase != entry_phase::returning) - { + if (!contains(session) || session->phase != entry_phase::returning) { return; } session->phase = entry_phase::resetting; @@ -879,20 +760,15 @@ class pool std::chrono::duration_cast(deadline - std::chrono::steady_clock::now())); session->client->async_reset( [weak = weak_from_this(), session](connection_result result) mutable { - if (auto self = weak.lock()) - { + if (auto self = weak.lock()) { asio::post(self->executor, [self, session, result = std::move(result)] { - if (!self->contains(session)) - { + if (!self->contains(session)) { return; } - if (!result || self->closed || self->close_requested) - { + if (!result || self->closed || self->close_requested) { self->discard(session); self->pump(); - } - else - { + } else { self->restore_database(session); } }); @@ -903,18 +779,15 @@ class pool void restore_database(const std::shared_ptr& session) { const auto deadline = session->return_deadline->expiry(); - if (closed || close_requested || std::chrono::steady_clock::now() >= deadline) - { + if (closed || close_requested || std::chrono::steady_clock::now() >= deadline) { discard(session); pump(); return; } std::string sql = "USE `"; - for (const char character : options.connection.database) - { + for (const char character : options.connection.database) { sql.push_back(character); - if (character == '`') - { + if (character == '`') { sql.push_back('`'); } } @@ -925,20 +798,15 @@ class pool session->client->async_query( std::move(sql), [weak = weak_from_this(), session](query_result result) mutable { - if (auto self = weak.lock()) - { + if (auto self = weak.lock()) { asio::post(self->executor, [self, session, result = std::move(result)] { - if (!self->contains(session)) - { + if (!self->contains(session)) { return; } if (!result || self->closed || self->close_requested || - std::chrono::steady_clock::now() >= session->return_deadline->expiry()) - { + std::chrono::steady_clock::now() >= session->return_deadline->expiry()) { self->discard(session); - } - else - { + } else { session->return_deadline->cancel(); session->phase = entry_phase::idle; } @@ -950,20 +818,16 @@ class pool } void shutdown() { - if (std::exchange(closed, true)) - { + if (std::exchange(closed, true)) { return; } queue.clear(); - while (!pending.empty()) - { + while (!pending.empty()) { auto request = pending.begin()->second; complete(request, {failure(connection_errc::cancelled, "MySQL pool closed"), {}}); } - for (std::size_t index = 0; index != entries.size();) - { - if (!discard(entries[index], true)) - { + for (std::size_t index = 0; index != entries.size();) { + if (!discard(entries[index], true)) { ++index; } } @@ -973,16 +837,13 @@ class pool { std::unique_lock lock(gate->mutex); if ((runtime && runtime->running_in_threads()) || publication::active_on_this_thread(*gate) || - gate->finalizer == std::this_thread::get_id()) - { + gate->finalizer == std::this_thread::get_id()) { arknet::set_last_error(asio::error::operation_not_supported); return false; } - if (gate->terminating) - { + if (gate->terminating) { gate->changed.wait(lock, [&] { return gate->terminated || gate->failure; }); - if (gate->failure) - { + if (gate->failure) { std::rethrow_exception(gate->failure); } arknet::clear_last_error(); @@ -992,8 +853,7 @@ class pool gate->finalizer = std::this_thread::get_id(); gate->changed.wait(lock, [&] { return gate->publishers == 0; }); } - try - { + try { close_requested = true; // The pool actor is the only writer of these weak indexes. // Its shutdown fence prevents creation of any further child or @@ -1003,13 +863,10 @@ class pool auto ready = completion->get_future(); const auto self = shared_from_this(); asio::post(executor, [self, completion] { - try - { + try { self->shutdown(); completion->set_value({std::move(self->children), std::move(self->controls)}); - } - catch (...) - { + } catch (...) { const auto failure = std::current_exception(); completion->set_exception(failure); } @@ -1017,14 +874,11 @@ class pool auto [children, controls] = ready.get(); std::vector> live_children; std::vector> live_sessions; - for (auto& record : children) - { - if (auto session = record.session.lock()) - { + for (auto& record : children) { + if (auto session = record.session.lock()) { live_sessions.push_back(std::move(session)); } - if (auto child = record.child.lock()) - { + if (auto child = record.child.lock()) { { std::unique_lock lock(child->publishing_mutex); child->terminating = true; @@ -1041,8 +895,7 @@ class pool // All accepted callbacks and outer Asio captures have retired. // Retained leases and statements must become pure tombstones, // without timers, SSL objects, executors or library callables. - for (auto& child : live_children) - { + for (auto& child : live_children) { child->retire_context_resources(); { std::lock_guard lock(child->publishing_mutex); @@ -1051,18 +904,14 @@ class pool } child->publishing_changed.notify_all(); } - for (auto& record : children) - { + for (auto& record : children) { connection::state::retire_statements(*record.statements); } - for (auto& session : live_sessions) - { + for (auto& session : live_sessions) { session->return_deadline.reset(); } - for (auto& weak : controls) - { - if (auto control = weak.lock()) - { + for (auto& weak : controls) { + if (auto control = weak.lock()) { std::lock_guard lock(control->mutex); control->released = true; control->available = {}; @@ -1087,9 +936,7 @@ class pool gate->changed.notify_all(); arknet::clear_last_error(); return true; - } - catch (...) - { + } catch (...) { { std::lock_guard lock(gate->mutex); gate->failure = std::current_exception(); @@ -1118,10 +965,8 @@ class pool acquire_handler handler, std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()) { auto self = state_; publication publishing(*self->gate); - if (!publishing) - { - if (handler) - { + if (!publishing) { + if (handler) { handler({state::failure(connection_errc::not_connected, "MySQL pool permanently closed"), {}}); } return 0; @@ -1133,24 +978,17 @@ class pool timeout == std::chrono::milliseconds::zero() ? self->options.acquire_timeout : timeout); request->admitted = expires && self->reserve(); request->deadline = expires.value_or(std::chrono::steady_clock::time_point{}); - try - { + try { asio::post(self->executor, [self, request, valid = bool(expires)] { - if (!valid) - { + if (!valid) { self->complete(request, {state::failure(connection_errc::invalid_options, "Invalid MySQL pool acquire deadline"), {}}); - } - else - { + } else { self->acquire(request); } }); - } - catch (...) - { - if (request->admitted) - { + } catch (...) { + if (request->admitted) { --self->admitted; } throw; @@ -1161,18 +999,14 @@ class pool void cancel(request_id id) { auto self = state_; publication publishing(*self->gate); - if (!publishing) - { + if (!publishing) { return; } asio::post(self->executor, [self, id] { const auto found = self->pending.find(id); - if (found != self->pending.end()) - { + if (found != self->pending.end()) { self->cancel(found->second, connection_errc::cancelled); - } - else - { + } else { self->recover_failed_returns(); self->pump(); } @@ -1182,12 +1016,10 @@ class pool void close() { auto self = state_; publication publishing(*self->gate); - if (!publishing) - { + if (!publishing) { return; } - if (!self->close_requested.exchange(true)) - { + if (!self->close_requested.exchange(true)) { asio::post(self->executor, [self] { self->shutdown(); }); } } diff --git a/include/arknet/redis/client.hpp b/include/arknet/redis/client.hpp index 563cd40..3d2f580 100644 --- a/include/arknet/redis/client.hpp +++ b/include/arknet/redis/client.hpp @@ -35,14 +35,12 @@ using value_type = detail::resp_type; using command = std::vector; using request_id = std::uint64_t; -enum class protocol_version -{ +enum class protocol_version { resp2, resp3 }; -enum class client_errc -{ +enum class client_errc { none, not_connected, already_started, @@ -59,8 +57,7 @@ enum class client_errc closed }; -struct result -{ +struct result { client_errc error{client_errc::none}; arknet::error_code transport_error; std::vector replies; @@ -72,8 +69,7 @@ struct result } }; -struct connect_options -{ +struct connect_options { std::string host{"127.0.0.1"}; std::string port{"6379"}; protocol_version protocol{protocol_version::resp3}; @@ -83,8 +79,7 @@ struct connect_options std::uint32_t database{}; }; -struct client_options -{ +struct client_options { std::size_t max_pending_commands{1024}; std::size_t max_pending_bytes{16 * 1024 * 1024}; std::size_t max_pipeline_response_bytes{64 * 1024 * 1024}; @@ -108,15 +103,13 @@ class basic_pool; // rejection after close_and_wait() closes the permanent submission gate. // Pub/Sub, replication, MONITOR and CLIENT REPLY OFF/SKIP require other response models and are rejected. template -class basic_client -{ +class basic_client { public: using completion_handler = std::function; using push_handler = std::function; private: - struct request - { + struct request { request_id id{}; std::size_t remaining{}; std::size_t commands{}; @@ -131,8 +124,7 @@ class basic_client std::shared_ptr timer; }; - struct state : std::enable_shared_from_this - { + struct state : std::enable_shared_from_this { client_options options; const bool owns_runtime; std::shared_ptr runtime; @@ -175,47 +167,35 @@ class basic_client if (!options.max_pending_commands || !options.max_pending_bytes || !options.max_pipeline_response_bytes || !arknet::detail::checked_deadline(now, options.connect_timeout) || !arknet::detail::checked_deadline(now, options.command_timeout) || - !arknet::detail::checked_deadline(now, options.drain_timeout)) - { + !arknet::detail::checked_deadline(now, options.drain_timeout)) { throw std::invalid_argument("Redis queue limits and timeouts must be positive and representable"); } const auto& authority = certificate_authority.empty() ? options.tls_certificate_authority : certificate_authority; - if (!options.tls_certificate_file.empty() || !options.tls_private_key_file.empty()) - { + if (!options.tls_certificate_file.empty() || !options.tls_private_key_file.empty()) { if constexpr (requires { transport->set_cert_file(authority, options.tls_certificate_file, options.tls_private_key_file, options.tls_private_key_password); - }) - { + }) { transport->set_cert_file(authority, options.tls_certificate_file, options.tls_private_key_file, options.tls_private_key_password); - if (auto error = arknet::get_last_error()) - { + if (auto error = arknet::get_last_error()) { throw arknet::system_error(error); } - } - else - { + } else { throw std::invalid_argument("Client certificates require a Redis TLS client"); } - } - else if (!authority.empty()) - { - if constexpr (requires { transport->load_verify_file(authority); }) - { + } else if (!authority.empty()) { + if constexpr (requires { transport->load_verify_file(authority); }) { transport->load_verify_file(authority); - } - else - { + } else { throw std::invalid_argument("A certificate authority requires a Redis TLS client"); } } transport->set_auto_reconnect(false); } - class publication - { + class publication { inline static thread_local publication* current_{}; state* owner_{}; publication* previous_{}; @@ -223,8 +203,7 @@ class basic_client public: explicit publication(state& owner) { std::lock_guard lock(owner.publishing_mutex); - if (!owner.terminating) - { + if (!owner.terminating) { ++owner.publishers; owner_ = &owner; previous_ = current_; @@ -236,8 +215,7 @@ class basic_client publication& operator=(const publication&) = delete; ~publication() { - if (owner_) - { + if (owner_) { current_ = previous_; { std::lock_guard lock(owner_->publishing_mutex); @@ -252,10 +230,8 @@ class basic_client } static bool active_on_this_thread(const state& owner) noexcept { - for (auto* current = current_; current; current = current->previous_) - { - if (current->owner_ == &owner) - { + for (auto* current = current_; current; current = current->previous_) { + if (current->owner_ == &owner) { return true; } } @@ -270,41 +246,34 @@ class basic_client void initialize() { auto weak = this->weak_from_this(); transport->bind_connect([weak] { - if (auto self = weak.lock()) - { + if (auto self = weak.lock()) { self->connected(arknet::get_last_error()); } }); transport->bind_recv([weak](std::string_view bytes) { - if (auto self = weak.lock()) - { + if (auto self = weak.lock()) { self->received(bytes); } }); transport->bind_disconnect([weak] { - if (auto self = weak.lock()) - { + if (auto self = weak.lock()) { self->shutdown(client_errc::disconnected, arknet::get_last_error()); } }); // Keep rejection/cancellation callbacks runnable even before connect and after close. arknet::detail::pool_cleanup(); - if (!runtime->start()) - { + if (!runtime->start()) { throw arknet::system_error(arknet::get_last_error()); } } static std::string encode(const std::vector& commands, std::size_t maximum) { - if (commands.empty()) - { + if (commands.empty()) { throw std::invalid_argument("A Redis pipeline requires at least one command"); } std::string bytes; - for (const auto& arguments : commands) - { - if (!arguments.empty() && unsupported(arguments)) - { + for (const auto& arguments : commands) { + if (!arguments.empty() && unsupported(arguments)) { throw std::domain_error("Redis command requires a different response model"); } std::vector views(arguments.begin(), arguments.end()); @@ -314,19 +283,15 @@ class basic_client } static bool matches(std::string_view left, std::string_view right) { - if (left.size() != right.size()) - { + if (left.size() != right.size()) { return false; } - for (std::size_t i = 0; i < left.size(); ++i) - { + for (std::size_t i = 0; i < left.size(); ++i) { auto character = left[i]; - if (character >= 'a' && character <= 'z') - { + if (character >= 'a' && character <= 'z') { character = static_cast(character - 'a' + 'A'); } - if (character != right[i]) - { + if (character != right[i]) { return false; } } @@ -335,10 +300,8 @@ class basic_client static bool unsupported(const command& arguments) { for (std::string_view name : {"SUBSCRIBE", "PSUBSCRIBE", "SSUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", - "SUNSUBSCRIBE", "MONITOR", "SYNC", "PSYNC", "REPLCONF"}) - { - if (matches(arguments.front(), name)) - { + "SUNSUBSCRIBE", "MONITOR", "SYNC", "PSYNC", "REPLCONF"}) { + if (matches(arguments.front(), name)) { return true; } } @@ -349,8 +312,7 @@ class basic_client bool reserve(const std::shared_ptr& operation) { std::lock_guard lock(admission_mutex); if (operation->commands > options.max_pending_commands - pending_commands || - operation->wire_bytes > options.max_pending_bytes - pending_bytes) - { + operation->wire_bytes > options.max_pending_bytes - pending_bytes) { return false; } pending_commands += operation->commands; @@ -361,8 +323,7 @@ class basic_client void release(const std::shared_ptr& operation) { std::lock_guard lock(admission_mutex); - if (operation->reserved) - { + if (operation->reserved) { operation->reserved = false; pending_commands -= operation->commands; pending_bytes -= operation->wire_bytes; @@ -370,57 +331,46 @@ class basic_client } static void cancel_timer(const std::shared_ptr& timer) { - if (timer) - { + if (timer) { timer->cancel(); } } void complete(std::shared_ptr operation, result response) { - if (operation->completed) - { + if (operation->completed) { return; } operation->completed = true; cancel_timer(operation->timer); auto handler = std::exchange(operation->handler, {}); operation->replies.clear(); - if (handler) - { + if (handler) { handler(std::move(response)); } } void connect( connect_options target, completion_handler handler, std::chrono::steady_clock::time_point deadline) { - if (terminal || close_requested) - { - if (handler) - { + if (terminal || close_requested) { + if (handler) { handler(result{client_errc::closed}); } return; } - if (started) - { - if (handler) - { + if (started) { + if (handler) { handler(result{client_errc::already_started}); } return; } - if (target.host.empty() || target.port.empty()) - { - if (handler) - { + if (target.host.empty() || target.port.empty()) { + if (handler) { handler(result{client_errc::invalid_command}); } return; } - if (std::chrono::steady_clock::now() >= deadline) - { - if (handler) - { + if (std::chrono::steady_clock::now() >= deadline) { + if (handler) { handler(result{client_errc::timeout}); } return; @@ -431,100 +381,82 @@ class basic_client connect_timer = std::make_shared(executor()); connect_timer->expires_at(deadline); connect_timer->async_wait([weak = this->weak_from_this()](arknet::error_code error) { - if (!error) - { - if (auto self = weak.lock()) - { + if (!error) { + if (auto self = weak.lock()) { self->shutdown(client_errc::timeout); } } }); transport->set_connect_timeout(options.connect_timeout); - if (!transport->async_start(connection.host, connection.port)) - { + if (!transport->async_start(connection.host, connection.port)) { shutdown(client_errc::connection_failed, arknet::get_last_error()); } } void connected(arknet::error_code error) { - if (terminal) - { + if (terminal) { // The transport connect event can finish after Redis shutdown // closed an earlier socket. Retire that late-opened transport // without completing the Redis request a second time. cleanup_transport(); return; } - if (close_requested) - { + if (close_requested) { shutdown(client_errc::closed); return; } - if (error) - { + if (error) { shutdown(client_errc::connection_failed, error); return; } std::vector handshake; - if (connection.protocol == protocol_version::resp3) - { + if (connection.protocol == protocol_version::resp3) { command hello{"HELLO", "3"}; - if (!connection.password.empty() || !connection.username.empty()) - { + if (!connection.password.empty() || !connection.username.empty()) { hello.insert(hello.end(), {"AUTH", connection.username.empty() ? "default" : connection.username, connection.password}); } - if (!connection.client_name.empty()) - { + if (!connection.client_name.empty()) { hello.insert(hello.end(), {"SETNAME", connection.client_name}); } handshake.push_back(std::move(hello)); - } - else - { - if (!connection.password.empty() || !connection.username.empty()) - { + } else { + if (!connection.password.empty() || !connection.username.empty()) { command authenticate{"AUTH"}; - if (!connection.username.empty()) - { + if (!connection.username.empty()) { authenticate.push_back(connection.username); } authenticate.push_back(connection.password); handshake.push_back(std::move(authenticate)); } - if (!connection.client_name.empty()) - { + if (!connection.client_name.empty()) { handshake.push_back({"CLIENT", "SETNAME", connection.client_name}); } handshake.push_back({"PING"}); } - if (connection.database) - { + if (connection.database) { handshake.push_back({"SELECT", std::to_string(connection.database)}); } auto operation = std::make_shared(); operation->handshake = true; operation->commands = operation->remaining = handshake.size(); - try - { + try { auto bytes = encode(handshake, options.max_pending_bytes); enqueue(operation, std::move(bytes)); + } catch (const std::exception&) { + shutdown(client_errc::invalid_command); } - catch (const std::exception&) - { shutdown(client_errc::invalid_command); } } void submit( std::shared_ptr operation, std::string bytes, std::chrono::steady_clock::time_point deadline) { - if (terminal || close_requested || !ready) - { + if (terminal || close_requested || !ready) { release(operation); complete( operation, result{terminal || close_requested ? client_errc::closed : client_errc::not_connected}); return; } - if (std::chrono::steady_clock::now() >= deadline) - { + if (std::chrono::steady_clock::now() >= deadline) { release(operation); complete(operation, result{client_errc::timeout}); return; @@ -533,12 +465,9 @@ class basic_client operation->timer->expires_at(deadline); operation->timer->async_wait( [weak = this->weak_from_this(), operation = std::weak_ptr(operation)](arknet::error_code error) { - if (!error) - { - if (auto self = weak.lock()) - { - if (auto current = operation.lock()) - { + if (!error) { + if (auto self = weak.lock()) { + if (auto current = operation.lock()) { self->cancel_request(current, client_errc::timeout); } } @@ -548,17 +477,14 @@ class basic_client } void enqueue(const std::shared_ptr& operation, std::string bytes) { - for (std::size_t i = 0; i < operation->remaining; ++i) - { + for (std::size_t i = 0; i < operation->remaining; ++i) { slots.push_back(operation); } operation->sent = true; transport->async_send( std::move(bytes), [weak = this->weak_from_this()](arknet::error_code error, std::size_t) { - if (error) - { - if (auto self = weak.lock()) - { + if (error) { + if (auto self = weak.lock()) { self->shutdown(client_errc::disconnected, error); } } @@ -566,32 +492,26 @@ class basic_client } void cancel_request(std::shared_ptr operation, client_errc reason) { - if (operation->completed || terminal) - { + if (operation->completed || terminal) { return; } complete(operation, result{reason, {}, std::move(operation->replies), operation->sent}); - if (terminal || !operation->remaining) - { + if (terminal || !operation->remaining) { return; } // Do not remove sent slots or free their admission budget: later replies must still be drained in order. const auto deadline = arknet::detail::checked_deadline(std::chrono::steady_clock::now(), options.drain_timeout); - if (!deadline) - { + if (!deadline) { shutdown(client_errc::invalid_command); return; } operation->timer->expires_at(*deadline); operation->timer->async_wait( [weak = this->weak_from_this(), operation = std::weak_ptr(operation)](arknet::error_code error) { - if (!error) - { - if (auto self = weak.lock()) - { - if (auto current = operation.lock(); current && current->remaining) - { + if (!error) { + if (auto self = weak.lock()) { + if (auto current = operation.lock(); current && current->remaining) { self->shutdown(client_errc::timeout); } } @@ -600,50 +520,40 @@ class basic_client } void received(std::string_view bytes) { - if (terminal) - { + if (terminal) { return; } - if (!decoder.append(bytes)) - { + if (!decoder.append(bytes)) { shutdown(client_errc::protocol_error); return; } - while (!terminal) - { + while (!terminal) { value reply; auto status = decoder.next(reply); - if (status == detail::resp_status::need_more) - { + if (status == detail::resp_status::need_more) { return; } - if (status != detail::resp_status::ready) - { + if (status != detail::resp_status::ready) { shutdown(client_errc::protocol_error); return; } - if (reply.type == value_type::push) - { - if (push) - { + if (reply.type == value_type::push) { + if (push) { push(std::move(reply)); } continue; } - if (slots.empty()) - { + if (slots.empty()) { shutdown(client_errc::protocol_error); return; } auto operation = slots.front(); slots.pop_front(); --operation->remaining; - if (!operation->completed) - { + if (!operation->completed) { const auto maximum = options.max_pipeline_response_bytes; auto size = operation->response_bytes; - if (!accumulate_response_size(reply, size, maximum)) - { + if (!accumulate_response_size(reply, size, maximum)) { // Keep this slot visible to shutdown so it also receives its failure. slots.push_front(operation); ++operation->remaining; @@ -653,16 +563,12 @@ class basic_client operation->response_bytes = size; operation->replies.push_back(std::move(reply)); } - if (!operation->remaining) - { + if (!operation->remaining) { release(operation); cancel_timer(operation->timer); - if (operation->handshake) - { + if (operation->handshake) { finish_handshake(operation); - } - else - { + } else { complete(operation, result{client_errc::none, {}, std::move(operation->replies)}); } } @@ -670,22 +576,17 @@ class basic_client } static bool accumulate_response_size(const value& reply, std::size_t& bytes, std::size_t maximum) { - if (sizeof(value) > maximum - bytes) - { + if (sizeof(value) > maximum - bytes) { return false; } bytes += sizeof(value); - if (reply.text.size() > maximum - bytes) - { + if (reply.text.size() > maximum - bytes) { return false; } bytes += reply.text.size(); - for (const auto* children : {&reply.elements, &reply.attributes}) - { - for (const auto& child : *children) - { - if (!accumulate_response_size(child, bytes, maximum)) - { + for (const auto* children : {&reply.elements, &reply.attributes}) { + for (const auto& child : *children) { + if (!accumulate_response_size(child, bytes, maximum)) { return false; } } @@ -694,71 +595,57 @@ class basic_client } void finish_handshake(const std::shared_ptr& operation) { - if (terminal || close_requested) - { + if (terminal || close_requested) { shutdown(client_errc::closed); return; } bool accepted = !operation->replies.empty(); - for (const auto& reply : operation->replies) - { + for (const auto& reply : operation->replies) { accepted = accepted && reply.type != value_type::error && reply.type != value_type::bulk_error; } - if (connection.protocol == protocol_version::resp3) - { + if (connection.protocol == protocol_version::resp3) { const auto& hello = operation->replies.front(); bool protocol_matches = false; - if (hello.type == value_type::map) - { - for (std::size_t i = 0; i + 1 < hello.elements.size(); i += 2) - { + if (hello.type == value_type::map) { + for (std::size_t i = 0; i + 1 < hello.elements.size(); i += 2) { protocol_matches = protocol_matches || (hello.elements[i].text == "proto" && hello.elements[i + 1].type == value_type::integer && hello.elements[i + 1].integer == 3); } } accepted = accepted && protocol_matches; - for (std::size_t i = 1; i < operation->replies.size(); ++i) - { + for (std::size_t i = 1; i < operation->replies.size(); ++i) { accepted = accepted && operation->replies[i].type == value_type::simple_string && operation->replies[i].text == "OK"; } - } - else - { + } else { const auto ping = connection.database ? operation->replies.size() - 2 : operation->replies.size() - 1; accepted = accepted && operation->replies[ping].type == value_type::simple_string && operation->replies[ping].text == "PONG"; - for (std::size_t i = 0; i < operation->replies.size(); ++i) - { - if (i != ping) - { + for (std::size_t i = 0; i < operation->replies.size(); ++i) { + if (i != ping) { accepted = accepted && operation->replies[i].type == value_type::simple_string && operation->replies[i].text == "OK"; } } } - if (!accepted) - { + if (!accepted) { auto handler = std::exchange(connect_handler, {}); auto replies = std::move(operation->replies); shutdown(client_errc::handshake_failed); - if (handler) - { + if (handler) { handler(result{client_errc::handshake_failed, {}, std::move(replies)}); } return; } cancel_timer(connect_timer); ready = true; - if (close_requested) - { + if (close_requested) { shutdown(client_errc::closed); return; } auto handler = std::exchange(connect_handler, {}); - if (handler) - { + if (handler) { handler(result{client_errc::none, {}, std::move(operation->replies)}); } } @@ -767,22 +654,20 @@ class basic_client // outside this boundary and retain their documented no-throw contract. void cleanup_transport() noexcept { native_cleanup_pending = true; - try - { + try { arknet::error_code ignored; transport->socket().cancel(ignored); transport->socket().close(ignored); transport->stop(); native_cleanup_pending = false; native_cleanup_failure = {}; + } catch (...) { + native_cleanup_failure = std::current_exception(); } - catch (...) - { native_cleanup_failure = std::current_exception(); } } void shutdown(client_errc reason, arknet::error_code error = {}) { - if (terminal) - { + if (terminal) { return; } terminal = true; @@ -790,12 +675,10 @@ class basic_client cancel_timer(connect_timer); cleanup_transport(); auto connecting = std::exchange(connect_handler, {}); - if (connecting) - { + if (connecting) { connecting(result{reason, error}); } - while (!slots.empty()) - { + while (!slots.empty()) { auto operation = std::move(slots.front()); slots.pop_front(); release(operation); @@ -815,28 +698,22 @@ class basic_client // failed native stop. Successful submission is followed by runtime join. std::exception_ptr reconcile_close_on_lane() { close_on_lane(); - if (native_cleanup_pending) - { + if (native_cleanup_pending) { cleanup_transport(); } return native_cleanup_failure; } void request_close() { - if (!close_requested.exchange(true)) - { - try - { + if (!close_requested.exchange(true)) { + try { asio::dispatch(executor(), [self = this->shared_from_this()] { self->close_on_lane(); }); - } - catch (...) - { + } catch (...) { // A failed allocation before actor entry did not close the // transport. Permit a later ordinary close() to retry. // Inline actor/user code may already have begun shutdown; // never roll back that externally visible transition. - if (!close_started.load()) - { + if (!close_started.load()) { close_requested = false; } throw; @@ -863,16 +740,13 @@ class basic_client { std::unique_lock lock(publishing_mutex); if (!owns_runtime || (runtime && runtime->running_in_threads()) || - publication::active_on_this_thread(*this) || finalizer == std::this_thread::get_id()) - { + publication::active_on_this_thread(*this) || finalizer == std::this_thread::get_id()) { arknet::set_last_error(asio::error::operation_not_supported); return false; } - if (terminating) - { + if (terminating) { publishing_changed.wait(lock, [&] { return terminated || shutdown_failure; }); - if (shutdown_failure) - { + if (shutdown_failure) { std::rethrow_exception(shutdown_failure); } arknet::clear_last_error(); @@ -882,35 +756,29 @@ class basic_client finalizer = std::this_thread::get_id(); publishing_changed.wait(lock, [&] { return publishers == 0; }); } - try - { + try { // The permanent gate has sealed every API publication. Reconcile // failed native cleanup on its lane before joining the runtime. auto completion = std::make_shared>(); auto ready = completion->get_future(); const auto self = this->shared_from_this(); asio::post(executor(), [self, completion] { - try - { - if (auto failure = self->reconcile_close_on_lane()) - { + try { + if (auto failure = self->reconcile_close_on_lane()) { completion->set_exception(std::move(failure)); - } - else - { + } else { completion->set_value(); } + } catch (...) { + completion->set_exception(std::current_exception()); } - catch (...) - { completion->set_exception(std::current_exception()); } }); ready.get(); auto scheduler = runtime; scheduler->stop(); // A late connect callback can attempt native cleanup after the // fence. The joined lane makes its final status safe to inspect. - if (native_cleanup_pending) - { + if (native_cleanup_pending) { std::rethrow_exception(native_cleanup_failure); } @@ -927,9 +795,7 @@ class basic_client publishing_changed.notify_all(); arknet::clear_last_error(); return true; - } - catch (...) - { + } catch (...) { { std::lock_guard lock(publishing_mutex); shutdown_failure = std::current_exception(); @@ -942,8 +808,7 @@ class basic_client }; static request_id reject_terminated(completion_handler handler) { - if (handler) - { + if (handler) { handler(result{client_errc::closed}); } return 0; @@ -965,8 +830,7 @@ class basic_client void async_connect(connect_options options, completion_handler handler) { auto self = state_; typename state::publication publishing(*self); - if (!publishing) - { + if (!publishing) { reject_terminated(std::move(handler)); return; } @@ -974,15 +838,11 @@ class basic_client arknet::detail::checked_deadline(std::chrono::steady_clock::now(), self->options.connect_timeout); asio::post( self->executor(), [self, options = std::move(options), handler = std::move(handler), deadline]() mutable { - if (!deadline) - { - if (handler) - { + if (!deadline) { + if (handler) { handler(result{client_errc::invalid_command}); } - } - else - { + } else { self->connect(std::move(options), std::move(handler), *deadline); } }); @@ -992,8 +852,7 @@ class basic_client std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()) { auto self = state_; typename state::publication publishing(*self); - if (!publishing) - { + if (!publishing) { return reject_terminated(std::move(handler)); } std::vector commands; @@ -1005,8 +864,7 @@ class basic_client std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()) { auto self = state_; typename state::publication publishing(*self); - if (!publishing) - { + if (!publishing) { return reject_terminated(std::move(handler)); } auto operation = std::make_shared(); @@ -1017,44 +875,34 @@ class basic_client timeout == std::chrono::milliseconds::zero() ? self->options.command_timeout : timeout); std::string bytes; client_errc error = deadline ? client_errc::none : client_errc::invalid_command; - try - { - if (deadline) - { + try { + if (deadline) { bytes = state::encode(commands, self->options.max_pending_bytes); operation->wire_bytes = bytes.size(); - if (!self->reserve(operation)) - { + if (!self->reserve(operation)) { error = client_errc::queue_full; } } + } catch (const std::length_error&) { + error = client_errc::queue_full; + } catch (const std::invalid_argument&) { + error = client_errc::invalid_command; + } catch (const std::domain_error&) { + error = client_errc::unsupported_command; } - catch (const std::length_error&) - { error = client_errc::queue_full; } - catch (const std::invalid_argument&) - { error = client_errc::invalid_command; } - catch (const std::domain_error&) - { error = client_errc::unsupported_command; } - if (error != client_errc::none) - { + if (error != client_errc::none) { // A rejected submission must not retain its large payload in the IO callback queue. std::string{}.swap(bytes); } - try - { + try { asio::post(self->executor(), [self, operation, bytes = std::move(bytes), deadline, error]() mutable { - if (error != client_errc::none) - { + if (error != client_errc::none) { self->complete(operation, result{error}); - } - else - { + } else { self->submit(operation, std::move(bytes), *deadline); } }); - } - catch (...) - { + } catch (...) { self->release(operation); throw; } @@ -1065,15 +913,12 @@ class basic_client void cancel(request_id id) { auto self = state_; typename state::publication publishing(*self); - if (!publishing) - { + if (!publishing) { return; } asio::post(self->executor(), [self, id] { - for (const auto& operation : self->slots) - { - if (!operation->handshake && operation->id == id) - { + for (const auto& operation : self->slots) { + if (!operation->handshake && operation->id == id) { self->cancel_request(operation, client_errc::cancelled); break; } @@ -1084,8 +929,7 @@ class basic_client void set_push_handler(push_handler handler) { auto self = state_; typename state::publication publishing(*self); - if (!publishing) - { + if (!publishing) { return; } asio::post( @@ -1099,8 +943,7 @@ class basic_client void close() { auto self = state_; typename state::publication publishing(*self); - if (publishing) - { + if (publishing) { self->request_close(); } } @@ -1133,39 +976,34 @@ class basic_client void async_reset(completion_handler handler, std::chrono::milliseconds timeout) { auto self = state_; typename state::publication publishing(*self); - if (!publishing) - { + if (!publishing) { reject_terminated(std::move(handler)); return; } const auto deadline = arknet::detail::checked_deadline(std::chrono::steady_clock::now(), timeout); asio::post(self->executor(), [self, handler = std::move(handler), deadline]() mutable { - if (!deadline) - { - if (handler) - { + if (!deadline) { + if (handler) { handler(result{client_errc::invalid_command}); } return; } - if (self->terminal || !self->ready) - { + if (self->terminal || !self->ready) { handler(result{client_errc::closed}); return; } std::string bytes; auto encoding_error = client_errc::none; - try - { bytes = state::encode({{"RESET"}}, self->options.max_pending_bytes); } - catch (const std::length_error&) - { encoding_error = client_errc::queue_full; } - catch (const std::bad_alloc&) - { encoding_error = client_errc::queue_full; } - if (encoding_error != client_errc::none) - { + try { + bytes = state::encode({{"RESET"}}, self->options.max_pending_bytes); + } catch (const std::length_error&) { + encoding_error = client_errc::queue_full; + } catch (const std::bad_alloc&) { + encoding_error = client_errc::queue_full; + } + if (encoding_error != client_errc::none) { self->shutdown(encoding_error); - if (handler) - { + if (handler) { handler(result{encoding_error}); } return; @@ -1175,10 +1013,8 @@ class basic_client self->connect_timer = std::make_shared(self->executor()); self->connect_timer->expires_at(*deadline); self->connect_timer->async_wait([weak = std::weak_ptr(self)](arknet::error_code error) { - if (!error) - { - if (auto current = weak.lock()) - { + if (!error) { + if (auto current = weak.lock()) { current->shutdown(client_errc::timeout); } } @@ -1186,16 +1022,12 @@ class basic_client auto operation = std::make_shared(); operation->commands = operation->remaining = 1; operation->handler = [weak = std::weak_ptr(self)](result response) { - if (auto current = weak.lock(); current && !current->terminal) - { + if (auto current = weak.lock(); current && !current->terminal) { if (!response || response.replies.size() != 1 || response.replies.front().type != value_type::simple_string || - response.replies.front().text != "RESET") - { + response.replies.front().text != "RESET") { current->shutdown(client_errc::handshake_failed); - } - else - { + } else { current->connected({}); } } diff --git a/include/arknet/redis/detail/resp.hpp b/include/arknet/redis/detail/resp.hpp index 8a817fc..8395e05 100644 --- a/include/arknet/redis/detail/resp.hpp +++ b/include/arknet/redis/detail/resp.hpp @@ -18,8 +18,7 @@ namespace arknet::redis::detail { -enum class resp_type -{ +enum class resp_type { simple_string, error, integer, @@ -37,8 +36,7 @@ enum class resp_type push }; -struct resp_value -{ +struct resp_value { resp_type type{resp_type::null}; std::string text; std::int64_t integer{}; @@ -49,8 +47,7 @@ struct resp_value std::vector attributes; }; -struct resp_limits -{ +struct resp_limits { std::size_t max_buffer_bytes{64 * 1024 * 1024}; std::size_t max_message_bytes{64 * 1024 * 1024}; std::size_t max_bulk_bytes{64 * 1024 * 1024}; @@ -59,8 +56,7 @@ struct resp_limits std::size_t max_depth{64}; }; -enum class resp_status -{ +enum class resp_status { need_more, ready, malformed, @@ -70,25 +66,21 @@ enum class resp_status // Incremental RESP2/RESP3 decoder. A ready result owns its data; subsequent input cannot invalidate it. // This internal codec does not correlate replies with requests. Pushes are distinct values and // attributes decorate the next value at their nesting level, without consuming a reply slot. -class resp_decoder -{ +class resp_decoder { public: explicit resp_decoder(resp_limits limits = {}) : limits_(limits) { if (!limits_.max_buffer_bytes || !limits_.max_message_bytes || !limits_.max_bulk_bytes || - !limits_.max_line_bytes || !limits_.max_values || !limits_.max_depth || limits_.max_depth > 128) - { + !limits_.max_line_bytes || !limits_.max_values || !limits_.max_depth || limits_.max_depth > 128) { throw std::invalid_argument("RESP limits must be positive and max_depth must not exceed 128"); } } bool append(std::string_view input) { - if (failed()) - { + if (failed()) { return false; } - if (input.size() > limits_.max_buffer_bytes - (buffer_.size() - position_)) - { + if (input.size() > limits_.max_buffer_bytes - (buffer_.size() - position_)) { fail(resp_status::limit_exceeded); return false; } @@ -98,23 +90,18 @@ class resp_decoder } resp_status next(resp_value& output) { - if (failed()) - { + if (failed()) { return status_; } - while (position_ < buffer_.size()) - { - if (phase_ == phase::bulk) - { + while (position_ < buffer_.size()) { + if (phase_ == phase::bulk) { const auto count = std::min(remaining_, buffer_.size() - position_); - if (!consume(count)) - { + if (!consume(count)) { return status_; } current_.text.append(buffer_.data() + position_ - count, count); remaining_ -= count; - if (remaining_ != 0) - { + if (remaining_ != 0) { break; } phase_ = phase::bulk_cr; @@ -122,104 +109,69 @@ class resp_decoder } const char byte = buffer_[position_]; - if (!consume(1)) - { + if (!consume(1)) { return status_; } - if (phase_ == phase::prefix) - { - if (byte == '.') - { - if (frames_.empty() || !frames_.back().streamed || !pending_attributes_.empty()) - { + if (phase_ == phase::prefix) { + if (byte == '.') { + if (frames_.empty() || !frames_.back().streamed || !pending_attributes_.empty()) { return fail(resp_status::malformed); } - } - else if (std::string_view("+-:$*_#,(!=%|~>").find(byte) == std::string_view::npos) - { + } else if (std::string_view("+-:$*_#,(!=%|~>").find(byte) == std::string_view::npos) { return fail(resp_status::malformed); - } - else if (byte == '>' && !frames_.empty()) - { + } else if (byte == '>' && !frames_.empty()) { return fail(resp_status::malformed); - } - else if (++values_ > limits_.max_values || frames_.size() >= limits_.max_depth) - { + } else if (++values_ > limits_.max_values || frames_.size() >= limits_.max_depth) { return fail(resp_status::limit_exceeded); } prefix_ = byte; phase_ = phase::line; - } - else if (phase_ == phase::chunk_prefix) - { - if (byte != ';') - { + } else if (phase_ == phase::chunk_prefix) { + if (byte != ';') { return fail(resp_status::malformed); } prefix_ = byte; phase_ = phase::line; - } - else if (phase_ == phase::line) - { - if (byte == '\r') - { + } else if (phase_ == phase::line) { + if (byte == '\r') { phase_ = phase::line_lf; - } - else if (byte == '\n') - { + } else if (byte == '\n') { return fail(resp_status::malformed); - } - else if (line_.size() == limits_.max_line_bytes) - { + } else if (line_.size() == limits_.max_line_bytes) { return fail(resp_status::limit_exceeded); - } - else - { + } else { line_.push_back(byte); } - } - else if (phase_ == phase::line_lf) - { - if (byte != '\n') - { + } else if (phase_ == phase::line_lf) { + if (byte != '\n') { return fail(resp_status::malformed); } const auto result = finish_line(output); line_.clear(); - if (result != resp_status::need_more) - { + if (result != resp_status::need_more) { compact(); return result; } - } - else if (phase_ == phase::bulk_cr) - { - if (byte != '\r') - { + } else if (phase_ == phase::bulk_cr) { + if (byte != '\r') { return fail(resp_status::malformed); } phase_ = phase::bulk_lf; - } - else if (phase_ == phase::bulk_lf) - { - if (byte != '\n') - { + } else if (phase_ == phase::bulk_lf) { + if (byte != '\n') { return fail(resp_status::malformed); } - if (streamed_bulk_) - { + if (streamed_bulk_) { phase_ = phase::chunk_prefix; continue; } if (current_.type == resp_type::verbatim_string && - (current_.text.size() < 4 || current_.text[3] != ':')) - { + (current_.text.size() < 4 || current_.text[3] != ':')) { return fail(resp_status::malformed); } phase_ = phase::prefix; const auto result = complete(std::move(current_), output); - if (result == resp_status::ready) - { + if (result == resp_status::ready) { compact(); return result; } @@ -245,8 +197,7 @@ class resp_decoder } private: - enum class phase - { + enum class phase { prefix, line, line_lf, @@ -256,8 +207,7 @@ class resp_decoder chunk_prefix }; - struct frame - { + struct frame { resp_value value; std::size_t count{}; bool streamed{}; @@ -273,8 +223,7 @@ class resp_decoder } bool consume(std::size_t count) noexcept { - if (count > limits_.max_message_bytes - wire_bytes_) - { + if (count > limits_.max_message_bytes - wire_bytes_) { fail(resp_status::limit_exceeded); return false; } @@ -284,13 +233,10 @@ class resp_decoder } void compact(bool force = false) { - if (position_ == buffer_.size()) - { + if (position_ == buffer_.size()) { buffer_.clear(); position_ = 0; - } - else if (position_ != 0 && (force || (position_ >= 4096 && position_ >= buffer_.size() / 2))) - { + } else if (position_ != 0 && (force || (position_ >= 4096 && position_ >= buffer_.size() / 2))) { buffer_.erase(0, position_); position_ = 0; } @@ -301,22 +247,19 @@ class resp_decoder } static bool signed_digits(std::string_view value) noexcept { - if (!value.empty() && (value.front() == '+' || value.front() == '-')) - { + if (!value.empty() && (value.front() == '+' || value.front() == '-')) { value.remove_prefix(1); } return digits(value); } bool size_value(std::size_t& result) { - if (!digits(line_)) - { + if (!digits(line_)) { fail(resp_status::malformed); return false; } const auto parsed = std::from_chars(line_.data(), line_.data() + line_.size(), result); - if (parsed.ec == std::errc::result_out_of_range) - { + if (parsed.ec == std::errc::result_out_of_range) { fail(resp_status::limit_exceeded); return false; } @@ -332,17 +275,14 @@ class resp_decoder } resp_status complete(resp_value value, resp_value& output) { - for (;;) - { - if (value.type == resp_type::attribute) - { + for (;;) { + if (value.type == resp_type::attribute) { pending_attributes_ = std::move(value.attributes); pending_attributes_.insert(pending_attributes_.end(), std::make_move_iterator(value.elements.begin()), std::make_move_iterator(value.elements.end())); return resp_status::need_more; } - if (frames_.empty()) - { + if (frames_.empty()) { output = std::move(value); wire_bytes_ = 0; values_ = 0; @@ -350,8 +290,7 @@ class resp_decoder } auto& parent = frames_.back(); parent.value.elements.push_back(std::move(value)); - if (parent.streamed || parent.value.elements.size() != parent.count) - { + if (parent.streamed || parent.value.elements.size() != parent.count) { return resp_status::need_more; } value = std::move(parent.value); @@ -361,29 +300,23 @@ class resp_decoder resp_status finish_line(resp_value& output) { phase_ = phase::prefix; - if (prefix_ == '.') - { + if (prefix_ == '.') { if (!line_.empty() || - (frames_.back().value.type == resp_type::map && frames_.back().value.elements.size() % 2 != 0)) - { + (frames_.back().value.type == resp_type::map && frames_.back().value.elements.size() % 2 != 0)) { return fail(resp_status::malformed); } auto value = std::move(frames_.back().value); frames_.pop_back(); return complete(std::move(value), output); } - if (prefix_ == ';') - { - if (!size_value(remaining_)) - { + if (prefix_ == ';') { + if (!size_value(remaining_)) { return status_; } - if (remaining_ > limits_.max_bulk_bytes - current_.text.size()) - { + if (remaining_ > limits_.max_bulk_bytes - current_.text.size()) { return fail(resp_status::limit_exceeded); } - if (remaining_ == 0) - { + if (remaining_ == 0) { streamed_bulk_ = false; return complete(std::move(current_), output); } @@ -392,8 +325,7 @@ class resp_decoder } resp_type type; - switch (prefix_) - { + switch (prefix_) { case '+': type = resp_type::simple_string; break; @@ -444,26 +376,21 @@ class resp_decoder } auto value = new_value(type); - if (type == resp_type::bulk_string || type == resp_type::bulk_error || type == resp_type::verbatim_string) - { - if (type == resp_type::bulk_string && line_ == "-1") - { + if (type == resp_type::bulk_string || type == resp_type::bulk_error || type == resp_type::verbatim_string) { + if (type == resp_type::bulk_string && line_ == "-1") { value.type = resp_type::null; return complete(std::move(value), output); } - if (type == resp_type::bulk_string && line_ == "?") - { + if (type == resp_type::bulk_string && line_ == "?") { current_ = std::move(value); streamed_bulk_ = true; phase_ = phase::chunk_prefix; return resp_status::need_more; } - if (!size_value(remaining_)) - { + if (!size_value(remaining_)) { return status_; } - if (remaining_ > limits_.max_bulk_bytes || remaining_ > limits_.max_message_bytes - wire_bytes_) - { + if (remaining_ > limits_.max_bulk_bytes || remaining_ > limits_.max_message_bytes - wire_bytes_) { return fail(resp_status::limit_exceeded); } current_ = std::move(value); @@ -471,30 +398,24 @@ class resp_decoder return resp_status::need_more; } if (type == resp_type::array || type == resp_type::map || type == resp_type::set || type == resp_type::push || - type == resp_type::attribute) - { - if (type == resp_type::array && line_ == "-1") - { + type == resp_type::attribute) { + if (type == resp_type::array && line_ == "-1") { value.type = resp_type::null; return complete(std::move(value), output); } const bool streamed = line_ == "?" && (type == resp_type::array || type == resp_type::map || type == resp_type::set); std::size_t count = 0; - if (!streamed) - { - if (!size_value(count)) - { + if (!streamed) { + if (!size_value(count)) { return status_; } const std::size_t factor = (type == resp_type::map || type == resp_type::attribute) ? 2 : 1; - if (count > (limits_.max_values - values_) / factor) - { + if (count > (limits_.max_values - values_) / factor) { return fail(resp_status::limit_exceeded); } count *= factor; - if (count == 0) - { + if (count == 0) { return complete(std::move(value), output); } } @@ -502,80 +423,65 @@ class resp_decoder return resp_status::need_more; } - switch (type) - { + switch (type) { case resp_type::simple_string: case resp_type::error: value.text = line_; break; - case resp_type::integer: - { + case resp_type::integer: { auto number = std::string_view(line_); - if (!signed_digits(number)) - { + if (!signed_digits(number)) { return fail(resp_status::malformed); } - if (number.front() == '+') - { + if (number.front() == '+') { number.remove_prefix(1); } - if (std::from_chars(number.data(), number.data() + number.size(), value.integer).ec != std::errc{}) - { + if (std::from_chars(number.data(), number.data() + number.size(), value.integer).ec != std::errc{}) { return fail(resp_status::malformed); } break; } case resp_type::null: - if (!line_.empty()) - { + if (!line_.empty()) { return fail(resp_status::malformed); } break; case resp_type::boolean: - if (line_ != "t" && line_ != "f") - { + if (line_ != "t" && line_ != "f") { return fail(resp_status::malformed); } value.boolean = line_ == "t"; break; case resp_type::big_number: - if (!signed_digits(line_)) - { + if (!signed_digits(line_)) { return fail(resp_status::malformed); } value.text = line_; break; - case resp_type::real: - { - if (line_ == "inf" || line_ == "-inf" || line_ == "nan") - { + case resp_type::real: { + if (line_ == "inf" || line_ == "-inf" || line_ == "nan") { value.real = line_ == "nan" ? std::numeric_limits::quiet_NaN() : (line_ == "inf" ? 1 : -1) * std::numeric_limits::infinity(); break; } auto number = std::string_view(line_); - if (!number.empty() && number.front() == '+') - { + if (!number.empty() && number.front() == '+') { number.remove_prefix(1); } auto integral = number; - if (!integral.empty() && integral.front() == '-') - { + if (!integral.empty() && integral.front() == '-') { integral.remove_prefix(1); } - if (integral.empty() || integral.front() < '0' || integral.front() > '9') - { + if (integral.empty() || integral.front() < '0' || integral.front() > '9') { return fail(resp_status::malformed); } const auto point = integral.find('.'); if (point != std::string_view::npos && - (point + 1 == integral.size() || integral[point + 1] < '0' || integral[point + 1] > '9')) - { + (point + 1 == integral.size() || integral[point + 1] < '0' || integral[point + 1] > '9')) { return fail(resp_status::malformed); } const auto parsed = std::from_chars(number.data(), number.data() + number.size(), value.real); - if (parsed.ec != std::errc{} || parsed.ptr != number.data() + number.size() || !std::isfinite(value.real)) - { + if (parsed.ec != std::errc{} || parsed.ptr != number.data() + number.size() || !std::isfinite(value.real)) { return fail(resp_status::malformed); } break; @@ -604,21 +510,17 @@ class resp_decoder inline std::string encode_command( std::span arguments, std::size_t max_bytes = 64 * 1024 * 1024) { - if (arguments.empty()) - { + if (arguments.empty()) { throw std::invalid_argument("A Redis command requires at least one argument"); } const auto count = std::to_string(arguments.size()); std::size_t size = 1 + count.size() + 2; - if (size > max_bytes) - { + if (size > max_bytes) { throw std::length_error("Redis command exceeds the configured byte limit"); } - for (const auto argument : arguments) - { + for (const auto argument : arguments) { const auto overhead = 1 + std::to_string(argument.size()).size() + 4; - if (overhead > max_bytes - size || argument.size() > max_bytes - size - overhead) - { + if (overhead > max_bytes - size || argument.size() > max_bytes - size - overhead) { throw std::length_error("Redis command exceeds the configured byte limit"); } size += overhead + argument.size(); @@ -626,8 +528,7 @@ inline std::string encode_command( std::string encoded; encoded.reserve(size); encoded += '*' + count + "\r\n"; - for (const auto argument : arguments) - { + for (const auto argument : arguments) { encoded += '$' + std::to_string(argument.size()) + "\r\n"; encoded.append(argument); encoded += "\r\n"; diff --git a/include/arknet/redis/pool.hpp b/include/arknet/redis/pool.hpp index 676b439..be0f2b8 100644 --- a/include/arknet/redis/pool.hpp +++ b/include/arknet/redis/pool.hpp @@ -25,8 +25,7 @@ namespace arknet::redis { -struct pool_options -{ +struct pool_options { std::size_t max_connections{8}; std::size_t max_waiters{1024}; std::chrono::milliseconds acquire_timeout{5000}; @@ -36,8 +35,7 @@ struct pool_options std::string certificate_authority; }; -struct pool_diagnostics -{ +struct pool_diagnostics { std::uint64_t return_submission_failures{}; std::exception_ptr last_return_submission_exception; }; @@ -46,13 +44,11 @@ struct pool_diagnostics // then restores the configured authentication/protocol/database before another borrower can use it. // Servers without RESET can still be used: their connections are discarded on return. template -class basic_pool -{ +class basic_pool { using client_type = basic_client; struct waiter; - struct publication_gate - { + struct publication_gate { std::mutex mutex; std::condition_variable changed; std::size_t publishers{}; @@ -62,8 +58,7 @@ class basic_pool std::exception_ptr failure; }; - class publication - { + class publication { inline static thread_local publication* current_{}; publication_gate* owner_{}; publication* previous_{}; @@ -71,8 +66,7 @@ class basic_pool public: explicit publication(publication_gate& gate) { std::lock_guard lock(gate.mutex); - if (!gate.terminating) - { + if (!gate.terminating) { ++gate.publishers; owner_ = &gate; previous_ = current_; @@ -84,8 +78,7 @@ class basic_pool publication& operator=(const publication&) = delete; ~publication() { - if (owner_) - { + if (owner_) { current_ = previous_; { std::lock_guard lock(owner_->mutex); @@ -100,10 +93,8 @@ class basic_pool } static bool active_on_this_thread(const publication_gate& gate) noexcept { - for (auto* current = current_; current; current = current->previous_) - { - if (current->owner_ == &gate) - { + for (auto* current = current_; current; current = current->previous_) { + if (current->owner_ == &gate) { return true; } } @@ -111,8 +102,7 @@ class basic_pool } }; - enum class entry_phase - { + enum class entry_phase { connecting, idle, leased, @@ -120,23 +110,20 @@ class basic_pool retiring }; - struct entry - { + struct entry { std::shared_ptr client; entry_phase phase{entry_phase::connecting}; std::weak_ptr waiting; std::atomic_bool failed_return{}; }; - struct return_diagnostics - { + struct return_diagnostics { mutable std::mutex mutex; pool_diagnostics info; void record(std::exception_ptr error) noexcept { std::lock_guard lock(mutex); - if (info.return_submission_failures != (std::numeric_limits::max)()) - { + if (info.return_submission_failures != (std::numeric_limits::max)()) { ++info.return_submission_failures; } info.last_return_submission_exception = std::move(error); @@ -148,8 +135,7 @@ class basic_pool } }; - struct lease_control - { + struct lease_control { std::shared_ptr connection; std::shared_ptr gate; std::function, bool)> return_connection; @@ -165,14 +151,12 @@ class basic_pool { std::lock_guard lock(mutex); --pending; - if (publishing && released && !pending) - { + if (publishing && released && !pending) { returning = std::exchange(return_connection, {}); unhealthy = discard; } } - if (returning) - { + if (returning) { returning(connection, unhealthy); } } @@ -185,22 +169,19 @@ class basic_pool std::lock_guard lock(mutex); released = true; discard = discard || unhealthy; - if (publishing && !pending) - { + if (publishing && !pending) { returning = std::exchange(return_connection, {}); discard_connection = discard; } } - if (returning) - { + if (returning) { returning(connection, discard_connection); } } }; public: - class lease - { + class lease { public: lease() = default; lease(const lease&) = delete; @@ -208,8 +189,7 @@ class basic_pool lease(lease&& other) noexcept = default; lease& operator=(lease&& other) noexcept { - if (this != &other) - { + if (this != &other) { release(); control_ = std::move(other.control_); } @@ -221,8 +201,7 @@ class basic_pool } explicit operator bool() const noexcept { - if (!control_) - { + if (!control_) { return false; } std::lock_guard lock(control_->mutex); @@ -239,37 +218,31 @@ class basic_pool request_id async_pipeline(std::vector commands, typename client_type::completion_handler handler, std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()) { auto control = control_; - if (!control) - { + if (!control) { // An empty/moved-from lease has no executor. It rejects synchronously without a submission. - if (handler) - { + if (handler) { handler(result{client_errc::closed}); } return 0; } publication publishing(*control->gate); - if (!publishing) - { + if (!publishing) { return client_type::reject_terminated(std::move(handler)); } std::unique_lock lock(control->mutex); - if (control->released) - { + if (control->released) { auto client = control->connection->client; auto id = client->state_->next_id.fetch_add(1); lock.unlock(); asio::post(client->state_->executor(), [handler = std::move(handler)]() mutable { - if (handler) - { + if (handler) { handler(result{client_errc::closed}); } }); return id; } ++control->pending; - try - { + try { return control->connection->client->async_pipeline( std::move(commands), [control, handler = std::move(handler)](result response) mutable { @@ -280,16 +253,13 @@ class basic_pool response.error == client_errc::response_limit || response.error == client_errc::closed; } - if (handler) - { + if (handler) { handler(std::move(response)); } control->finish(); }, timeout); - } - catch (...) - { + } catch (...) { --control->pending; throw; } @@ -297,16 +267,13 @@ class basic_pool void cancel(request_id id) { const auto control = control_; - if (control) - { + if (control) { publication publishing(*control->gate); - if (!publishing) - { + if (!publishing) { return; } std::lock_guard lock(control->mutex); - if (!control->released) - { + if (!control->released) { control->connection->client->cancel(id); } } @@ -314,16 +281,14 @@ class basic_pool void release() { const auto control = control_; - if (control) - { + if (control) { control->release(); } } void discard() { const auto control = control_; - if (control) - { + if (control) { control->release(true); } } @@ -337,8 +302,7 @@ class basic_pool std::shared_ptr control_; }; - struct acquire_result - { + struct acquire_result { client_errc error{client_errc::none}; arknet::error_code transport_error; lease connection; @@ -352,8 +316,7 @@ class basic_pool using completion_handler = std::function; private: - struct waiter - { + struct waiter { request_id id{}; completion_handler handler; std::shared_ptr timer; @@ -363,8 +326,7 @@ class basic_pool bool admitted{}; }; - struct state : std::enable_shared_from_this - { + struct state : std::enable_shared_from_this { pool_options options; std::shared_ptr runtime; std::shared_ptr gate{std::make_shared()}; @@ -389,13 +351,11 @@ class basic_pool const auto now = std::chrono::steady_clock::now(); if (!options.max_connections || !options.max_waiters || !arknet::detail::checked_deadline(now, options.acquire_timeout) || - !arknet::detail::checked_deadline(now, options.reset_timeout)) - { + !arknet::detail::checked_deadline(now, options.reset_timeout)) { throw std::invalid_argument("Redis pool limits and deadlines must be positive and representable"); } arknet::detail::pool_cleanup(); - if (!runtime->start()) - { + if (!runtime->start()) { throw arknet::system_error(arknet::get_last_error()); } } @@ -406,10 +366,8 @@ class basic_pool bool reserve() { auto count = admitted.load(); - while (count < options.max_waiters) - { - if (admitted.compare_exchange_weak(count, count + 1)) - { + while (count < options.max_waiters) { + if (admitted.compare_exchange_weak(count, count + 1)) { return true; } } @@ -427,17 +385,13 @@ class basic_pool connection->phase = entry_phase::retiring; auto child = connection->client->state_; std::exception_ptr failure; - if (child->terminal) - { + if (child->terminal) { failure = child->reconcile_close_on_lane(); - } - else - { + } else { child->close_on_lane(); failure = child->native_cleanup_failure; } - if (failure) - { + if (failure) { connection->failed_return = true; diagnostics->record(std::move(failure)); return false; @@ -447,23 +401,19 @@ class basic_pool } void recover_failed_returns() { - if (closed || close_requested || recovering_returns) - { + if (closed || close_requested || recovering_returns) { return; } recovering_returns = true; // Only the existing bounded entry set is scanned. Keep failed closes // tracked, and leave their next attempt to a later actor pass. - for (std::size_t index{}, remaining = entries.size(); remaining && index < entries.size(); --remaining) - { + for (std::size_t index{}, remaining = entries.size(); remaining && index < entries.size(); --remaining) { auto connection = entries[index]; - if (!connection->failed_return.load()) - { + if (!connection->failed_return.load()) { ++index; continue; } - if (!retire_entry(connection)) - { + if (!retire_entry(connection)) { ++index; } } @@ -475,23 +425,19 @@ class basic_pool } void complete(std::shared_ptr request, acquire_result response) { - if (request->completed) - { + if (request->completed) { return; } request->completed = true; - if (request->timer) - { + if (request->timer) { request->timer->cancel(); } pending.erase(request->id); - if (std::exchange(request->admitted, false)) - { + if (std::exchange(request->admitted, false)) { --admitted; } auto handler = std::exchange(request->handler, {}); - if (handler) - { + if (handler) { handler(std::move(response)); } } @@ -505,22 +451,16 @@ class basic_pool control->return_connection = [weak = this->weak_from_this(), diagnostics = diagnostics]( std::shared_ptr current, bool unhealthy) noexcept { auto self = weak.lock(); - try - { - if (self) - { + try { + if (self) { // Keep current pinned across post: a throwing submission // may already have consumed its handler's moved captures. asio::post( self->executor(), [self, current, unhealthy] { self->returned(current, unhealthy); }); - } - else - { + } else { current->client->close(); } - } - catch (...) - { + } catch (...) { current->failed_return = true; diagnostics->record(std::current_exception()); } @@ -532,19 +472,16 @@ class basic_pool void acquire(std::shared_ptr request, std::chrono::steady_clock::time_point deadline) { request->deadline = deadline; recover_failed_returns(); - if (closed || close_requested) - { + if (closed || close_requested) { complete(request, acquire_result{client_errc::closed}); return; } - if (!request->admitted) - { + if (!request->admitted) { complete(request, acquire_result{client_errc::queue_full}); pump(false); return; } - if (std::chrono::steady_clock::now() >= deadline) - { + if (std::chrono::steady_clock::now() >= deadline) { complete(request, acquire_result{client_errc::timeout}); pump(false); return; @@ -553,12 +490,9 @@ class basic_pool request->timer->expires_at(deadline); request->timer->async_wait( [weak = this->weak_from_this(), request = std::weak_ptr(request)](arknet::error_code error) { - if (!error) - { - if (auto self = weak.lock()) - { - if (auto current = request.lock()) - { + if (!error) { + if (auto self = weak.lock()) { + if (auto current = request.lock()) { self->cancel(current, client_errc::timeout); } } @@ -571,13 +505,11 @@ class basic_pool void cancel(std::shared_ptr request, client_errc reason) { recover_failed_returns(); - if (request->completed) - { + if (request->completed) { pump(false); return; } - if (auto assigned = request->assigned.lock()) - { + if (auto assigned = request->assigned.lock()) { discard(assigned); } complete(request, acquire_result{reason}); @@ -585,21 +517,17 @@ class basic_pool } void pump(bool recover = true) { - if (recover) - { + if (recover) { recover_failed_returns(); } std::erase_if(children, [](const auto& current) { return current.expired(); }); - while (!closed && !close_requested && !queue.empty()) - { + while (!closed && !close_requested && !queue.empty()) { auto request = queue.front(); - if (request->completed) - { + if (request->completed) { queue.pop_front(); continue; } - if (std::chrono::steady_clock::now() >= request->deadline) - { + if (std::chrono::steady_clock::now() >= request->deadline) { queue.pop_front(); complete(request, acquire_result{client_errc::timeout}); continue; @@ -607,11 +535,9 @@ class basic_pool auto idle = std::find_if(entries.begin(), entries.end(), [](const auto& connection) { return connection->phase == entry_phase::idle && !connection->failed_return.load(); }); - if (idle != entries.end()) - { + if (idle != entries.end()) { auto connection = *idle; - if (!connection->client->is_connected()) - { + if (!connection->client->is_connected()) { discard(connection); continue; } @@ -619,24 +545,18 @@ class basic_pool complete(request, acquire_result{client_errc::none, {}, make_lease(connection)}); continue; } - if (entries.size() >= options.max_connections) - { + if (entries.size() >= options.max_connections) { return; } queue.pop_front(); auto connection = std::make_shared(); - try - { + try { connection->client = std::shared_ptr( new client_type(options.client_configuration, options.certificate_authority, runtime)); - } - catch (const arknet::system_error& error) - { + } catch (const arknet::system_error& error) { complete(request, acquire_result{client_errc::connection_failed, error.code()}); continue; - } - catch (const std::invalid_argument&) - { + } catch (const std::invalid_argument&) { complete(request, acquire_result{client_errc::invalid_command}); continue; } @@ -646,38 +566,28 @@ class basic_pool children.push_back(connection->client->state_); connection->client->async_connect( options.connection, [weak = this->weak_from_this(), connection](result response) mutable { - if (auto self = weak.lock(); self && self->contains(connection)) - { + if (auto self = weak.lock(); self && self->contains(connection)) { auto request = connection->waiting.lock(); bool discarded{}; - if (!response || self->closed || self->close_requested) - { + if (!response || self->closed || self->close_requested) { discarded = true; self->discard(connection); - if (request) - { + if (request) { self->complete(request, acquire_result{self->closed || self->close_requested ? client_errc::closed : response.error, response.transport_error, {}, std::move(response.replies)}); } - } - else if (request && !request->completed) - { - if (std::chrono::steady_clock::now() >= request->deadline) - { + } else if (request && !request->completed) { + if (std::chrono::steady_clock::now() >= request->deadline) { discarded = true; self->discard(connection); self->complete(request, acquire_result{client_errc::timeout}); - } - else - { + } else { self->complete( request, acquire_result{client_errc::none, {}, self->make_lease(connection)}); } - } - else - { + } else { connection->phase = entry_phase::idle; } self->pump(!discarded); @@ -687,18 +597,15 @@ class basic_pool } void returned(const std::shared_ptr& connection, bool unhealthy) { - if (connection->failed_return.load()) - { + if (connection->failed_return.load()) { pump(); return; } - if (!contains(connection)) - { + if (!contains(connection)) { connection->client->close(); return; } - if (closed || unhealthy || !connection->client->is_connected()) - { + if (closed || unhealthy || !connection->client->is_connected()) { discard(connection); pump(false); return; @@ -706,15 +613,11 @@ class basic_pool connection->phase = entry_phase::resetting; connection->client->async_reset( [weak = this->weak_from_this(), connection](result response) { - if (auto self = weak.lock(); self && self->contains(connection)) - { + if (auto self = weak.lock(); self && self->contains(connection)) { const bool discarded = !response || self->closed || self->close_requested; - if (discarded) - { + if (discarded) { self->discard(connection); - } - else - { + } else { connection->phase = entry_phase::idle; } self->pump(!discarded); @@ -724,24 +627,20 @@ class basic_pool } void shutdown() { - if (std::exchange(closed, true)) - { + if (std::exchange(closed, true)) { return; } queue.clear(); // Suppress reentrant membership without dropping failed children. // The closed pool retains this bounded set through finalization. - for (const auto& connection : entries) - { + for (const auto& connection : entries) { connection->phase = entry_phase::retiring; } - while (!pending.empty()) - { + while (!pending.empty()) { auto request = pending.begin()->second; complete(request, acquire_result{client_errc::closed}); } - for (const auto& connection : entries) - { + for (const auto& connection : entries) { connection->client->state_->close_on_lane(); } } @@ -750,16 +649,13 @@ class basic_pool { std::unique_lock lock(gate->mutex); if ((runtime && runtime->running_in_threads()) || publication::active_on_this_thread(*gate) || - gate->finalizer == std::this_thread::get_id()) - { + gate->finalizer == std::this_thread::get_id()) { arknet::set_last_error(asio::error::operation_not_supported); return false; } - if (gate->terminating) - { + if (gate->terminating) { gate->changed.wait(lock, [&] { return gate->terminated || gate->failure; }); - if (gate->failure) - { + if (gate->failure) { std::rethrow_exception(gate->failure); } arknet::clear_last_error(); @@ -769,8 +665,7 @@ class basic_pool gate->finalizer = std::this_thread::get_id(); gate->changed.wait(lock, [&] { return gate->publishers == 0; }); } - try - { + try { close_requested = true; // Only the pool actor creates children/leases. The closing // flags and same-lane fence prevent any later catalog writer. @@ -780,28 +675,24 @@ class basic_pool auto ready = completion->get_future(); const auto self = this->shared_from_this(); asio::post(executor(), [self, completion] { - try - { + try { // Pin before shutdown can release the last entry or // invoke a callback that destroys its lease/facade. std::vector> children; - for (auto& weak : self->children) - { - if (auto child = weak.lock()) - { + for (auto& weak : self->children) { + if (auto child = weak.lock()) { children.push_back(std::move(child)); } } self->shutdown(); self->children.clear(); completion->set_value({std::move(children), std::move(self->controls)}); + } catch (...) { + completion->set_exception(std::current_exception()); } - catch (...) - { completion->set_exception(std::current_exception()); } }); auto [children, controls] = ready.get(); - for (auto& child : children) - { + for (auto& child : children) { { std::unique_lock lock(child->publishing_mutex); child->terminating = true; @@ -814,44 +705,35 @@ class basic_pool auto cleaned = std::make_shared>(); auto cleanup_ready = cleaned->get_future(); asio::post(executor(), [children, cleaned] { - try - { + try { std::exception_ptr failure; - for (const auto& child : children) - { + for (const auto& child : children) { auto current = child->reconcile_close_on_lane(); - if (!failure) - { + if (!failure) { failure = std::move(current); } } - if (failure) - { + if (failure) { cleaned->set_exception(std::move(failure)); - } - else - { + } else { cleaned->set_value(); } + } catch (...) { + cleaned->set_exception(std::current_exception()); } - catch (...) - { cleaned->set_exception(std::current_exception()); } }); cleanup_ready.get(); auto owned_runtime = runtime; owned_runtime->stop(); - for (const auto& child : children) - { - if (child->native_cleanup_pending) - { + for (const auto& child : children) { + if (child->native_cleanup_pending) { std::rethrow_exception(child->native_cleanup_failure); } } // Accepted outer Asio handlers/captures have retired. Retained // leases become inert without keeping TLS, timers or contexts. - for (auto& child : children) - { + for (auto& child : children) { child->retire_context_resources(); { std::lock_guard lock(child->publishing_mutex); @@ -860,10 +742,8 @@ class basic_pool } child->publishing_changed.notify_all(); } - for (auto& weak : controls) - { - if (auto control = weak.lock()) - { + for (auto& weak : controls) { + if (auto control = weak.lock()) { std::lock_guard lock(control->mutex); control->released = true; control->return_connection = {}; @@ -886,9 +766,7 @@ class basic_pool gate->changed.notify_all(); arknet::clear_last_error(); return true; - } - catch (...) - { + } catch (...) { { std::lock_guard lock(gate->mutex); gate->failure = std::current_exception(); @@ -915,10 +793,8 @@ class basic_pool completion_handler handler, std::chrono::milliseconds timeout = std::chrono::milliseconds::zero()) { auto self = state_; publication publishing(*self->gate); - if (!publishing) - { - if (handler) - { + if (!publishing) { + if (handler) { handler(acquire_result{client_errc::closed}); } return 0; @@ -929,23 +805,16 @@ class basic_pool const auto deadline = arknet::detail::checked_deadline(std::chrono::steady_clock::now(), timeout == std::chrono::milliseconds::zero() ? self->options.acquire_timeout : timeout); request->admitted = deadline && self->reserve(); - try - { + try { asio::post(self->executor(), [self, request, deadline] { - if (!deadline) - { + if (!deadline) { self->complete(request, acquire_result{client_errc::invalid_command}); - } - else - { + } else { self->acquire(request, *deadline); } }); - } - catch (...) - { - if (request->admitted) - { + } catch (...) { + if (request->admitted) { --self->admitted; } throw; @@ -956,17 +825,13 @@ class basic_pool void cancel(request_id id) { auto self = state_; publication publishing(*self->gate); - if (!publishing) - { + if (!publishing) { return; } asio::post(self->executor(), [self, id] { - if (auto found = self->pending.find(id); found != self->pending.end()) - { + if (auto found = self->pending.find(id); found != self->pending.end()) { self->cancel(found->second, client_errc::cancelled); - } - else - { + } else { self->pump(); } }); @@ -979,12 +844,10 @@ class basic_pool void close() { auto self = state_; publication publishing(*self->gate); - if (!publishing) - { + if (!publishing) { return; } - if (!self->close_requested.exchange(true)) - { + if (!self->close_requested.exchange(true)) { asio::dispatch(self->executor(), [self] { self->shutdown(); }); } } diff --git a/include/arknet/rudp/detail/transport.hpp b/include/arknet/rudp/detail/transport.hpp index 496ea45..481070a 100644 --- a/include/arknet/rudp/detail/transport.hpp +++ b/include/arknet/rudp/detail/transport.hpp @@ -22,8 +22,7 @@ namespace arknet::rudp { -struct statistics -{ +struct statistics { std::uint64_t transmissions{}, retransmissions{}, wire_bytes_tx{}, wire_bytes_rx{}; std::uint32_t smoothed_rtt_ms{}; std::size_t arq_pending_bytes{}, raw_queued_bytes{}; @@ -42,24 +41,21 @@ inline std::uint32_t rudp_clock() noexcept { inline std::uint32_t rudp_next_id() noexcept { static std::atomic next{rudp_clock() | 1U}; auto value = next.fetch_add(1, std::memory_order_relaxed); - if (!value) - { + if (!value) { value = next.fetch_add(1, std::memory_order_relaxed); } return value; } inline void rudp_put_id(char* destination, std::uint32_t value) noexcept { - for (unsigned i = 0; i < 4; ++i) - { + for (unsigned i = 0; i < 4; ++i) { destination[i] = static_cast(value >> (8 * i)); } } inline std::uint32_t rudp_read_id(const char* source) noexcept { std::uint32_t value{}; - for (unsigned i = 0; i < 4; ++i) - { + for (unsigned i = 0; i < 4; ++i) { value |= std::uint32_t(static_cast(source[i])) << (8 * i); } return value; @@ -70,8 +66,7 @@ inline std::string rudp_control(unsigned char type, std::uint32_t sync, std::uin std::string bytes(type == 1 ? 5 : 9, '\0'); bytes[0] = static_cast(type); rudp_put_id(bytes.data() + 1, sync); - if (bytes.size() == 9) - { + if (bytes.size() == 9) { rudp_put_id(bytes.data() + 5, conversation); } return bytes; @@ -86,11 +81,9 @@ inline std::string rudp_fec_control( std::string bytes(type == 4 ? 8 : type == 5 ? 12 : 5, '\0'); bytes[0] = static_cast(type); rudp_put_id(bytes.data() + 1, sync); - if (type == 4 || type == 5) - { + if (type == 4 || type == 5) { const auto offset = type == 4 ? 5 : 9; - if (type == 5) - { + if (type == 5) { rudp_put_id(bytes.data() + 5, conversation); } bytes[offset] = 1; @@ -114,10 +107,8 @@ inline std::size_t rudp_fec_mtu(std::string_view bytes, unsigned char type) noex } inline arq::options rudp_arq_options(arq::options options, const fec::options& fec_options) { - if (fec_options.mode != fec::policy::disabled) - { - if (options.mtu <= fec::transport::overhead + 24 || options.mtu > 65507) - { + if (fec_options.mode != fec::policy::disabled) { + if (options.mtu <= fec::transport::overhead + 24 || options.mtu > 65507) { throw std::invalid_argument("invalid FEC wire MTU"); } options.mtu -= fec::transport::overhead; @@ -126,15 +117,12 @@ inline arq::options rudp_arq_options(arq::options options, const fec::options& f } inline bool rudp_ack_only(std::string_view bytes) noexcept { - while (bytes.size() >= 24) - { - if (static_cast(bytes[4]) == 81) - { + while (bytes.size() >= 24) { + if (static_cast(bytes[4]) == 81) { return false; } const auto size = 24 + std::size_t(rudp_read_id(bytes.data() + 20)); - if (size > bytes.size()) - { + if (size > bytes.size()) { return false; } bytes.remove_prefix(size); @@ -142,8 +130,7 @@ inline bool rudp_ack_only(std::string_view bytes) noexcept { return bytes.empty(); } -class rudp_channel : public std::enable_shared_from_this -{ +class rudp_channel : public std::enable_shared_from_this { public: using delivery = std::function; using failure = std::function; @@ -159,28 +146,21 @@ class rudp_channel : public std::enable_shared_from_this , options_(options) , deliver_(std::move(deliver)) , fail_(std::move(fail)) { - if (fec_options.mode != fec::policy::disabled) - { + if (fec_options.mode != fec::policy::disabled) { fec_ = std::make_unique( conversation, options.mtu, fec_options, [this](std::string_view bytes) { raw_send(bytes); }, [this](std::string_view bytes) { return accept_arq(bytes); }); tick_interval_ = std::min(options.interval, fec_options.group_wait_ms); - } - else - { + } else { tick_interval_ = options.interval; } core_.set_output([this](std::string_view bytes) { - if (stopped_) - { + if (stopped_) { return; } - if (fec_) - { + if (fec_) { fec_->send(bytes, rudp_clock(), rudp_ack_only(bytes)); - } - else - { + } else { raw_send(bytes); } }); @@ -192,14 +172,12 @@ class rudp_channel : public std::enable_shared_from_this } void stop() { - if (stopped_) - { + if (stopped_) { return; } stopped_ = true; timer_.cancel(); - if (fec_) - { + if (fec_) { fec_->stop(); } deliver_ = {}; @@ -221,21 +199,18 @@ class rudp_channel : public std::enable_shared_from_this value.wire_bytes_tx = wire_bytes_tx_; value.wire_bytes_rx = wire_bytes_rx_; value.raw_queued_bytes = queued_bytes_; - if (fec_) - { + if (fec_) { value.fec = fec_->snapshot(); } return value; } int send(std::string_view bytes) { - if (stopped_) - { + if (stopped_) { return -3; } const auto result = core_.send(bytes); - if (!result) - { + if (!result) { core_.update(rudp_clock()); core_.flush(); } @@ -243,8 +218,7 @@ class rudp_channel : public std::enable_shared_from_this } bool input(std::string_view bytes) { - if (stopped_) - { + if (stopped_) { return false; } wire_bytes_rx_ += bytes.size(); @@ -252,28 +226,23 @@ class rudp_channel : public std::enable_shared_from_this } bool accept_arq(std::string_view bytes) { - if (stopped_) - { + if (stopped_) { return false; } const auto result = core_.input(bytes); - if (result == -5) - { + if (result == -5) { fail(asio::error::no_buffer_space); return false; } - if (result != 0) - { + if (result != 0) { return false; } std::string message; - while (!stopped_ && core_.receive(message)) - { + while (!stopped_ && core_.receive(message)) { const auto callback = deliver_; callback(message); } - if (!stopped_) - { + if (!stopped_) { core_.update(rudp_clock()); core_.flush(); } @@ -281,28 +250,24 @@ class rudp_channel : public std::enable_shared_from_this } void raw_send(std::string_view bytes) { - if (stopped_) - { + if (stopped_) { return; } - if (bytes.size() > options_.max_pending_bytes || queued_bytes_ > options_.max_pending_bytes - bytes.size()) - { + if (bytes.size() > options_.max_pending_bytes || queued_bytes_ > options_.max_pending_bytes - bytes.size()) { fail(asio::error::no_buffer_space); return; } queue_.push_back(std::make_shared(bytes)); queued_bytes_ += bytes.size(); wire_bytes_tx_ += bytes.size(); - if (!writing_) - { + if (!writing_) { write(); } } private: void fail(error_code error) { - if (stopped_) - { + if (stopped_) { return; } const auto callback = fail_; @@ -311,73 +276,56 @@ class rudp_channel : public std::enable_shared_from_this } void arm() { - if (stopped_) - { + if (stopped_) { return; } timer_.expires_after(std::chrono::milliseconds(tick_interval_)); timer_.async_wait([self = shared_from_this()](error_code error) { - if (error || self->stopped_) - { + if (error || self->stopped_) { return; } const auto now = rudp_clock(); self->core_.update(now); - if (self->fec_ && !self->stopped_) - { + if (self->fec_ && !self->stopped_) { self->fec_->adapt(now, self->core_.transmissions(), self->core_.retransmissions(), self->core_.smoothed_rtt_ms(), std::max(self->queued_bytes_, self->core_.pending_bytes()), self->options_.max_pending_bytes); self->fec_->update(now); } - if (self->core_.failed()) - { + if (self->core_.failed()) { self->fail(asio::error::timed_out); - } - else - { + } else { self->arm(); } }); } void write() { - if (stopped_ || queue_.empty()) - { + if (stopped_ || queue_.empty()) { return; } writing_ = true; auto bytes = queue_.front(); auto completion = [self = shared_from_this(), bytes](error_code error, std::size_t) { self->writing_ = false; - if (self->stopped_) - { + if (self->stopped_) { return; } self->queued_bytes_ -= bytes->size(); self->queue_.pop_front(); - if (error) - { + if (error) { self->fail(error); - } - else - { + } else { self->write(); } }; - try - { - if (connected_) - { + try { + if (connected_) { socket_->async_send(asio::buffer(*bytes), std::move(completion)); - } - else - { + } else { socket_->async_send_to(asio::buffer(*bytes), remote_, std::move(completion)); } - } - catch (const std::bad_alloc&) - { + } catch (const std::bad_alloc&) { writing_ = false; fail(asio::error::no_memory); throw; diff --git a/include/arknet/rudp/rudp_client.hpp b/include/arknet/rudp/rudp_client.hpp index c0bbe99..2b058f8 100644 --- a/include/arknet/rudp/rudp_client.hpp +++ b/include/arknet/rudp/rudp_client.hpp @@ -20,15 +20,13 @@ namespace arknet::detail { template -class rudp_client_impl_t : public udp_client_t -{ +class rudp_client_impl_t : public udp_client_t { ARKNET_CLASS_FRIEND_DECLARE_BASE; ARKNET_CLASS_FRIEND_DECLARE_UDP_BASE; ARKNET_CLASS_FRIEND_DECLARE_UDP_CLIENT; using udp_base = udp_client_t; - struct handshake - { + struct handshake { handshake(asio::any_io_executor executor, std::uint32_t sync, std::uint16_t mtu) : retry(executor) , request(mtu ? detail::rudp_fec_control(4, sync, 0, mtu) : detail::rudp_control(1, sync)) @@ -52,8 +50,7 @@ class rudp_client_impl_t : public udp_client_t } Derived& set_rudp_options(const arq::options& options) { - if (!this->is_stopped()) - { + if (!this->is_stopped()) { throw std::logic_error("Configure RUDP before start"); } arq::control_block validation(0, options); @@ -67,8 +64,7 @@ class rudp_client_impl_t : public udp_client_t } Derived& set_fec_options(const fec::options& options) { - if (!this->is_stopped()) - { + if (!this->is_stopped()) { throw std::logic_error("Configure FEC before start"); } options.validate(); @@ -82,14 +78,12 @@ class rudp_client_impl_t : public udp_client_t } std::future get_rudp_statistics() { - if (!this->is_started()) - { + if (!this->is_started()) { throw std::logic_error("Read RUDP statistics while started"); } return this->post( [this] { - if (!channel_) - { + if (!channel_) { throw std::logic_error("RUDP disconnected before statistics snapshot"); } return channel_->statistics(); @@ -111,11 +105,9 @@ class rudp_client_impl_t : public udp_client_t void stop() { stop_connect_ = true; - if (this->state_ == detail::state_t::starting) - { + if (this->state_ == detail::state_t::starting) { this->dispatch([this, lifetime = this->selfptr()] { - if (this->state_ == detail::state_t::starting) - { + if (this->state_ == detail::state_t::starting) { error_code ignored; this->socket().cancel(ignored); } @@ -135,8 +127,7 @@ class rudp_client_impl_t : public udp_client_t template void _handle_connect(const error_code& error, std::shared_ptr lifetime, std::shared_ptr> condition, Chain chain) { - if (error || stop_connect_.load()) - { + if (error || stop_connect_.load()) { udp_base::_handle_connect(error ? error : error_code(asio::error::operation_aborted), std::move(lifetime), std::move(condition), std::move(chain)); return; @@ -147,19 +138,16 @@ class rudp_client_impl_t : public udp_client_t auto completion = std::make_shared(std::move(chain)); operation->complete = [this, lifetime, condition, completion, sync = operation->sync]( error_code error, std::uint32_t conversation, std::uint16_t mtu) mutable { - if (!error) - { + if (!error) { sync_ = sync; auto negotiated_options = options_; - if (mtu) - { + if (mtu) { negotiated_options.mtu = mtu; } channel_ = std::make_shared( this->socket_, this->remote_endpoint_, true, conversation, negotiated_options, [this](std::string_view message) { - if (this->is_started()) - { + if (this->is_started()) { this->listener_.notify(detail::event_type::recv, detail::call_data_filter_before_recv(this->derived(), message)); } @@ -170,8 +158,7 @@ class rudp_client_impl_t : public udp_client_t } set_last_error(error); this->listener_.notify(detail::event_type::handshake, error_code(error)); - if (!error && stop_connect_.load()) - { + if (!error && stop_connect_.load()) { error = asio::error::operation_aborted; } udp_base::_handle_connect(error, std::move(lifetime), std::move(condition), std::move(*completion)); @@ -184,17 +171,13 @@ class rudp_client_impl_t : public udp_client_t void _fire_recv( std::shared_ptr& lifetime, std::shared_ptr>&, std::string_view bytes) { auto channel = channel_; - if (!channel) - { + if (!channel) { return; } if (detail::rudp_is_control(bytes, 3) && detail::rudp_read_id(bytes.data() + 1) == sync_ && - detail::rudp_read_id(bytes.data() + 5) == channel->conversation()) - { + detail::rudp_read_id(bytes.data() + 5) == channel->conversation()) { this->derived()._do_disconnect(asio::error::eof, lifetime); - } - else - { + } else { channel->input(bytes); } } @@ -205,8 +188,7 @@ class rudp_client_impl_t : public udp_client_t const auto channel = channel_; const auto result = channel ? channel->send({static_cast(buffer.data()), buffer.size()}) : -3; error_code error; - if (result) - { + if (result) { error = result == -2 ? asio::error::no_buffer_space : result == -3 ? asio::error::not_connected : asio::error::message_size; @@ -218,18 +200,15 @@ class rudp_client_impl_t : public udp_client_t template void _handle_disconnect(const error_code& error, std::shared_ptr lifetime, Chain chain) { - if (channel_) - { + if (channel_) { auto finish = std::make_shared(detail::rudp_control(3, sync_, channel_->conversation())); - if (this->socket().is_open()) - { + if (this->socket().is_open()) { this->socket().async_send(asio::buffer(*finish), [finish](error_code, std::size_t) {}); } channel_->stop(); channel_.reset(); } - if (handshake_) - { + if (handshake_) { handshake_->retry.cancel(); } udp_base::_handle_disconnect(error, std::move(lifetime), std::move(chain)); @@ -238,22 +217,19 @@ class rudp_client_impl_t : public udp_client_t private: static void validate_fec_mtu(const arq::options& transport, const fec::options& settings) { if (settings.mode != fec::policy::disabled && - (transport.mtu <= fec::transport::overhead + 24 || transport.mtu > 65507)) - { + (transport.mtu <= fec::transport::overhead + 24 || transport.mtu > 65507)) { throw std::invalid_argument("FEC wire MTU must be between 51 and 65507 bytes"); } } void send_syn(const std::shared_ptr& operation, const std::shared_ptr& lifetime) { - if (operation->finished) - { + if (operation->finished) { return; } this->socket().async_send(asio::buffer(operation->request), [operation, lifetime](error_code, std::size_t) {}); operation->retry.expires_after(std::chrono::milliseconds(200)); operation->retry.async_wait([this, operation, lifetime](error_code error) { - if (!error && !operation->finished && this->state_ == detail::state_t::starting) - { + if (!error && !operation->finished && this->state_ == detail::state_t::starting) { send_syn(operation, lifetime); } }); @@ -261,46 +237,39 @@ class rudp_client_impl_t : public udp_client_t void receive_ack(std::shared_ptr operation, std::shared_ptr lifetime) { this->reading_ = true; - this->socket().async_receive(asio::buffer(operation->response), [this, operation, lifetime](error_code error, - std::size_t count) mutable { - this->reading_ = false; - std::uint32_t conversation{}; - std::uint16_t mtu{}; - std::string_view bytes(operation->response.data(), error ? 0 : count); - if (!error) - { - if (detail::rudp_is_fec_control(bytes, 6) && detail::rudp_read_id(bytes.data() + 1) == operation->sync) - { - error = asio::error::operation_not_supported; - } - else if (operation->requested_mtu && detail::rudp_is_fec_control(bytes, 5) && - detail::rudp_read_id(bytes.data() + 1) == operation->sync) - { - mtu = detail::rudp_fec_mtu(bytes, 5); - conversation = detail::rudp_read_id(bytes.data() + 5); - if (static_cast(bytes[9]) != 1 || !conversation || - mtu <= fec::transport::overhead + 24 || mtu > operation->requested_mtu) - { - error = asio::error::invalid_argument; + this->socket().async_receive(asio::buffer(operation->response), + [this, operation, lifetime](error_code error, std::size_t count) mutable { + this->reading_ = false; + std::uint32_t conversation{}; + std::uint16_t mtu{}; + std::string_view bytes(operation->response.data(), error ? 0 : count); + if (!error) { + if (detail::rudp_is_fec_control(bytes, 6) && + detail::rudp_read_id(bytes.data() + 1) == operation->sync) { + error = asio::error::operation_not_supported; + } else if (operation->requested_mtu && detail::rudp_is_fec_control(bytes, 5) && + detail::rudp_read_id(bytes.data() + 1) == operation->sync) { + mtu = detail::rudp_fec_mtu(bytes, 5); + conversation = detail::rudp_read_id(bytes.data() + 5); + if (static_cast(bytes[9]) != 1 || !conversation || + mtu <= fec::transport::overhead + 24 || mtu > operation->requested_mtu) { + error = asio::error::invalid_argument; + } + } else if (operation->requested_mtu || !detail::rudp_is_control(bytes, 2) || + detail::rudp_read_id(bytes.data() + 1) != operation->sync || + !(conversation = detail::rudp_read_id(bytes.data() + 5))) { + receive_ack(operation, lifetime); + return; } } - else if (operation->requested_mtu || !detail::rudp_is_control(bytes, 2) || - detail::rudp_read_id(bytes.data() + 1) != operation->sync || - !(conversation = detail::rudp_read_id(bytes.data() + 5))) - { - receive_ack(operation, lifetime); - return; + operation->finished = true; + operation->retry.cancel(); + if (handshake_ == operation) { + handshake_.reset(); } - } - operation->finished = true; - operation->retry.cancel(); - if (handshake_ == operation) - { - handshake_.reset(); - } - auto complete = std::move(operation->complete); - complete(error, conversation, mtu); - }); + auto complete = std::move(operation->complete); + complete(error, conversation, mtu); + }); } arq::options options_; @@ -317,8 +286,7 @@ namespace arknet { template using rudp_client_t = detail::rudp_client_impl_t; -class rudp_client : public rudp_client_t -{ +class rudp_client : public rudp_client_t { public: using rudp_client_t::rudp_client_t; }; diff --git a/include/arknet/rudp/rudp_server.hpp b/include/arknet/rudp/rudp_server.hpp index df64e46..ddaf8ac 100644 --- a/include/arknet/rudp/rudp_server.hpp +++ b/include/arknet/rudp/rudp_server.hpp @@ -18,8 +18,7 @@ namespace arknet::detail { template -class rudp_server_impl_t : public udp_server_impl_t -{ +class rudp_server_impl_t : public udp_server_impl_t { ARKNET_CLASS_FRIEND_DECLARE_BASE; ARKNET_CLASS_FRIEND_DECLARE_UDP_BASE; ARKNET_CLASS_FRIEND_DECLARE_UDP_SERVER; @@ -33,8 +32,7 @@ class rudp_server_impl_t : public udp_server_impl_t } Derived& set_rudp_options(const arq::options& options) { - if (!this->is_stopped()) - { + if (!this->is_stopped()) { throw std::logic_error("Configure RUDP before start"); } arq::control_block validation(0, options); @@ -48,8 +46,7 @@ class rudp_server_impl_t : public udp_server_impl_t } Derived& set_fec_options(const fec::options& options) { - if (!this->is_stopped()) - { + if (!this->is_stopped()) { throw std::logic_error("Configure FEC before start"); } options.validate(); @@ -63,12 +60,10 @@ class rudp_server_impl_t : public udp_server_impl_t } Derived& set_max_sessions(std::size_t maximum) { - if (!this->is_stopped()) - { + if (!this->is_stopped()) { throw std::logic_error("Configure RUDP before start"); } - if (!maximum) - { + if (!maximum) { throw std::invalid_argument("RUDP session limit must be positive"); } max_sessions_ = maximum; @@ -90,8 +85,7 @@ class rudp_server_impl_t : public udp_server_impl_t protected: template void _post_recv(std::shared_ptr lifetime, std::shared_ptr> condition) { - if (!closing_) - { + if (!closing_) { base::_post_recv(std::move(lifetime), std::move(condition)); } } @@ -100,21 +94,18 @@ class rudp_server_impl_t : public udp_server_impl_t void _handle_recv(const error_code& error, std::size_t count, std::shared_ptr lifetime, std::shared_ptr> condition) { // A successful receive can already be queued when shutdown cancels the socket. - if (!closing_) - { + if (!closing_) { base::_handle_recv(error, count, std::move(lifetime), std::move(condition)); } } void _do_stop(const error_code& error, std::shared_ptr lifetime) { - if (closing_ || this->state_ == state_t::stopped || this->state_ == state_t::stopping) - { + if (closing_ || this->state_ == state_t::stopped || this->state_ == state_t::stopping) { return; } std::vector> sessions; this->sessions_.for_each([&](auto& session) { sessions.push_back(session); }); - if (sessions.empty()) - { + if (sessions.empty()) { base::_do_stop(error, std::move(lifetime)); return; } @@ -122,14 +113,12 @@ class rudp_server_impl_t : public udp_server_impl_t error_code ignored; this->acceptor_->cancel(ignored); auto remaining = std::make_shared(sessions.size()); - for (auto& session : sessions) - { + for (auto& session : sessions) { auto bytes = std::make_shared(rudp_control(3, session->sync_, session->conversation_)); // Complete close notices before the shared UDP socket is closed. this->acceptor_->async_send_to(asio::buffer(*bytes), session->remote_endpoint_, [this, lifetime, session, bytes, remaining, error](error_code, std::size_t) mutable { - if (--*remaining == 0) - { + if (--*remaining == 0) { closing_ = false; base::_do_stop(error, std::move(lifetime)); } @@ -140,25 +129,20 @@ class rudp_server_impl_t : public udp_server_impl_t template void _handle_accept(const error_code& error, std::string_view bytes, std::shared_ptr session, std::shared_ptr>& condition) { - if (detail::rudp_is_fec_control(bytes, 4)) - { + if (detail::rudp_is_fec_control(bytes, 4)) { const auto mtu = detail::rudp_fec_mtu(bytes, 4); if (fec_options_.mode == fec::policy::disabled || static_cast(bytes[5]) != 1 || - mtu <= fec::transport::overhead + 24 || mtu > 65507) - { + mtu <= fec::transport::overhead + 24 || mtu > 65507) { auto rejection = std::make_shared(detail::rudp_fec_control(6, detail::rudp_read_id(bytes.data() + 1))); this->acceptor_->async_send_to( asio::buffer(*rejection), this->remote_endpoint_, [rejection](error_code, std::size_t) {}); return; } - } - else if (!detail::rudp_is_control(bytes, 1)) - { + } else if (!detail::rudp_is_control(bytes, 1)) { return; } - if (this->get_session_count() >= max_sessions_) - { + if (this->get_session_count() >= max_sessions_) { return; } base::_handle_accept(error, bytes, std::move(session), condition); @@ -171,8 +155,7 @@ class rudp_server_impl_t : public udp_server_impl_t auto negotiated_options = options_; fec::options negotiated_fec; std::uint16_t requested_mtu{}; - if (detail::rudp_is_fec_control(request, 4)) - { + if (detail::rudp_is_fec_control(request, 4)) { requested_mtu = detail::rudp_fec_mtu(request, 4); negotiated_options.mtu = std::min(options_.mtu, static_cast(requested_mtu)); negotiated_fec = fec_options_; @@ -183,8 +166,7 @@ class rudp_server_impl_t : public udp_server_impl_t private: static void validate_fec_mtu(const arq::options& transport, const fec::options& settings) { if (settings.mode != fec::policy::disabled && - (transport.mtu <= fec::transport::overhead + 24 || transport.mtu > 65507)) - { + (transport.mtu <= fec::transport::overhead + 24 || transport.mtu > 65507)) { throw std::invalid_argument("FEC wire MTU must be between 51 and 65507 bytes"); } } @@ -202,8 +184,7 @@ template using rudp_server_impl_t = detail::rudp_server_impl_t; template -class rudp_server_t : public rudp_server_impl_t, Session> -{ +class rudp_server_t : public rudp_server_impl_t, Session> { public: using rudp_server_impl_t, Session>::rudp_server_impl_t; }; diff --git a/include/arknet/rudp/rudp_session.hpp b/include/arknet/rudp/rudp_session.hpp index 8e98192..3a47e1f 100644 --- a/include/arknet/rudp/rudp_session.hpp +++ b/include/arknet/rudp/rudp_session.hpp @@ -16,8 +16,7 @@ namespace arknet::detail { template -class rudp_session_impl_t : public udp_session_impl_t -{ +class rudp_session_impl_t : public udp_session_impl_t { template friend class rudp_server_impl_t; ARKNET_CLASS_FRIEND_DECLARE_BASE; @@ -39,8 +38,7 @@ class rudp_session_impl_t : public udp_session_impl_t , conversation_(conversation) {} ~rudp_session_impl_t() { - if (channel_) - { + if (channel_) { channel_->stop(); } } @@ -58,14 +56,12 @@ class rudp_session_impl_t : public udp_session_impl_t } std::future get_rudp_statistics() { - if (!this->is_started()) - { + if (!this->is_started()) { throw std::logic_error("Read RUDP statistics while started"); } return this->post( [this] { - if (!channel_) - { + if (!channel_) { throw std::logic_error("RUDP disconnected before statistics snapshot"); } return channel_->statistics(); @@ -80,15 +76,13 @@ class rudp_session_impl_t : public udp_session_impl_t channel_ = std::make_shared( this->socket_, this->remote_endpoint_, false, conversation_, options_, [this, weak](std::string_view message) { - if (auto owner = weak.lock(); owner && this->is_started()) - { + if (auto owner = weak.lock(); owner && this->is_started()) { this->listener_.notify(detail::event_type::recv, owner, detail::call_data_filter_before_recv(this->derived(), message)); } }, [this, weak](error_code error) { - if (auto owner = weak.lock()) - { + if (auto owner = weak.lock()) { this->derived()._do_disconnect(error, std::move(owner)); } }, @@ -97,8 +91,7 @@ class rudp_session_impl_t : public udp_session_impl_t channel_->start(); clear_last_error(); this->listener_.notify(detail::event_type::handshake, lifetime, error_code{}); - if (!this->is_started() || this->stop_requested_) - { + if (!this->is_started() || this->stop_requested_) { return; } base::_fire_connect(lifetime, condition); @@ -107,8 +100,7 @@ class rudp_session_impl_t : public udp_session_impl_t template void _receive_datagram(std::shared_ptr lifetime, std::shared_ptr> condition, std::string_view bytes) { - if (this->is_started() && !this->stop_requested_) - { + if (this->is_started() && !this->stop_requested_) { this->derived()._fire_recv(lifetime, condition, bytes); } } @@ -117,35 +109,25 @@ class rudp_session_impl_t : public udp_session_impl_t void _fire_recv( std::shared_ptr& lifetime, std::shared_ptr>&, std::string_view bytes) { auto channel = channel_; - if (!channel) - { + if (!channel) { return; } - if (fec_options_.mode == fec::policy::disabled && detail::rudp_is_control(bytes, 1)) - { - if (detail::rudp_read_id(bytes.data() + 1) == sync_) - { + if (fec_options_.mode == fec::policy::disabled && detail::rudp_is_control(bytes, 1)) { + if (detail::rudp_read_id(bytes.data() + 1) == sync_) { this->update_alive_time(); channel->raw_send(acknowledgement()); } - } - else if (fec_options_.mode != fec::policy::disabled && detail::rudp_is_fec_control(bytes, 4)) - { + } else if (fec_options_.mode != fec::policy::disabled && detail::rudp_is_fec_control(bytes, 4)) { if (detail::rudp_read_id(bytes.data() + 1) == sync_ && static_cast(bytes[5]) == 1 && - detail::rudp_fec_mtu(bytes, 4) == requested_mtu_) - { + detail::rudp_fec_mtu(bytes, 4) == requested_mtu_) { this->update_alive_time(); channel->raw_send(acknowledgement()); } - } - else if (detail::rudp_is_control(bytes, 3) && detail::rudp_read_id(bytes.data() + 1) == sync_ && - detail::rudp_read_id(bytes.data() + 5) == conversation_) - { + } else if (detail::rudp_is_control(bytes, 3) && detail::rudp_read_id(bytes.data() + 1) == sync_ && + detail::rudp_read_id(bytes.data() + 5) == conversation_) { this->update_alive_time(); this->derived()._do_disconnect(asio::error::eof, lifetime); - } - else if (channel->input(bytes)) - { + } else if (channel->input(bytes)) { this->update_alive_time(); } } @@ -156,8 +138,7 @@ class rudp_session_impl_t : public udp_session_impl_t auto channel = channel_; const auto result = channel ? channel->send({static_cast(buffer.data()), buffer.size()}) : -3; error_code error; - if (result) - { + if (result) { error = result == -2 ? asio::error::no_buffer_space : result == -3 ? asio::error::not_connected : asio::error::message_size; @@ -186,13 +167,11 @@ class rudp_session_impl_t : public udp_session_impl_t } void close_channel() { - if (!channel_) - { + if (!channel_) { return; } auto finish = std::make_shared(detail::rudp_control(3, sync_, conversation_)); - if (this->socket().is_open()) - { + if (this->socket().is_open()) { this->socket().async_send_to( asio::buffer(*finish), this->remote_endpoint_, [finish](error_code, std::size_t) {}); } @@ -214,8 +193,7 @@ namespace arknet { template using rudp_session_t = detail::rudp_session_impl_t; -class rudp_session : public rudp_session_t -{ +class rudp_session : public rudp_session_t { public: using rudp_session_t::rudp_session_t; }; diff --git a/include/arknet/tcp/impl/ssl_context_cp.hpp b/include/arknet/tcp/impl/ssl_context_cp.hpp index a9ead67..ee1d545 100644 --- a/include/arknet/tcp/impl/ssl_context_cp.hpp +++ b/include/arknet/tcp/impl/ssl_context_cp.hpp @@ -10,18 +10,15 @@ namespace arknet::detail { template -class ssl_context_cp : public asio::ssl::context -{ +class ssl_context_cp : public asio::ssl::context { public: explicit ssl_context_cp(asio::ssl::context::method method) : asio::ssl::context(method) { set_options(default_workarounds | no_sslv2 | no_sslv3); - if (SSL_CTX_set_min_proto_version(native_handle(), TLS1_2_VERSION) != 1) - { + if (SSL_CTX_set_min_proto_version(native_handle(), TLS1_2_VERSION) != 1) { throw system_error(asio::error::invalid_argument, "Unable to require TLS 1.2"); } - if constexpr (Args::is_client) - { + if constexpr (Args::is_client) { set_verify_mode(asio::ssl::verify_peer); set_default_verify_paths(); } @@ -30,18 +27,15 @@ class ssl_context_cp : public asio::ssl::context Derived& set_cert_buffer( std::string_view ca, std::string_view certificate, std::string_view key, std::string_view password) noexcept { return load_identity(password, [&, this](error_code& ec) { - if (certificate.empty() || key.empty()) - { + if (certificate.empty() || key.empty()) { ec = asio::error::invalid_argument; return; } use_certificate_chain(asio::buffer(certificate), ec); - if (!ec) - { + if (!ec) { use_private_key(asio::buffer(key), pem, ec); } - if (!ec && !ca.empty()) - { + if (!ec && !ca.empty()) { add_certificate_authority(asio::buffer(ca), ec); } }); @@ -50,18 +44,15 @@ class ssl_context_cp : public asio::ssl::context Derived& set_cert_file(const std::string& ca, const std::string& certificate, const std::string& key, const std::string& password) noexcept { return load_identity(password, [&, this](error_code& ec) { - if (certificate.empty() || key.empty()) - { + if (certificate.empty() || key.empty()) { ec = asio::error::invalid_argument; return; } use_certificate_chain_file(certificate, ec); - if (!ec) - { + if (!ec) { use_private_key_file(key, pem, ec); } - if (!ec && !ca.empty()) - { + if (!ec && !ca.empty()) { load_verify_file(ca, ec); } }); @@ -69,8 +60,7 @@ class ssl_context_cp : public asio::ssl::context Derived& set_dh_buffer(std::string_view parameters) noexcept { error_code ec; - if (!parameters.empty()) - { + if (!parameters.empty()) { use_tmp_dh(asio::buffer(parameters), ec); } set_last_error(ec); @@ -79,8 +69,7 @@ class ssl_context_cp : public asio::ssl::context Derived& set_dh_file(const std::string& path) noexcept { error_code ec; - if (!path.empty()) - { + if (!path.empty()) { use_tmp_dh_file(path, ec); } set_last_error(ec); @@ -90,13 +79,11 @@ class ssl_context_cp : public asio::ssl::context protected: error_code _check_tls_identity() noexcept { auto* context = native_handle(); - if (!SSL_CTX_get0_certificate(context) || !SSL_CTX_get0_privatekey(context)) - { + if (!SSL_CTX_get0_certificate(context) || !SSL_CTX_get0_privatekey(context)) { return asio::error::invalid_argument; } ERR_clear_error(); - if (SSL_CTX_check_private_key(context) == 1) - { + if (SSL_CTX_check_private_key(context) == 1) { return {}; } const int native = static_cast(ERR_get_error()); @@ -110,12 +97,10 @@ class ssl_context_cp : public asio::ssl::context Derived& load_identity(std::string_view password, Load load) noexcept { error_code ec; set_password_callback([secret = std::string{password}](std::size_t, password_purpose) { return secret; }, ec); - if (!ec) - { + if (!ec) { load(ec); } - if (!ec) - { + if (!ec) { ec = _check_tls_identity(); } identity_error_ = ec; diff --git a/include/arknet/tcp/impl/ssl_stream_cp.hpp b/include/arknet/tcp/impl/ssl_stream_cp.hpp index c842c8f..26bdd62 100644 --- a/include/arknet/tcp/impl/ssl_stream_cp.hpp +++ b/include/arknet/tcp/impl/ssl_stream_cp.hpp @@ -15,8 +15,7 @@ namespace arknet::detail { template -class ssl_stream_cp : public ssl_stream_tag -{ +class ssl_stream_cp : public ssl_stream_tag { public: using ssl_socket_type = typename Args::socket_t; using ssl_stream_type = asio::ssl::stream; @@ -53,8 +52,7 @@ class ssl_stream_cp : public ssl_stream_tag void _ssl_stop(std::shared_ptr lifetime, Chain chain) { auto& owner = static_cast(*this); ARKNET_ASSERT(owner.io_->running_in_this_thread()); - if (!ssl_stream_) - { + if (!ssl_stream_) { return; } owner.disp_event( @@ -63,8 +61,7 @@ class ssl_stream_cp : public ssl_stream_tag auto timer = std::make_shared(owner.io_->executor()); timer->expires_after(owner.get_disconnect_timeout()); timer->async_wait(owner._track_async_operation([&owner, lifetime](error_code ec) { - if (!ec) - { + if (!ec) { error_code ignored; owner.socket().cancel(ignored); owner.socket().close(ignored); @@ -92,14 +89,12 @@ class ssl_stream_cp : public ssl_stream_tag void _post_handshake(std::shared_ptr lifetime, std::shared_ptr> policy, Chain chain) { auto& owner = static_cast(*this); ARKNET_ASSERT(owner.io_->running_in_this_thread()); - if (auto ec = _prepare_handshake()) - { + if (auto ec = _prepare_handshake()) { owner._handle_handshake(ec, std::move(lifetime), std::move(policy), std::move(chain)); return; } - struct deadline - { + struct deadline { asio::steady_timer timer; bool expired = false; @@ -110,8 +105,7 @@ class ssl_stream_cp : public ssl_stream_tag auto timeout = std::make_shared(owner.io_->executor()); timeout->timer.expires_after(owner.get_connect_timeout()); timeout->timer.async_wait(owner._track_async_operation([&owner, lifetime, timeout](error_code ec) { - if (ec) - { + if (ec) { return; } timeout->expired = true; @@ -136,48 +130,36 @@ class ssl_stream_cp : public ssl_stream_tag } error_code _prepare_handshake() { - if constexpr (!Args::is_client) - { + if constexpr (!Args::is_client) { return {}; - } - else - { + } else { const auto& host = static_cast(*this).host_; - if (host.empty() || host.find('\0') != std::string::npos) - { + if (host.empty() || host.find('\0') != std::string::npos) { return asio::error::invalid_argument; } SSL* ssl = ssl_stream().native_handle(); auto* identity = SSL_get0_param(ssl); ERR_clear_error(); if (SSL_set_tlsext_host_name(ssl, nullptr) != 1 || X509_VERIFY_PARAM_set1_host(identity, nullptr, 0) != 1 || - X509_VERIFY_PARAM_set1_ip(identity, nullptr, 0) != 1) - { + X509_VERIFY_PARAM_set1_ip(identity, nullptr, 0) != 1) { return _ssl_configuration_error(); } error_code parse_error; const auto address = asio::ip::make_address(host, parse_error); - if (parse_error && SSL_set_tlsext_host_name(ssl, host.c_str()) != 1) - { + if (parse_error && SSL_set_tlsext_host_name(ssl, host.c_str()) != 1) { return _ssl_configuration_error(); } - if (!(SSL_get_verify_mode(ssl) & SSL_VERIFY_PEER)) - { + if (!(SSL_get_verify_mode(ssl) & SSL_VERIFY_PEER)) { return {}; } X509_VERIFY_PARAM_set_hostflags(identity, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); int accepted; - if (parse_error) - { + if (parse_error) { accepted = SSL_set1_host(ssl, host.c_str()); - } - else if (address.is_v4()) - { + } else if (address.is_v4()) { const auto bytes = address.to_v4().to_bytes(); accepted = X509_VERIFY_PARAM_set1_ip(identity, bytes.data(), bytes.size()); - } - else - { + } else { const auto bytes = address.to_v6().to_bytes(); accepted = X509_VERIFY_PARAM_set1_ip(identity, bytes.data(), bytes.size()); } @@ -198,12 +180,9 @@ class ssl_stream_cp : public ssl_stream_tag chain = std::move(chain)]() mutable { set_last_error(ec); owner._fire_handshake(lifetime); - if (ec) - { + if (ec) { owner._do_disconnect(ec, std::move(lifetime), std::move(chain)); - } - else - { + } else { owner._done_connect(ec, std::move(lifetime), std::move(policy), std::move(chain)); } }); @@ -221,13 +200,10 @@ class ssl_stream_cp : public ssl_stream_tag template void _handle_handshake(const error_code& ec, std::shared_ptr lifetime, std::shared_ptr> policy, Chain chain) { - if constexpr (Args::is_session) - { + if constexpr (Args::is_session) { static_cast(*this)._session_handle_handshake( ec, std::move(lifetime), std::move(policy), std::move(chain)); - } - else - { + } else { static_cast(*this)._client_handle_handshake( ec, std::move(lifetime), std::move(policy), std::move(chain)); } diff --git a/include/arknet/tcp/impl/tcp_keepalive_cp.hpp b/include/arknet/tcp/impl/tcp_keepalive_cp.hpp index bb7ccb8..ecbe454 100644 --- a/include/arknet/tcp/impl/tcp_keepalive_cp.hpp +++ b/include/arknet/tcp/impl/tcp_keepalive_cp.hpp @@ -6,8 +6,7 @@ namespace arknet::detail { template -class tcp_keepalive_cp -{ +class tcp_keepalive_cp { public: bool set_keep_alive_options( bool enabled = true, unsigned idle = 60, unsigned interval = 3, unsigned probes = 3) noexcept { diff --git a/include/arknet/tcp/impl/tcp_recv_op.hpp b/include/arknet/tcp/impl/tcp_recv_op.hpp index 36733e6..f32b6eb 100644 --- a/include/arknet/tcp/impl/tcp_recv_op.hpp +++ b/include/arknet/tcp/impl/tcp_recv_op.hpp @@ -12,12 +12,10 @@ namespace arknet::detail { template -class tcp_recv_op -{ +class tcp_recv_op { protected: template - struct has_member_dgram : std::bool_constant < requires(T& value) - { + struct has_member_dgram : std::bool_constant < requires(T& value) { value.dgram_; } > {}; @@ -25,10 +23,8 @@ class tcp_recv_op void _tcp_post_recv(std::shared_ptr lifetime, std::shared_ptr> policy) { auto& owner = static_cast(*this); ARKNET_ASSERT(owner.io_->running_in_this_thread()); - if (!owner.is_started()) - { - if (owner.state_ == state_t::started) - { + if (!owner.is_started()) { + if (owner.state_ == state_t::started) { owner._do_disconnect(get_last_error(), std::move(lifetime)); } return; @@ -48,12 +44,9 @@ class tcp_recv_op owner._handle_recv(ec, size, std::move(lifetime), std::move(policy)); })); auto&& condition = policy->get_condition().lowest(); - if constexpr (std::is_invocable_v) - { + if constexpr (std::is_invocable_v) { asio::async_read(owner.stream(), owner.buffer().base(), condition, std::move(completion)); - } - else - { + } else { asio::async_read_until(owner.stream(), owner.buffer().base(), condition, std::move(completion)); } } @@ -76,41 +69,33 @@ class tcp_recv_op ARKNET_ASSERT(owner.io_->running_in_this_thread()); set_last_error(ec); // A completion from an earlier connection must not stop a new connection attempt. - if (!owner.is_started()) - { - if (owner.state_ == state_t::started) - { + if (!owner.is_started()) { + if (owner.state_ == state_t::started) { owner._do_disconnect(ec, lifetime); } owner._stop_readend_timer(std::move(lifetime)); return; } - if (ec) - { + if (ec) { owner._do_disconnect(ec, lifetime); owner._stop_readend_timer(std::move(lifetime)); return; } using Mode = typename ecs_t::condition_lowest_type; owner.update_alive_time(); - if constexpr (std::is_same_v) - { - if (size == 0) - { + if constexpr (std::is_same_v) { + if (size == 0) { owner._do_disconnect(asio::error::no_data, lifetime); owner._stop_readend_timer(std::move(lifetime)); return; } owner._tcp_dgram_fire_recv(ec, size, lifetime, policy); - } - else - { + } else { const auto delivered = std::is_same_v ? owner.buffer().size() : size; owner._fire_recv( lifetime, policy, std::string_view{static_cast(owner.buffer().data().data()), delivered}); } - if constexpr (!std::is_same_v) - { + if constexpr (!std::is_same_v) { owner.buffer().consume(size); } owner._post_recv(std::move(lifetime), std::move(policy)); diff --git a/include/arknet/tcp/impl/tcp_send_op.hpp b/include/arknet/tcp/impl/tcp_send_op.hpp index 80c0fb0..89a20c1 100644 --- a/include/arknet/tcp/impl/tcp_send_op.hpp +++ b/include/arknet/tcp/impl/tcp_send_op.hpp @@ -13,22 +13,18 @@ namespace arknet::detail { template -class tcp_send_op -{ +class tcp_send_op { protected: template - struct has_member_dgram : std::bool_constant < requires(T& value) - { + struct has_member_dgram : std::bool_constant < requires(T& value) { value.dgram_; } > {}; template bool _tcp_send(Data& data, Completion&& completion) { auto& owner = static_cast(*this); - if constexpr (requires { owner.dgram_; }) - { - if (owner.dgram_) - { + if constexpr (requires { owner.dgram_; }) { + if (owner.dgram_) { return owner._tcp_send_dgram(asio::buffer(data), std::forward(completion)); } } @@ -41,8 +37,7 @@ class tcp_send_op const auto size = payload.size(); const std::size_t prefix = size < 254 ? 1 : size <= UINT16_MAX ? 3 : 9; (*header)[0] = prefix == 1 ? static_cast(size) : prefix == 3 ? 254 : 255; - for (std::size_t index = 1; index < prefix; ++index) - { + for (std::size_t index = 1; index < prefix; ++index) { (*header)[index] = static_cast(static_cast(size) >> (8 * (index - 1))); } std::array buffers{asio::buffer(header->data(), prefix), payload}; diff --git a/include/arknet/tcp/tcp_client.hpp b/include/arknet/tcp/tcp_client.hpp index e00befd..461ee2e 100644 --- a/include/arknet/tcp/tcp_client.hpp +++ b/include/arknet/tcp/tcp_client.hpp @@ -8,8 +8,7 @@ namespace arknet::detail { -struct template_args_tcp_client : tcp_tag -{ +struct template_args_tcp_client : tcp_tag { static constexpr bool is_session = false, is_client = true, is_server = false; using socket_t = asio::ip::tcp::socket; using buffer_t = asio::streambuf; @@ -27,8 +26,7 @@ class tcp_client_impl_t , public tcp_keepalive_cp , public tcp_send_op , public tcp_recv_op - , public tcp_tag -{ + , public tcp_tag { ARKNET_CLASS_FRIEND_DECLARE_BASE; ARKNET_CLASS_FRIEND_DECLARE_TCP_BASE; ARKNET_CLASS_FRIEND_DECLARE_TCP_CLIENT; @@ -78,16 +76,13 @@ class tcp_client_impl_t void stop() { this->_set_async_wait_acceptance(false); const bool worker = this->iopool().running_in_threads(); - if (this->is_iopool_stopped()) - { - if (!worker && !this->io_->context().stopped()) - { + if (this->is_iopool_stopped()) { + if (!worker && !this->io_->context().stopped()) { this->_wait_async_operations(); } return; } - if (this->io_->context().stopped() && super::is_stopped()) - { + if (this->io_->context().stopped() && super::is_stopped()) { this->stop_iopool(); return; } @@ -106,8 +101,7 @@ class tcp_client_impl_t }, std::move(guard)}); }); - if (!worker) - { + if (!worker) { result.get(); this->_wait_async_operations(); } @@ -140,8 +134,7 @@ class tcp_client_impl_t const auto hostname = detail::to_string(std::forward(host)); const auto service = detail::to_string(std::forward(port)); this->start_iopool(); - if (!this->is_iopool_started()) - { + if (!this->is_iopool_started()) { set_last_error(asio::error::operation_aborted); return false; } @@ -153,14 +146,12 @@ class tcp_client_impl_t auto& owner = this->derived(); defer_event finish{[completion](event_queue_guard) { completion->set_value(get_last_error()); }, std::move(guard)}; - if (owner.is_iopool_stop_requested()) - { + if (owner.is_iopool_stop_requested()) { set_last_error(asio::error::operation_aborted); return; } auto expected = state_t::stopped; - if (!owner.state_.compare_exchange_strong(expected, state_t::starting)) - { + if (!owner.state_.compare_exchange_strong(expected, state_t::starting)) { set_last_error(asio::error::already_started); return; } @@ -180,10 +171,8 @@ class tcp_client_impl_t owner._do_init(policy); owner.template _start_connect(std::move(lifetime), std::move(policy), std::move(finish)); }); - if constexpr (!Async) - { - if (!this->iopool().running_in_threads()) - { + if constexpr (!Async) { + if (!this->iopool().running_in_threads()) { set_last_error(result.get()); return !get_last_error(); } @@ -236,8 +225,7 @@ class tcp_client_impl_t void _start_recv(std::shared_ptr lifetime, std::shared_ptr> policy, Chain chain) { this->derived().dispatch( [this, lifetime = std::move(lifetime), policy = std::move(policy), chain = std::move(chain)]() mutable { - if constexpr (!std::is_same_v::condition_lowest_type, hook_buffer_t>) - { + if constexpr (!std::is_same_v::condition_lowest_type, hook_buffer_t>) { this->buffer().consume(this->buffer().size()); } this->derived()._post_recv(std::move(lifetime), std::move(policy)); @@ -306,13 +294,11 @@ class tcp_client_impl_t error_code ignored, option_error; asio::socket_base::linger linger; auto& socket = this->socket(); - if (!socket.is_open()) - { + if (!socket.is_open()) { return; } socket.lowest_layer().get_option(linger, option_error); - if (!option_error && !(linger.enabled() && linger.timeout() == 0)) - { + if (!option_error && !(linger.enabled() && linger.timeout() == 0)) { socket.shutdown(asio::socket_base::shutdown_both, ignored); } socket.cancel(ignored); @@ -328,14 +314,12 @@ template using tcp_client_impl_t = detail::tcp_client_impl_t; template -class tcp_client_t : public detail::tcp_client_impl_t -{ +class tcp_client_t : public detail::tcp_client_impl_t { public: using detail::tcp_client_impl_t::tcp_client_impl_t; }; -class tcp_client : public tcp_client_t -{ +class tcp_client : public tcp_client_t { public: using tcp_client_t::tcp_client_t; }; @@ -345,20 +329,17 @@ class tcp_client : public tcp_client_t namespace arknet { -struct tcp_rate_client_args : tcp_client_args -{ +struct tcp_rate_client_args : tcp_client_args { using socket_t = tcp_stream; }; template -class tcp_rate_client_t : public tcp_client_impl_t -{ +class tcp_rate_client_t : public tcp_client_impl_t { public: using tcp_client_impl_t::tcp_client_impl_t; }; -class tcp_rate_client : public tcp_rate_client_t -{ +class tcp_rate_client : public tcp_rate_client_t { public: using tcp_rate_client_t::tcp_rate_client_t; }; diff --git a/include/arknet/tcp/tcp_server.hpp b/include/arknet/tcp/tcp_server.hpp index 78a6f44..abf94a0 100644 --- a/include/arknet/tcp/tcp_server.hpp +++ b/include/arknet/tcp/tcp_server.hpp @@ -13,8 +13,7 @@ ARKNET_CLASS_FORWARD_DECLARE_TCP_SERVER; template class tcp_server_impl_t : public server_impl_t - , public tcp_tag -{ + , public tcp_tag { ARKNET_CLASS_FRIEND_DECLARE_BASE; ARKNET_CLASS_FRIEND_DECLARE_TCP_BASE; ARKNET_CLASS_FRIEND_DECLARE_TCP_SERVER; @@ -59,32 +58,25 @@ class tcp_server_impl_t void stop() { const bool worker = this->iopool().running_in_threads(); std::unique_lock owner_lock(owner_stop_mutex_, std::defer_lock); - if (!worker) - { + if (!worker) { owner_lock.lock(); - if (owner_stop_active_) - { + if (owner_stop_active_) { set_last_error(asio::error::operation_not_supported); return; } } defer_event release_owner{[this, worker] { - if (!worker) - { + if (!worker) { owner_stop_active_ = false; } }}; - if (!worker) - { + if (!worker) { owner_stop_active_ = true; } this->_set_async_wait_acceptance(false); - if (this->is_iopool_stopped()) - { - if (!worker) - { - if (!this->io_->context().stopped()) - { + if (this->is_iopool_stopped()) { + if (!worker) { + if (!this->io_->context().stopped()) { _wait_stop_operations(); } // Another external owner may still be completing lane fences. @@ -92,8 +84,7 @@ class tcp_server_impl_t } return; } - if (this->io_->context().stopped() && is_stopped()) - { + if (this->io_->context().stopped() && is_stopped()) { this->stop_iopool(); return; } @@ -102,18 +93,14 @@ class tcp_server_impl_t auto completion = std::make_shared>(); auto result = completion->get_future(); owner.post([&owner, lifetime = owner.selfptr(), completion] { - if (owner.is_stopped()) - { + if (owner.is_stopped()) { completion->set_value(); - } - else - { + } else { owner.stop_waiters_.push_back(completion); owner._do_stop(asio::error::operation_aborted, owner.selfptr()); } }); - if (!worker) - { + if (!worker) { result.get(); _wait_stop_operations(); } @@ -121,14 +108,12 @@ class tcp_server_impl_t } bool wait_stopped() { - if (this->iopool().running_in_threads() || this->io_->context().get_executor().running_in_this_thread()) - { + if (this->iopool().running_in_threads() || this->io_->context().get_executor().running_in_this_thread()) { set_last_error(asio::error::operation_not_supported); return false; } std::lock_guard owner_lock(owner_stop_mutex_); - if (owner_stop_active_) - { + if (owner_stop_active_) { set_last_error(asio::error::operation_not_supported); return false; } @@ -205,8 +190,7 @@ class tcp_server_impl_t const auto hostname = detail::to_string(std::forward(host)); const auto service = detail::to_string(std::forward(port)); this->start_iopool(); - if (!this->is_iopool_started()) - { + if (!this->is_iopool_started()) { set_last_error(asio::error::operation_aborted); return false; } @@ -218,8 +202,7 @@ class tcp_server_impl_t auto& owner = this->derived(); defer_event finish{[completion] { completion->set_value(get_last_error()); }}; auto expected = state_t::stopped; - if (!this->state_.compare_exchange_strong(expected, state_t::starting)) - { + if (!this->state_.compare_exchange_strong(expected, state_t::starting)) { set_last_error(asio::error::already_started); return; } @@ -235,8 +218,7 @@ class tcp_server_impl_t [&owner](void*) { owner._exec_stop(asio::error::operation_aborted, owner.selfptr()); }); owner._handle_start(open_listener(hostname, service), std::move(lifetime), std::move(policy)); }); - if (!this->iopool().running_in_threads()) - { + if (!this->iopool().running_in_threads()) { set_last_error(result.get()); return !get_last_error(); } @@ -248,22 +230,17 @@ class tcp_server_impl_t void _handle_start(error_code ec, std::shared_ptr lifetime, std::shared_ptr> policy) { ARKNET_ASSERT(this->io_->running_in_this_thread()); auto expected = state_t::starting; - if (!ec && !this->state_.compare_exchange_strong(expected, state_t::started)) - { + if (!ec && !this->state_.compare_exchange_strong(expected, state_t::started)) { ec = asio::error::operation_aborted; } set_last_error(ec); this->derived()._fire_start(); - if (!ec && this->state_ != state_t::started) - { + if (!ec && this->state_ != state_t::started) { ec = asio::error::operation_aborted; } - if (ec) - { + if (ec) { this->derived()._do_stop(ec, std::move(lifetime)); - } - else - { + } else { this->derived()._post_accept(std::move(lifetime), std::move(policy)); } } @@ -271,10 +248,8 @@ class tcp_server_impl_t void _do_stop(const error_code& ec, std::shared_ptr lifetime) { ARKNET_ASSERT(this->io_->running_in_this_thread()); auto previous = this->state_.load(); - while (previous == state_t::starting || previous == state_t::started) - { - if (this->state_.compare_exchange_weak(previous, state_t::stopping)) - { + while (previous == state_t::starting || previous == state_t::started) { + if (this->state_.compare_exchange_weak(previous, state_t::stopping)) { this->derived()._post_stop(ec, std::move(lifetime), previous); return; } @@ -290,10 +265,8 @@ class tcp_server_impl_t this->sessions_.quick_for_each([](std::shared_ptr& session) { session->stop(); }); // TLS and protocol-upgrade sessions can still be starting before // they join sessions_. Stop those accepted children as well. - for (auto& pending : _session_operations_snapshot()) - { - if (auto session = pending.lock(); session && session->state_.load() == state_t::starting) - { + for (auto& pending : _session_operations_snapshot()) { + if (auto session = pending.lock(); session && session->state_.load() == state_t::starting) { session->stop(); } } @@ -313,8 +286,7 @@ class tcp_server_impl_t auto expected = state_t::stopping; const bool changed = this->state_.compare_exchange_strong(expected, state_t::stopped); ARKNET_ASSERT(changed); - if (changed) - { + if (changed) { this->derived()._handle_stop(ec, std::move(lifetime)); } }); @@ -328,8 +300,7 @@ class tcp_server_impl_t super::stop(); close_listener(); ARKNET_ASSERT(this->state_ == state_t::stopped); - for (auto& completion : stop_waiters_) - { + for (auto& completion : stop_waiters_) { completion->set_value(); } stop_waiters_.clear(); @@ -338,8 +309,7 @@ class tcp_server_impl_t template std::shared_ptr _make_session(Construction&&... construction) { auto lane = this->_get_io(); - if (this->iots_.size() > 1 && lane == this->_get_io(0)) - { + if (this->iots_.size() > 1 && lane == this->_get_io(0)) { lane = this->_get_io(); } return std::make_shared(std::forward(construction)..., this->sessions_, this->listener_, @@ -349,15 +319,13 @@ class tcp_server_impl_t template void _post_accept(std::shared_ptr lifetime, std::shared_ptr> policy) { ARKNET_ASSERT(this->io_->running_in_this_thread()); - if (!this->derived().is_started()) - { + if (!this->derived().is_started()) { return; } auto session = this->derived()._make_session(); { std::lock_guard lock(session_operations_mutex_); - if (session_operations_.size() >= session_operations_prune_at_) - { + if (session_operations_.size() >= session_operations_prune_at_) { std::erase_if(session_operations_, [](const auto& session) { return session.expired(); }); session_operations_prune_at_ = (std::max)(std::size_t{64}, session_operations_.size() * 2); } @@ -384,22 +352,19 @@ class tcp_server_impl_t void _handle_accept(const error_code& ec, std::shared_ptr session, std::shared_ptr lifetime, std::shared_ptr> policy) { set_last_error(ec); - if (ec == asio::error::operation_aborted || !this->derived().is_started()) - { + if (ec == asio::error::operation_aborted || !this->derived().is_started()) { return; } session->counter_ptr_ = this->counter_ptr_; session->start(detail::to_shared_ptr(policy->clone())); - if (!ec) - { + if (!ec) { this->derived()._post_accept(std::move(lifetime), std::move(policy)); return; } acceptor_timer_->expires_after(std::chrono::seconds(1)); acceptor_timer_->async_wait(this->derived()._track_async_operation( [this, lifetime = std::move(lifetime), policy = std::move(policy)](error_code retry_error) mutable { - if (!retry_error) - { + if (!retry_error) { this->derived().post([this, lifetime = std::move(lifetime), policy = std::move(policy)]() mutable { this->derived()._post_accept(std::move(lifetime), std::move(policy)); }); @@ -444,10 +409,8 @@ class tcp_server_impl_t auto pending_sessions = _session_operations_snapshot(); // Stopped sessions may still own canceled native callbacks. They borrow // the listener and manager even when a user retains the session itself. - for (auto& pending : pending_sessions) - { - if (auto session = pending.lock()) - { + for (auto& pending : pending_sessions) { + if (auto session = pending.lock()) { session->_wait_async_operations(); } } @@ -484,32 +447,27 @@ class tcp_server_impl_t asio::ip::tcp::resolver resolver(this->io_->executor()); const auto candidates = resolver.resolve( host, service, asio::ip::resolver_base::passive | asio::ip::resolver_base::address_configured, ec); - if (ec) - { + if (ec) { return ec; } - if (candidates.empty()) - { + if (candidates.empty()) { return asio::error::host_not_found; } const asio::ip::tcp::endpoint endpoint = *candidates.begin(); acceptor_->open(endpoint.protocol(), ec); this->update_listen_endpoint(); - if (ec) - { + if (ec) { return ec; } acceptor_->set_option(asio::socket_base::reuse_address{true}, ec); - if (ec) - { + if (ec) { return ec; } clear_last_error(); this->derived()._fire_init(); acceptor_->bind(endpoint, ec); this->update_listen_endpoint(); - if (!ec) - { + if (!ec) { acceptor_->listen(asio::socket_base::max_listen_connections, ec); } return ec; @@ -529,8 +487,7 @@ template using tcp_server_impl_t = detail::tcp_server_impl_t; template -class tcp_server_t : public detail::tcp_server_impl_t, Session> -{ +class tcp_server_t : public detail::tcp_server_impl_t, Session> { public: using detail::tcp_server_impl_t, Session>::tcp_server_impl_t; }; @@ -543,8 +500,7 @@ using tcp_server = tcp_server_t; namespace arknet { template -class tcp_rate_server_t : public tcp_server_impl_t, Session> -{ +class tcp_rate_server_t : public tcp_server_impl_t, Session> { public: using tcp_server_impl_t, Session>::tcp_server_impl_t; }; diff --git a/include/arknet/tcp/tcp_session.hpp b/include/arknet/tcp/tcp_session.hpp index f896e3e..932ef34 100644 --- a/include/arknet/tcp/tcp_session.hpp +++ b/include/arknet/tcp/tcp_session.hpp @@ -8,8 +8,7 @@ namespace arknet::detail { -struct template_args_tcp_session : tcp_tag -{ +struct template_args_tcp_session : tcp_tag { static constexpr bool is_session = true, is_client = false, is_server = false; using socket_t = asio::ip::tcp::socket; using buffer_t = asio::streambuf; @@ -28,8 +27,7 @@ class tcp_session_impl_t , public tcp_keepalive_cp , public tcp_send_op , public tcp_recv_op - , public tcp_tag -{ + , public tcp_tag { ARKNET_CLASS_FRIEND_DECLARE_BASE; ARKNET_CLASS_FRIEND_DECLARE_TCP_BASE; ARKNET_CLASS_FRIEND_DECLARE_TCP_SERVER; @@ -58,8 +56,7 @@ class tcp_session_impl_t void stop() { this->_set_async_wait_acceptance(false); const auto state = this->state_.load(); - if (state == state_t::stopped || state == state_t::stopping) - { + if (state == state_t::stopped || state == state_t::stopping) { return; } stop_requested_ = true; @@ -72,8 +69,7 @@ class tcp_session_impl_t owner.dispatch([&owner, lifetime] { // A handshake holds the event queue guard, so cancellation // must run before the disconnect event queued behind it. - if (owner.state_.load() == state_t::starting) - { + if (owner.state_.load() == state_t::starting) { owner.set_linger(true, 0); error_code ignored; owner.socket().cancel(ignored); @@ -89,16 +85,13 @@ class tcp_session_impl_t })); // Different strands can share one worker, so check context membership before waiting. while (!owner.io_->context().get_executor().running_in_this_thread() && - !owner.sessions_.io_->context().get_executor().running_in_this_thread()) - { - if (result.wait_for(std::chrono::milliseconds(100)) == std::future_status::ready) - { + !owner.sessions_.io_->context().get_executor().running_in_this_thread()) { + if (result.wait_for(std::chrono::milliseconds(100)) == std::future_status::ready) { result.get(); break; } if (owner.io_->context().stopped() || owner.get_thread_id() == std::thread::id{} || - owner.sessions_.io_->get_thread_id() == std::thread::id{}) - { + owner.sessions_.io_->get_thread_id() == std::thread::id{}) { break; } } @@ -113,14 +106,12 @@ class tcp_session_impl_t auto lifetime = owner.selfptr(); error_code endpoint_error; auto peer = owner.socket().lowest_layer().remote_endpoint(endpoint_error); - if (!endpoint_error) - { + if (!endpoint_error) { this->remote_endpoint_ = peer; } auto expected = state_t::stopped; stop_requested_ = false; - if (!this->state_.compare_exchange_strong(expected, state_t::starting)) - { + if (!this->state_.compare_exchange_strong(expected, state_t::starting)) { owner._do_disconnect(asio::error::already_started, std::move(lifetime)); return; } @@ -133,8 +124,7 @@ class tcp_session_impl_t owner._do_init(lifetime, policy); set_last_error(accept_error); owner._fire_accept(lifetime); - if (accept_error || stop_requested_ || this->state_ != state_t::starting || !owner.socket().is_open()) - { + if (accept_error || stop_requested_ || this->state_ != state_t::starting || !owner.socket().is_open()) { owner._do_disconnect( accept_error ? accept_error : error_code(asio::error::operation_aborted), std::move(lifetime)); return; @@ -155,8 +145,7 @@ class tcp_session_impl_t this->derived().reset_connect_time(); this->derived().update_alive_time(); dgram_ = std::is_same_v::condition_lowest_type, use_dgram_t>; - if constexpr (requires { policy->get_condition().lowest().init(lifetime); }) - { + if constexpr (requires { policy->get_condition().lowest().init(lifetime); }) { policy->get_condition().lowest().init(lifetime); } this->derived().set_keep_alive_options(); @@ -169,12 +158,9 @@ class tcp_session_impl_t event_queue_guard guard) mutable { auto& owner = this->derived(); defer_event next{std::move(event), std::move(guard)}; - if (stop_requested_ || !owner.is_started()) - { + if (stop_requested_ || !owner.is_started()) { owner._do_disconnect(asio::error::operation_aborted, std::move(lifetime), std::move(next)); - } - else - { + } else { owner._join_session(std::move(lifetime), std::move(policy), std::move(next)); } }, @@ -185,12 +171,9 @@ class tcp_session_impl_t void _join_session(std::shared_ptr lifetime, std::shared_ptr> policy, Chain chain) { this->sessions_.emplace( lifetime, [this, lifetime, policy = std::move(policy), chain = std::move(chain)](bool inserted) mutable { - if (inserted) - { + if (inserted) { this->derived()._start_recv(std::move(lifetime), std::move(policy), std::move(chain)); - } - else - { + } else { this->derived()._do_disconnect(asio::error::address_in_use, std::move(lifetime), std::move(chain)); } }); @@ -200,12 +183,10 @@ class tcp_session_impl_t void _handle_disconnect(const error_code& ec, std::shared_ptr lifetime, Chain chain) { error_code ignored, option_error; auto& socket = this->socket(); - if (socket.is_open()) - { + if (socket.is_open()) { asio::socket_base::linger linger; socket.lowest_layer().get_option(linger, option_error); - if (!option_error && !(linger.enabled() && linger.timeout() == 0)) - { + if (!option_error && !(linger.enabled() && linger.timeout() == 0)) { socket.shutdown(asio::socket_base::shutdown_both, ignored); } socket.cancel(ignored); @@ -234,8 +215,7 @@ class tcp_session_impl_t void _start_recv(std::shared_ptr lifetime, std::shared_ptr> policy, Chain chain) { this->derived().dispatch( [this, lifetime = std::move(lifetime), policy = std::move(policy), chain = std::move(chain)]() mutable { - if constexpr (!std::is_same_v::condition_lowest_type, hook_buffer_t>) - { + if constexpr (!std::is_same_v::condition_lowest_type, hook_buffer_t>) { this->buffer().consume(this->buffer().size()); } this->derived()._post_silence_timer(this->silence_timeout_, lifetime); @@ -311,14 +291,12 @@ template using tcp_session_impl_t = detail::tcp_session_impl_t; template -class tcp_session_t : public detail::tcp_session_impl_t -{ +class tcp_session_t : public detail::tcp_session_impl_t { public: using detail::tcp_session_impl_t::tcp_session_impl_t; }; -class tcp_session : public tcp_session_t -{ +class tcp_session : public tcp_session_t { public: using tcp_session_t::tcp_session_t; }; @@ -328,20 +306,17 @@ class tcp_session : public tcp_session_t namespace arknet { -struct tcp_rate_session_args : tcp_session_args -{ +struct tcp_rate_session_args : tcp_session_args { using socket_t = tcp_stream; }; template -class tcp_rate_session_t : public tcp_session_impl_t -{ +class tcp_rate_session_t : public tcp_session_impl_t { public: using tcp_session_impl_t::tcp_session_impl_t; }; -class tcp_rate_session : public tcp_rate_session_t -{ +class tcp_rate_session : public tcp_rate_session_t { public: using tcp_rate_session_t::tcp_rate_session_t; }; diff --git a/include/arknet/tcp/tcps_client.hpp b/include/arknet/tcp/tcps_client.hpp index 751ace9..0aede2c 100644 --- a/include/arknet/tcp/tcps_client.hpp +++ b/include/arknet/tcp/tcps_client.hpp @@ -12,8 +12,7 @@ template class tcps_client_impl_t : public ssl_context_cp , public tcp_client_impl_t - , public ssl_stream_cp -{ + , public ssl_stream_cp { ARKNET_CLASS_FRIEND_DECLARE_BASE; ARKNET_CLASS_FRIEND_DECLARE_TCP_BASE; ARKNET_CLASS_FRIEND_DECLARE_TCP_CLIENT; @@ -81,12 +80,9 @@ class tcps_client_impl_t void _handle_connect(const error_code& ec, std::shared_ptr lifetime, std::shared_ptr> policy, Chain chain) { set_last_error(ec); - if (ec) - { + if (ec) { this->derived()._done_connect(ec, std::move(lifetime), std::move(policy), std::move(chain)); - } - else - { + } else { this->_ssl_start(lifetime, policy, this->socket(), *this); this->derived()._post_handshake(std::move(lifetime), std::move(policy), std::move(chain)); } @@ -106,14 +102,12 @@ template using tcps_client_impl_t = detail::tcps_client_impl_t; template -class tcps_client_t : public detail::tcps_client_impl_t -{ +class tcps_client_t : public detail::tcps_client_impl_t { public: using detail::tcps_client_impl_t::tcps_client_impl_t; }; -class tcps_client : public tcps_client_t -{ +class tcps_client : public tcps_client_t { public: using tcps_client_t::tcps_client_t; }; diff --git a/include/arknet/tcp/tcps_server.hpp b/include/arknet/tcp/tcps_server.hpp index 055e5e3..91d8164 100644 --- a/include/arknet/tcp/tcps_server.hpp +++ b/include/arknet/tcp/tcps_server.hpp @@ -7,16 +7,14 @@ namespace arknet::detail { -struct template_args_tcps_server -{ +struct template_args_tcps_server { static constexpr bool is_session = false, is_client = false, is_server = true; }; template class tcps_server_impl_t : public ssl_context_cp - , public tcp_server_impl_t -{ + , public tcp_server_impl_t { ARKNET_CLASS_FRIEND_DECLARE_BASE; ARKNET_CLASS_FRIEND_DECLARE_TCP_BASE; ARKNET_CLASS_FRIEND_DECLARE_TCP_SERVER; @@ -50,12 +48,10 @@ class tcps_server_impl_t template bool _do_start(Host&& host, Port&& port, std::shared_ptr> policy) { auto ec = this->identity_error_; - if (!ec) - { + if (!ec) { ec = this->_check_tls_identity(); } - if (ec) - { + if (ec) { set_last_error(ec); return false; } @@ -75,8 +71,7 @@ template using tcps_server_impl_t = detail::tcps_server_impl_t; template -class tcps_server_t : public detail::tcps_server_impl_t, Session> -{ +class tcps_server_t : public detail::tcps_server_impl_t, Session> { public: using detail::tcps_server_impl_t, Session>::tcps_server_impl_t; }; diff --git a/include/arknet/tcp/tcps_session.hpp b/include/arknet/tcp/tcps_session.hpp index 6ea1f35..432af67 100644 --- a/include/arknet/tcp/tcps_session.hpp +++ b/include/arknet/tcp/tcps_session.hpp @@ -11,8 +11,7 @@ namespace arknet::detail { template class tcps_session_impl_t : public tcp_session_impl_t - , public ssl_stream_cp -{ + , public ssl_stream_cp { ARKNET_CLASS_FRIEND_DECLARE_BASE; ARKNET_CLASS_FRIEND_DECLARE_TCP_BASE; ARKNET_CLASS_FRIEND_DECLARE_TCP_SERVER; @@ -90,14 +89,12 @@ template using tcps_session_impl_t = detail::tcps_session_impl_t; template -class tcps_session_t : public detail::tcps_session_impl_t -{ +class tcps_session_t : public detail::tcps_session_impl_t { public: using detail::tcps_session_impl_t::tcps_session_impl_t; }; -class tcps_session : public tcps_session_t -{ +class tcps_session : public tcps_session_t { public: using tcps_session_t::tcps_session_t; }; diff --git a/include/arknet/udp/impl/udp_recv_op.hpp b/include/arknet/udp/impl/udp_recv_op.hpp index 836703c..633d248 100644 --- a/include/arknet/udp/impl/udp_recv_op.hpp +++ b/include/arknet/udp/impl/udp_recv_op.hpp @@ -9,14 +9,12 @@ namespace arknet::detail { template -class udp_recv_op -{ +class udp_recv_op { protected: template void _udp_post_recv(std::shared_ptr lifetime, std::shared_ptr> condition) { auto& owner = static_cast(*this); - if (!owner.is_started()) - { + if (!owner.is_started()) { return; } ARKNET_ASSERT(owner.io_->running_in_this_thread()); @@ -37,19 +35,16 @@ class udp_recv_op std::shared_ptr> condition) { auto& owner = static_cast(*this); set_last_error(error); - if (!owner.is_started()) - { + if (!owner.is_started()) { owner._stop_readend_timer(std::move(lifetime)); return; } - if (error == asio::error::operation_aborted || error == asio::error::connection_refused) - { + if (error == asio::error::operation_aborted || error == asio::error::connection_refused) { owner._do_disconnect(error, lifetime); owner._stop_readend_timer(std::move(lifetime)); return; } - if (!error) - { + if (!error) { owner.buffer().commit(count); owner.update_alive_time(); auto bytes = owner.buffer().data(); diff --git a/include/arknet/udp/impl/udp_send_cp.hpp b/include/arknet/udp/impl/udp_send_cp.hpp index b3c5204..a6e73f8 100644 --- a/include/arknet/udp/impl/udp_send_cp.hpp +++ b/include/arknet/udp/impl/udp_send_cp.hpp @@ -20,8 +20,7 @@ ARKNET_CLASS_FORWARD_DECLARE_BASE; template class udp_send_cp : public data_persistence_cp - , public send_queue_cp -{ + , public send_queue_cp { ARKNET_CLASS_FRIEND_DECLARE_BASE; using endpoint_type = asio::ip::udp::endpoint; using address_type = std::pair; @@ -117,8 +116,7 @@ class udp_send_cp auto completion = async_send(std::forward(input)..., asio::use_future); auto& owner = static_cast(*this); if (owner.io_->context().get_executor().running_in_this_thread() && - completion.wait_for(std::chrono::seconds(0)) != std::future_status::ready) - { + completion.wait_for(std::chrono::seconds(0)) != std::future_status::ready) { set_last_error(asio::error::in_progress); return 0; } @@ -147,13 +145,11 @@ class udp_send_cp template bool submit_range(Destination destination, Element* data, Count count, Completion&& completion) { if (!data || (std::is_signed_v && count < 0) || - static_cast(count) > std::numeric_limits::max() / sizeof(Element)) - { + static_cast(count) > std::numeric_limits::max() / sizeof(Element)) { return reject(completion, asio::error::invalid_argument); } const auto bytes = static_cast(count) * sizeof(Element); - if (!this->_can_send_buffer_size(bytes)) - { + if (!this->_can_send_buffer_size(bytes)) { return reject(completion, asio::error::no_buffer_space); } return submit(std::move(destination), asio::const_buffer(data, bytes), std::forward(completion)); @@ -161,61 +157,47 @@ class udp_send_cp template bool submit(Destination destination, Data&& input, Completion&& completion) { - if constexpr (is_char_pointer_v>) - { - if (!input) - { + if constexpr (is_char_pointer_v>) { + if (!input) { return reject(completion, asio::error::invalid_argument); } } auto& owner = static_cast(*this); std::optional>> pending(std::in_place, owner.io_->pending()); const auto generation = owner.life_id(); - if (owner.state_ != state_t::started) - { + if (owner.state_ != state_t::started) { pending.reset(); return reject(completion, asio::error::not_connected); } const auto input_size = [&] { - if constexpr (is_span>::value) - { + if constexpr (is_span>::value) { return input.size_bytes(); - } - else if constexpr (is_char_array_v>) - { + } else if constexpr (is_char_array_v>) { std::size_t count = 0; while (count < std::extent_v> && - input[count] != std::remove_cv_t>>{}) - { + input[count] != std::remove_cv_t>>{}) { ++count; } return count * sizeof(*input); - } - else if constexpr (is_char_pointer_v>) - { + } else if constexpr (is_char_pointer_v>) { return std::char_traits>>>::length(input) * sizeof(*input); - } - else - { + } else { return asio::buffer_size(asio::buffer(input)); } }(); - if (!this->_can_send_buffer_size(input_size)) - { + if (!this->_can_send_buffer_size(input_size)) { pending.reset(); return reject(completion, asio::error::no_buffer_space); } auto payload = owner._data_persistence(std::forward(input)); const auto bytes = asio::buffer_size(asio::buffer(payload)); - if (!this->_reserve_send_buffer(bytes)) - { + if (!this->_reserve_send_buffer(bytes)) { pending.reset(); return reject(completion, asio::error::no_buffer_space); } - struct request - { + struct request { decltype(payload) data; remove_cvref_t callback; reservation_type reservation; @@ -232,46 +214,37 @@ class udp_send_cp set_last_error(error); this->_call_completion(operation->callback, error, count); }; - if (!owner.is_started() || generation != owner.life_id()) - { + if (!owner.is_started() || generation != owner.life_id()) { finish(owner.is_started() ? error_code(asio::error::operation_aborted) : error_code(asio::error::not_connected), 0); return; } - if constexpr (std::is_same_v) - { + if constexpr (std::is_same_v) { owner._do_send(destination, operation->data, [finish = std::move(finish), guard = std::move(guard)]( const error_code& error, std::size_t count) mutable { finish(error, count); }); - } - else - { + } else { auto resolver = std::make_shared(owner.io_->executor()); resolver->async_resolve(destination.first, destination.second, [&owner, generation, resolver, operation, finish = std::move(finish), guard = std::move(guard)]( const error_code& error, const asio::ip::udp::resolver::results_type& endpoints) mutable { - if (error) - { + if (error) { finish(error, 0); return; } - if (!owner.is_started() || generation != owner.life_id()) - { + if (!owner.is_started() || generation != owner.life_id()) { finish(asio::error::operation_aborted, 0); return; } error_code endpoint_error; auto local = owner.socket().local_endpoint(endpoint_error); - if (endpoint_error) - { + if (endpoint_error) { finish(endpoint_error, 0); return; } - for (const auto& result : endpoints) - { - if (result.endpoint().protocol() != local.protocol()) - { + for (const auto& result : endpoints) { + if (result.endpoint().protocol() != local.protocol()) { continue; } owner._do_send(result.endpoint(), operation->data, diff --git a/include/arknet/udp/impl/udp_send_op.hpp b/include/arknet/udp/impl/udp_send_op.hpp index b200803..9382035 100644 --- a/include/arknet/udp/impl/udp_send_op.hpp +++ b/include/arknet/udp/impl/udp_send_op.hpp @@ -8,8 +8,7 @@ namespace arknet::detail { template -class udp_send_op -{ +class udp_send_op { protected: template bool _udp_send(Data& data, Completion&& completion) { diff --git a/include/arknet/udp/udp_cast.hpp b/include/arknet/udp/udp_cast.hpp index 01e8d4a..f50b617 100644 --- a/include/arknet/udp/udp_cast.hpp +++ b/include/arknet/udp/udp_cast.hpp @@ -21,8 +21,7 @@ namespace arknet::detail { -struct template_args_udp_cast : udp_tag -{ +struct template_args_udp_cast : udp_tag { using socket_t = asio::ip::udp::socket; using buffer_t = arknet::linear_buffer; static constexpr std::size_t function_storage_size = 88; @@ -50,8 +49,7 @@ class udp_cast_impl_t , public udp_send_op , public connect_cp_member_variables , public udp_tag - , public cast_tag -{ + , public cast_tag { ARKNET_CLASS_FRIEND_DECLARE_BASE; ARKNET_CLASS_FRIEND_DECLARE_UDP_BASE; ARKNET_CLASS_FRIEND_DECLARE_UDP_CLIENT; @@ -100,13 +98,11 @@ class udp_cast_impl_t } void stop() { - if (is_stopped()) - { + if (is_stopped()) { this->stop_iopool(); return; } - if (this->is_iopool_stopped()) - { + if (this->is_iopool_stopped()) { return; } auto& owner = this->derived(); @@ -114,8 +110,7 @@ class udp_cast_impl_t auto promise = std::make_shared>(); auto completion = promise->get_future(); owner.post_event([&owner, lifetime = owner.selfptr(), promise](event_queue_guard guard) mutable { - if (owner.is_stopped()) - { + if (owner.is_stopped()) { promise->set_value(); return; } @@ -123,24 +118,21 @@ class udp_cast_impl_t owner.stop_guard_.emplace(std::move(guard)); owner._do_stop(asio::error::operation_aborted, std::move(lifetime)); }); - if (!this->iopool().running_in_threads()) - { + if (!this->iopool().running_in_threads()) { completion.get(); } this->stop_iopool(); } void request_stop() { - if (this->derived().is_stopped()) - { + if (this->derived().is_stopped()) { return; } this->derived().post([this, lifetime = this->selfptr()] { this->derived().stop(); }); } bool wait_stopped() { - if (this->iopool().running_in_threads()) - { + if (this->iopool().running_in_threads()) { set_last_error(asio::error::operation_not_supported); return false; } @@ -192,16 +184,14 @@ class udp_cast_impl_t protected: template bool _do_start(Host&& host, Service&& service, std::shared_ptr> condition) { - if (buffer_.max_size() < udp_frame_size) - { + if (buffer_.max_size() < udp_frame_size) { set_last_error(asio::error::message_size); return false; } auto host_text = detail::to_string(std::forward(host)); auto service_text = detail::to_string(std::forward(service)); this->start_iopool(); - if (!this->is_iopool_started()) - { + if (!this->is_iopool_started()) { set_last_error(asio::error::operation_aborted); return false; } @@ -211,8 +201,7 @@ class udp_cast_impl_t owner.post_event( [&owner, lifetime = owner.selfptr(), condition = std::move(condition), promise, host = std::move(host_text), service = std::move(service_text)](event_queue_guard) mutable { - if (owner.state_ != state_t::stopped) - { + if (owner.state_ != state_t::stopped) { promise->set_value(asio::error::already_started); return; } @@ -225,16 +214,13 @@ class udp_cast_impl_t error_code error; asio::ip::udp::resolver resolver(owner.io_->executor()); auto endpoints = resolver.resolve(owner.host_, owner.port_, asio::ip::resolver_base::passive, error); - if (!error && endpoints.empty()) - { + if (!error && endpoints.empty()) { error = asio::error::host_not_found; } - if (!error) - { + if (!error) { auto endpoint = endpoints.begin()->endpoint(); owner.socket().open(endpoint.protocol(), error); - if (!error) - { + if (!error) { error_code ignored; owner.socket().set_option(asio::socket_base::reuse_address(true), ignored); clear_last_error(); @@ -245,13 +231,11 @@ class udp_cast_impl_t owner._handle_start(error, std::move(lifetime), std::move(condition)); promise->set_value(error); }); - if constexpr (Async) - { + if constexpr (Async) { set_last_error(asio::error::in_progress); return true; } - if (this->iopool().running_in_threads()) - { + if (this->iopool().running_in_threads()) { set_last_error(asio::error::in_progress); return owner.is_started(); } @@ -262,19 +246,15 @@ class udp_cast_impl_t template void _handle_start( const error_code& error, std::shared_ptr lifetime, std::shared_ptr> condition) { - if (!error) - { + if (!error) { reset_life_id(); state_ = state_t::started; } set_last_error(error); this->derived()._fire_start(); - if (error) - { + if (error) { this->derived()._do_stop(error, std::move(lifetime)); - } - else - { + } else { this->derived()._post_recv(std::move(lifetime), std::move(condition)); } } @@ -287,8 +267,7 @@ class udp_cast_impl_t } void _do_stop(const error_code& error, std::shared_ptr lifetime) { - if (state_ == state_t::stopping || state_ == state_t::stopped) - { + if (state_ == state_t::stopping || state_ == state_t::stopped) { return; } state_ = state_t::stopping; @@ -299,8 +278,7 @@ class udp_cast_impl_t error_code ignored; this->socket().cancel(ignored); this->socket().close(ignored); - if (!read_pending_) - { + if (!read_pending_) { this->derived()._handle_stop(error, std::move(lifetime)); } } @@ -316,8 +294,7 @@ class udp_cast_impl_t set_last_error(error); this->derived()._fire_stop(); auto waiters = std::exchange(stop_waiters_, {}); - for (const auto& waiter : waiters) - { + for (const auto& waiter : waiters) { waiter->set_value(); } } @@ -329,8 +306,7 @@ class udp_cast_impl_t template void _post_recv(std::shared_ptr lifetime, std::shared_ptr> condition) { - if (!is_started()) - { + if (!is_started()) { return; } read_pending_ = true; @@ -345,23 +321,19 @@ class udp_cast_impl_t template void _handle_recv(const error_code& error, std::size_t count, std::shared_ptr lifetime, std::shared_ptr> condition) { - if (state_ == state_t::stopping) - { + if (state_ == state_t::stopping) { this->derived()._handle_stop(stop_error_, std::move(lifetime)); return; } set_last_error(error); - if (!is_started()) - { + if (!is_started()) { return; } - if (error == asio::error::operation_aborted) - { + if (error == asio::error::operation_aborted) { this->derived()._do_stop(error, std::move(lifetime)); return; } - if (!error) - { + if (!error) { buffer_.commit(count); this->update_alive_time(); auto bytes = buffer_.data(); @@ -435,14 +407,12 @@ template using udp_cast_impl_t = detail::udp_cast_impl_t; template -class udp_cast_t : public detail::udp_cast_impl_t -{ +class udp_cast_t : public detail::udp_cast_impl_t { public: using detail::udp_cast_impl_t::udp_cast_impl_t; }; -class udp_cast : public udp_cast_t -{ +class udp_cast : public udp_cast_t { public: using udp_cast_t::udp_cast_t; }; diff --git a/include/arknet/udp/udp_client.hpp b/include/arknet/udp/udp_client.hpp index 18a43dc..2dfe095 100644 --- a/include/arknet/udp/udp_client.hpp +++ b/include/arknet/udp/udp_client.hpp @@ -9,8 +9,7 @@ namespace arknet::detail { -struct template_args_udp_client : udp_tag -{ +struct template_args_udp_client : udp_tag { static constexpr bool is_session = false; static constexpr bool is_client = true; static constexpr bool is_server = false; @@ -30,8 +29,7 @@ class udp_client_impl_t : public client_impl_t , public udp_send_op , public udp_recv_op - , public udp_tag -{ + , public udp_tag { ARKNET_CLASS_FRIEND_DECLARE_BASE; ARKNET_CLASS_FRIEND_DECLARE_UDP_BASE; ARKNET_CLASS_FRIEND_DECLARE_UDP_CLIENT; @@ -80,13 +78,11 @@ class udp_client_impl_t } void stop() { - if (base::is_stopped()) - { + if (base::is_stopped()) { this->stop_iopool(); return; } - if (this->is_iopool_stopped()) - { + if (this->is_iopool_stopped()) { return; } auto& owner = this->derived(); @@ -104,8 +100,7 @@ class udp_client_impl_t }, std::move(guard)}); }); - if (!this->iopool().running_in_threads()) - { + if (!this->iopool().running_in_threads()) { completion.get(); } this->stop_iopool(); @@ -138,16 +133,14 @@ class udp_client_impl_t protected: template bool _do_connect(Host&& host, Service&& service, std::shared_ptr> condition) { - if (this->buffer_.max_size() < udp_frame_size) - { + if (this->buffer_.max_size() < udp_frame_size) { set_last_error(asio::error::message_size); return false; } auto host_text = detail::to_string(std::forward(host)); auto service_text = detail::to_string(std::forward(service)); this->start_iopool(); - if (!this->is_iopool_started()) - { + if (!this->is_iopool_started()) { set_last_error(asio::error::operation_aborted); return false; } @@ -159,8 +152,7 @@ class udp_client_impl_t service = std::move(service_text), promise](event_queue_guard guard) mutable { defer_event chain{ [promise](event_queue_guard) { promise->set_value(get_last_error()); }, std::move(guard)}; - if (owner.state_ != state_t::stopped) - { + if (owner.state_ != state_t::stopped) { set_last_error(asio::error::already_started); return; } @@ -175,13 +167,11 @@ class udp_client_impl_t owner._do_init(condition); owner.template _start_connect(std::move(lifetime), std::move(condition), std::move(chain)); }); - if constexpr (Async) - { + if constexpr (Async) { set_last_error(asio::error::in_progress); return true; } - if (this->iopool().running_in_threads()) - { + if (this->iopool().running_in_threads()) { set_last_error(asio::error::in_progress); return owner.is_started(); } @@ -287,14 +277,12 @@ template using udp_client_impl_t = detail::udp_client_impl_t; template -class udp_client_t : public detail::udp_client_impl_t -{ +class udp_client_t : public detail::udp_client_impl_t { public: using detail::udp_client_impl_t::udp_client_impl_t; }; -class udp_client : public udp_client_t -{ +class udp_client : public udp_client_t { public: using udp_client_t::udp_client_t; }; diff --git a/include/arknet/udp/udp_server.hpp b/include/arknet/udp/udp_server.hpp index ac29834..1e7129f 100644 --- a/include/arknet/udp/udp_server.hpp +++ b/include/arknet/udp/udp_server.hpp @@ -16,8 +16,7 @@ ARKNET_CLASS_FORWARD_DECLARE_UDP_SERVER; template class udp_server_impl_t : public server_impl_t - , public udp_tag -{ + , public udp_tag { ARKNET_CLASS_FRIEND_DECLARE_BASE; ARKNET_CLASS_FRIEND_DECLARE_UDP_BASE; ARKNET_CLASS_FRIEND_DECLARE_UDP_SERVER; @@ -57,13 +56,11 @@ class udp_server_impl_t } void stop() { - if (is_stopped()) - { + if (is_stopped()) { this->stop_iopool(); return; } - if (this->is_iopool_stopped()) - { + if (this->is_iopool_stopped()) { return; } auto& owner = this->derived(); @@ -71,8 +68,7 @@ class udp_server_impl_t auto promise = std::make_shared>(); auto completion = promise->get_future(); owner.post([&owner, lifetime = owner.selfptr(), promise]() mutable { - if (owner.is_stopped()) - { + if (owner.is_stopped()) { promise->set_value(); return; } @@ -80,13 +76,11 @@ class udp_server_impl_t owner._do_stop(asio::error::operation_aborted, std::move(lifetime)); }); const bool may_wait = !this->iopool().running_in_threads(); - if (may_wait && this->is_external_iopool()) - { + if (may_wait && this->is_external_iopool()) { completion.get(); } this->stop_iopool(); - if (may_wait && !this->is_external_iopool()) - { + if (may_wait && !this->is_external_iopool()) { completion.get(); } } @@ -150,16 +144,14 @@ class udp_server_impl_t protected: template bool _do_start(Host&& host, Service&& service, std::shared_ptr> condition) { - if (buffer_.max_size() < udp_frame_size) - { + if (buffer_.max_size() < udp_frame_size) { set_last_error(asio::error::message_size); return false; } auto host_text = detail::to_string(std::forward(host)); auto service_text = detail::to_string(std::forward(service)); this->start_iopool(); - if (!this->is_iopool_started()) - { + if (!this->is_iopool_started()) { set_last_error(asio::error::operation_aborted); return false; } @@ -168,8 +160,7 @@ class udp_server_impl_t auto& owner = this->derived(); owner.post([&owner, lifetime = owner.selfptr(), condition = std::move(condition), promise, host = std::move(host_text), service = std::move(service_text)]() mutable { - if (owner.state_ != state_t::stopped) - { + if (owner.state_ != state_t::stopped) { promise->set_value(asio::error::already_started); return; } @@ -185,17 +176,14 @@ class udp_server_impl_t error_code error; asio::ip::udp::resolver resolver(owner.io_->executor()); auto endpoints = resolver.resolve(host, service, asio::ip::resolver_base::passive, error); - if (!error && endpoints.empty()) - { + if (!error && endpoints.empty()) { error = asio::error::host_not_found; } - if (!error) - { + if (!error) { auto endpoint = endpoints.begin()->endpoint(); owner.acceptor_->open(endpoint.protocol(), error); owner.update_listen_endpoint(); - if (!error) - { + if (!error) { error_code ignored; owner.acceptor_->set_option(asio::socket_base::reuse_address(true), ignored); clear_last_error(); @@ -207,8 +195,7 @@ class udp_server_impl_t owner._handle_start(error, std::move(lifetime), std::move(condition)); promise->set_value(error); }); - if (this->iopool().running_in_threads()) - { + if (this->iopool().running_in_threads()) { set_last_error(asio::error::in_progress); return owner.is_started(); } @@ -219,14 +206,12 @@ class udp_server_impl_t template void _handle_start( error_code error, std::shared_ptr lifetime, std::shared_ptr> condition) { - if (!error) - { + if (!error) { this->state_ = state_t::started; } set_last_error(error); this->derived()._fire_start(); - if (error || this->state_ != state_t::started) - { + if (error || this->state_ != state_t::started) { this->derived()._do_stop(error ? error : error_code(asio::error::operation_aborted), std::move(lifetime)); return; } @@ -236,8 +221,7 @@ class udp_server_impl_t } void _do_stop(const error_code& error, std::shared_ptr) { - if (this->state_ == state_t::stopping || this->state_ == state_t::stopped) - { + if (this->state_ == state_t::stopping || this->state_ == state_t::stopped) { return; } this->state_ = state_t::stopping; @@ -262,16 +246,14 @@ class udp_server_impl_t set_last_error(error); this->derived()._fire_stop(); auto waiters = std::exchange(stop_waiters_, {}); - for (const auto& waiter : waiters) - { + for (const auto& waiter : waiters) { waiter->set_value(); } } template void _post_recv(std::shared_ptr lifetime, std::shared_ptr> condition) { - if (!is_started()) - { + if (!is_started()) { return; } this->acceptor_->async_receive_from(buffer_.prepare(udp_frame_size), remote_endpoint_, @@ -286,27 +268,21 @@ class udp_server_impl_t void _handle_recv(const error_code& error, std::size_t count, std::shared_ptr lifetime, std::shared_ptr> condition) { set_last_error(error); - if (!is_started()) - { + if (!is_started()) { return; } - if (error == asio::error::operation_aborted) - { + if (error == asio::error::operation_aborted) { this->derived()._do_stop(error, std::move(lifetime)); return; } - if (!error) - { + if (!error) { buffer_.commit(count); const auto storage = buffer_.data(); std::string_view bytes(static_cast(storage.data()), count); auto session = this->sessions_.find(remote_endpoint_); - if (session) - { + if (session) { session->_receive_datagram(session, condition, bytes); - } - else - { + } else { this->derived()._handle_accept(error, bytes, session, condition); } buffer_.consume(buffer_.size()); @@ -369,8 +345,7 @@ template using udp_server_impl_t = detail::udp_server_impl_t; template -class udp_server_t : public detail::udp_server_impl_t, Session> -{ +class udp_server_t : public detail::udp_server_impl_t, Session> { public: using detail::udp_server_impl_t, Session>::udp_server_impl_t; }; diff --git a/include/arknet/udp/udp_session.hpp b/include/arknet/udp/udp_session.hpp index e892523..c1df67c 100644 --- a/include/arknet/udp/udp_session.hpp +++ b/include/arknet/udp/udp_session.hpp @@ -8,8 +8,7 @@ namespace arknet::detail { -struct template_args_udp_session : udp_tag -{ +struct template_args_udp_session : udp_tag { static constexpr bool is_session = true; static constexpr bool is_client = false; static constexpr bool is_server = false; @@ -29,8 +28,7 @@ template class udp_session_impl_t : public session_impl_t , public udp_send_op - , public udp_tag -{ + , public udp_tag { ARKNET_CLASS_FRIEND_DECLARE_BASE; ARKNET_CLASS_FRIEND_DECLARE_UDP_BASE; ARKNET_CLASS_FRIEND_DECLARE_UDP_SERVER; @@ -56,8 +54,7 @@ class udp_session_impl_t } void stop() { - if (this->state_ == state_t::stopped || this->state_ == state_t::stopping) - { + if (this->state_ == state_t::stopped || this->state_ == state_t::stopping) { return; } stop_requested_ = true; @@ -66,8 +63,7 @@ class udp_session_impl_t this->derived()._do_disconnect(asio::error::operation_aborted, this->selfptr(), defer_event{[promise](event_queue_guard) { promise->set_value(); }, event_queue_guard{}}); if (!this->io_->context().get_executor().running_in_this_thread() && - !this->sessions_.io_->context().get_executor().running_in_this_thread()) - { + !this->sessions_.io_->context().get_executor().running_in_this_thread()) { completed.get(); } } @@ -77,14 +73,11 @@ class udp_session_impl_t } std::string get_remote_address() const noexcept { - try - { + try { auto address = this->remote_endpoint_.address().to_string(); clear_last_error(); return address; - } - catch (const system_error& error) - { + } catch (const system_error& error) { set_last_error(error.code()); return {}; } @@ -129,15 +122,13 @@ class udp_session_impl_t template void _do_start(std::shared_ptr lifetime, std::shared_ptr> condition, Chain chain) { - if (!this->is_started() || stop_requested_) - { + if (!this->is_started() || stop_requested_) { return; } // Routing must be ready before the server posts its next receive. this->sessions_.emplace( lifetime, [this, lifetime, condition = std::move(condition), chain = std::move(chain)](bool added) mutable { - if (!added) - { + if (!added) { this->derived()._do_disconnect(asio::error::address_in_use, std::move(lifetime)); return; } @@ -147,8 +138,7 @@ class udp_session_impl_t template void _start_recv(std::shared_ptr lifetime, std::shared_ptr> condition, Chain) { - if (!this->is_started() || stop_requested_) - { + if (!this->is_started() || stop_requested_) { return; } this->derived()._post_silence_timer(this->silence_timeout_, lifetime); @@ -159,8 +149,7 @@ class udp_session_impl_t template void _receive_datagram( std::shared_ptr lifetime, std::shared_ptr> condition, std::string_view bytes) { - if (!this->is_started() || stop_requested_) - { + if (!this->is_started() || stop_requested_) { return; } this->update_alive_time(); @@ -175,8 +164,7 @@ class udp_session_impl_t event_queue_guard guard) mutable { defer_event continuation{std::move(callback), std::move(guard)}; const auto previous = this->state_.exchange(state_t::stopping); - if (previous == state_t::stopped) - { + if (previous == state_t::stopped) { this->state_ = state_t::stopped; return; } @@ -184,8 +172,7 @@ class udp_session_impl_t [this, error, previous, lifetime, continuation = std::move(continuation)](bool erased) mutable { this->state_ = state_t::stopped; set_last_error(error); - if (erased && previous == state_t::started) - { + if (erased && previous == state_t::started) { this->derived()._fire_disconnect(lifetime); } this->derived()._do_stop(error, std::move(lifetime), std::move(continuation)); @@ -244,14 +231,12 @@ template using udp_session_impl_t = detail::udp_session_impl_t; template -class udp_session_t : public detail::udp_session_impl_t -{ +class udp_session_t : public detail::udp_session_impl_t { public: using detail::udp_session_impl_t::udp_session_impl_t; }; -class udp_session : public udp_session_t -{ +class udp_session : public udp_session_t { public: using udp_session_t::udp_session_t; }; diff --git a/include/arknet/util/spin_lock.hpp b/include/arknet/util/spin_lock.hpp index f730a6a..de9b59e 100644 --- a/include/arknet/util/spin_lock.hpp +++ b/include/arknet/util/spin_lock.hpp @@ -5,16 +5,14 @@ namespace arknet { -class spin_lock -{ +class spin_lock { public: bool try_lock() noexcept { return !v_.test_and_set(std::memory_order_acquire); } void lock() noexcept { - while (!try_lock()) - { + while (!try_lock()) { v_.wait(true, std::memory_order_relaxed); } } diff --git a/include/arknet/util/string.hpp b/include/arknet/util/string.hpp index ce16398..dccf991 100644 --- a/include/arknet/util/string.hpp +++ b/include/arknet/util/string.hpp @@ -13,24 +13,15 @@ namespace arknet { template std::string to_string(T&& value) { using type = std::remove_cvref_t; - if constexpr (std::is_pointer_v && std::is_same_v>, char>) - { + if constexpr (std::is_pointer_v && std::is_same_v>, char>) { return value ? std::string(value) : std::string{}; - } - else if constexpr (std::is_same_v) - { + } else if constexpr (std::is_same_v) { return std::string(1, value); - } - else if constexpr (std::constructible_from) - { + } else if constexpr (std::constructible_from) { return std::string(std::forward(value)); - } - else if constexpr (std::is_arithmetic_v) - { + } else if constexpr (std::is_arithmetic_v) { return std::to_string(value); - } - else - { + } else { std::ostringstream stream; stream << std::forward(value); return stream.str(); @@ -40,16 +31,11 @@ std::string to_string(T&& value) { template std::string_view to_string_view(const T& value) { using type = std::remove_cvref_t; - if constexpr (std::is_pointer_v && std::is_same_v>, char>) - { + if constexpr (std::is_pointer_v && std::is_same_v>, char>) { return value ? std::string_view(value) : std::string_view{}; - } - else if constexpr (std::is_same_v) - { + } else if constexpr (std::is_same_v) { return std::string_view(&value, 1); - } - else - { + } else { return std::string_view(value); } } diff --git a/scripts/test_format.sh b/scripts/test_format.sh index b480f6f..d9b14b5 100644 --- a/scripts/test_format.sh +++ b/scripts/test_format.sh @@ -27,6 +27,134 @@ printf '%s\n' '#include ' '#include ' '#include '#include ' '#include ' '' '#include "vendor/doctest/doctest.h"' \ '#include ' '#include ' '#include ' \ '#include ' '' '#include ' '#include "check.hpp"' > expected-includes.cpp +cat > examples/braces.cpp <<'EOF' +class probe +{ +public: + void clear() + { + value = 0; + } + +private: + int value = 1; +}; + +class inherited_probe : public probe +{ +public: + using probe::clear; +}; + +struct record_probe +{ + int value; +}; + +enum class enum_probe +{ + first, + second +}; + +union union_probe +{ + int integer; + double floating; +}; + +void control_probe(int count) +{ + if (count > 0) + --count; + else + ++count; + for (int index = 0; index < count; ++index) + --count; + while (count > 0) + --count; + do + { + --count; + } + while (count > 0); + switch (count) + { + case 0: + break; + default: + ++count; + break; + } + try + { + ++count; + } + catch (...) + { + throw; + } +} +EOF +cat > expected-braces.cpp <<'EOF' +class probe { +public: + void clear() { + value = 0; + } + +private: + int value = 1; +}; + +class inherited_probe : public probe { +public: + using probe::clear; +}; + +struct record_probe { + int value; +}; + +enum class enum_probe { + first, + second +}; + +union union_probe { + int integer; + double floating; +}; + +void control_probe(int count) { + if (count > 0) { + --count; + } else { + ++count; + } + for (int index = 0; index < count; ++index) { + --count; + } + while (count > 0) { + --count; + } + do { + --count; + } while (count > 0); + switch (count) { + case 0: + break; + default: + ++count; + break; + } + try { + ++count; + } catch (...) { + throw; + } +} +EOF expect_failure() { local expected=$1 @@ -46,6 +174,7 @@ expect_failure 2 env CLANG_FORMAT=true bash scripts/format.sh --check bash scripts/format.sh --fix bash scripts/format.sh --check cmp expected-includes.cpp examples/probe.cpp +cmp expected-braces.cpp examples/braces.cpp for directory in include/arknet/thirdparty tests/vendor tests/results benchmarks/results; do cmp original.hpp "$directory/ignored.hpp" "${CLANG_FORMAT:-clang-format}" --dry-run --Werror "$directory/ignored.hpp" diff --git a/tests/allocation_failure.cpp b/tests/allocation_failure.cpp index e2aae69..76003d2 100644 --- a/tests/allocation_failure.cpp +++ b/tests/allocation_failure.cpp @@ -21,10 +21,11 @@ DOCTEST_TEST_CASE("Allocation failure is one-shot and normal aligned allocation bool threw = false, disarmed = false, recovered = false; { scoped_allocation_failure::scope failure; - try - { ::operator delete(::operator new(128)); } - catch (const std::bad_alloc&) - { threw = true; } + try { + ::operator delete(::operator new(128)); + } catch (const std::bad_alloc&) { + threw = true; + } disarmed = !scoped_allocation_failure::armed; auto* pointer = ::operator new(128, std::nothrow); recovered = pointer != nullptr; @@ -49,12 +50,11 @@ DOCTEST_TEST_CASE("Asio post allocation failure leaves no handler and permits no bool threw = false; { scoped_allocation_failure::scope failure; - try - { + try { asio::post(context, [&] { ++invoked; }); + } catch (const std::bad_alloc&) { + threw = true; } - catch (const std::bad_alloc&) - { threw = true; } } CHECK(threw); CHECK(scoped_allocation_failure::injections == before + 1); @@ -83,10 +83,11 @@ DOCTEST_TEST_CASE("RUDP failed packet allocation preserves byte accounting and s bool threw = false; { scoped_allocation_failure::scope failure; - try - { channel->raw_send(payload); } - catch (const std::bad_alloc&) - { threw = true; } + try { + channel->raw_send(payload); + } catch (const std::bad_alloc&) { + threw = true; + } } CHECK(threw); CHECK(scoped_allocation_failure::injections == before + 1); @@ -107,11 +108,9 @@ DOCTEST_TEST_CASE("RUDP failed packet allocation preserves byte accounting and s std::size_t count = 0; const auto deadline = std::chrono::steady_clock::now() + 5s; // Local send completion can precede receive readiness, even on loopback. - do - { + do { count = receiver.receive_from(asio::buffer(received), sender, 0, error); - if (error != asio::error::would_block && error != asio::error::try_again) - { + if (error != asio::error::would_block && error != asio::error::try_again) { break; } std::this_thread::sleep_for(1ms); @@ -123,14 +122,12 @@ DOCTEST_TEST_CASE("RUDP failed packet allocation preserves byte accounting and s } DOCTEST_TEST_CASE("RUDP send initiation allocation failure stops the channel and propagates") { - for (const bool connected : {false, true}) - { + for (const bool connected : {false, true}) { DOCTEST_CAPTURE(connected); asio::io_context context; asio::ip::udp::socket receiver(context, {asio::ip::make_address("127.0.0.1"), 0}); auto socket = std::make_shared(context, asio::ip::udp::v4()); - if (connected) - { + if (connected) { socket->connect(receiver.local_endpoint()); } arknet::arq::options options; @@ -155,10 +152,11 @@ DOCTEST_TEST_CASE("RUDP send initiation allocation failure stops the channel and bool threw = false; { scoped_allocation_failure::scope failure; - try - { context.run_one(); } - catch (const std::bad_alloc&) - { threw = true; } + try { + context.run_one(); + } catch (const std::bad_alloc&) { + threw = true; + } } CHECK(threw); CHECK(scoped_allocation_failure::injections == before + 1); @@ -186,10 +184,11 @@ DOCTEST_TEST_CASE("MSVC Asio CRT allocation preserves alignment and throws throu bool threw = false; { scoped_allocation_failure::scope failure; - try - { asio::aligned_delete(asio::aligned_new(64, 257)); } - catch (const std::bad_alloc&) - { threw = true; } + try { + asio::aligned_delete(asio::aligned_new(64, 257)); + } catch (const std::bad_alloc&) { + threw = true; + } } CHECK(threw); CHECK(scoped_allocation_failure::injections == before + 1); diff --git a/tests/arq_core.cpp b/tests/arq_core.cpp index 4986a8e..9d99616 100644 --- a/tests/arq_core.cpp +++ b/tests/arq_core.cpp @@ -14,16 +14,14 @@ namespace { constexpr std::uint32_t conversation = 0x12345678; void put32(std::string& data, std::size_t offset, std::uint32_t value) { - for (std::size_t index = 0; index < 4; ++index) - { + for (std::size_t index = 0; index < 4; ++index) { data[offset + index] = static_cast((value >> (8 * index)) & 0xff); } } std::uint32_t get32(std::string_view data, std::size_t offset) { std::uint32_t value = 0; - for (std::size_t index = 0; index < 4; ++index) - { + for (std::size_t index = 0; index < 4; ++index) { value |= static_cast(static_cast(data[offset + index])) << (8 * index); } return value; @@ -53,10 +51,8 @@ arknet::arq::options uncongested(std::size_t mtu = 1200) { return settings; } -struct simulated_link -{ - struct delivery - { +struct simulated_link { + struct delivery { std::uint32_t due; bool to_right; std::string data; @@ -81,15 +77,13 @@ struct simulated_link void transmit(bool to_right, std::string_view data) { ++transmissions; - if (impair && transmissions % 7 == 0) - { + if (impair && transmissions % 7 == 0) { ++dropped; return; } const auto delay = impair ? static_cast((transmissions % 5) * 5 + 5) : 5; pending.push_back({now + delay, to_right, std::string(data)}); - if (impair && transmissions % 11 == 0) - { + if (impair && transmissions % 11 == 0) { ++duplicated; pending.push_back({now + delay + 15, to_right, std::string(data)}); } @@ -99,10 +93,8 @@ struct simulated_link left.update(now); right.update(now); // Reverse delivery order deliberately; delay values reorder adjacent frames as well. - for (std::size_t index = pending.size(); index != 0; --index) - { - if (pending[index - 1].due > now) - { + for (std::size_t index = pending.size(); index != 0; --index) { + if (pending[index - 1].due > now) { continue; } auto delivery = std::move(pending[index - 1]); @@ -118,13 +110,11 @@ DOCTEST_TEST_CASE("Duplicate ACK bursts cannot exhaust ARQ retries before a time auto settings = uncongested(60); arknet::arq::control_block sender(conversation, settings); sender.set_output([](std::string_view) {}); - for (unsigned i = 0; i < 128; ++i) - { + for (unsigned i = 0; i < 128; ++i) { check(sender.send(std::string(16, 'x')) == 0, "message queues"); } sender.update(0); - for (unsigned i = 0; i < 128 && !sender.failed(); ++i) - { + for (unsigned i = 0; i < 128 && !sender.failed(); ++i) { check(sender.input(packet(82, 127, {}, 512)) == 0, "late duplicate ACK is valid"); sender.flush(); } @@ -153,32 +143,27 @@ DOCTEST_TEST_CASE("ARQ fragments and reassembles empty binary and maximum messag auto settings = uncongested(80); simulated_link link(settings); const std::vector expected{"", std::string("a\0b", 3), std::string(56 * 256, 'z'), "last"}; - for (const auto& message : expected) - { + for (const auto& message : expected) { check(link.left.send(message) == 0, "message queues"); } std::vector actual; - for (int step = 0; step < 2000 && (actual.size() < expected.size() || link.left.pending_segments() != 0); ++step) - { + for (int step = 0; step < 2000 && (actual.size() < expected.size() || link.left.pending_segments() != 0); ++step) { link.step(); std::string message; - while (link.right.receive(message)) - { + while (link.right.receive(message)) { actual.push_back(message); } } check(actual == expected, "fragment boundaries and message ordering survive"); check(link.left.pending_segments() == 0 && !link.left.failed(), "all messages acknowledged"); - for (const auto& delivery : link.pending) - { + for (const auto& delivery : link.pending) { check(delivery.data.size() <= settings.mtu, "encoded datagrams fit configured MTU"); } check(link.left.send(std::string(56 * 256 + 1, 'x')) == -1, "fragment field overflow rejected"); } DOCTEST_TEST_CASE("ARQ reliable delivery survives deterministic loss duplication and reordering") { - for (const bool congestion_control : {false, true}) - { + for (const bool congestion_control : {false, true}) { DOCTEST_INFO("congestion_control=", congestion_control); auto settings = uncongested(300); settings.congestion_control = congestion_control; @@ -186,8 +171,7 @@ DOCTEST_TEST_CASE("ARQ reliable delivery survives deterministic loss duplication simulated_link link(settings, true); std::vector expected_left; std::vector expected_right; - for (int index = 0; index < 80; ++index) - { + for (int index = 0; index < 80; ++index) { const auto message = std::to_string(index) + std::string(static_cast(index * 17), 'x'); expected_right.push_back(message); expected_left.push_back("reply:" + message); @@ -196,21 +180,17 @@ DOCTEST_TEST_CASE("ARQ reliable delivery survives deterministic loss duplication } std::vector received_left; std::vector received_right; - for (int step = 0; step < 20000; ++step) - { + for (int step = 0; step < 20000; ++step) { link.step(); std::string message; - while (link.left.receive(message)) - { + while (link.left.receive(message)) { received_left.push_back(message); } - while (link.right.receive(message)) - { + while (link.right.receive(message)) { received_right.push_back(message); } if (received_left.size() == expected_left.size() && received_right.size() == expected_right.size() && - link.left.pending_segments() == 0 && link.right.pending_segments() == 0) - { + link.left.pending_segments() == 0 && link.right.pending_segments() == 0) { break; } } @@ -239,8 +219,7 @@ DOCTEST_TEST_CASE("ARQ rejects malformed datagrams before changing receive state invalid.push_back(valid + "trailing"); invalid.push_back(valid + packet(90)); invalid.push_back(std::string(65508, 'x')); - for (const auto& datagram : invalid) - { + for (const auto& datagram : invalid) { arknet::arq::control_block receiver(conversation); check(receiver.input(datagram) < 0, "malformed packet rejected"); std::string message = "unchanged"; @@ -295,8 +274,7 @@ DOCTEST_TEST_CASE("ARQ duplicate acknowledgement backlog and receive buffers rem std::vector output; receiver.set_output([&](auto data) { output.emplace_back(data); }); check(receiver.input(packet(81, 0, "first")) == 0, "first message queues"); - for (std::uint32_t index = 1; index < 5000; ++index) - { + for (std::uint32_t index = 1; index < 5000; ++index) { const auto old_sequence = std::numeric_limits::max() - index; check(receiver.input(packet(81, old_sequence, "duplicate")) == 0, "old sequence is acknowledged as duplicate"); check(receiver.input(packet(81, 4, "last in window")) == 0, "duplicate out of order sequence accepted once"); @@ -304,8 +282,7 @@ DOCTEST_TEST_CASE("ARQ duplicate acknowledgement backlog and receive buffers rem } receiver.update(10); std::size_t acknowledgement_bytes = 0; - for (const auto& data : output) - { + for (const auto& data : output) { acknowledgement_bytes += data.size(); } check(acknowledgement_bytes <= settings.receive_window * 24, "ACK storage is bounded before a flush"); @@ -313,8 +290,7 @@ DOCTEST_TEST_CASE("ARQ duplicate acknowledgement backlog and receive buffers rem "missing in-window messages accepted"); std::vector actual; std::string message; - while (receiver.receive(message)) - { + while (receiver.receive(message)) { actual.push_back(message); } check((actual == std::vector{"first", "second", "third", "fourth", "last in window"}), @@ -330,8 +306,7 @@ DOCTEST_TEST_CASE("ARQ bounds pending messages including empty payloads") { check(sender.send(std::string(56, 'x')) == 0, "bounded message accepted"); check(sender.send("") == -2 && sender.pending_bytes() == 56, "empty message cannot bypass queue bound"); arknet::arq::control_block empty_sender(conversation, settings); - for (int index = 0; index < 4; ++index) - { + for (int index = 0; index < 4; ++index) { check(empty_sender.send("") == 0, "bounded empty message accepted"); } check(empty_sender.send("") == -2, "empty message count bounded"); @@ -385,8 +360,7 @@ DOCTEST_TEST_CASE("ARQ retries stop at the configured retransmission limit") { int transmissions = 0; sender.set_output([&](auto) { ++transmissions; }); check(sender.send("unreachable") == 0, "message queued"); - for (std::uint32_t now = 0; now < 3000; now += 10) - { + for (std::uint32_t now = 0; now < 3000; now += 10) { sender.update(now); } check(sender.failed() && transmissions == 3, "initial transmission plus two retries then failure"); @@ -438,26 +412,21 @@ DOCTEST_TEST_CASE("ARQ full receiver advertises zero window and reopens after re settings.receive_window = 2; simulated_link link(settings); check(link.left.input(packet(84, 0, {}, 2)) == 0, "peer advertises its initial receive window"); - for (const auto* message : {"first", "second", "third", "fourth"}) - { + for (const auto* message : {"first", "second", "third", "fourth"}) { check(link.left.send(message) == 0, "message queues"); } - for (int index = 0; index < 100; ++index) - { + for (int index = 0; index < 100; ++index) { link.step(); } check(link.left.pending_segments() == 2, "sender stops at receiver window"); std::vector actual; - for (int index = 0; index < 1000; ++index) - { + for (int index = 0; index < 1000; ++index) { std::string message; - while (link.right.receive(message)) - { + while (link.right.receive(message)) { actual.push_back(message); } link.step(); - if (actual.size() == 4 && link.left.pending_segments() == 0) - { + if (actual.size() == 4 && link.left.pending_segments() == 0) { break; } } @@ -496,11 +465,9 @@ DOCTEST_TEST_CASE("ARQ absent output retains pending work until output is instal } DOCTEST_TEST_CASE("ARQ constructor rejects unusable configuration") { - for (int index = 0; index < 7; ++index) - { + for (int index = 0; index < 7; ++index) { arknet::arq::options settings; - switch (index) - { + switch (index) { case 0: settings.mtu = 24; break; diff --git a/tests/arq_interop.cpp b/tests/arq_interop.cpp index fccbee2..0d191d2 100644 --- a/tests/arq_interop.cpp +++ b/tests/arq_interop.cpp @@ -19,16 +19,14 @@ constexpr std::uint32_t interop_conversation = 0x83a27104; std::uint32_t read_wire32(std::string_view data, std::size_t offset) { std::uint32_t value = 0; - for (std::size_t index = 0; index < 4; ++index) - { + for (std::size_t index = 0; index < 4; ++index) { value |= static_cast(static_cast(data[offset + index])) << (8 * index); } return value; } void write_wire32(std::string& data, std::size_t offset, std::uint32_t value) { - for (std::size_t index = 0; index < 4; ++index) - { + for (std::size_t index = 0; index < 4; ++index) { data[offset + index] = static_cast((value >> (8 * index)) & 0xff); } } @@ -46,10 +44,8 @@ std::string wire_packet(std::uint8_t command, std::string_view payload = {}, std return data; } -struct reference_link -{ - struct delivery - { +struct reference_link { + struct delivery { std::uint32_t due; bool to_modern; std::string data; @@ -91,8 +87,7 @@ struct reference_link void transmit(bool to_modern, std::string_view data) { ++transmissions; bool contains_window = false; - for (std::size_t offset = 0; offset < data.size();) - { + for (std::size_t offset = 0; offset < data.size();) { check(data.size() - offset >= 24, "reference output contains complete KCP headers"); const auto command = static_cast(data[offset + 4]); check(command >= 81 && command <= 84, "reference output contains standard KCP commands"); @@ -103,46 +98,38 @@ struct reference_link offset += 24 + length; } auto& drop_window = to_modern ? drop_reference_window : drop_modern_window; - if (contains_window && drop_window) - { + if (contains_window && drop_window) { drop_window = false; ++losses; return; } - if (impair && transmissions % 7 == 0) - { + if (impair && transmissions % 7 == 0) { ++losses; return; } const auto delay = impair ? static_cast(5 + (transmissions % 5) * 5) : 5; pending.push_back({now + delay, to_modern, std::string(data)}); - if (impair && transmissions % 11 == 0) - { + if (impair && transmissions % 11 == 0) { ++duplicates; pending.push_back({now + delay + 15, to_modern, std::string(data)}); } } void send(bool from_modern, std::string_view message) { - if (from_modern) - { + if (from_modern) { check(modern.send(message) == 0, "modern peer queues a message"); - } - else - { + } else { check(ikcp_send(reference.get(), message.data(), static_cast(message.size())) >= 0, "canonical peer queues a message"); } } bool receive(bool on_modern, std::string& message) { - if (on_modern) - { + if (on_modern) { return modern.receive(message); } const auto size = ikcp_peeksize(reference.get()); - if (size < 0) - { + if (size < 0) { return false; } message.resize(static_cast(size)); @@ -154,20 +141,15 @@ struct reference_link modern.update(now); ikcp_update(reference.get(), now); // Deliver due datagrams in reverse order to exercise reordered segments. - for (std::size_t index = pending.size(); index != 0; --index) - { - if (pending[index - 1].due > now) - { + for (std::size_t index = pending.size(); index != 0; --index) { + if (pending[index - 1].due > now) { continue; } auto value = std::move(pending[index - 1]); pending.erase(pending.begin() + static_cast(index - 1)); - if (value.to_modern) - { + if (value.to_modern) { check(modern.input(value.data) == 0, "modern peer accepts canonical wire frames"); - } - else - { + } else { check(ikcp_input(reference.get(), value.data.data(), static_cast(value.data.size())) == 0, "canonical peer accepts modern wire frames"); } @@ -182,10 +164,8 @@ struct reference_link } // namespace DOCTEST_TEST_CASE("ARQ interoperates bidirectionally with the pinned canonical C implementation") { - for (const bool congestion : {false, true}) - { - for (const auto [modern_mtu, reference_mtu] : {std::pair{600, 600}, std::pair{1200, 1400}}) - { + for (const bool congestion : {false, true}) { + for (const auto [modern_mtu, reference_mtu] : {std::pair{600, 600}, std::pair{1200, 1400}}) { DOCTEST_INFO("congestion=", congestion, ", modern MTU=", modern_mtu, ", reference MTU=", reference_mtu); arknet::arq::options settings; settings.mtu = static_cast(modern_mtu); @@ -194,14 +174,11 @@ DOCTEST_TEST_CASE("ARQ interoperates bidirectionally with the pinned canonical C reference_link link(settings, reference_mtu, true); std::vector expected_modern; std::vector expected_reference; - for (int round = 0; round < 8; ++round) - { - for (const std::size_t size : {0, 1, 64, 4119, 16384}) - { + for (int round = 0; round < 8; ++round) { + for (const std::size_t size : {0, 1, 64, 4119, 16384}) { expected_modern.emplace_back(size, static_cast('a' + round)); expected_reference.emplace_back(size, static_cast('A' + round)); - if (size > 1) - { + if (size > 1) { expected_modern.back()[size / 2] = '\0'; expected_reference.back()[size / 2] = '\0'; } @@ -211,21 +188,17 @@ DOCTEST_TEST_CASE("ARQ interoperates bidirectionally with the pinned canonical C } std::vector received_modern; std::vector received_reference; - for (int step = 0; step < 30000; ++step) - { + for (int step = 0; step < 30000; ++step) { link.step(); std::string message; - while (link.receive(true, message)) - { + while (link.receive(true, message)) { received_modern.push_back(message); } - while (link.receive(false, message)) - { + while (link.receive(false, message)) { received_reference.push_back(message); } if (link.idle() && received_modern.size() == expected_modern.size() && - received_reference.size() == expected_reference.size()) - { + received_reference.size() == expected_reference.size()) { break; } } @@ -239,46 +212,37 @@ DOCTEST_TEST_CASE("ARQ interoperates bidirectionally with the pinned canonical C } DOCTEST_TEST_CASE("ARQ canonical interoperability recovers zero windows and lost window announcements") { - for (const bool receiver_is_modern : {false, true}) - { + for (const bool receiver_is_modern : {false, true}) { DOCTEST_INFO("receiver_is_modern=", receiver_is_modern); arknet::arq::options settings; settings.receive_window = 128; settings.congestion_control = false; reference_link link(settings, 1200, false); std::vector expected; - for (int index = 0; index < 200; ++index) - { + for (int index = 0; index < 200; ++index) { expected.push_back(std::to_string(index)); link.send(!receiver_is_modern, expected.back()); } - while (link.now < 8000) - { + while (link.now < 8000) { link.step(); } const auto sender_index = receiver_is_modern ? 1 : 0; const auto receiver_index = receiver_is_modern ? 0 : 1; check(!link.idle() && link.commands[sender_index][2] > 0 && link.commands[receiver_index][3] > 0, "sender probes the full peer and the receiver reports its zero window"); - if (receiver_is_modern) - { + if (receiver_is_modern) { link.drop_modern_window = true; - } - else - { + } else { link.drop_reference_window = true; } std::vector received; - while (link.now < 30000) - { + while (link.now < 30000) { std::string message; - while (link.receive(receiver_is_modern, message)) - { + while (link.receive(receiver_is_modern, message)) { received.push_back(message); } link.step(); - if (received.size() == expected.size() && link.idle()) - { + if (received.size() == expected.size() && link.idle()) { break; } } diff --git a/tests/check.hpp b/tests/check.hpp index 2b1a05c..b29dce2 100644 --- a/tests/check.hpp +++ b/tests/check.hpp @@ -17,8 +17,7 @@ using namespace std::chrono_literals; #define check(value, message) DOCTEST_REQUIRE_MESSAGE((value), (message)) -class messages -{ +class messages { public: void push(std::string_view data) { std::lock_guard lock(mutex_); diff --git a/tests/concurrency.cpp b/tests/concurrency.cpp index 6fdf1b5..c190d67 100644 --- a/tests/concurrency.cpp +++ b/tests/concurrency.cpp @@ -18,14 +18,12 @@ constexpr std::size_t sender_count = 8; constexpr std::size_t payload_size = 256; constexpr std::size_t operation_limit = 1024; -class watchdog -{ +class watchdog { public: watchdog() : thread_([this] { std::unique_lock lock(mutex_); - if (!ready_.wait_for(lock, 45s, [this] { return finished_; })) - { + if (!ready_.wait_for(lock, 45s, [this] { return finished_; })) { std::cerr << "concurrent send/stop fixture hung\n"; std::_Exit(EXIT_FAILURE); } @@ -47,8 +45,7 @@ class watchdog std::thread thread_; }; -struct io_runner -{ +struct io_runner { asio::io_context context; asio::executor_work_guard work{context.get_executor()}; std::thread thread{[this] { context.run(); }}; @@ -60,14 +57,12 @@ struct io_runner } }; -struct latch_release -{ +struct latch_release { std::latch& latch; bool released{}; void open() { - if (!released) - { + if (!released) { released = true; latch.count_down(); } @@ -78,10 +73,8 @@ struct latch_release } }; -class io_pause -{ - struct state - { +class io_pause { + struct state { std::promise entered; std::promise released; std::shared_future release = released.get_future().share(); @@ -105,8 +98,7 @@ class io_pause } void open() { - if (!state_->open.exchange(true)) - { + if (!state_->open.exchange(true)) { state_->released.set_value(); } } @@ -115,10 +107,8 @@ class io_pause std::shared_ptr state_; }; -class outcomes -{ - struct result - { +class outcomes { + struct result { bool submitted{}; bool accepted{}; bool after_stop{}; @@ -132,10 +122,8 @@ class outcomes outcomes(std::size_t attempts, bool empty = false) : payloads_(attempts) , results_(attempts) { - for (std::size_t id = 0; id < attempts; ++id) - { - if (!empty || id + 1 == attempts) - { + for (std::size_t id = 0; id < attempts; ++id) { + if (!empty || id + 1 == attempts) { payloads_[id] = std::to_string(id) + ':'; payloads_[id].resize(payload_size, static_cast('A' + id % 26)); } @@ -170,23 +158,17 @@ class outcomes { std::lock_guard lock(mutex_); ++received_[side]; - if (payload.empty()) - { + if (payload.empty()) { ++empty_received_[side]; - } - else - { + } else { const auto end = payload.find(':'); std::size_t id{}; const auto parsed = std::from_chars( payload.data(), payload.data() + (end == std::string_view::npos ? payload.size() : end), id); if (end == std::string_view::npos || parsed.ec != std::errc{} || parsed.ptr != payload.data() + end || - id >= payloads_.size() || payload != payloads_[id]) - { + id >= payloads_.size() || payload != payloads_[id]) { invalid_payload_ = true; - } - else - { + } else { ++results_[id].received[side]; } } @@ -195,8 +177,7 @@ class outcomes } void queue_size(std::size_t bytes, std::size_t limit) { - if (bytes > limit) - { + if (bytes > limit) { queue_overflow_.store(true); } } @@ -208,8 +189,7 @@ class outcomes std::size_t accepted() const { std::lock_guard lock(mutex_); std::size_t count{}; - for (const auto& result : results_) - { + for (const auto& result : results_) { count += result.submitted && result.accepted; } return count; @@ -231,23 +211,17 @@ class outcomes check(!invalid_payload_ && !queue_overflow_ && !submission_exception_, "invalid data, queue overflow, or send exception"); std::size_t accepted_empty{}; - for (std::size_t id = 0; id < results_.size(); ++id) - { + for (std::size_t id = 0; id < results_.size(); ++id) { const auto& result = results_[id]; const auto size = payloads_[id].size(); check(result.submitted && result.callbacks == 1, "each submission must complete exactly once"); - if (!result.accepted) - { + if (!result.accepted) { check((result.error == asio::error::no_buffer_space || result.error == asio::error::not_connected) && result.bytes == 0, "rejected send error or byte count"); - } - else if (!result.error) - { + } else if (!result.error) { check(result.bytes == size, "successful send byte count"); - } - else - { + } else { bool cancelled = result.error == asio::error::operation_aborted || result.error == asio::error::not_connected || result.error == asio::error::bad_descriptor || result.error == asio::error::broken_pipe || @@ -258,31 +232,24 @@ class outcomes #endif check(stopping && result.bytes <= size && cancelled, "accepted send cancellation error or byte count"); } - if (result.after_stop) - { + if (result.after_stop) { check(!result.accepted && result.error == asio::error::not_connected, "send after stop must reject as not_connected"); } check(result.received[0] <= 1 && result.received[1] <= result.received[0], "duplicate or unexpected echo"); - if (!result.accepted) - { + if (!result.accepted) { check(result.received[0] == 0, "rejected payload must never reach the server"); } - if (!stopping && result.accepted) - { - if (size == 0) - { + if (!stopping && result.accepted) { + if (size == 0) { ++accepted_empty; - } - else - { + } else { check(result.received[0] == 1 && result.received[1] == 1, "accepted payload must arrive unchanged once"); } } } - if (!stopping) - { + if (!stopping) { check(empty_received_[0] == accepted_empty && empty_received_[1] == accepted_empty, "empty payload delivery count"); } @@ -302,15 +269,13 @@ class outcomes }; template -struct shutdown -{ +struct shutdown { Client& client; Server& server; bool stopped{}; void stop() { - if (!stopped) - { + if (!stopped) { client.stop(); server.stop(); stopped = true; @@ -326,8 +291,7 @@ template void connect(Client& client, Server& server, outcomes& results, const std::string& certificates) { client.set_auto_reconnect(false); client.set_disconnect_timeout(2s); - if constexpr (requires { client.ssl_stream(); }) - { + if constexpr (requires { client.ssl_stream(); }) { server.set_cert_file("", certificates + "/server.pem", certificates + "/server-key.pem", ""); check(!arknet::get_last_error(), "concurrency server identity"); client.load_verify_file(certificates + "/ca.pem"); @@ -337,13 +301,10 @@ void connect(Client& client, Server& server, outcomes& results, const std::strin session->async_send(data); }); client.bind_recv([&](std::string_view data) { results.receive(1, data); }); - if constexpr (requires { client.ws_stream(); }) - { + if constexpr (requires { client.ws_stream(); }) { check(server.start("127.0.0.1", 0), "concurrency WebSocket server start"); check(client.start("127.0.0.1", server.get_listen_port(), "/concurrency"), "concurrency WebSocket connect"); - } - else - { + } else { check(server.start("127.0.0.1", 0, arknet::use_dgram), "concurrency TCP server start"); check(client.start("127.0.0.1", server.get_listen_port(), arknet::use_dgram), "concurrency TCP connect"); } @@ -351,15 +312,14 @@ void connect(Client& client, Server& server, outcomes& results, const std::strin template void submit(Client& client, outcomes& results, std::size_t id, std::size_t limit, bool after_stop = false) { - try - { + try { const bool accepted = client.async_send(std::string_view(results.payload(id)), [&, id](const arknet::error_code& error, std::size_t bytes) { results.complete(id, error, bytes); }); results.submitted(id, accepted, after_stop); results.queue_size(client.get_queued_send_buffer_size(), limit); + } catch (...) { + results.submission_threw(); } - catch (...) - { results.submission_threw(); } } template @@ -374,18 +334,15 @@ void backpressure(Client& client, Server& server, bool operations, const std::st io_pause pause(client); std::barrier start(static_cast(sender_count)); std::vector senders; - for (std::size_t thread = 0; thread < sender_count; ++thread) - { + for (std::size_t thread = 0; thread < sender_count; ++thread) { senders.emplace_back([&, thread] { start.arrive_and_wait(); - for (std::size_t row = 0; row < rows; ++row) - { + for (std::size_t row = 0; row < rows; ++row) { submit(client, results, thread * rows + row, limit); } }); } - for (auto& sender : senders) - { + for (auto& sender : senders) { sender.join(); } const auto admitted = results.accepted(); @@ -418,21 +375,18 @@ void stop_race(Client& client, Server& server, bool from_callback) { std::latch racing_sent(sender_count); std::latch stopped(1); std::vector senders; - for (std::size_t thread = 0; thread < sender_count; ++thread) - { + for (std::size_t thread = 0; thread < sender_count; ++thread) { senders.emplace_back([&, thread] { const auto base = thread * rows; submit(client, results, base, limit); initial_sent.count_down(); race.wait(); - for (std::size_t row = 1; row <= racing_rows; ++row) - { + for (std::size_t row = 1; row <= racing_rows; ++row) { submit(client, results, base + row, limit); } racing_sent.count_down(); stopped.wait(); - for (std::size_t row = racing_rows + 1; row < rows; ++row) - { + for (std::size_t row = racing_rows + 1; row < rows; ++row) { submit(client, results, base + row, limit, true); } }); @@ -443,39 +397,31 @@ void stop_race(Client& client, Server& server, bool from_callback) { latch_release release_senders{stopped}; std::optional owner; const auto request = [&] { - try - { + try { client.stop(); stop_done.set_value(); + } catch (...) { + stop_done.set_exception(std::current_exception()); } - catch (...) - { stop_done.set_exception(std::current_exception()); } }; - if (from_callback) - { + if (from_callback) { client.post([&] { bool released{}; - try - { + try { client.stop(); race.count_down(); released = true; // Finish concurrent admission while stop completion is still pending. racing_sent.wait(); stop_done.set_value(); - } - catch (...) - { - if (!released) - { + } catch (...) { + if (!released) { race.count_down(); } stop_done.set_exception(std::current_exception()); } }); - } - else - { + } else { owner.emplace([&] { race.wait(); request(); @@ -484,14 +430,12 @@ void stop_race(Client& client, Server& server, bool from_callback) { } pause.open(); await(completion); - if (owner) - { + if (owner) { owner->join(); } check(client.wait_stopped(), "concurrent send/stop owner completion"); release_senders.open(); - for (auto& sender : senders) - { + for (auto& sender : senders) { sender.join(); } results.wait_completions(sender_count * rows); @@ -508,8 +452,7 @@ void stop_before_write() { std::promise accepted; auto connected = accepted.get_future(); listener.async_accept(peer, [&](arknet::error_code ec) { - if (!ec) - { + if (!ec) { peer.set_option(asio::socket_base::receive_buffer_size(4096), ec); } accepted.set_value(ec); @@ -527,8 +470,7 @@ void stop_before_write() { check(!await(connected), "stop-before-write nonreading peer accept"); peer_watchdog.expires_after(2s); peer_watchdog.async_wait([&](const arknet::error_code& ec) { - if (!ec) - { + if (!ec) { expired = true; arknet::error_code ignored; peer.close(ignored); @@ -542,8 +484,7 @@ void stop_before_write() { client.stop(); // Inline send admission can precede the stop event queued by this callback. const bool admitted = client.async_send(payload, [&](const arknet::error_code& ec, std::size_t bytes) { - if (calls.fetch_add(1) == 0) - { + if (calls.fetch_add(1) == 0) { sent.set_value({ec, bytes}); } }); @@ -578,30 +519,25 @@ void stop_before_write() { template void transport(bool external, Function&& function) { std::optional runner; - if (external) - { + if (external) { runner.emplace(); } auto server = external ? std::make_unique(runner->context) : std::make_unique(); auto client = external ? std::make_unique(runner->context) : std::make_unique(); function(*client, *server); - if (external) - { + if (external) { check(!runner->context.stopped(), "concurrent fixture must preserve external context"); } } template void cases(const char* protocol) { - for (bool external : {false, true}) - { - for (bool operations : {false, true}) - { + for (bool external : {false, true}) { + for (bool operations : {false, true}) { transport( external, [&](auto& client, auto& server) { backpressure(client, server, operations, ""); }); } - for (bool callback : {false, true}) - { + for (bool callback : {false, true}) { transport( external, [&](auto& client, auto& server) { stop_race(client, server, callback); }); } @@ -621,8 +557,7 @@ namespace { DOCTEST_TEST_CASE("TCP auto reconnect rejects a send copied in the previous generation") { watchdog timeout; - class paused_client : public arknet::tcp_client_t - { + class paused_client : public arknet::tcp_client_t { public: using arknet::tcp_client_t::tcp_client_t; std::promise entered; @@ -633,8 +568,7 @@ DOCTEST_TEST_CASE("TCP auto reconnect rejects a send copied in the previous gene } std::string_view data_filter_before_send(std::string_view data) { - if (data == "old-generation") - { + if (data == "old-generation") { entered.set_value(); released.wait(); } @@ -659,16 +593,14 @@ DOCTEST_TEST_CASE("TCP auto reconnect rejects a send copied in the previous gene server.bind_connect([](auto& session) { session->set_disconnect_timeout(200ms); }); server.bind_recv([&](auto&, std::string_view data) { received.push(data); - if (data == "current-generation" && fresh_receipts.fetch_add(1) == 0) - { + if (data == "current-generation" && fresh_receipts.fetch_add(1) == 0) { fresh_received.set_value(); } }); client.set_auto_reconnect(true, 20ms); client.set_disconnect_timeout(200ms); client.bind_connect([&] { - if (!arknet::get_last_error()) - { + if (!arknet::get_last_error()) { connections.push(std::to_string(client.generation())); } }); @@ -678,18 +610,16 @@ DOCTEST_TEST_CASE("TCP auto reconnect rejects a send copied in the previous gene const auto previous_generation = connections.wait(1).front(); auto filtered = client.entered.get_future(); std::jthread sender([&] { - try - { + try { submitted.set_value(client.async_send( std::string_view("old-generation"), [&](const arknet::error_code& ec, std::size_t bytes) { - if (old_calls.fetch_add(1) == 0) - { + if (old_calls.fetch_add(1) == 0) { old_sent.set_value({ec, bytes}); } })); + } catch (...) { + submitted.set_exception(std::current_exception()); } - catch (...) - { submitted.set_exception(std::current_exception()); } }); latch_release release{client.released}; await(filtered); @@ -738,8 +668,7 @@ DOCTEST_TEST_CASE("WebSocket concurrent admission, recovery, and stop") { #if ARKNET_USE_BOOST_ASIO DOCTEST_TEST_CASE("WebSocket TLS concurrent admission and recovery") { watchdog timeout; - for (bool external : {false, true}) - { + for (bool external : {false, true}) { transport( external, [&](auto& client, auto& server) { backpressure(client, server, false, ARKNET_TEST_CERT_DIR); }); } @@ -758,8 +687,7 @@ DOCTEST_TEST_CASE("One io_context with four workers serializes each IO lane") { std::barrier rendezvous(4); std::mutex workers_mutex; std::set workers; - for (std::size_t i = 0; i < actors.size(); ++i) - { + for (std::size_t i = 0; i < actors.size(); ++i) { actors[i] = std::make_unique(runtime.context_at()); actors[i]->post([&, i] { { @@ -769,17 +697,13 @@ DOCTEST_TEST_CASE("One io_context with four workers serializes each IO lane") { rendezvous.arrive_and_wait(); }); } - for (std::size_t i = 0; i < actors.size(); ++i) - { - for (unsigned task = 0; task < 200; ++task) - { + for (std::size_t i = 0; i < actors.size(); ++i) { + for (unsigned task = 0; task < 200; ++task) { actors[i]->post([&, i] { - if (active[i].fetch_add(1) != 0) - { + if (active[i].fetch_add(1) != 0) { ++overlaps; } - if (!actors[i]->running_in_this_thread()) - { + if (!actors[i]->running_in_this_thread()) { ++wrong_executor; } ++counts[i]; @@ -788,8 +712,7 @@ DOCTEST_TEST_CASE("One io_context with four workers serializes each IO lane") { }); } } - for (auto& actor : actors) - { + for (auto& actor : actors) { auto drained = actor->post([] {}, asio::use_future); await(drained); actor->stop(); diff --git a/tests/connection_kernel_samples.cpp b/tests/connection_kernel_samples.cpp index b165349..2fc8667 100644 --- a/tests/connection_kernel_samples.cpp +++ b/tests/connection_kernel_samples.cpp @@ -34,8 +34,7 @@ std::string summary(const tcp_kernel_samples& samples) { std::vector numbers(const std::string& text, const std::string& field) { const auto marker = '"' + field + "\":"; std::vector result; - for (auto position = text.find(marker); position != std::string::npos; position = text.find(marker, position)) - { + for (auto position = text.find(marker); position != std::string::npos; position = text.find(marker, position)) { position += marker.size(); std::uint64_t value{}; const auto parsed = std::from_chars(text.data() + position, text.data() + text.size(), value); @@ -85,10 +84,8 @@ DOCTEST_TEST_CASE("kernel retention schema preserves every qualifying sample fro "\"time_unit\":\"microseconds_since_process_start\",\"samples\":[],\"total_samples\":0," "\"overwritten_samples\":0}"); std::vector ordinals, times; - for (std::uint64_t count = 0; count <= 8; ++count) - { - if (count) - { + for (std::uint64_t count = 0; count <= 8; ++count) { + if (count) { ordinals.push_back(count); times.push_back(1999999 + count); samples.sample(first, times.back(), 1000000, 136, 65, 69, [] { return tcp_kernel_info{}; }); @@ -98,8 +95,7 @@ DOCTEST_TEST_CASE("kernel retention schema preserves every qualifying sample fro DOCTEST_CHECK(numbers(text, "sample_time_us") == times); DOCTEST_CHECK(numbers(text, "total_samples") == std::vector{count}); DOCTEST_CHECK(numbers(text, "overwritten_samples") == std::vector{0}); - if (count == 1) - { + if (count == 1) { DOCTEST_CHECK( text == "{\"event\":\"tcp_kernel_sample_history\",\"schema\":2,\"clock_domain\":\"local_steady_clock\"," @@ -120,8 +116,7 @@ DOCTEST_TEST_CASE("kernel retention schema preserves every qualifying sample fro DOCTEST_TEST_CASE("kernel history clock origin uses the supplied steady epoch in microseconds only") { tcp_kernel_samples samples(true); - for (const auto origin : {std::int64_t{-1234567}, std::int64_t{0}, std::int64_t{1234567}}) - { + for (const auto origin : {std::int64_t{-1234567}, std::int64_t{0}, std::int64_t{1234567}}) { std::ostringstream out; samples.write_history(out, first, std::chrono::steady_clock::time_point{std::chrono::microseconds{origin}}); DOCTEST_CHECK(out.str().starts_with("\"clock_domain\":\"local_steady_clock\",")); @@ -171,8 +166,7 @@ DOCTEST_TEST_CASE("kernel sample capacity preserves persistent stalls and reuses ++calls; return tcp_kernel_info{}; }; - for (std::uint64_t index = 1; index <= tcp_kernel_samples::session_capacity; ++index) - { + for (std::uint64_t index = 1; index <= tcp_kernel_samples::session_capacity; ++index) { samples.sample({7, 1, index}, 2000000, 1000000, 4, 2, 3, query); } const identity extra{7, 1, 17}; @@ -196,15 +190,12 @@ DOCTEST_TEST_CASE("kernel sample capacity preserves persistent stalls and reuses DOCTEST_TEST_CASE("kernel samples preserve the first four and latest four across repeated tail wraps") { tcp_kernel_samples samples(true); - for (std::uint64_t count = 1; count <= 20; ++count) - { + for (std::uint64_t count = 1; count <= 20; ++count) { samples.sample(first, 1999999 + count, 1000000 + count, 136, 65, 69, [] { return tcp_kernel_info{}; }); - if (count > 8) - { + if (count > 8) { const std::vector expected{1, 2, 3, 4, count - 3, count - 2, count - 1, count}; auto times = expected; - for (auto& time : times) - { + for (auto& time : times) { time += 1999999; } DOCTEST_CHECK(numbers(history(samples), "sample_ordinal") == expected); @@ -237,8 +228,7 @@ DOCTEST_TEST_CASE("kernel samples preserve the first four and latest four across DOCTEST_TEST_CASE("unavailable kernel samples emit null with a reason rather than zero metrics") { for (const auto status : {tcp_kernel_info::status::unsupported, tcp_kernel_info::status::closed, - tcp_kernel_info::status::query_failed, tcp_kernel_info::status::short_response}) - { + tcp_kernel_info::status::query_failed, tcp_kernel_info::status::short_response}) { tcp_kernel_samples samples(true); samples.sample(first, 2000000, 1000000, 136, 65, 69, [status] { tcp_kernel_info value; @@ -256,8 +246,7 @@ DOCTEST_TEST_CASE("unavailable kernel samples emit null with a reason rather tha DOCTEST_TEST_CASE("largest kernel sample history fits the existing bounded record output") { tcp_kernel_samples samples(true); - for (unsigned index = 0; index < tcp_kernel_samples::sample_capacity; ++index) - { + for (unsigned index = 0; index < tcp_kernel_samples::sample_capacity; ++index) { samples.sample(first, (std::numeric_limits::max)(), (std::numeric_limits::max)(), 136, (std::numeric_limits::max)(), (std::numeric_limits::max)(), [] { tcp_kernel_info value; diff --git a/tests/connection_load_backpressure.cpp b/tests/connection_load_backpressure.cpp index 10314e6..a0a4cec 100644 --- a/tests/connection_load_backpressure.cpp +++ b/tests/connection_load_backpressure.cpp @@ -11,37 +11,30 @@ #include int main(int argc, char** argv) { - if (argc != 2) - { + if (argc != 2) { std::cerr << "usage: connection_load_backpressure BENCHMARK_EXECUTABLE\n"; return 1; } int descriptors[2]; - if (pipe(descriptors) || fcntl(descriptors[1], F_SETFL, O_NONBLOCK) == -1) - { + if (pipe(descriptors) || fcntl(descriptors[1], F_SETFL, O_NONBLOCK) == -1) { std::cerr << "could not prepare test-owned output pipe\n"; return 1; } const std::array bytes{}; - while (write(descriptors[1], bytes.data(), bytes.size()) >= 0) - { + while (write(descriptors[1], bytes.data(), bytes.size()) >= 0) { } - if (errno != EAGAIN || fcntl(descriptors[1], F_SETFL, 0) == -1) - { + if (errno != EAGAIN || fcntl(descriptors[1], F_SETFL, 0) == -1) { std::cerr << "could not fill test-owned output pipe\n"; return 1; } const auto child = fork(); - if (child == -1) - { + if (child == -1) { std::cerr << "fork failed\n"; return 1; } - if (!child) - { + if (!child) { const auto errors = open("/dev/null", O_WRONLY); - if (errors == -1 || dup2(descriptors[1], STDOUT_FILENO) == -1 || dup2(errors, STDERR_FILENO) == -1) - { + if (errors == -1 || dup2(descriptors[1], STDOUT_FILENO) == -1 || dup2(errors, STDERR_FILENO) == -1) { std::_Exit(127); } close(descriptors[0]); @@ -56,29 +49,24 @@ int main(int argc, char** argv) { // flush blocks; its watchdog must not depend on flushing any tied stream. const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); int status{}; - while (std::chrono::steady_clock::now() < deadline) - { + while (std::chrono::steady_clock::now() < deadline) { const auto result = waitpid(child, &status, WNOHANG); - if (result == child) - { + if (result == child) { close(descriptors[0]); - if (WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) - { + if (WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) { std::cout << "watchdog terminated with SIGKILL despite an unread full stdout pipe\n"; return 0; } std::cerr << "unexpected child status " << status << '\n'; return 1; } - if (result == -1 && errno != EINTR) - { + if (result == -1 && errno != EINTR) { break; } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } kill(child, SIGKILL); - while (waitpid(child, &status, 0) == -1 && errno == EINTR) - { + while (waitpid(child, &status, 0) == -1 && errno == EINTR) { } close(descriptors[0]); std::cerr << "watchdog failed to terminate under stdout backpressure; child was reaped\n"; diff --git a/tests/connection_load_cleanup.cpp b/tests/connection_load_cleanup.cpp index a77ae2d..fdc61b4 100644 --- a/tests/connection_load_cleanup.cpp +++ b/tests/connection_load_cleanup.cpp @@ -5,8 +5,7 @@ namespace { -class silent_peer -{ +class silent_peer { public: explicit silent_peer(std::size_t count) : acceptor_(context_, {asio::ip::address_v4::loopback(), 0}) @@ -20,8 +19,7 @@ class silent_peer asio::post(context_, [this] { arknet::error_code ignored; acceptor_.close(ignored); - for (const auto& socket : sockets_) - { + for (const auto& socket : sockets_) { socket->close(ignored); } work_.reset(); @@ -41,13 +39,11 @@ class silent_peer void accept() { auto socket = std::make_shared(context_); acceptor_.async_accept(*socket, [this, socket](const arknet::error_code& error) { - if (!error) - { + if (!error) { // Keep the peer open without acknowledging HELLO or closing on FIN. sockets_.push_back(socket); ++accepted_; - if (--remaining_) - { + if (--remaining_) { accept(); } } @@ -82,14 +78,12 @@ DOCTEST_TEST_CASE("Connection load cleanup overlaps per-client shutdown deadline settings.cleanup_seconds = 1; counters values; client_run client(settings, values, runtime); - for (std::size_t index = 0; index < count; ++index) - { + for (std::size_t index = 0; index < count; ++index) { client.launch(); } const auto ready_deadline = clock_type::now() + 2s; while (clock_type::now() < ready_deadline && - (values.established != count || peer.accepted() != count || values.pending_sends)) - { + (values.established != count || peer.accepted() != count || values.pending_sends)) { std::this_thread::sleep_for(1ms); } CHECK(values.established == count); diff --git a/tests/connection_load_native_control.cpp b/tests/connection_load_native_control.cpp index 1fc7563..5d2537e 100644 --- a/tests/connection_load_native_control.cpp +++ b/tests/connection_load_native_control.cpp @@ -32,8 +32,7 @@ std::string read_file(const std::string& path) { return std::string(std::istreambuf_iterator(input), {}); } -struct child -{ +struct child { pid_t pid = -1; std::string log; bool reaped{}; @@ -43,21 +42,17 @@ struct child log = directory + "/" + std::to_string(++serial) + ".log"; arguments.insert(arguments.begin(), executable); std::vector argv; - for (auto& value : arguments) - { + for (auto& value : arguments) { argv.push_back(value.data()); } argv.push_back(nullptr); pid = fork(); - if (pid == -1) - { + if (pid == -1) { throw std::runtime_error("fork failed"); } - if (!pid) - { + if (!pid) { const auto fd = open(log.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0600); - if (fd == -1 || dup2(fd, STDOUT_FILENO) == -1 || dup2(fd, STDERR_FILENO) == -1) - { + if (fd == -1 || dup2(fd, STDOUT_FILENO) == -1 || dup2(fd, STDERR_FILENO) == -1) { std::_Exit(127); } close(fd); @@ -67,28 +62,23 @@ struct child } ~child() { - if (!reaped && pid > 0) - { + if (!reaped && pid > 0) { kill(pid, SIGKILL); - while (waitpid(pid, nullptr, 0) == -1 && errno == EINTR) - { + while (waitpid(pid, nullptr, 0) == -1 && errno == EINTR) { } } } bool poll() { - if (reaped) - { + if (reaped) { return true; } int status{}; const auto result = waitpid(pid, &status, WNOHANG); - if (result == -1 && errno != EINTR) - { + if (result == -1 && errno != EINTR) { throw std::runtime_error("waitpid failed"); } - if (result == pid) - { + if (result == pid) { reaped = true; exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : 128 + WTERMSIG(status); } @@ -97,10 +87,8 @@ struct child int wait() { const auto until = clock_type::now() + std::chrono::seconds(8); - while (clock_type::now() < until) - { - if (poll()) - { + while (clock_type::now() < until) { + if (poll()) { std::ofstream(log + ".exit") << exit_code << '\n'; return exit_code; } @@ -111,22 +99,18 @@ struct child std::string port() { const auto until = clock_type::now() + std::chrono::seconds(2); - while (clock_type::now() < until) - { + while (clock_type::now() < until) { const auto output = read_file(log); const auto offset = output.find("\"port\":"); - if (offset != std::string::npos) - { + if (offset != std::string::npos) { const auto begin = offset + 7; const auto end = output.find_first_not_of("0123456789", begin); const auto value = output.substr(begin, end - begin); - if (!value.empty()) - { + if (!value.empty()) { return value; } } - if (poll()) - { + if (poll()) { break; } std::this_thread::sleep_for(std::chrono::milliseconds(2)); @@ -138,16 +122,14 @@ struct child std::uint64_t counter(const std::string& line, const std::string& name) { const auto key = "\"" + name + "\":"; const auto offset = line.find(key); - if (offset == std::string::npos) - { + if (offset == std::string::npos) { throw std::runtime_error("missing counter " + name + ": " + line); } const auto begin = line.data() + offset + key.size(); std::uint64_t value{}; const auto parsed = std::from_chars(begin, line.data() + line.size(), value); if (parsed.ec != std::errc{} || parsed.ptr == line.data() + line.size() || - (*parsed.ptr != ',' && *parsed.ptr != '}')) - { + (*parsed.ptr != ',' && *parsed.ptr != '}')) { throw std::runtime_error("invalid counter " + name + ": " + line); } return value; @@ -159,25 +141,20 @@ void sample_phases(const std::string& path, unsigned count, bool native) { bool before_start{}, previous_idle{}, idle_interval{}, complete{}; unsigned samples{}; std::string line; - while (std::getline(lines, line)) - { - if (line.find("\"event\":\"sample\"") != std::string::npos) - { + while (std::getline(lines, line)) { + if (line.find("\"event\":\"sample\"") != std::string::npos) { ++samples; const auto ready = counter(line, "ready"); const auto started = counter(line, "started"); const auto finished = counter(line, "finished"); if (ready < previous_ready || started < previous_started || finished < previous_finished || ready > count || - started > count || finished > count) - { + started > count || finished > count) { throw std::runtime_error("nonmonotonic phase counter: " + path + " " + line); } - if (native && counter(line, "sample_interval_ms") != 1000) - { + if (native && counter(line, "sample_interval_ms") != 1000) { throw std::runtime_error("native sampling cadence changed: " + path); } - if (line.find("\"resource_cpu_interval_s\":") == std::string::npos) - { + if (line.find("\"resource_cpu_interval_s\":") == std::string::npos) { throw std::runtime_error("missing CPU interval evidence: " + path); } before_start = before_start || (ready < count && !started && !finished); @@ -188,15 +165,12 @@ void sample_phases(const std::string& path, unsigned count, bool native) { previous_ready = ready; previous_started = started; previous_finished = finished; - } - else if (line.find("\"event\":\"result\"") != std::string::npos) - { + } else if (line.find("\"event\":\"result\"") != std::string::npos) { complete = counter(line, "ready") == count && counter(line, "started") == count && counter(line, "finished") == count; } } - if (!before_start || !idle_interval || !complete) - { + if (!before_start || !idle_interval || !complete) { throw std::runtime_error("missing handshake/idle interval/finish evidence: " + path + "\n" + read_file(path)); } std::cout << "phase samples=" << samples << " include consecutive idle endpoints and completed FIN: " << path @@ -208,8 +182,7 @@ void pair(const std::string& server_binary, const std::string& client_binary, un std::vector common{"--run-id", "71", "--scenario", scenario, "--payload", std::to_string(payload), "--window", "4", "--max-connections", std::to_string(count), "--hold-seconds", phases ? "3.2" : "0.03", "--deadline-seconds", phases ? "7" : "5", "--cleanup-seconds", "1"}; - if (trace) - { + if (trace) { common.insert(common.end(), {"--operation-trace", "on"}); } auto server_args = common; @@ -223,39 +196,32 @@ void pair(const std::string& server_binary, const std::string& client_binary, un child client(client_binary, client_args); const auto client_status = client.wait(); const auto server_status = server.wait(); - if (client_status || server_status) - { + if (client_status || server_status) { throw std::runtime_error( "pair failed: " + client.log + " " + server.log + "\n" + read_file(client.log) + read_file(server.log)); } - for (const auto& path : {client.log, server.log}) - { + for (const auto& path : {client.log, server.log}) { const auto output = read_file(path); if (output.find("\"event\":\"result\"") == std::string::npos || - output.find("\"finished\":" + std::to_string(count)) == std::string::npos) - { + output.find("\"finished\":" + std::to_string(count)) == std::string::npos) { throw std::runtime_error("missing final target evidence: " + path); } if (output.find("arknet_asio_connection_control") != std::string::npos && (output.find("\"success\":true") == std::string::npos || output.find("\"cleanup_complete\":true") == std::string::npos || output.find("\"controller_on_io_worker\":false") == std::string::npos || - output.find("\"initial_buffer_bytes\":4096") == std::string::npos)) - { + output.find("\"initial_buffer_bytes\":4096") == std::string::npos)) { throw std::runtime_error("native terminal/cleanup evidence missing: " + path); } if (trace && (output.find("\"operation_trace_enabled\":true,\"operation_trace_emitted\":false") == std::string::npos || - output.find("\"event\":\"operation_trace\"") != std::string::npos)) - { + output.find("\"event\":\"operation_trace\"") != std::string::npos)) { throw std::runtime_error("successful opt-in run emitted a failure trace: " + path); } - if (!trace && output.find("operation_trace") != std::string::npos) - { + if (!trace && output.find("operation_trace") != std::string::npos) { throw std::runtime_error("default run changed diagnostic output: " + path); } - if (phases) - { + if (phases) { sample_phases(path, count, output.find("arknet_asio_connection_control") != std::string::npos); } } @@ -269,13 +235,11 @@ void invalid_arguments(const std::string& executable) { {"--hold-seconds", "inf"}, {"--port", "2"}, {"--unknown", "1"}, {"--connections"}, {"--connections", "5000", "--window", "4", "--max-inflight", "19999"}, {"--operation-trace", "true"}, {"--operation-trace", "ON"}, {"--operation-trace"}, - {"--operation-trace", "on", "--operation-trace", "off"}}) - { + {"--operation-trace", "on", "--operation-trace", "off"}}) { auto args = base; args.insert(args.end(), suffix.begin(), suffix.end()); child value(executable, args); - if (value.wait() != 1 || read_file(value.log).find("native control:") == std::string::npos) - { + if (value.wait() != 1 || read_file(value.log).find("native control:") == std::string::npos) { throw std::runtime_error("invalid arguments not rejected: " + value.log); } } @@ -288,20 +252,17 @@ void invalid_arguments(const std::string& executable) { const auto output = read_file(server.log); if (status != 1 || output.find("\"success\":false") == std::string::npos || output.find("\"cleanup_complete\":true") == std::string::npos || - output.find("deadline: target incomplete") == std::string::npos) - { + output.find("deadline: target incomplete") == std::string::npos) { throw std::runtime_error("deadline/cleanup evidence missing: " + server.log); } } void silent_peer(const std::string& executable, bool trace = false) { - struct listener - { + struct listener { int fd = socket(AF_INET, SOCK_STREAM, 0); ~listener() { - if (fd != -1) - { + if (fd != -1) { close(fd); } } @@ -311,21 +272,18 @@ void silent_peer(const std::string& executable, bool trace = false) { address.sin_family = AF_INET; address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); if (peer.fd == -1 || fcntl(peer.fd, F_SETFD, FD_CLOEXEC) == -1 || - bind(peer.fd, reinterpret_cast(&address), sizeof(address)) == -1 || listen(peer.fd, 1) == -1) - { + bind(peer.fd, reinterpret_cast(&address), sizeof(address)) == -1 || listen(peer.fd, 1) == -1) { throw std::runtime_error("silent peer setup failed"); } socklen_t length = sizeof(address); - if (getsockname(peer.fd, reinterpret_cast(&address), &length) == -1) - { + if (getsockname(peer.fd, reinterpret_cast(&address), &length) == -1) { throw std::runtime_error("silent peer port lookup failed"); } // The kernel accepts the connection, but no application replies to HELLO. std::vector args{"--role", "client", "--target", "127.0.0.1", "--port", std::to_string(ntohs(address.sin_port)), "--run-id", "41", "--connections", "1", "--request-timeout-ms", trace ? "1200" : "100", "--hold-seconds", "0.01", "--deadline-seconds", "3", "--cleanup-seconds", "1"}; - if (trace) - { + if (trace) { args.insert(args.end(), {"--operation-trace", "on"}); } child client(executable, args); @@ -340,13 +298,11 @@ void silent_peer(const std::string& executable, bool trace = false) { output.find("\"last_write_kind\":1") == std::string::npos || output.find("\"incomplete_sessions\":1,\"emitted\":1,\"omitted\":0") == std::string::npos || output.find("\"cleanup_complete\":true") == std::string::npos || - output.find("request timeout:") == std::string::npos) - { + output.find("request timeout:") == std::string::npos) { throw std::runtime_error("pre-cancel connection diagnostics missing: " + client.log); } const auto trace_position = output.find("\"event\":\"operation_trace\""); - if (trace) - { + if (trace) { const auto summary = output.find("\"event\":\"operation_trace_summary\""); if (trace_position == std::string::npos || summary == std::string::npos || summary >= snapshot || trace_position >= summary || @@ -358,29 +314,23 @@ void silent_peer(const std::string& executable, bool trace = false) { output.find("\"output_truncated\":false") == std::string::npos || output.find("\"emitted_sessions\":1,\"omitted_incomplete_sessions\":0") == std::string::npos || output.find("\"operation_trace_enabled\":true,\"operation_trace_emitted\":true") == std::string::npos || - counter(output.substr(summary), "worker_markers_completed") < 1 || snapshot - trace_position > 256 * 1024) - { + counter(output.substr(summary), "worker_markers_completed") < 1 || snapshot - trace_position > 256 * 1024) { throw std::runtime_error("bounded frozen trace/worker progress evidence missing: " + client.log); } - } - else if (output.find("operation_trace") != std::string::npos) - { + } else if (output.find("operation_trace") != std::string::npos) { throw std::runtime_error("disabled diagnostics emitted a trace: " + client.log); } } void progress_then_stall(const std::string& executable) { - struct raw_peer - { + struct raw_peer { int listener = socket(AF_INET, SOCK_STREAM, 0), connection = -1; ~raw_peer() { - if (connection != -1) - { + if (connection != -1) { close(connection); } - if (listener != -1) - { + if (listener != -1) { close(listener); } } @@ -391,13 +341,11 @@ void progress_then_stall(const std::string& executable) { address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); if (peer.listener == -1 || fcntl(peer.listener, F_SETFD, FD_CLOEXEC) == -1 || fcntl(peer.listener, F_SETFL, O_NONBLOCK) == -1 || - bind(peer.listener, reinterpret_cast(&address), sizeof(address)) || listen(peer.listener, 1)) - { + bind(peer.listener, reinterpret_cast(&address), sizeof(address)) || listen(peer.listener, 1)) { throw std::runtime_error("kernel sample peer listener setup failed"); } socklen_t length = sizeof(address); - if (getsockname(peer.listener, reinterpret_cast(&address), &length)) - { + if (getsockname(peer.listener, reinterpret_cast(&address), &length)) { throw std::runtime_error("kernel sample peer port query failed"); } child client(executable, @@ -407,46 +355,38 @@ void progress_then_stall(const std::string& executable) { "--operation-trace", "on", "--tcp-kernel-samples", "on", "--socket-observer", "on"}); const auto deadline = clock_type::now() + std::chrono::seconds(6); auto wait_ready = [&](int fd, short events) { - while (clock_type::now() < deadline) - { + while (clock_type::now() < deadline) { pollfd descriptor{fd, events, 0}; const auto remaining = std::chrono::duration_cast(deadline - clock_type::now()); const auto wait_ms = remaining.count() > 0 ? remaining.count() : 0; const auto result = ::poll(&descriptor, 1, static_cast(wait_ms) + 1); - if (result > 0) - { + if (result > 0) { return; } - if (result < 0 && errno != EINTR) - { + if (result < 0 && errno != EINTR) { throw std::runtime_error("kernel sample peer poll failed"); } } throw std::runtime_error("kernel sample peer IO deadline exceeded: " + client.log); }; - while (peer.connection == -1) - { + while (peer.connection == -1) { wait_ready(peer.listener, POLLIN); peer.connection = accept(peer.listener, nullptr, nullptr); - if (peer.connection == -1 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) - { + if (peer.connection == -1 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) { throw std::runtime_error("kernel sample peer accept failed"); } } - if (fcntl(peer.connection, F_SETFD, FD_CLOEXEC) == -1 || fcntl(peer.connection, F_SETFL, O_NONBLOCK) == -1) - { + if (fcntl(peer.connection, F_SETFD, FD_CLOEXEC) == -1 || fcntl(peer.connection, F_SETFL, O_NONBLOCK) == -1) { throw std::runtime_error("kernel sample peer socket setup failed"); } #if defined(SO_NOSIGPIPE) const int no_sigpipe = 1; - if (setsockopt(peer.connection, SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, sizeof(no_sigpipe))) - { + if (setsockopt(peer.connection, SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, sizeof(no_sigpipe))) { throw std::runtime_error("kernel sample peer SIGPIPE setup failed"); } #endif auto send_bytes = [&](std::string_view bytes) { - while (!bytes.empty()) - { + while (!bytes.empty()) { wait_ready(peer.connection, POLLOUT); #if defined(MSG_NOSIGNAL) constexpr int flags = MSG_NOSIGNAL; @@ -454,12 +394,9 @@ void progress_then_stall(const std::string& executable) { constexpr int flags = 0; #endif const auto size = send(peer.connection, bytes.data(), bytes.size(), flags); - if (size > 0) - { + if (size > 0) { bytes.remove_prefix(static_cast(size)); - } - else if (size != -1 || (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK)) - { + } else if (size != -1 || (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK)) { throw std::runtime_error("kernel sample peer send failed: " + client.log); } } @@ -467,21 +404,16 @@ void progress_then_stall(const std::string& executable) { auto receive_exact = [&](const std::string& expected) { std::string received(expected.size(), '\0'); std::size_t offset{}; - while (offset < received.size()) - { + while (offset < received.size()) { wait_ready(peer.connection, POLLIN); const auto size = recv(peer.connection, received.data() + offset, received.size() - offset, 0); - if (size > 0) - { + if (size > 0) { offset += static_cast(size); - } - else if (size != -1 || (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK)) - { + } else if (size != -1 || (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK)) { throw std::runtime_error("kernel sample peer receive failed: " + client.log); } } - if (received != expected) - { + if (received != expected) { throw std::runtime_error("kernel sample peer frame mismatch: " + client.log); } }; @@ -509,8 +441,7 @@ void progress_then_stall(const std::string& executable) { if (status != 1 || history_position == std::string::npos || summary_position == std::string::npos || snapshot_position == std::string::npos || result_position == std::string::npos || !(history_position < summary_position && summary_position < snapshot_position && - snapshot_position < result_position)) - { + snapshot_position < result_position)) { throw std::runtime_error("periodic kernel sample output missing: " + client.log); } const auto history = output.substr(history_position, output.find('\n', history_position) - history_position); @@ -536,18 +467,15 @@ void progress_then_stall(const std::string& executable) { result.find("\"operation_trace_enabled\":true,\"operation_trace_emitted\":true") == std::string::npos || result.find("\"tcp_kernel_samples_enabled\":true") == std::string::npos || output.find("socket_operation_trace_enabled") != std::string::npos || counter(result, "data_received") != 1 || - counter(result, "finished") != 0) - { + counter(result, "finished") != 0) { throw std::runtime_error("periodic kernel sample scope/drain evidence missing: " + client.log); } #if defined(__APPLE__) - if (history.find("\"tcp_info\":{\"state\":4,") == std::string::npos) - { + if (history.find("\"tcp_info\":{\"state\":4,") == std::string::npos) { throw std::runtime_error("periodic established Apple TCP info missing: " + client.log); } #else - if (history.find("\"tcp_info\":null,\"unavailable_reason\":\"unsupported_platform\"") == std::string::npos) - { + if (history.find("\"tcp_info\":null,\"unavailable_reason\":\"unsupported_platform\"") == std::string::npos) { throw std::runtime_error("unsupported periodic TCP info reason missing: " + client.log); } #endif @@ -558,33 +486,28 @@ void progress_then_stall(const std::string& executable) { output.find("\"read_pending\":true,\"input_bytes\":0,\"input_frame_ready\":false", observation) == std::string::npos || counter(output.substr(observation), "global_marker_completed_us") < 1 || - counter(output.substr(observation), "strand_probe_submitted_us") < 1) - { + counter(output.substr(observation), "strand_probe_submitted_us") < 1) { throw std::runtime_error("independent controller observation missing: " + client.log); } if (output.find("\"event\":\"socket_observer_before_cancel\"") == std::string::npos || output.find("\"clock_anchor\":{\"steady_before_ns\":\"", observation) == std::string::npos || - output.find("\"query_completed_steady_ns\":\"", observation) == std::string::npos) - { + output.find("\"query_completed_steady_ns\":\"", observation) == std::string::npos) { throw std::runtime_error("observer wall/steady anchors missing: " + client.log); } bool strand_completed = false; for (auto position = observation; position != std::string::npos; - position = output.find("\"event\":\"socket_observation\"", position + 1)) - { + position = output.find("\"event\":\"socket_observation\"", position + 1)) { const auto line = output.substr(position, output.find('\n', position) - position); strand_completed = strand_completed || counter(line, "strand_probe_completed_us") >= counter(line, "strand_probe_submitted_us"); } - if (!strand_completed) - { + if (!strand_completed) { throw std::runtime_error("session strand probe did not complete: " + client.log); } #if defined(__APPLE__) if (counter(output.substr(observation), "tcp_requested_length") < 100 || counter(output.substr(observation), "tcp_returned_length") < 100 || - output.find("\"tcp_raw_hex\":\"\"", observation) != std::string::npos) - { + output.find("\"tcp_raw_hex\":\"\"", observation) != std::string::npos) { throw std::runtime_error("raw TCP ABI provenance missing: " + client.log); } #endif @@ -600,26 +523,22 @@ void fragmented_window(const std::string& executable) { "--hold-seconds", "0.01", "--deadline-seconds", "5", "--cleanup-seconds", "1"}); const auto port = std::stoi(server.port()); - struct raw_peer - { + struct raw_peer { int fd = socket(AF_INET, SOCK_STREAM, 0); ~raw_peer() { - if (fd != -1) - { + if (fd != -1) { close(fd); } } } peer; - if (peer.fd == -1 || fcntl(peer.fd, F_SETFD, FD_CLOEXEC) == -1) - { + if (peer.fd == -1 || fcntl(peer.fd, F_SETFD, FD_CLOEXEC) == -1) { throw std::runtime_error("fragmented peer socket setup failed"); } #if defined(SO_NOSIGPIPE) const int no_sigpipe = 1; - if (setsockopt(peer.fd, SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, sizeof(no_sigpipe))) - { + if (setsockopt(peer.fd, SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, sizeof(no_sigpipe))) { throw std::runtime_error("fragmented peer SIGPIPE setup failed"); } #endif @@ -628,31 +547,26 @@ void fragmented_window(const std::string& executable) { address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); address.sin_port = htons(static_cast(port)); if (connect(peer.fd, reinterpret_cast(&address), sizeof(address)) || - fcntl(peer.fd, F_SETFL, O_NONBLOCK) == -1) - { + fcntl(peer.fd, F_SETFL, O_NONBLOCK) == -1) { throw std::runtime_error("fragmented peer connect failed"); } const auto deadline = clock_type::now() + std::chrono::seconds(4); auto wait_ready = [&](short events) { - while (clock_type::now() < deadline) - { + while (clock_type::now() < deadline) { pollfd descriptor{peer.fd, events, 0}; const auto remaining = std::chrono::duration_cast(deadline - clock_type::now()); const auto result = ::poll(&descriptor, 1, static_cast(remaining.count()) + 1); - if (result > 0) - { + if (result > 0) { return; } - if (result < 0 && errno != EINTR) - { + if (result < 0 && errno != EINTR) { throw std::runtime_error("fragmented peer poll failed"); } } throw std::runtime_error("fragmented peer IO deadline exceeded: " + server.log); }; auto send_bytes = [&](std::string_view bytes) { - while (!bytes.empty()) - { + while (!bytes.empty()) { wait_ready(POLLOUT); #if defined(MSG_NOSIGNAL) constexpr int flags = MSG_NOSIGNAL; @@ -660,16 +574,11 @@ void fragmented_window(const std::string& executable) { constexpr int flags = 0; #endif const auto written = send(peer.fd, bytes.data(), bytes.size(), flags); - if (written > 0) - { + if (written > 0) { bytes.remove_prefix(static_cast(written)); - } - else if (written == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) - { + } else if (written == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) { continue; - } - else - { + } else { throw std::runtime_error("fragmented peer send failed: " + server.log); } } @@ -677,37 +586,27 @@ void fragmented_window(const std::string& executable) { auto receive_exact = [&](const std::string& expected) { std::string received(expected.size(), '\0'); std::size_t offset{}; - while (offset < received.size()) - { + while (offset < received.size()) { wait_ready(POLLIN); const auto size = recv(peer.fd, received.data() + offset, received.size() - offset, 0); - if (size > 0) - { + if (size > 0) { offset += static_cast(size); - } - else if (size == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) - { + } else if (size == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) { continue; - } - else - { + } else { throw std::runtime_error("fragmented peer receive failed: " + server.log); } } - if (received != expected) - { + if (received != expected) { throw std::runtime_error("fragmented peer response content/order mismatch: " + server.log); } }; auto wire = [&](frame_kind kind, std::uint64_t sequence = 0) { const auto body = connection_load::make_frame(kind, owner, sequence, kind == frame_kind::data ? 1024 : 0); std::string prefix; - if (body.size() < 254) - { + if (body.size() < 254) { prefix += static_cast(body.size()); - } - else - { + } else { prefix += '\xfe'; prefix += static_cast(body.size()); prefix += static_cast(body.size() >> 8); @@ -719,8 +618,7 @@ void fragmented_window(const std::string& executable) { // only gates this withheld-byte phase; elapsed time is not a throughput assertion. pollfd descriptor{peer.fd, POLLIN, 0}; const auto result = ::poll(&descriptor, 1, 100); - if (result != 0) - { + if (result != 0) { throw std::runtime_error("incomplete frame unexpectedly produced socket input: " + server.log); } }; @@ -729,12 +627,10 @@ void fragmented_window(const std::string& executable) { send_bytes(wire(frame_kind::start)); receive_exact(wire(frame_kind::start_ack)); std::string first_window; - for (unsigned sequence = 1; sequence <= 4; ++sequence) - { + for (unsigned sequence = 1; sequence <= 4; ++sequence) { first_window += wire(frame_kind::data, sequence); } - if (first_window.size() != 4364) - { + if (first_window.size() != 4364) { throw std::runtime_error("fragmented window no longer covers the 4096-byte receive cap"); } send_bytes(std::string_view(first_window).substr(0, 1)); // Partial three-byte prefix. @@ -744,8 +640,7 @@ void fragmented_window(const std::string& executable) { send_bytes(std::string_view(first_window).substr(1090)); // Complete frame plus three queued frames. receive_exact(first_window); std::string second_window; - for (unsigned sequence = 5; sequence <= 8; ++sequence) - { + for (unsigned sequence = 5; sequence <= 8; ++sequence) { second_window += wire(frame_kind::data, sequence); } send_bytes(second_window); // One application write larger than the receive cap. @@ -759,8 +654,7 @@ void fragmented_window(const std::string& executable) { const auto output = read_file(server.log); if (status || output.find("\"finished\":1") == std::string::npos || output.find("\"data_sent\":8,") == std::string::npos || - output.find("\"data_received\":8,") == std::string::npos) - { + output.find("\"data_received\":8,") == std::string::npos) { throw std::runtime_error("fragmented window did not finish cleanly: " + server.log + "\n" + output); } std::cout << "fragmented prefix/body and two 4364-byte windows recovered 8 exact Echo frames: " << server.log @@ -769,22 +663,18 @@ void fragmented_window(const std::string& executable) { } // namespace int main(int argc, char** argv) { - try - { - if (argc != 3) - { + try { + if (argc != 3) { throw std::runtime_error("usage: native_control_test NATIVE_EXECUTABLE ARKNET_EXECUTABLE"); } char path[] = "/tmp/arknet-native-process-test-XXXXXX"; const auto result = mkdtemp(path); - if (!result) - { + if (!result) { throw std::runtime_error("mkdtemp failed"); } directory = result; std::cout << "child logs and exits: " << directory << '\n'; - if (std::string_view(argv[2]) == "--observer-only") - { + if (std::string_view(argv[2]) == "--observer-only") { progress_then_stall(argv[1]); return 0; } @@ -798,8 +688,7 @@ int main(int argc, char** argv) { pair(argv[1], argv[1], 1, "idle"); pair(argv[1], argv[1], 3, "idle", 1024, true); pair(argv[2], argv[2], 3, "idle", 1024, true); - for (const auto count : {1u, 8u, 32u}) - { + for (const auto count : {1u, 8u, 32u}) { pair(argv[1], argv[1], count); } pair(argv[1], argv[1], 1, "traffic", 1); @@ -807,9 +696,7 @@ int main(int argc, char** argv) { pair(argv[1], argv[2], 8); pair(argv[2], argv[1], 8); return 0; - } - catch (const std::exception& error) - { + } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } diff --git a/tests/connection_load_protocol.cpp b/tests/connection_load_protocol.cpp index 38c0599..47b710d 100644 --- a/tests/connection_load_protocol.cpp +++ b/tests/connection_load_protocol.cpp @@ -26,8 +26,7 @@ DOCTEST_TEST_CASE("Connection load frames reject malformed control and length fi const identity owner{1, 2, 3}; frame_header header; const auto valid = make_frame(frame_kind::hello, owner); - for (std::size_t length = 0; length < header_bytes; ++length) - { + for (std::size_t length = 0; length < header_bytes; ++length) { DOCTEST_CHECK_FALSE(decode(std::string_view(valid).substr(0, length), header)); } auto corrupt = valid; @@ -51,21 +50,18 @@ DOCTEST_TEST_CASE("Connection latency buckets contain every observation and reta latency_distribution first, second, combined; constexpr std::array observations{0, 1, 2, 7, 8, 9, 15, 16, 17, 127, 128, 1000, 1000000, std::uint64_t{1} << 63, (std::numeric_limits::max)()}; - for (std::size_t index = 0; index < observations.size(); ++index) - { + for (std::size_t index = 0; index < observations.size(); ++index) { const auto value = observations[index]; const auto position = latency_distribution::bucket(value); DOCTEST_REQUIRE(position < latency_distribution::bucket_count); DOCTEST_CHECK(latency_distribution::upper_bound(position) >= value); - if (position) - { + if (position) { DOCTEST_CHECK(latency_distribution::upper_bound(position - 1) < value); } (index % 2 ? first : second).observe(value); combined.observe(value); } - for (std::size_t index = 0; index < latency_distribution::bucket_count; ++index) - { + for (std::size_t index = 0; index < latency_distribution::bucket_count; ++index) { DOCTEST_CHECK(first.count(index) + second.count(index) == combined.count(index)); } DOCTEST_CHECK(combined.total() == observations.size()); diff --git a/tests/connection_load_resources.cpp b/tests/connection_load_resources.cpp index 8912bc9..7bc4d08 100644 --- a/tests/connection_load_resources.cpp +++ b/tests/connection_load_resources.cpp @@ -25,19 +25,15 @@ void check_sample(const connection_load::resource_snapshot& snapshot) { DOCTEST_CHECK(std::isfinite(snapshot.elapsed_seconds)); DOCTEST_CHECK(snapshot.elapsed_seconds >= 0); DOCTEST_CHECK(snapshot.cpu_interval_seconds.has_value() == snapshot.metrics.cpu_percent.value.has_value()); - if (snapshot.cpu_interval_seconds) - { + if (snapshot.cpu_interval_seconds) { DOCTEST_CHECK(std::isfinite(*snapshot.cpu_interval_seconds)); DOCTEST_CHECK(*snapshot.cpu_interval_seconds > 0); DOCTEST_CHECK(*snapshot.cpu_interval_seconds <= snapshot.elapsed_seconds); } - if (snapshot.metrics.cpu_percent.value) - { + if (snapshot.metrics.cpu_percent.value) { DOCTEST_CHECK(std::isfinite(*snapshot.metrics.cpu_percent.value)); DOCTEST_CHECK(*snapshot.metrics.cpu_percent.value >= 0); - } - else - { + } else { DOCTEST_CHECK_FALSE(snapshot.metrics.cpu_percent.unavailable_reason.empty()); } } @@ -50,22 +46,19 @@ DOCTEST_TEST_CASE("Port diagnostics validate OS ranges and namespace-wide TIME_W DOCTEST_REQUIRE(range.value); DOCTEST_CHECK(range.value->first == 49152); DOCTEST_CHECK(range.value->last == 65535); - for (const auto text : {"", "0 65535", "49152 65536", "60000 50000", "-1 20", "1", "1 2 3", "1 x"}) - { + for (const auto text : {"", "0 65535", "49152 65536", "60000 50000", "-1 20", "1", "1 2 3", "1 x"}) { const auto invalid = parse_port_range(text); DOCTEST_CHECK_FALSE(invalid.value); DOCTEST_CHECK_FALSE(invalid.unavailable_reason.empty()); } - for (const auto text : {"TCP: inuse 5 orphan 0 tw 0 alloc 7 mem 1\n", "UDP: inuse 4\nTCP: tw 0\n"}) - { + for (const auto text : {"TCP: inuse 5 orphan 0 tw 0 alloc 7 mem 1\n", "UDP: inuse 4\nTCP: tw 0\n"}) { const auto zero = parse_time_wait(text); DOCTEST_REQUIRE(zero.value); DOCTEST_CHECK(*zero.value == 0); } DOCTEST_CHECK(parse_time_wait("TCP: inuse 1 tw 15000 mem 3\n").value == 15000); for (const auto text : {"", "TCP6: tw 10", "TCP: inuse 5", "TCP: tw", "TCP: tw -1", "TCP: tw 1x", "TCP: tw 1 tw 2", - "TCP: tw 1\nTCP: tw 2", "TCP: tw 18446744073709551616"}) - { + "TCP: tw 1\nTCP: tw 2", "TCP: tw 18446744073709551616"}) { DOCTEST_CHECK_FALSE(parse_time_wait(text).value); } const auto missing = read_query_file(""); @@ -94,14 +87,11 @@ DOCTEST_TEST_CASE("Native port queries retain their source scope and explicit OS }); DOCTEST_CHECK_FALSE(short_result.value); const auto actual = connection_load::read_port_resources(); - if (actual.ephemeral_ports.value) - { + if (actual.ephemeral_ports.value) { DOCTEST_CHECK(actual.ephemeral_ports.value->first > 0); DOCTEST_CHECK(actual.ephemeral_ports.value->first <= actual.ephemeral_ports.value->last); DOCTEST_CHECK(actual.ephemeral_ports.value->last <= 65535); - } - else - { + } else { DOCTEST_CHECK_FALSE(actual.ephemeral_ports.unavailable_reason.empty()); } #if defined(__linux__) @@ -170,8 +160,7 @@ DOCTEST_TEST_CASE("Connection resource JSON preserves numbers, real zero and exp for (const auto field : {"\"cpu_percent\":175.25", "\"peak_rss_bytes\":8192", "\"current_rss_bytes\":0", "\"fd_count\":0", "\"thread_count\":4", "\"handle_count\":null", "\"resource_cpu_interval_s\":0.5", "\"nofile_soft\":0", - "\"nofile_hard\":\"unlimited\"", "\"resource_sample_elapsed_s\":2.5", "\"resource_phase\":\"running\""}) - { + "\"nofile_hard\":\"unlimited\"", "\"resource_sample_elapsed_s\":2.5", "\"resource_phase\":\"running\""}) { DOCTEST_CHECK(text.find(field) != std::string::npos); } DOCTEST_CHECK( @@ -191,19 +180,16 @@ DOCTEST_TEST_CASE("Connection resource JSON explains absent CPU intervals and es sample.metrics.thread_count = {std::nullopt, "Thread query unavailable"}; const auto text = output(sample, true); for (const auto field : {"cpu_percent", "current_rss_bytes", "peak_rss_bytes", "fd_count", "handle_count", - "thread_count", "nofile_soft", "nofile_hard", "resource_cpu_interval_s"}) - { + "thread_count", "nofile_soft", "nofile_hard", "resource_cpu_interval_s"}) { DOCTEST_CHECK(text.find('"' + std::string(field) + "\":null") != std::string::npos); DOCTEST_CHECK(text.find('"' + std::string(field) + "\":\"") != std::string::npos); } DOCTEST_CHECK(text.find("FD query \\\"failed\\\"\\\\line\\u000anext") != std::string::npos); - for (unsigned char character : text) - { + for (unsigned char character : text) { DOCTEST_CHECK(character >= 32); } std::string controls; - for (char character = 0; character < 32; ++character) - { + for (char character = 0; character < 32; ++character) { controls += character; } std::ostringstream escaped; @@ -251,12 +237,10 @@ DOCTEST_TEST_CASE("Process resource limits report the calling process without mo DOCTEST_REQUIRE(sample.nofile_hard.value); DOCTEST_CHECK(sample.nofile_soft.value->unlimited == (actual.rlim_cur == RLIM_INFINITY)); DOCTEST_CHECK(sample.nofile_hard.value->unlimited == (actual.rlim_max == RLIM_INFINITY)); - if (actual.rlim_cur != RLIM_INFINITY) - { + if (actual.rlim_cur != RLIM_INFINITY) { DOCTEST_CHECK(sample.nofile_soft.value->descriptors == static_cast(actual.rlim_cur)); } - if (actual.rlim_max != RLIM_INFINITY) - { + if (actual.rlim_max != RLIM_INFINITY) { DOCTEST_CHECK(sample.nofile_hard.value->descriptors == static_cast(actual.rlim_max)); } #else @@ -309,8 +293,7 @@ DOCTEST_TEST_CASE("Connection resource periodic and whole-run samplers retain in check_sample(whole); DOCTEST_CHECK(first.elapsed_seconds <= second.elapsed_seconds); DOCTEST_CHECK(second.elapsed_seconds <= whole.elapsed_seconds); - if (first.cpu_interval_seconds && second.cpu_interval_seconds && whole.cpu_interval_seconds) - { + if (first.cpu_interval_seconds && second.cpu_interval_seconds && whole.cpu_interval_seconds) { DOCTEST_CHECK(second.elapsed_seconds - *second.cpu_interval_seconds == doctest::Approx(first.elapsed_seconds)); DOCTEST_CHECK(*whole.cpu_interval_seconds >= *first.cpu_interval_seconds + *second.cpu_interval_seconds); DOCTEST_CHECK(*whole.cpu_interval_seconds > *second.cpu_interval_seconds); diff --git a/tests/connection_operation_trace.cpp b/tests/connection_operation_trace.cpp index 554c90c..1dcecbd 100644 --- a/tests/connection_operation_trace.cpp +++ b/tests/connection_operation_trace.cpp @@ -53,8 +53,7 @@ DOCTEST_TEST_CASE("Composed operation history pairs concurrent directions and fr DOCTEST_TEST_CASE("Ring wrap retains chronological tail and discloses missing history and invalid transitions") { operation_ring ring; - for (int index = 0; index < 12; ++index) - { + for (int index = 0; index < 12; ++index) { ring.record(operation_phase::write_start, 2 * index, 1091, index + 1); ring.record(operation_phase::write_complete, 2 * index + 1, 1091, index + 1, index == 11 ? 5 : 0); } @@ -78,8 +77,7 @@ DOCTEST_TEST_CASE("Trace storage admission is capped independently of declared c DOCTEST_CHECK(recorder.slots() == operation_recorder::maximum_slots()); DOCTEST_CHECK(recorder.storage_bytes() + trace_output::limit + operation_recorder::scratch_reservation <= operation_recorder::memory_limit); - for (std::size_t index = 0; index < recorder.slots(); ++index) - { + for (std::size_t index = 0; index < recorder.slots(); ++index) { DOCTEST_REQUIRE(recorder.acquire() != nullptr); } DOCTEST_CHECK(recorder.acquire() == nullptr); @@ -93,13 +91,11 @@ DOCTEST_TEST_CASE("Trace storage admission is capped independently of declared c DOCTEST_TEST_CASE("Worker marker admission bounds queued work and retains the frozen observation") { operation_recorder recorder(true, 1); DOCTEST_CHECK(recorder.request_marker()); - for (int index = 0; index < 100; ++index) - { + for (int index = 0; index < 100; ++index) { DOCTEST_CHECK_FALSE(recorder.request_marker()); } recorder.complete_marker(10, 5000); - for (int index = 0; index < 20; ++index) - { + for (int index = 0; index < 20; ++index) { DOCTEST_REQUIRE(recorder.request_marker()); recorder.complete_marker(6000 + index, 7000 + index); } @@ -154,8 +150,7 @@ DOCTEST_TEST_CASE("Trace output stays bounded, rejects partial records and reser trace_output output; const auto write = [](std::ostream& out) { out << "{\"fixed\":\"" << std::string(6000, 'x') << "\"}"; }; unsigned retained{}; - while (output.append(write)) - { + while (output.append(write)) { ++retained; } DOCTEST_CHECK(retained > 16); @@ -172,8 +167,7 @@ DOCTEST_TEST_CASE("Trace output stays bounded, rejects partial records and reser DOCTEST_TEST_CASE("Both full operation rings fit one complete failure record") { operation_ring composed, socket; - for (std::uint64_t index = 0; index < 8; ++index) - { + for (std::uint64_t index = 0; index < 8; ++index) { const auto sequence = (std::numeric_limits::max)() - index; const auto time = (std::numeric_limits::max)() - 16 + 2 * index; composed.record(operation_phase::read_start, time, 0, sequence); diff --git a/tests/connection_socket_observer.cpp b/tests/connection_socket_observer.cpp index ef3e10c..0989b82 100644 --- a/tests/connection_socket_observer.cpp +++ b/tests/connection_socket_observer.cpp @@ -44,8 +44,7 @@ DOCTEST_TEST_CASE("peek preserves bytes and reports EAGAIN before data arrives") DOCTEST_CHECK(empty.peek_bytes == -1); DOCTEST_CHECK((empty.peek_error == EAGAIN || empty.peek_error == EWOULDBLOCK)); DOCTEST_REQUIRE(::send(pair[0], "abc", 3, 0) == 3); - for (unsigned index = 0; index < 2; ++index) - { + for (unsigned index = 0; index < 2; ++index) { const auto observed = connection_load::observe_socket(pair[1]); DOCTEST_CHECK(observed.poll_result == 1); DOCTEST_CHECK((observed.events & POLLIN) != 0); @@ -105,8 +104,7 @@ DOCTEST_TEST_CASE("recovered slots reject stale generation markers and bound obs const auto current = observer.publish(pair[1], state); DOCTEST_CHECK(current.generation > old.generation); observer.complete(old, 999); - for (unsigned index = 0; index < 20; ++index) - { + for (unsigned index = 0; index < 20; ++index) { observer.sample(index * socket_observer::interval_us, 1); } observer.close(state.owner); diff --git a/tests/connection_socket_trace.cpp b/tests/connection_socket_trace.cpp index e97528e..908efbb 100644 --- a/tests/connection_socket_trace.cpp +++ b/tests/connection_socket_trace.cpp @@ -19,8 +19,7 @@ using connection_load::operation_ring; using connection_load::socket_trace_clock; // Deterministic AsyncReadStream/AsyncWriteStream: no sockets, workers or sleeps. -struct fake_stream -{ +struct fake_stream { using executor_type = asio::io_context::executor_type; asio::io_context& context; std::string input, output; @@ -39,53 +38,41 @@ struct fake_stream const auto executor = asio::get_associated_executor(handler, get_executor()); const auto allocator = asio::get_associated_allocator(handler); auto slot = asio::get_associated_cancellation_slot(handler); - if (expected_executor) - { + if (expected_executor) { DOCTEST_CHECK(asio::any_io_executor(executor) == *expected_executor); } - if (expected_immediate_executor) - { + if (expected_immediate_executor) { DOCTEST_CHECK(asio::any_io_executor(asio::get_associated_immediate_executor(handler, get_executor())) == *expected_immediate_executor); } - if (expected_allocator) - { - if constexpr (requires { allocator.resource(); }) - { + if (expected_allocator) { + if constexpr (requires { allocator.resource(); }) { DOCTEST_CHECK(allocator.resource() == expected_allocator); - } - else - { + } else { DOCTEST_FAIL("associated allocator was lost"); } } - if (expected_slot.is_connected()) - { + if (expected_slot.is_connected()) { DOCTEST_CHECK(slot == expected_slot); } auto cancelled = std::make_shared(false); - if (slot.is_connected()) - { + if (slot.is_connected()) { slot.assign([cancelled](asio::cancellation_type) { *cancelled = true; }); } asio::post(executor, asio::bind_allocator(allocator, [this, buffers, handler = std::move(handler), cancelled, read]() mutable { - if (*cancelled) - { + if (*cancelled) { std::move(handler)(asio::error::operation_aborted, 0); return; } const auto size = (std::min)(chunk, asio::buffer_size(buffers)); - if constexpr (asio::is_mutable_buffer_sequence::value) - { + if constexpr (asio::is_mutable_buffer_sequence::value) { DOCTEST_REQUIRE(read); const auto received = (std::min)(size, input.size()); asio::buffer_copy(buffers, asio::buffer(input), received); input.erase(0, received); std::move(handler)(error, received); - } - else - { + } else { DOCTEST_REQUIRE_FALSE(read); std::string bytes(size, '\0'); asio::buffer_copy(asio::buffer(bytes), buffers, size); @@ -174,8 +161,7 @@ DOCTEST_TEST_CASE("Socket trace distinguishes a partial buffer-sequence write fr DOCTEST_CHECK(stream.writes == 3); DOCTEST_CHECK(stream.output == "abcdef"); DOCTEST_REQUIRE(ring.size() == 6); - for (std::size_t index = 0; index < ring.size(); index += 2) - { + for (std::size_t index = 0; index < ring.size(); index += 2) { DOCTEST_CHECK(ring.at(index).bytes == 6 - index); DOCTEST_CHECK(ring.at(index + 1).bytes == 2); DOCTEST_CHECK(ring.at(index).sequence == 9); diff --git a/tests/consumer/fec.cpp b/tests/consumer/fec.cpp index 3a98f5e..859762d 100644 --- a/tests/consumer/fec.cpp +++ b/tests/consumer/fec.cpp @@ -19,12 +19,10 @@ int installed_fec_selfcheck() { return true; }); const std::string first(24, 'a'), second(24, 'b'); - if (!sender.send(first, 0) || !sender.send(second, 0) || wire.size() != 3) - { + if (!sender.send(first, 0) || !sender.send(second, 0) || wire.size() != 3) { return 1; } - if (!receiver.input(wire[1], 0) || !receiver.input(wire[2], 0)) - { + if (!receiver.input(wire[1], 0) || !receiver.input(wire[2], 0)) { return 2; } return received == std::vector{second, first} ? 0 : 3; diff --git a/tests/consumer_launcher.cpp b/tests/consumer_launcher.cpp index 089b5d7..6dcd219 100644 --- a/tests/consumer_launcher.cpp +++ b/tests/consumer_launcher.cpp @@ -23,10 +23,8 @@ std::string quote_argument(std::string_view argument) { // quotes and trailing backslashes for the compiler's C runtime parser. std::string quoted = "\""; std::size_t backslashes = 0; - for (const char character : argument) - { - if (character == '\\') - { + for (const char character : argument) { + if (character == '\\') { ++backslashes; continue; } @@ -41,53 +39,44 @@ std::string quote_argument(std::string_view argument) { int main(int argc, char** argv) { const auto* log = std::getenv("ARKNET_TEST_LAUNCHER_LOG"); - if (!log) - { + if (!log) { return 2; } std::ostringstream record; int compiler = 1; - while (compiler < argc && std::string_view(argv[compiler]) == "--record-token") - { - if (++compiler == argc) - { + while (compiler < argc && std::string_view(argv[compiler]) == "--record-token") { + if (++compiler == argc) { return 2; } record << "token=" << argv[compiler++] << '\n'; } - if (compiler == argc) - { + if (compiler == argc) { return 2; } record << "compiler=" << argv[compiler] << '\n'; - for (int index = compiler + 1; index < argc; ++index) - { + for (int index = compiler + 1; index < argc; ++index) { record << "argument=" << argv[index] << '\n'; } { std::ofstream output(log, std::ios::app); output << record.str(); output.flush(); - if (!output) - { + if (!output) { return 2; } } #ifdef _WIN32 std::vector quoted; - for (int index = compiler; index < argc; ++index) - { + for (int index = compiler; index < argc; ++index) { quoted.push_back(quote_argument(argv[index])); } std::vector command; - for (const auto& argument : quoted) - { + for (const auto& argument : quoted) { command.push_back(argument.c_str()); } command.push_back(nullptr); const auto result = _spawnv(_P_WAIT, argv[compiler], command.data()); - if (result != -1) - { + if (result != -1) { return static_cast(result); } #else diff --git a/tests/coroutine_shutdown.cpp b/tests/coroutine_shutdown.cpp index 86b401a..072df3b 100644 --- a/tests/coroutine_shutdown.cpp +++ b/tests/coroutine_shutdown.cpp @@ -4,10 +4,8 @@ #include "check.hpp" DOCTEST_TEST_CASE("Native TCP benchmark drains both execution modes after an exception before traffic") { - for (const auto* execution : {"callback", "coroutine"}) - { - for (const auto* model : {"shared", "sharded"}) - { + for (const auto* execution : {"callback", "coroutine"}) { + for (const auto* model : {"shared", "sharded"}) { options settings; settings.model = model; settings.execution = execution; @@ -15,12 +13,11 @@ DOCTEST_TEST_CASE("Native TCP benchmark drains both execution modes after an exc settings.clients = 16; const auto started = clock_type::now(); bool propagated = false; - try - { + try { run(settings, [] { throw std::runtime_error("injected failure after connect"); }); + } catch (const std::runtime_error& error) { + propagated = std::string_view(error.what()) == "injected failure after connect"; } - catch (const std::runtime_error& error) - { propagated = std::string_view(error.what()) == "injected failure after connect"; } check(propagated, "injected failure reaches the caller after listener and socket cancellation"); check(clock_type::now() - started < 5s, "waiting client gates and server reads drain promptly"); } @@ -28,12 +25,9 @@ DOCTEST_TEST_CASE("Native TCP benchmark drains both execution modes after an exc } DOCTEST_TEST_CASE("Native callback and coroutine clients validate complete TCP batches") { - for (const auto* execution : {"callback", "coroutine"}) - { - for (const auto* model : {"shared", "sharded"}) - { - for (const auto window : {1u, 64u}) - { + for (const auto* execution : {"callback", "coroutine"}) { + for (const auto* model : {"shared", "sharded"}) { + for (const auto window : {1u, 64u}) { options settings; settings.model = model; settings.execution = execution; @@ -50,10 +44,8 @@ DOCTEST_TEST_CASE("Native callback and coroutine clients validate complete TCP b } DOCTEST_TEST_CASE("Native TCP maximum batches complete without opposing write stalls") { - for (const auto* execution : {"callback", "coroutine"}) - { - for (const auto* model : {"shared", "sharded"}) - { + for (const auto* execution : {"callback", "coroutine"}) { + for (const auto* model : {"shared", "sharded"}) { options settings; settings.model = model; settings.execution = execution; diff --git a/tests/detail.cpp b/tests/detail.cpp index 847d367..1a7b951 100644 --- a/tests/detail.cpp +++ b/tests/detail.cpp @@ -9,8 +9,7 @@ #include #include "check.hpp" -struct test_option : arknet::detail::component_tag -{ +struct test_option : arknet::detail::component_tag { int value = 7; }; @@ -24,10 +23,11 @@ DOCTEST_TEST_CASE("Move-only function wrappers replace, reset and remove themsel moved.reset(); check(!moved, "empty callable"); bool bad_call = false; - try - { moved(1); } - catch (const std::bad_function_call&) - { bad_call = true; } + try { + moved(1); + } catch (const std::bad_function_call&) { + bad_call = true; + } check(bad_call, "empty callable throws"); std::unique_ptr> self_removing; @@ -63,13 +63,11 @@ DOCTEST_TEST_CASE("ECS clone owns independent delimiter and component state") { } DOCTEST_TEST_CASE("Datagram matchers retain incomplete frames and find complete boundaries") { - for (auto size : {std::size_t(253), std::size_t(254), std::size_t(65536)}) - { + for (auto size : {std::size_t(253), std::size_t(254), std::size_t(65536)}) { const std::size_t header = size < 254 ? 1 : size <= 65535 ? 3 : 9; std::string wire(header + size, 'x'); wire[0] = static_cast(header == 1 ? size : header == 3 ? 254 : 255); - for (std::size_t i = 1; i < header; ++i) - { + for (std::size_t i = 1; i < header; ++i) { wire[i] = static_cast((std::uint64_t(size) >> ((i - 1) * 8)) & 0xff); } @@ -110,18 +108,20 @@ DOCTEST_TEST_CASE("Linear buffers enforce limits after compaction and capacity r check(std::string_view(static_cast(data.data()), data.size()) == "ghijklmnopqrstuv", "compaction preserves unread data"); bool bounded = false; - try - { buffer.prepare(1); } - catch (const std::length_error&) - { bounded = true; } + try { + buffer.prepare(1); + } catch (const std::length_error&) { + bounded = true; + } check(bounded && buffer.size() == 16, "full buffer rejects another byte"); buffer.consume(16); check(buffer.capacity() >= 16, "capacity retained for reuse"); bounded = false; - try - { buffer.prepare(17); } - catch (const std::length_error&) - { bounded = true; } + try { + buffer.prepare(17); + } catch (const std::length_error&) { + bounded = true; + } check(bounded && buffer.size() == 0, "retained capacity does not bypass the limit"); buffer.prepare(16); buffer.commit(100); @@ -140,8 +140,7 @@ DOCTEST_TEST_CASE("Handler allocators use reusable storage and aligned heap fall check(reused == first, "local storage reused after release"); memory.deallocate(reused, 1); - struct alignas(64) aligned_value - { + struct alignas(64) aligned_value { std::array bytes{}; }; @@ -151,10 +150,11 @@ DOCTEST_TEST_CASE("Handler allocators use reusable storage and aligned heap fall check(reinterpret_cast(aligned) % alignof(aligned_value) == 0, "overaligned heap allocation"); allocator.deallocate(aligned, 2); bool overflow = false; - try - { allocator.allocate(std::numeric_limits::max()); } - catch (const std::bad_array_new_length&) - { overflow = true; } + try { + allocator.allocate(std::numeric_limits::max()); + } catch (const std::bad_array_new_length&) { + overflow = true; + } check(overflow, "allocator rejects multiplication overflow"); } @@ -174,8 +174,7 @@ DOCTEST_TEST_CASE("Listeners can replace and clear themselves during notificatio listener.notify(event_type::start); check(value == 11, "listener replacement and removal remain valid"); - struct receiver - { + struct receiver { int value = 0; void receive(int increment) { @@ -200,14 +199,12 @@ DOCTEST_TEST_CASE("Listeners can replace and clear themselves during notificatio struct queue_fixture : arknet::detail::event_queue_cp , arknet::detail::post_cp - , std::enable_shared_from_this -{ + , std::enable_shared_from_this { explicit queue_fixture(std::shared_ptr context) : io_(std::make_shared(std::move(context))) {} ~queue_fixture() { - if (destroyed) - { + if (destroyed) { *destroyed = true; } } @@ -234,8 +231,7 @@ DOCTEST_TEST_CASE("Event queues drain long synchronous completion chains without queue_fixture queue(context); std::size_t completed = 0; queue.post_event([&](arknet::detail::event_queue_guard) { - for (std::size_t i = 0; i < 50000; ++i) - { + for (std::size_t i = 0; i < 50000; ++i) { queue.push_event([&](arknet::detail::event_queue_guard) { ++completed; }); } }); @@ -261,8 +257,7 @@ DOCTEST_TEST_CASE("Event queues retain their owner through synchronous final-own DOCTEST_CHECK(destroyed); } -struct managed_session_fixture -{ +struct managed_session_fixture { using args_type = void; using key_type = int; @@ -297,8 +292,7 @@ DOCTEST_TEST_CASE("Session registries keep the active session when duplicate can check(active_erased && sessions.empty(), "active session can still be removed"); } -struct condition_fixture : arknet::condition_event -{ +struct condition_fixture : arknet::condition_event { using condition_event::async_wait; using condition_event::condition_event; }; diff --git a/tests/external_stop.cpp b/tests/external_stop.cpp index 900e60e..ea3aeb1 100644 --- a/tests/external_stop.cpp +++ b/tests/external_stop.cpp @@ -10,8 +10,7 @@ namespace { -struct io_runner -{ +struct io_runner { asio::io_context context; asio::executor_work_guard work{context.get_executor()}; std::thread worker{[this] { context.run(); }}; @@ -25,8 +24,7 @@ struct io_runner } }; -struct retirement -{ +struct retirement { std::atomic created{}, destroyed{}, canceled_accepts{}, callbacks{}; std::atomic destructor_on_lane{}; std::promise entered; @@ -38,8 +36,7 @@ struct retirement std::shared_future released{release.get_future().share()}; }; -struct observed_session : arknet::tcp_session_t -{ +struct observed_session : arknet::tcp_session_t { using base = arknet::tcp_session_t; using base::base; std::shared_ptr observed; @@ -50,14 +47,11 @@ struct observed_session : arknet::tcp_session_t std::function destructor_check; ~observed_session() { - if (observed) - { - if (pause_in_destructor) - { + if (observed) { + if (pause_in_destructor) { observed->destructor_on_lane = running_in_this_thread(); observed->entered.set_value(); - if (destructor_check) - { + if (destructor_check) { destructor_check(); } observed->released.wait(); @@ -73,8 +67,7 @@ struct observed_session : arknet::tcp_session_t void _wait_async_operations() { base::_wait_async_operations(); - if (pause_waiter) - { + if (pause_waiter) { observed->pin_acquired.set_value(); observed->pin_released.wait(); } @@ -83,8 +76,7 @@ struct observed_session : arknet::tcp_session_t template void _complete_connect(arknet::error_code error, std::shared_ptr owner, std::shared_ptr> policy, Chain chain) { - if (stop_before_complete) - { + if (stop_before_complete) { stop(); observed->entered.set_value(); } @@ -94,12 +86,10 @@ struct observed_session : arknet::tcp_session_t template void _handle_stop(const arknet::error_code& error, std::shared_ptr owner, Chain chain) { base::_handle_stop(error, std::move(owner), std::move(chain)); - if (pause_in_destructor) - { + if (pause_in_destructor) { observed->stopped.set_value(); } - if (pause_after_stop) - { + if (pause_after_stop) { // The session counter has already retired on this lane. Its queued // continuation must still keep the parent listener alive. post([state = observed] { @@ -111,8 +101,7 @@ struct observed_session : arknet::tcp_session_t } }; -struct observed_server : arknet::tcp_server_impl_t -{ +struct observed_server : arknet::tcp_server_impl_t { using base = arknet::tcp_server_impl_t; using base::base; std::shared_ptr observed; @@ -127,23 +116,20 @@ struct observed_server : arknet::tcp_server_impl_t void _handle_accept(const arknet::error_code& error, std::shared_ptr session, std::shared_ptr owner, std::shared_ptr> policy) { - if (error == asio::error::operation_aborted) - { + if (error == asio::error::operation_aborted) { ++observed->canceled_accepts; } base::_handle_accept(error, std::move(session), std::move(owner), std::move(policy)); } }; -struct allocator_probe -{ +struct allocator_probe { bool fail = false; unsigned allocations = 0; }; template -struct tagged_allocator -{ +struct tagged_allocator { using value_type = T; allocator_probe* state; @@ -156,8 +142,7 @@ struct tagged_allocator T* allocate(std::size_t count) { ++state->allocations; - if (state->fail) - { + if (state->fail) { throw std::bad_alloc(); } return std::allocator{}.allocate(count); @@ -180,8 +165,7 @@ DOCTEST_TEST_CASE("External TCP owner stop retires canceled native accepts befor auto observed = std::make_shared(); observed_server server(runner.context); server.observed = observed; - for (unsigned generation = 1; generation <= 32; ++generation) - { + for (unsigned generation = 1; generation <= 32; ++generation) { check(server.start("127.0.0.1", 0), "listener starts with a pending native accept"); auto armed = server.post([] {}, asio::use_future); await(armed); @@ -241,8 +225,7 @@ DOCTEST_TEST_CASE("External TCP stop retires user timer, delayed post and condit auto captured = std::make_shared(7); std::weak_ptr released = captured; std::vector> posts; - for (unsigned index = 0; index < 32; ++index) - { + for (unsigned index = 0; index < 32; ++index) { server.start_timer(index, 1h, [captured] {}); client.start_timer(index, 1h, [captured] {}); posts.push_back(server.post([captured] {}, 1h, asio::use_future)); @@ -259,8 +242,7 @@ DOCTEST_TEST_CASE("External TCP stop retires user timer, delayed post and condit server.stop(); DOCTEST_CHECK(released.expired()); DOCTEST_CHECK(conditions == 64); - for (auto& post : posts) - { + for (auto& post : posts) { DOCTEST_CHECK(post.wait_for(0s) == std::future_status::ready); } } @@ -438,8 +420,7 @@ DOCTEST_TEST_CASE("Concurrent external TCP owner stops each wait for the final s DOCTEST_CHECK(observed->destructor_on_lane); auto first = std::async(std::launch::async, [&] { server.stop(); }); const auto deadline = std::chrono::steady_clock::now() + 5s; - while (server.iopool().started() && std::chrono::steady_clock::now() < deadline) - { + while (server.iopool().started() && std::chrono::steady_clock::now() < deadline) { std::this_thread::yield(); } const bool pool_closing = !server.iopool().started(); @@ -542,8 +523,7 @@ DOCTEST_TEST_CASE("Ordinary TCP reconnect keeps user timers available") { arknet::tcp_client client(runner.context); client.set_auto_reconnect(true, 1ms); client.bind_connect([&] { - if (!arknet::get_last_error() && ++connects == 2) - { + if (!arknet::get_last_error() && ++connects == 2) { reconnected.set_value(); } }); @@ -586,8 +566,7 @@ DOCTEST_TEST_CASE("Native retirement wrapper preserves explicit associations and } DOCTEST_TEST_CASE("Native retirement token outlives captures and rolls back failed initiation") { - struct capture_probe - { + struct capture_probe { std::shared_ptr pending; std::atomic_size_t& observed; @@ -617,13 +596,12 @@ DOCTEST_TEST_CASE("Native retirement token outlives captures and rolls back fail timer.expires_after(1h); allocator_probe failure{true}; bool threw = false; - try - { + try { timer.async_wait(asio::bind_allocator( tagged_allocator(failure), arknet::detail::pending_handler(pending, [](arknet::error_code) {}))); + } catch (const std::bad_alloc&) { + threw = true; } - catch (const std::bad_alloc&) - { threw = true; } DOCTEST_CHECK(threw); DOCTEST_CHECK(failure.allocations == 1); DOCTEST_CHECK(pending->load() == 0); @@ -632,16 +610,14 @@ DOCTEST_TEST_CASE("Native retirement token outlives captures and rolls back fail #if defined(ARKNET_ENABLE_SSL) namespace { -struct silent_tls_session : arknet::tcps_session_t -{ +struct silent_tls_session : arknet::tcps_session_t { using base = arknet::tcps_session_t; using base::base; std::function handshake_started; std::function starting_canceled; silent_tls_session& set_linger(bool enabled, int seconds) noexcept { - if (starting_canceled) - { + if (starting_canceled) { starting_canceled(running_in_this_thread()); } return base::set_linger(enabled, seconds); diff --git a/tests/fec.cpp b/tests/fec.cpp index 4911e6c..81b9948 100644 --- a/tests/fec.cpp +++ b/tests/fec.cpp @@ -23,16 +23,14 @@ using arknet::fec::transport; std::string message(unsigned id, std::size_t size = 64) { std::string result(size, '\0'); - for (std::size_t i = 0; i < size; ++i) - { + for (std::size_t i = 0; i < size; ++i) { result[i] = static_cast((i * 47 + id * 13) & 255); } return result; } void put(std::string& bytes, std::size_t offset, std::uint64_t value, unsigned count) { - for (unsigned i = 0; i < count; ++i) - { + for (unsigned i = 0; i < count; ++i) { bytes[offset + i] = static_cast(value >> (i * 8)); } } @@ -51,8 +49,7 @@ arknet::fec::options fixed(unsigned k = 4, unsigned p = 2) { return result; } -struct fec_pair -{ +struct fec_pair { std::vector wire, feedback, received; transport sender, receiver; @@ -68,8 +65,7 @@ struct fec_pair std::vector send(unsigned count, std::uint32_t now = 0) { std::vector original; - for (unsigned i = 0; i < count; ++i) - { + for (unsigned i = 0; i < count; ++i) { original.push_back(message(i, 64 + i)); check(sender.send(original.back(), now), "datagram queues"); } @@ -105,8 +101,7 @@ DOCTEST_TEST_CASE("Rejected FEC metadata cannot advance the receive replay windo } DOCTEST_TEST_CASE("FEC input allocation failure releases incomplete recovery state and permits retry") { - for (const unsigned index : {1U, 2U}) - { + for (const unsigned index : {1U, 2U}) { DOCTEST_CAPTURE(index); const auto settings = fixed(2, 1); arknet::arq::options arq_settings; @@ -125,8 +120,7 @@ DOCTEST_TEST_CASE("FEC input allocation failure releases incomplete recovery sta conversation, mtu, settings, [&](auto bytes) { wire.emplace_back(bytes); }, [](auto) { return true; }); arq_sender.set_output([&](auto bytes) { sender.send(bytes, 0); }); const std::vector original{message(0), message(1)}; - for (const auto& bytes : original) - { + for (const auto& bytes : original) { check(arq_sender.send(bytes) == 0, "complete message queues in ARQ"); } arq_sender.update(0); @@ -136,10 +130,11 @@ DOCTEST_TEST_CASE("FEC input allocation failure releases incomplete recovery sta bool threw = false; { scoped_allocation_failure::scope failure; - try - { receiver.input(wire[index], 0); } - catch (const std::bad_alloc&) - { threw = true; } + try { + receiver.input(wire[index], 0); + } catch (const std::bad_alloc&) { + threw = true; + } } check(threw && scoped_allocation_failure::injections == before + 1, "systematic delivery staging or parity storage allocation fails once"); @@ -151,8 +146,7 @@ DOCTEST_TEST_CASE("FEC input allocation failure releases incomplete recovery sta check(receiver.input(wire[index == 1 ? 0 : 1], 1), "an intact original completes the replacement group"); std::vector received; std::string bytes; - while (arq_receiver.receive(bytes)) - { + while (arq_receiver.receive(bytes)) { received.push_back(bytes); } check(forwarded == 3, "the discarded group permits a previously forwarded original to reach ARQ again"); @@ -170,8 +164,7 @@ DOCTEST_TEST_CASE("FEC staging cleanup preserves delivery callback exception sem transport receiver( conversation, mtu, fixed(2, 1), [](auto) {}, [&](auto bytes) { - if (throw_delivery) - { + if (throw_delivery) { throw std::bad_alloc(); } received.emplace_back(bytes); @@ -192,8 +185,7 @@ DOCTEST_TEST_CASE("Adaptive FEC compares segment and datagram loss ratios indepe auto settings = arknet::fec::options::adaptive(); transport sender(conversation, mtu, settings, [](auto) {}, [](auto) { return true; }); sender.adapt(0, 0, 0, 10, 0, 4096); - for (unsigned i = 0; i < 100; ++i) - { + for (unsigned i = 0; i < 100; ++i) { check(sender.send(message(i), 0), "systematic data sends without parity"); } check(sender.input(feedback(1, 100, 10, 0), 1000), "valid peer datagram loss feedback accepted"); @@ -216,14 +208,12 @@ DOCTEST_TEST_CASE("FEC supports maximum shard counts and wire MTU with mixed pay received.emplace_back(value); return true; }); - for (unsigned i = 0; i < 32; ++i) - { + for (unsigned i = 0; i < 32; ++i) { original.push_back(message(i, wire_mtu - transport::overhead - i * 19)); check(sender.send(original.back(), 0), "maximum sized datagram queues"); } check(wire.size() == 48, "maximum requested parity emitted within byte budget"); - for (std::size_t i = 16; i < wire.size(); ++i) - { + for (std::size_t i = 16; i < wire.size(); ++i) { check(wire[i].size() <= wire_mtu, "all headers and parity fit maximum wire MTU"); check(receiver.input(wire[i], 0), "any 32 surviving shards reconstruct the group"); check(receiver.snapshot().retained_bytes <= settings.max_buffer_bytes, "retained allocation stays bounded"); @@ -294,16 +284,14 @@ DOCTEST_TEST_CASE("FEC immediately delivers systematic datagrams and suppresses fec_pair connection; const auto original = connection.send(4); check(connection.wire.size() == 6, "four systematic and two parity shards"); - for (std::size_t i = 0; i < original.size(); ++i) - { + for (std::size_t i = 0; i < original.size(); ++i) { check(kind(connection.wire[i]) == 0, "systematic frame type"); check(connection.receiver.input(connection.wire[i], 0), "systematic shard accepted"); check(connection.received.size() == i + 1 && connection.received.back() == original[i], "no decode wait for intact data"); check(!connection.receiver.input(connection.wire[i], 0), "same shard rejected as duplicate"); } - for (std::size_t i = 4; i < connection.wire.size(); ++i) - { + for (std::size_t i = 4; i < connection.wire.size(); ++i) { connection.receiver.input(connection.wire[i], 0); } check(connection.received == original, "parity never repeats delivered datagrams"); @@ -315,20 +303,16 @@ DOCTEST_TEST_CASE("FEC immediately delivers systematic datagrams and suppresses } DOCTEST_TEST_CASE("FEC repairs every pair of lost shards despite reversal and duplication") { - for (unsigned survivors = 0; survivors < 64; ++survivors) - { - if (std::popcount(survivors) != 4) - { + for (unsigned survivors = 0; survivors < 64; ++survivors) { + if (std::popcount(survivors) != 4) { continue; } DOCTEST_CAPTURE(survivors); fec_pair connection; auto original = connection.send(4); unsigned lost_data = 0; - for (int index = 5; index >= 0; --index) - { - if (!(survivors & (1U << index))) - { + for (int index = 5; index >= 0; --index) { + if (!(survivors & (1U << index))) { lost_data += index < 4; continue; } @@ -401,16 +385,13 @@ DOCTEST_TEST_CASE("FEC bounds group wait and bypasses control datagrams") { "only feedback is added before group deadline"); connection.sender.update(2); std::vector payload_frames; - for (const auto& bytes : connection.wire) - { - if (kind(bytes) == 0 || kind(bytes) == 1) - { + for (const auto& bytes : connection.wire) { + if (kind(bytes) == 0 || kind(bytes) == 1) { payload_frames.push_back(bytes); } } check(payload_frames.size() == 3, "deadline flushes two data plus one budget-safe parity frame"); - for (const auto& bytes : payload_frames) - { + for (const auto& bytes : payload_frames) { connection.receiver.input(bytes, 2); } check(connection.received.size() == 3 && connection.received[1] == original[0] && @@ -424,8 +405,7 @@ DOCTEST_TEST_CASE("FEC emits independently valid frames when redundancy budget d fec_pair connection(settings); const auto original = connection.send(4); check(connection.wire.size() == 4, "zero budget emits only data"); - for (const auto& bytes : connection.wire) - { + for (const auto& bytes : connection.wire) { check(connection.receiver.input(bytes, 0), "each data-only frame is valid"); } check(connection.received == original, "budget reduction never loses accepted data"); @@ -452,10 +432,8 @@ DOCTEST_TEST_CASE("FEC handles deadline and group-id wrap without duplicate deli check(connection.sender.snapshot().data_packets_tx == 0, "one millisecond remains before wrap"); connection.sender.update(0); check(connection.sender.snapshot().data_packets_tx == 2, "wrapped deadline flushes on time"); - for (const auto& value : connection.wire) - { - if (kind(value) == 0 || kind(value) == 1) - { + for (const auto& value : connection.wire) { + if (kind(value) == 0 || kind(value) == 1) { connection.receiver.input(value, start); } } @@ -465,8 +443,7 @@ DOCTEST_TEST_CASE("FEC handles deadline and group-id wrap without duplicate deli fec_pair ungrouped(arknet::fec::options{}); check(ungrouped.sender.send(message(7), 0), "ungrouped datagram queued"); const auto frame = ungrouped.wire[0]; - for (std::uint32_t id : {0xfffffffeU, 0xffffffffU, 0U, 1U}) - { + for (std::uint32_t id : {0xfffffffeU, 0xffffffffU, 0U, 1U}) { auto wrapped = frame; put(wrapped, 16, id, 4); check(ungrouped.receiver.input(wrapped, 0), "group sequence crossing zero is accepted"); @@ -486,8 +463,7 @@ DOCTEST_TEST_CASE("FEC applies receive group and aggregate byte limits") { check(connection.receiver.snapshot().retained_groups <= 1, "receive group count never exceeds configured bound"); check(connection.receiver.snapshot().retained_bytes <= settings.max_buffer_bytes, "receive bytes bounded"); check(connection.receiver.snapshot().evicted_groups >= 1, "admission loss is observable"); - for (std::size_t index : {std::size_t(7), std::size_t(8), std::size_t(9)}) - { + for (std::size_t index : {std::size_t(7), std::size_t(8), std::size_t(9)}) { check(connection.receiver.input(connection.wire[index], 1), "group saturation still forwards every intact original"); } @@ -495,8 +471,7 @@ DOCTEST_TEST_CASE("FEC applies receive group and aggregate byte limits") { auto aggregate = fixed(32, 2); aggregate.max_buffer_bytes = 4096; fec_pair bounded(aggregate); - for (unsigned i = 0; i < 31; ++i) - { + for (unsigned i = 0; i < 31; ++i) { bounded.sender.send(message(i, 900), i); check(bounded.sender.snapshot().retained_bytes <= aggregate.max_buffer_bytes, "pending transmit data is also bounded"); @@ -514,8 +489,7 @@ DOCTEST_TEST_CASE("FEC applies receive group and aggregate byte limits") { DOCTEST_TEST_CASE("FEC feedback is valid before and after recovery groups expire") { fec_pair connection; connection.send(4); - for (std::size_t index : {std::size_t(2), std::size_t(3), std::size_t(4), std::size_t(5)}) - { + for (std::size_t index : {std::size_t(2), std::size_t(3), std::size_t(4), std::size_t(5)}) { check(connection.receiver.input(connection.wire[index], 0), "loss pattern accepted"); } connection.receiver.update(0); @@ -593,8 +567,7 @@ DOCTEST_TEST_CASE("FEC tolerates stop called synchronously from output or delive emitting->stop(); }, [](auto) { return true; }); - for (unsigned i = 0; i < 4; ++i) - { + for (unsigned i = 0; i < 4; ++i) { emitting->send(message(i), 0); } check(output_count == 1, "stop aborts remaining encode callbacks"); @@ -616,8 +589,7 @@ DOCTEST_TEST_CASE("FEC tolerates stop called synchronously from output or delive DOCTEST_CHECK_NOTHROW(receiving->input(source.wire[0], 0)); check(deliveries == 1 && receiving->snapshot().retained_bytes == 0, "systematic delivery stop leaves no live group reference"); - for (const auto& bytes : source.wire) - { + for (const auto& bytes : source.wire) { check(!receiving->input(bytes, 1), "stopped transport rejects future packets"); } bool stop_on_recovery = false; @@ -626,8 +598,7 @@ DOCTEST_TEST_CASE("FEC tolerates stop called synchronously from output or delive conversation, mtu, settings, [](auto) {}, [&](auto) { ++deliveries; - if (stop_on_recovery) - { + if (stop_on_recovery) { receiving->stop(); } return true; @@ -646,14 +617,12 @@ DOCTEST_TEST_CASE("FEC validates malformed frames before allocation and survives source.send(4); fec_pair target; const auto valid = source.wire.front(); - for (std::size_t length = 0; length < valid.size(); ++length) - { + for (std::size_t length = 0; length < valid.size(); ++length) { check(!target.receiver.input(std::string_view(valid).substr(0, length), 0), "every truncated systematic frame rejected"); } for (const auto& [offset, value] : std::vector>{ - {0, 'X'}, {4, 2}, {5, 4}, {6, 0}, {6, 33}, {7, 17}, {8, 6}, {9, 1}, {12, 0}, {20, 0}, {22, 0}}) - { + {0, 'X'}, {4, 2}, {5, 4}, {6, 0}, {6, 33}, {7, 17}, {8, 6}, {9, 1}, {12, 0}, {20, 0}, {22, 0}}) { auto malformed = valid; malformed[offset] = static_cast(value); check(!target.receiver.input(malformed, 0), "invalid header, length, or CRC rejected"); @@ -665,19 +634,16 @@ DOCTEST_TEST_CASE("FEC validates malformed frames before allocation and survives random = random * 1664525U + 1013904223U; return random; }; - for (unsigned iteration = 0; iteration < 4000; ++iteration) - { + for (unsigned iteration = 0; iteration < 4000; ++iteration) { std::string bytes(next() % 1400, '\0'); - for (auto& byte : bytes) - { + for (auto& byte : bytes) { byte = static_cast(next() >> 24); } DOCTEST_CHECK_NOTHROW(target.receiver.input(bytes, iteration)); check(target.receiver.snapshot().retained_bytes <= fixed().max_buffer_bytes, "arbitrary bytes cannot exceed memory bound"); } - for (unsigned iteration = 0; iteration < 1000; ++iteration) - { + for (unsigned iteration = 0; iteration < 1000; ++iteration) { auto bytes = source.wire[next() % source.wire.size()]; bytes[next() % bytes.size()] ^= static_cast(1U << (next() % 8)); DOCTEST_CHECK_NOTHROW(target.receiver.input(bytes, 4000 + iteration)); diff --git a/tests/fec_interop.cpp b/tests/fec_interop.cpp index 1c1f55f..5c9a7f5 100644 --- a/tests/fec_interop.cpp +++ b/tests/fec_interop.cpp @@ -20,8 +20,7 @@ using shards = std::vector>; std::vector data(std::size_t k, std::size_t size) { std::vector value(k * size); - for (std::size_t i = 0; i < value.size(); ++i) - { + for (std::size_t i = 0; i < value.size(); ++i) { value[i] = static_cast((i * 131 + i / 7 + k * 17) & 255); } return value; @@ -41,10 +40,8 @@ shards encode(const Code& code, const std::vector& value) { template shards decode(const Code& code, const shards& encoded, unsigned survivors) { std::map selected; - for (std::size_t id = 0; id < encoded.size(); ++id) - { - if (survivors & (1U << id)) - { + for (std::size_t id = 0; id < encoded.size(); ++id) { + if (survivors & (1U << id)) { selected.emplace(id, encoded[id].data()); } } @@ -59,12 +56,9 @@ shards decode(const Code& code, const shards& encoded, unsigned survivors) { } // namespace DOCTEST_TEST_CASE("Reed-Solomon parity and recovery match the independent pinned fecpp reference") { - for (unsigned k = 1; k <= 6; ++k) - { - for (unsigned p = 0; p <= 3; ++p) - { - for (std::size_t size : {std::size_t(1), std::size_t(31), std::size_t(128)}) - { + for (unsigned k = 1; k <= 6; ++k) { + for (unsigned p = 0; p <= 3; ++p) { + for (std::size_t size : {std::size_t(1), std::size_t(31), std::size_t(128)}) { DOCTEST_CAPTURE(k); DOCTEST_CAPTURE(p); DOCTEST_CAPTURE(size); @@ -75,10 +69,8 @@ DOCTEST_TEST_CASE("Reed-Solomon parity and recovery match the independent pinned const auto upstream = encode(reference, input); check(ours == upstream, "all systematic and parity bytes match upstream"); const shards expected(upstream.begin(), upstream.begin() + k); - for (unsigned survivors = 0; survivors < (1U << (k + p)); ++survivors) - { - if (std::popcount(survivors) < k) - { + for (unsigned survivors = 0; survivors < (1U << (k + p)); ++survivors) { + if (std::popcount(survivors) < k) { continue; } DOCTEST_CAPTURE(survivors); @@ -121,11 +113,9 @@ DOCTEST_TEST_CASE("Reed-Solomon maximum field size remains interoperable") { DOCTEST_TEST_CASE("Reed-Solomon instances can initialize and encode concurrently") { std::atomic valid = true; std::vector workers; - for (unsigned thread = 0; thread < 8; ++thread) - { + for (unsigned thread = 0; thread < 8; ++thread) { workers.emplace_back([&, thread] { - for (unsigned round = 0; round < 32; ++round) - { + for (unsigned round = 0; round < 32; ++round) { arknet::detail::fecpp::fec_code code(4, 6); const auto input = data(4, 16 + thread + round); shards encoded(6); @@ -137,16 +127,15 @@ DOCTEST_TEST_CASE("Reed-Solomon instances can initialize and encode concurrently {1, encoded[1].data()}, {2, encoded[2].data()}, {4, encoded[4].data()}, {5, encoded[5].data()}}; code.decode(selected, input.size() / 4, [&](std::size_t id, std::size_t, const std::uint8_t* bytes, std::size_t count) { - if (!std::equal(bytes, bytes + count, input.begin() + static_cast(id * count))) - { + if (!std::equal( + bytes, bytes + count, input.begin() + static_cast(id * count))) { valid = false; } }); } }); } - for (auto& worker : workers) - { + for (auto& worker : workers) { worker.join(); } check(valid.load(), "concurrent codecs recover exact original bytes"); diff --git a/tests/http.cpp b/tests/http.cpp index 175f170..cff55e7 100644 --- a/tests/http.cpp +++ b/tests/http.cpp @@ -48,8 +48,7 @@ void pipeline_echo(Server& server, Client& client) { server.stop(); } -struct raw_peer -{ +struct raw_peer { asio::io_context io; asio::ip::tcp::socket socket{io}; asio::streambuf buffer; @@ -76,8 +75,7 @@ struct raw_peer // Redirect only resolver service lookup so default ports need no privileged listener. template class Base> -struct redirected_client : Base> -{ +struct redirected_client : Base> { std::string resolve_service; template @@ -108,10 +106,8 @@ void default_port_echo(Server& server, Client& client, std::string host, std::st } // namespace DOCTEST_TEST_CASE("HTTP host-only clients default to port 80 for names and IP literals") { - for (const auto* host : {"localhost", "127.0.0.1", "::1"}) - { - for (bool async : {false, true}) - { + for (const auto* host : {"localhost", "127.0.0.1", "::1"}) { + for (bool async : {false, true}) { arknet::http_server server; redirected_client client; default_port_echo(server, client, host, "80", async); @@ -126,8 +122,7 @@ DOCTEST_TEST_CASE("HTTP owns requests and preserves keep-alive pipeline order") } DOCTEST_TEST_CASE("HTTP works with an external io_context and a custom session") { - struct custom_session : arknet::http_session_t - { + struct custom_session : arknet::http_session_t { using http_session_t::http_session_t; }; @@ -135,8 +130,7 @@ DOCTEST_TEST_CASE("HTTP works with an external io_context and a custom session") auto work = asio::make_work_guard(io); std::thread worker([&] { io.run(); }); - struct joiner - { + struct joiner { asio::io_context& io; std::thread& worker; @@ -173,8 +167,7 @@ DOCTEST_TEST_CASE("HTTP client skips HEAD bodies while keeping the following res arknet::tcp_server server; std::atomic writes{}; server.bind_recv([&](auto& session, std::string_view) { - if (writes.fetch_add(1) == 0) - { + if (writes.fetch_add(1) == 0) { session->async_send( std::string("HTTP/1.1 200 OK\r\nContent-Length: 999\r\n\r\n" "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n3\r\nget\r\n0\r\n\r\n")); @@ -230,8 +223,7 @@ DOCTEST_TEST_CASE("HTTP sends 100 Continue before waiting for a request body") { DOCTEST_TEST_CASE("HTTP rejects missing or duplicate Host and unsupported expectations") { for (const auto& [headers, expected] : std::vector>{ - {"", 400}, {"Host: a\r\nHost: b\r\n", 400}, {"Host: localhost\r\nExpect: unsupported\r\n", 417}}) - { + {"", 400}, {"Host: a\r\nHost: b\r\n", 400}, {"Host: localhost\r\nExpect: unsupported\r\n", 417}}) { arknet::http_server server; std::atomic calls{}; server.bind_recv([&](auto&, request&) { ++calls; }); @@ -254,8 +246,7 @@ DOCTEST_TEST_CASE("HTTP parser rejects ambiguous framing and configured limits") "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "aa" "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n\r" - "\n"}) - { + "\n"}) { arknet::http_server server; server.set_http_body_limit(32).set_http_header_limit(128); std::promise disconnected; @@ -367,8 +358,7 @@ DOCTEST_TEST_CASE("HTTP bounded send rejects large bodies and recovers") { #ifdef ARKNET_ENABLE_SSL DOCTEST_TEST_CASE("HTTPS host-only clients default to port 443 and verify DNS identity") { const std::string certs = ARKNET_TEST_CERT_DIR; - for (bool async : {false, true}) - { + for (bool async : {false, true}) { arknet::https_server server; server.set_cert_file("", certs + "/server.pem", certs + "/server-key.pem", ""); redirected_client client; @@ -388,15 +378,13 @@ DOCTEST_TEST_CASE("HTTPS verifies identity and preserves HTTP pipeline behavior" DOCTEST_TEST_CASE("HTTPS rejects untrusted and mismatched server certificates") { const std::string certs = ARKNET_TEST_CERT_DIR; - for (const auto* certificate : {"server.pem", "wrong.pem"}) - { + for (const auto* certificate : {"server.pem", "wrong.pem"}) { arknet::https_server server; server.set_cert_file("", certs + "/" + certificate, certs + "/server-key.pem", ""); check(server.start("127.0.0.1", 0), "HTTPS rejection server start"); arknet::https_client client; client.set_auto_reconnect(false); - if (std::string_view(certificate) == "wrong.pem") - { + if (std::string_view(certificate) == "wrong.pem") { client.load_verify_file(certs + "/ca.pem"); } check(!client.start("localhost", server.get_listen_port()), "HTTPS certificate rejection"); diff --git a/tests/httprequest.cpp b/tests/httprequest.cpp index 1afde70..882e3b5 100644 --- a/tests/httprequest.cpp +++ b/tests/httprequest.cpp @@ -12,15 +12,13 @@ namespace { using request_type = arknet::http_request::request_type; using response_type = arknet::http_request::response_type; -class io_runner -{ +class io_runner { public: asio::io_context context; explicit io_runner(std::size_t count = 1) : guard_(asio::make_work_guard(context)) { - for (std::size_t i = 0; i < count; ++i) - { + for (std::size_t i = 0; i < count; ++i) { workers_.emplace_back([this] { context.run(); }); } } @@ -28,8 +26,7 @@ class io_runner ~io_runner() { guard_.reset(); context.stop(); - for (auto& worker : workers_) - { + for (auto& worker : workers_) { worker.join(); } } @@ -45,19 +42,16 @@ request_type message(arknet::http::verb method = arknet::http::verb::get, std::s return request; } -class wire_server -{ +class wire_server { public: explicit wire_server(std::string response, bool close = false) { server_.bind_recv([response = std::move(response), close](auto& session, std::string_view) { - if (session->template get_user_data()) - { + if (session->template get_user_data()) { return; } session->set_user_data(true); session->async_send(response, [session, close](const arknet::error_code&, std::size_t) { - if (close) - { + if (close) { session->stop(); } }); @@ -129,8 +123,7 @@ DOCTEST_TEST_CASE("HTTP host-only single requests preserve completion options an auto invalid_options = options_operation.async_request("localhost", message(), options, asio::use_future); check(await(invalid_options).first == asio::error::invalid_argument, "host-only future preserves options"); - for (bool explicit_options : {false, true}) - { + for (bool explicit_options : {false, true}) { arknet::http_request callback_operation(runtime.context); callback_operation.cancel(); std::promise completed; @@ -139,13 +132,10 @@ DOCTEST_TEST_CASE("HTTP host-only single requests preserve completion options an check(*owned == 42, "host-only callback retains move-only captures"); completed.set_value(ec); }; - if (explicit_options) - { + if (explicit_options) { callback_operation.async_request( "localhost", message(), arknet::http_request_options{}, std::move(callback)); - } - else - { + } else { callback_operation.async_request("localhost", message(), std::move(callback)); } check(await(future) == asio::error::operation_aborted, "host-only callback preserves cancellation"); @@ -167,8 +157,7 @@ DOCTEST_TEST_CASE("HTTP single requests parse informational chunked close-delimi DOCTEST_TEST_CASE("HTTP single requests reject malformed framing and enforce response limits") { for (const auto& response : {"HTTP/1.1 200 OK\r\nContent-Length: 1\r\nTransfer-Encoding: chunked\r\n\r\n", "HTTP/1.1 200 OK\r\nContent-Length: 1\r\nContent-Length: 1\r\n\r\nx", - "INVALID 200 OK\r\nContent-Length: 1\r\n\r\nx", "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhi"}) - { + "INVALID 200 OK\r\nContent-Length: 1\r\n\r\nx", "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhi"}) { check(bool(fetch(response, true).first), response); } arknet::http_request_options limits; @@ -195,8 +184,7 @@ DOCTEST_TEST_CASE("HTTP single request IO deadlines finish once and ignore late const auto start = std::chrono::steady_clock::now(); operation.async_request("127.0.0.1", peer.service(), message(), options, [&, owned = std::make_unique(42)](arknet::error_code ec, response_type) { - if (++calls == 1) - { + if (++calls == 1) { completion.set_value(ec); } }); @@ -214,10 +202,8 @@ DOCTEST_TEST_CASE("HTTP single request connect deadline covers DNS and " arknet::http_request_options options; options.connect_timeout = 1ns; options.io_timeout = 5s; - for (const auto* host : {"127.0.0.1", "localhost"}) - { - for (int repeat = 0; repeat < 32; ++repeat) - { + for (const auto* host : {"127.0.0.1", "localhost"}) { + for (int repeat = 0; repeat < 32; ++repeat) { arknet::http_request operation(runtime.context); std::promise completion; auto future = completion.get_future(); @@ -230,8 +216,7 @@ DOCTEST_TEST_CASE("HTTP single request connect deadline covers DNS and " DOCTEST_INFO("host=", host, ", repeat=", repeat, ", error=", ec.message()); check(ec == asio::error::timed_out, "connect deadline applies before TCP connection"); const auto limit = std::chrono::steady_clock::now() + 1s; - while (!released.expired() && std::chrono::steady_clock::now() < limit) - { + while (!released.expired() && std::chrono::steady_clock::now() < limit) { std::this_thread::yield(); } check(released.expired(), "completion releases callback captures while " @@ -242,14 +227,12 @@ DOCTEST_TEST_CASE("HTTP single request connect deadline covers DNS and " DOCTEST_TEST_CASE("HTTP single request deadline wins over queued response " "success and errors") { - for (bool truncated : {false, true}) - { + for (bool truncated : {false, true}) { arknet::tcp_server server; std::promise> connected; auto peer = connected.get_future(); server.bind_recv([&](auto& session, std::string_view) { - if (session->template get_user_data()) - { + if (session->template get_user_data()) { return; } session->set_user_data(true); @@ -266,14 +249,12 @@ DOCTEST_TEST_CASE("HTTP single request deadline wins over queued response " auto result = completion.get_future(); operation.async_request("127.0.0.1", std::to_string(server.get_listen_port()), message(), options, [&](arknet::error_code ec, response_type) { - if (++calls == 1) - { + if (++calls == 1) { completion.set_value(ec); } }); const auto setup_limit = std::chrono::steady_clock::now() + 2s; - while (peer.wait_for(0ms) != std::future_status::ready && std::chrono::steady_clock::now() < setup_limit) - { + while (peer.wait_for(0ms) != std::future_status::ready && std::chrono::steady_clock::now() < setup_limit) { context.poll_one(); std::this_thread::yield(); } @@ -285,8 +266,7 @@ DOCTEST_TEST_CASE("HTTP single request deadline wins over queued response " : "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello", [&, session, truncated](arknet::error_code ec, std::size_t) { check(!ec, "peer response sends while request IO is paused"); - if (truncated) - { + if (truncated) { session->stop(); } sent.set_value(); @@ -324,8 +304,7 @@ DOCTEST_TEST_CASE("HTTP single requests reject a TRACE body without throwing fro auto future = operation.async_request("127.0.0.1", "1", message(arknet::http::verb::trace, "body"), asio::use_future); DOCTEST_CHECK_NOTHROW(context.run()); - if (future.wait_for(0ms) != std::future_status::ready) - { + if (future.wait_for(0ms) != std::future_status::ready) { operation.cancel(); context.run(); } @@ -334,16 +313,14 @@ DOCTEST_TEST_CASE("HTTP single requests reject a TRACE body without throwing fro DOCTEST_TEST_CASE("HTTP single requests validate outgoing headers targets and timeout options") { io_runner runtime; - for (const auto& field : {arknet::http::field::cookie, arknet::http::field::content_type}) - { + for (const auto& field : {arknet::http::field::cookie, arknet::http::field::content_type}) { arknet::http_request operation(runtime.context); auto request = message(); request.set(field, "value\r\nInjected: yes"); auto future = operation.async_request("127.0.0.1", "1", std::move(request), asio::use_future); check(await(future).first == asio::error::invalid_argument, "header injection fails before DNS"); } - for (const auto& host : {"host\r\nInjected: yes", ""}) - { + for (const auto& host : {"host\r\nInjected: yes", ""}) { arknet::http_request operation(runtime.context); auto future = operation.async_request(host, "1", message(), asio::use_future); check(await(future).first == asio::error::invalid_argument, "invalid hosts fail before DNS"); diff --git a/tests/interop/impairment.hpp b/tests/interop/impairment.hpp index 8c58f99..c218d6b 100644 --- a/tests/interop/impairment.hpp +++ b/tests/interop/impairment.hpp @@ -16,29 +16,25 @@ namespace arknet_interop { -class data_impairment -{ +class data_impairment { using udp = asio::ip::udp; using clock = std::chrono::steady_clock; - struct pending_packet - { + struct pending_packet { std::string bytes; udp::endpoint destination; clock::time_point deadline; }; public: - struct statistics - { + struct statistics { std::uint64_t dropped{}, reordered{}, errors{}, fin_forwarded{}; }; explicit data_impairment(unsigned short server_port) : sockets_{udp::socket(context_), udp::socket(context_)} , server_(asio::ip::make_address("127.0.0.1"), server_port) { - for (auto& socket : sockets_) - { + for (auto& socket : sockets_) { socket.open(udp::v4()); socket.set_option(asio::socket_base::receive_buffer_size(1024 * 1024)); socket.bind({asio::ip::make_address("127.0.0.1"), 0}); @@ -70,8 +66,7 @@ class data_impairment void stop() noexcept { stopped_ = true; - if (worker_.joinable()) - { + if (worker_.joinable()) { worker_.join(); } } @@ -80,12 +75,9 @@ class data_impairment void forward(unsigned direction, const std::string& bytes, const udp::endpoint& destination) { arknet::error_code error; const auto written = sockets_[1 - direction].send_to(asio::buffer(bytes), destination, 0, error); - if (error || written != bytes.size()) - { + if (error || written != bytes.size()) { ++errors_; - } - else if (bytes.size() == 9 && bytes[0] == 3) - { + } else if (bytes.size() == 9 && bytes[0] == 3) { std::lock_guard lock(fin_mutex_); ++fin_forwarded_; fin_ready_.notify_all(); @@ -99,52 +91,43 @@ class data_impairment } void receive(unsigned direction) { - while (!stopped_) - { + while (!stopped_) { udp::endpoint sender; arknet::error_code error; const auto size = sockets_[direction].receive_from(asio::buffer(buffer_), sender, 0, error); - if (error == asio::error::would_block || error == asio::error::try_again) - { + if (error == asio::error::would_block || error == asio::error::try_again) { return; } - if (error) - { + if (error) { ++errors_; return; } - if (direction == 0 && !client_) - { + if (direction == 0 && !client_) { client_ = sender; } - if ((direction == 0 && sender != *client_) || (direction == 1 && (sender != server_ || !client_))) - { + if ((direction == 0 && sender != *client_) || (direction == 1 && (sender != server_ || !client_))) { ++errors_; continue; } const auto destination = direction == 0 ? server_ : *client_; std::string bytes(buffer_.data(), size); // SYN/ACK/FIN are 5 or 9 bytes; preserve them because FIN has no retry/ACK. - if (size < 24) - { + if (size < 24) { forward(direction, bytes, destination); continue; } const auto sequence = ++sequences_[direction]; - if (sequence % 9 == 0) - { + if (sequence % 9 == 0) { ++dropped_; continue; } - if (sequence % 7 == 0 && !pending_[direction]) - { + if (sequence % 7 == 0 && !pending_[direction]) { pending_[direction] = pending_packet{std::move(bytes), destination, clock::now() + std::chrono::milliseconds(6)}; continue; } forward(direction, bytes, destination); - if (pending_[direction]) - { + if (pending_[direction]) { ++reordered_; release(direction); } @@ -152,23 +135,19 @@ class data_impairment } void run() noexcept { - try - { - while (!stopped_) - { - for (unsigned direction = 0; direction < 2; ++direction) - { + try { + while (!stopped_) { + for (unsigned direction = 0; direction < 2; ++direction) { receive(direction); - if (pending_[direction] && pending_[direction]->deadline <= clock::now()) - { + if (pending_[direction] && pending_[direction]->deadline <= clock::now()) { release(direction); } } std::this_thread::sleep_for(std::chrono::milliseconds(1)); } + } catch (...) { + ++errors_; } - catch (...) - { ++errors_; } } asio::io_context context_; diff --git a/tests/interop/rudp_peer.cpp b/tests/interop/rudp_peer.cpp index 21365c2..9973c4e 100644 --- a/tests/interop/rudp_peer.cpp +++ b/tests/interop/rudp_peer.cpp @@ -40,8 +40,7 @@ using server_type = arknet::rudp_server; constexpr const char* provider = "arknet"; #endif constexpr std::string_view close_request = "arknet-interop-close-server"; -enum class scenario -{ +enum class scenario { baseline, reconnect, impaired @@ -52,8 +51,7 @@ const char* scenario_name(scenario value) { } void require(bool condition, const std::string& reason) { - if (!condition) - { + if (!condition) { throw std::runtime_error(reason); } } @@ -66,22 +64,19 @@ void append_native(std::string& bytes, Integer value) { std::vector application_messages() { constexpr std::array body_sizes{0, 1, 23, 1024, 4096, 4096, 511, 4096}; std::vector result; - for (std::uint16_t sequence = 0; sequence < 32; ++sequence) - { + for (std::uint16_t sequence = 0; sequence < 32; ++sequence) { const auto body_size = body_sizes[sequence % body_sizes.size()]; std::string bytes; // ARK's packed CS header is 7 bytes; SS adds actor/source/destination (16 bytes). append_native(bytes, static_cast(0x4000 + sequence)); append_native(bytes, body_size); append_native(bytes, std::uint8_t{0}); - if (sequence % 2) - { + if (sequence % 2) { append_native(bytes, std::int64_t{0x0102030405060708}); append_native(bytes, std::int32_t{0x12345678}); append_native(bytes, std::int32_t{0x76543210}); } - for (std::uint32_t i = 0; i < body_size; ++i) - { + for (std::uint32_t i = 0; i < body_size; ++i) { bytes.push_back(static_cast((i + sequence * 17) % 256)); } result.push_back(std::move(bytes)); @@ -89,8 +84,7 @@ std::vector application_messages() { return result; } -struct peer_state -{ +struct peer_state { explicit peer_state(const std::vector& messages) : expected(messages) {} @@ -106,12 +100,9 @@ struct peer_state bool accept(std::string_view bytes) { std::lock_guard lock(mutex); - if (received >= expected.size() || bytes != expected[received]) - { + if (received >= expected.size() || bytes != expected[received]) { failure = "message content/order mismatch at index " + std::to_string(received); - } - else - { + } else { ++received; ++total_received; } @@ -127,10 +118,8 @@ struct peer_state void disconnect(network::error_code error, bool reset_received = false) { std::lock_guard lock(mutex); - if (reset_received) - { - if (received != expected.size()) - { + if (reset_received) { + if (received != expected.size()) { failure = "server disconnected before receiving every message exactly once"; } received = 0; @@ -177,8 +166,7 @@ void configure_arq(Endpoint& endpoint) { } void verify_impairment(std::unique_ptr& relay) { - if (!relay) - { + if (!relay) { return; } require(relay->wait_fin(5s), "relay did not forward FIN before shutdown"); @@ -206,29 +194,24 @@ void run_client(bool server_closes, scenario test) { client.set_auto_reconnect(false).set_connect_timeout(5s); client.bind_disconnect([&] { state.disconnect(network::get_last_error()); }); std::unique_ptr relay; - if (test == scenario::impaired) - { + if (test == scenario::impaired) { relay = std::make_unique(static_cast(port)); port = relay->port(); } #endif client.bind_init([&] { client.socket().set_option(asio::socket_base::receive_buffer_size(1024 * 1024)); }); client.bind_recv([&](std::string_view bytes) { state.accept(bytes); }); - for (unsigned cycle = 0; cycle < cycles; ++cycle) - { + for (unsigned cycle = 0; cycle < cycles; ++cycle) { const auto started = start(client, static_cast(port)); require(started, std::string("client handshake failed: ") + network::get_last_error().message()); - for (std::size_t offset = 0; offset < expected.size(); offset += 4) - { + for (std::size_t offset = 0; offset < expected.size(); offset += 4) { const auto end = std::min(offset + 4, expected.size()); - for (auto i = offset; i < end; ++i) - { + for (auto i = offset; i < end; ++i) { require(send_async(client, expected[i]), "client rejected an application message"); } state.wait([&] { return state.received >= end; }, "echo deadline exceeded", 10s); } - if (server_closes) - { + if (server_closes) { require(send_async(client, close_request), "client rejected the server-close request"); state.wait([&] { return state.disconnects >= cycle + 1; }, "server FIN deadline exceeded"); } @@ -237,14 +220,12 @@ void run_client(bool server_closes, scenario test) { std::lock_guard lock(state.mutex); require(state.received == expected.size(), "client did not receive every echo exactly once"); require(state.disconnects == cycle + 1, "client must disconnect exactly once per session"); - if (server_closes) - { + if (server_closes) { require(state.disconnect_errors.back() == asio::error::eof, "client did not observe the server FIN: " + state.disconnect_errors.back().message()); } require(client.is_stopped(), "client did not finish shutdown"); - if (cycle + 1 < cycles) - { + if (cycle + 1 < cycles) { state.received = 0; } } @@ -273,29 +254,22 @@ void run_server(bool server_closes, scenario test) { ++connections; std::lock_guard lock(state.mutex); state.close_session = [weak = std::weak_ptr{session}] { - if (auto current = weak.lock()) - { + if (auto current = weak.lock()) { current->stop(); } }; }); server.bind_disconnect([&](auto&) { state.disconnect(network::get_last_error(), true); }); server.bind_recv([&](auto& session, std::string_view bytes) { - if (bytes == close_request) - { + if (bytes == close_request) { std::lock_guard lock(state.mutex); - if (!server_closes || state.received != expected.size() || state.server_close_requested) - { + if (!server_closes || state.received != expected.size() || state.server_close_requested) { state.failure = "unexpected server-close request"; - } - else - { + } else { state.server_close_requested = true; } state.ready.notify_all(); - } - else if (state.accept(bytes) && !send_async(*session, bytes)) - { + } else if (state.accept(bytes) && !send_async(*session, bytes)) { state.fail("server rejected an echo"); } }); @@ -304,28 +278,24 @@ void run_server(bool server_closes, scenario test) { const auto port = server.acceptor().local_endpoint().port(); #if !defined(ARKNET_INTEROP_LEGACY) std::unique_ptr relay; - if (test == scenario::impaired) - { + if (test == scenario::impaired) { relay = std::make_unique(port); } std::cout << (relay ? relay->port() : port) << std::endl; #else std::cout << port << std::endl; #endif - for (unsigned cycle = 0; cycle < cycles; ++cycle) - { + for (unsigned cycle = 0; cycle < cycles; ++cycle) { state.wait([&] { return state.disconnects >= cycle + 1 || state.server_close_requested; }, "server deadline exceeded", 30s); std::function close; { std::lock_guard lock(state.mutex); - if (state.server_close_requested) - { + if (state.server_close_requested) { close = state.close_session; } } - if (close) - { + if (close) { close(); } state.wait([&] { return state.disconnects >= cycle + 1; }, "server session shutdown deadline exceeded"); @@ -336,10 +306,8 @@ void run_server(bool server_closes, scenario test) { require(state.received == 0 && state.total_received == expected.size() * cycles, "server did not receive every message exactly once in each session"); require(state.disconnects == cycles, "server must disconnect exactly once per session"); - if (!server_closes) - { - for (const auto& error : state.disconnect_errors) - { + if (!server_closes) { + for (const auto& error : state.disconnect_errors) { require(error == asio::error::eof, "server did not observe the client FIN"); } } @@ -359,8 +327,7 @@ void run_server(bool server_closes, scenario test) { } // namespace int main(int argc, char** argv) { - try - { + try { require(argc == 3 || argc == 4, "usage: rudp_peer [baseline|reconnect|impaired]"); const std::string_view role(argv[1]), closer(argv[2]); @@ -370,19 +337,14 @@ int main(int argc, char** argv) { const auto test = name == "baseline" ? scenario::baseline : name == "reconnect" ? scenario::reconnect : scenario::impaired; - if (role == "client") - { + if (role == "client") { run_client(closer == "server", test); - } - else - { + } else { require(role == "server", "role must be client or server"); run_server(closer == "server", test); } return 0; - } - catch (const std::exception& error) - { + } catch (const std::exception& error) { std::cerr << provider << " interoperability failure: " << error.what() << '\n'; return 1; } diff --git a/tests/io_pool.cpp b/tests/io_pool.cpp index d15c941..803b1fb 100644 --- a/tests/io_pool.cpp +++ b/tests/io_pool.cpp @@ -7,18 +7,15 @@ #include "check.hpp" DOCTEST_TEST_CASE("Queued IO cancellation does not retain an unrun or stopped context") { - for (const bool serialized : {false, true}) - { - for (const bool stopped : {false, true}) - { + for (const bool serialized : {false, true}) { + for (const bool stopped : {false, true}) { DOCTEST_INFO("serialized=", serialized, ", stopped=", stopped); std::weak_ptr released; { auto context = std::make_shared(); released = context; arknet::io_t lane(context, serialized); - if (stopped) - { + if (stopped) { context->stop(); } lane.cancel(); @@ -32,13 +29,11 @@ DOCTEST_TEST_CASE("External IO lanes serialize handlers across multiple context auto context = std::make_shared(); arknet::io_t lane(context); std::atomic active{}, maximum{}, completed{}; - for (unsigned index = 0; index < 100; ++index) - { + for (unsigned index = 0; index < 100; ++index) { asio::post(lane.executor(), [&] { const auto concurrent = active.fetch_add(1) + 1; auto previous = maximum.load(); - while (previous < concurrent && !maximum.compare_exchange_weak(previous, concurrent)) - { + while (previous < concurrent && !maximum.compare_exchange_weak(previous, concurrent)) { } std::this_thread::sleep_for(100us); --active; @@ -46,12 +41,10 @@ DOCTEST_TEST_CASE("External IO lanes serialize handlers across multiple context }); } std::vector runners; - for (unsigned index = 0; index < 4; ++index) - { + for (unsigned index = 0; index < 4; ++index) { runners.emplace_back([&] { context->run(); }); } - for (auto& runner : runners) - { + for (auto& runner : runners) { runner.join(); } check(completed == 100 && maximum == 1, "one external lane never overlaps its handlers"); @@ -64,10 +57,11 @@ DOCTEST_TEST_CASE("IO pool returns task results and propagates exceptions withou check(await(result) == 7, "indexed post selects the requested worker"); auto failed = pool.post([]() -> int { throw std::runtime_error("task failure"); }); bool propagated = false; - try - { await(failed); } - catch (const std::runtime_error& error) - { propagated = std::string_view(error.what()) == "task failure"; } + try { + await(failed); + } catch (const std::runtime_error& error) { + propagated = std::string_view(error.what()) == "task failure"; + } check(propagated, "future preserves the exception"); auto continued = pool.post(1, [] { return 11; }); check(await(continued) == 11, "worker remains available after a failed task"); @@ -95,8 +89,7 @@ DOCTEST_TEST_CASE("IO pool tolerates concurrent owner stops and rejects submissi check(pool.start(), "pool starts"); std::atomic completed{}; std::vector> tasks; - for (unsigned index = 0; index < 100; ++index) - { + for (unsigned index = 0; index < 100; ++index) { tasks.push_back(pool.post([&] { ++completed; })); } std::latch start(1); @@ -111,17 +104,17 @@ DOCTEST_TEST_CASE("IO pool tolerates concurrent owner stops and rejects submissi start.count_down(); first.join(); second.join(); - for (auto& task : tasks) - { + for (auto& task : tasks) { await(task); } check(completed == 100 && pool.stopped(), "all admitted work completes exactly once"); auto rejected = pool.post([] { return 1; }); bool aborted = false; - try - { await(rejected); } - catch (const arknet::system_error& error) - { aborted = error.code() == asio::error::operation_aborted; } + try { + await(rejected); + } catch (const arknet::system_error& error) { + aborted = error.code() == asio::error::operation_aborted; + } check(aborted, "submission to a stopped pool completes with an error"); } diff --git a/tests/ipv6.cpp b/tests/ipv6.cpp index 73a1298..29992ae 100644 --- a/tests/ipv6.cpp +++ b/tests/ipv6.cpp @@ -34,16 +34,14 @@ void stream_echo() { Client client; client.set_auto_reconnect(false); #if defined(ARKNET_ENABLE_SSL) || defined(ARKNET_USE_SSL) - if constexpr (requires { client.ssl_stream(); }) - { + if constexpr (requires { client.ssl_stream(); }) { configure_identity(server, client); } #endif server.bind_recv([&](auto& session, std::string_view data) { peer_addresses.push(session->get_remote_address()); #if defined(ARKNET_ENABLE_SSL) || defined(ARKNET_USE_SSL) - if constexpr (requires { session->ssl_stream(); }) - { + if constexpr (requires { session->ssl_stream(); }) { const char* name = SSL_get_servername(session->ssl_stream().native_handle(), TLSEXT_NAMETYPE_host_name); server_names.push(name ? name : ""); } @@ -63,8 +61,7 @@ void stream_echo() { check(received.wait(1).front() == payload, "IPv6 stream preserves binary data"); check(peer_addresses.wait(1).front() == "::1", "IPv6 stream session preserves peer address"); #if defined(ARKNET_ENABLE_SSL) || defined(ARKNET_USE_SSL) - if constexpr (requires { client.ssl_stream(); }) - { + if constexpr (requires { client.ssl_stream(); }) { verified_ip(client); check(server_names.wait(1).front().empty(), "literal IPv6 address does not send DNS SNI"); } @@ -83,14 +80,12 @@ void websocket_echo(const char* host) { Client client; client.set_auto_reconnect(false); #if defined(ARKNET_ENABLE_SSL) || defined(ARKNET_USE_SSL) - if constexpr (requires { client.ssl_stream(); }) - { + if constexpr (requires { client.ssl_stream(); }) { configure_identity(server, client); } #endif server.bind_upgrade([&](auto& session) { - if (!arknet::get_last_error()) - { + if (!arknet::get_last_error()) { const auto& request = session->get_upgrade_request(); hosts.push(request[arknet::http::field::host]); targets.push(request.target()); @@ -118,8 +113,7 @@ void websocket_echo(const char* host) { check(client.socket().remote_endpoint().address().is_v6() == (std::string_view(host) == "::1"), "WebSocket connects using the requested address family"); #if defined(ARKNET_ENABLE_SSL) || defined(ARKNET_USE_SSL) - if constexpr (requires { client.ssl_stream(); }) - { + if constexpr (requires { client.ssl_stream(); }) { verified_ip(client); } #endif @@ -140,8 +134,7 @@ void http_echo() { Client client; client.set_auto_reconnect(false); #if defined(ARKNET_ENABLE_SSL) || defined(ARKNET_USE_SSL) - if constexpr (requires { client.ssl_stream(); }) - { + if constexpr (requires { client.ssl_stream(); }) { configure_identity(server, client); } #endif @@ -165,8 +158,7 @@ void http_echo() { check(client.socket().remote_endpoint().address().is_v6() && server.acceptor().local_endpoint().address().is_v6(), "HTTP transport uses IPv6"); #if defined(ARKNET_ENABLE_SSL) || defined(ARKNET_USE_SSL) - if constexpr (requires { client.ssl_stream(); }) - { + if constexpr (requires { client.ssl_stream(); }) { verified_ip(client); } #endif @@ -208,8 +200,7 @@ DOCTEST_TEST_CASE("UDP sessions echo empty and binary IPv6 datagrams") { check(server.acceptor().local_endpoint().address().is_v6() && client.socket().remote_endpoint().address().is_v6(), "UDP sockets use IPv6"); const std::vector expected{"", payload}; - for (std::size_t index = 0; index < expected.size(); ++index) - { + for (std::size_t index = 0; index < expected.size(); ++index) { auto sent = client.async_send(expected[index], asio::use_future); const auto completion = await(sent); check(!completion.first && completion.second == expected[index].size(), "IPv6 UDP send completes"); diff --git a/tests/keepalive.cpp b/tests/keepalive.cpp index 38944a9..4eafaa8 100644 --- a/tests/keepalive.cpp +++ b/tests/keepalive.cpp @@ -12,8 +12,7 @@ DOCTEST_TEST_CASE("TCP keepalive validates parameters before changing socket opt socket.open(asio::ip::tcp::v4()); check(arknet::detail::set_keepalive_options(socket, false), "disable keepalive"); for (const auto values : - {std::array{0, 3, 3}, {60, 0, 3}, {60, 3, 0}, {(std::numeric_limits::max)(), 3, 3}}) - { + {std::array{0, 3, 3}, {60, 0, 3}, {60, 3, 0}, {(std::numeric_limits::max)(), 3, 3}}) { check(!arknet::detail::set_keepalive_options(socket, true, values[0], values[1], values[2]), "invalid keepalive parameters fail"); check(arknet::get_last_error() == asio::error::invalid_argument, "invalid parameter error"); diff --git a/tests/lifecycle.cpp b/tests/lifecycle.cpp index f0e6cdf..9acb832 100644 --- a/tests/lifecycle.cpp +++ b/tests/lifecycle.cpp @@ -6,8 +6,7 @@ #include #include "check.hpp" -struct io_runner -{ +struct io_runner { asio::io_context context; asio::executor_work_guard work{context.get_executor()}; std::thread thread{[this] { context.run(); }}; @@ -19,8 +18,7 @@ struct io_runner } }; -struct late_deadline_session : arknet::tcp_session_t -{ +struct late_deadline_session : arknet::tcp_session_t { using super = arknet::tcp_session_t; using super::super; @@ -65,17 +63,14 @@ DOCTEST_TEST_CASE("Lifecycle status queries tolerate concurrent TCP and UDP tran const auto poll = [](const auto& endpoint) { return static_cast(endpoint.is_started()) + static_cast(endpoint.is_stopped()); }; - while (!stop.stop_requested()) - { + while (!stop.stop_requested()) { auto states = poll(tcp_server) + poll(tcp_client) + poll(udp_server) + poll(udp_client) + poll(udp_cast); { std::lock_guard lock(sessions_mutex); - if (tcp_session) - { + if (tcp_session) { states += poll(*tcp_session); } - if (udp_session) - { + if (udp_session) { states += poll(*udp_session); } } @@ -83,8 +78,7 @@ DOCTEST_TEST_CASE("Lifecycle status queries tolerate concurrent TCP and UDP tran std::this_thread::yield(); } }); - for (std::size_t round = 0; round < 16; ++round) - { + for (std::size_t round = 0; round < 16; ++round) { check(tcp_server.start("127.0.0.1", 0), "polled TCP server starts"); check(tcp_client.start("127.0.0.1", tcp_server.get_listen_port()), "polled TCP client connects"); tcp_connections.wait(round + 1); @@ -98,8 +92,7 @@ DOCTEST_TEST_CASE("Lifecycle status queries tolerate concurrent TCP and UDP tran check(udp_server.is_started() && udp_client.is_started() && udp_cast.is_started() && udp_session->is_started(), "connected UDP endpoints publish started state"); const auto previous_samples = samples.load(); - while (samples.load() == previous_samples) - { + while (samples.load() == previous_samples) { std::this_thread::yield(); } tcp_server.stop(); @@ -187,8 +180,7 @@ DOCTEST_TEST_CASE("TCP auto reconnect recovers after server restart") { arknet::tcp_client client; client.set_auto_reconnect(true, 20ms); client.bind_connect([&] { - if (!arknet::get_last_error()) - { + if (!arknet::get_last_error()) { connections.push("connected"); } }); @@ -292,8 +284,7 @@ DOCTEST_TEST_CASE("Send completions accept generic move-only and overloaded call const auto result = await(completion); check(!result.first && result.second == 5, "generic callback reports send result"); - struct overloaded_completion - { + struct overloaded_completion { std::promise& promise; void operator()(std::size_t bytes) { @@ -365,8 +356,7 @@ DOCTEST_TEST_CASE("External timer stop drains canceled waits and releases callba std::weak_ptr released = retained; std::atomic conditions{}; std::vector> delayed; - for (unsigned i = 0; i < 32; ++i) - { + for (unsigned i = 0; i < 32; ++i) { timer.start_timer(i, 1h, [retained] {}); delayed.push_back(timer.post([retained] { return *retained; }, 1h, asio::use_future)); timer.post_condition_event([retained, &conditions] { ++conditions; }); @@ -377,14 +367,14 @@ DOCTEST_TEST_CASE("External timer stop drains canceled waits and releases callba timer.stop(); check(conditions == 32, "stop waits for all condition completions"); check(released.expired(), "stop releases canceled timer and post captures"); - for (auto& future : delayed) - { + for (auto& future : delayed) { check(future.wait_for(0s) == std::future_status::ready, "canceled delayed future ready before stop returns"); bool canceled = false; - try - { future.get(); } - catch (const std::future_error& error) - { canceled = error.code() == std::make_error_code(std::future_errc::broken_promise); } + try { + future.get(); + } catch (const std::future_error& error) { + canceled = error.code() == std::make_error_code(std::future_errc::broken_promise); + } check(canceled, "canceled delayed task does not invoke its callback"); } check(!runner.context.stopped(), "timer stop preserves external context"); @@ -418,10 +408,11 @@ DOCTEST_TEST_CASE("Completed future posts release captures before result consump check(&reference.get() == &reference_result, "reference result preserves identity"); check(*delayed.get() == 7, "move-only result preserved"); bool propagated = false; - try - { failure.get(); } - catch (const std::runtime_error& error) - { propagated = std::string_view(error.what()) == "post callback failure"; } + try { + failure.get(); + } catch (const std::runtime_error& error) { + propagated = std::string_view(error.what()) == "post callback failure"; + } check(propagated, "future preserves the callback exception"); check(!runner.context.stopped(), "future completion preserves external context"); } @@ -451,14 +442,14 @@ DOCTEST_TEST_CASE("Future posts canceled before initialization release captures timer->stop(); check(released.expired() && unexpected_callbacks == 0, "uninitialized futures cannot retain or run canceled callbacks"); - for (auto& future : canceled) - { + for (auto& future : canceled) { check(future.wait_for(0s) == std::future_status::ready, "uninitialized canceled future is ready after stop"); bool abandoned = false; - try - { future.get(); } - catch (const std::future_error& error) - { abandoned = error.code() == std::make_error_code(std::future_errc::broken_promise); } + try { + future.get(); + } catch (const std::future_error& error) { + abandoned = error.code() == std::make_error_code(std::future_errc::broken_promise); + } check(abandoned, "uninitialized task reports broken_promise"); } check(!runner.context.stopped(), "canceling initialization preserves external context"); @@ -494,8 +485,7 @@ DOCTEST_TEST_CASE("Pool and queued-event futures release captures before result check(!runner.context.stopped(), "queued task preserves external context"); } -struct callback_lifetime_probe -{ +struct callback_lifetime_probe { explicit callback_lifetime_probe(std::promise& result) : released(result) {} @@ -592,8 +582,7 @@ DOCTEST_TEST_CASE("Timer queries reject a directly stopped external context") { namespace { -class context_destruction_probe final : public asio::execution_context::service -{ +class context_destruction_probe final : public asio::execution_context::service { public: inline static asio::execution_context::id id; @@ -613,20 +602,15 @@ class context_destruction_probe final : public asio::execution_context::service template void configure_release_endpoint(Endpoint& endpoint) { - if constexpr (Endpoint::is_client()) - { + if constexpr (Endpoint::is_client()) { endpoint.set_auto_reconnect(false); } #if defined(ARKNET_ENABLE_SSL) - if constexpr (requires { endpoint.load_verify_file(std::string{}); }) - { + if constexpr (requires { endpoint.load_verify_file(std::string{}); }) { const std::string certs = ARKNET_TEST_CERT_DIR; - if constexpr (Endpoint::is_client()) - { + if constexpr (Endpoint::is_client()) { endpoint.load_verify_file(certs + "/ca.pem"); - } - else - { + } else { endpoint.set_cert_file("", certs + "/server.pem", certs + "/server-key.pem", ""); } check(!arknet::get_last_error(), "endpoint certificate configuration succeeds"); @@ -636,8 +620,7 @@ void configure_release_endpoint(Endpoint& endpoint) { template void release_callback_owner(std::uint16_t port = 0) { - struct application_owner - { + struct application_owner { std::shared_ptr endpoint = std::make_shared(1024, 128 * 1024, 2); ~application_owner() { @@ -673,8 +656,7 @@ void release_client_callback_owner() { } // namespace DOCTEST_TEST_CASE("Owned listeners release final callback owners on an IO worker") { - for (unsigned repetition = 0; repetition < 8; ++repetition) - { + for (unsigned repetition = 0; repetition < 8; ++repetition) { release_callback_owner(); release_callback_owner(); release_callback_owner(); @@ -693,8 +675,7 @@ DOCTEST_TEST_CASE("Owned listeners release final callback owners on an IO worker } DOCTEST_TEST_CASE("Owned clients release final callback owners on an IO worker") { - for (unsigned repetition = 0; repetition < 8; ++repetition) - { + for (unsigned repetition = 0; repetition < 8; ++repetition) { release_client_callback_owner(); release_client_callback_owner(); release_client_callback_owner(); diff --git a/tests/manual_runtime.cpp b/tests/manual_runtime.cpp index e14269d..385dcc8 100644 --- a/tests/manual_runtime.cpp +++ b/tests/manual_runtime.cpp @@ -13,8 +13,7 @@ namespace { -struct capture_lifetime -{ +struct capture_lifetime { std::atomic& destroyed; std::thread::id& destruction_thread; @@ -50,8 +49,7 @@ DOCTEST_TEST_CASE("Manual runtime posts from other threads but runs only when it std::atomic calls{}; std::vector threads; std::thread producer([&] { - for (unsigned index = 0; index < 8; ++index) - { + for (unsigned index = 0; index < 8; ++index) { asio::post(executor, [&] { threads.push_back(std::this_thread::get_id()); ++calls; @@ -63,8 +61,7 @@ DOCTEST_TEST_CASE("Manual runtime posts from other threads but runs only when it DOCTEST_CHECK(runtime.poll(8) == 8); DOCTEST_CHECK(calls == 8); DOCTEST_REQUIRE(threads.size() == 8); - for (const auto thread : threads) - { + for (const auto thread : threads) { DOCTEST_CHECK(thread == owner); } DOCTEST_CHECK(runtime.poll() == 0); @@ -75,8 +72,7 @@ DOCTEST_TEST_CASE("Manual runtime remains usable after idle polls and honors its DOCTEST_CHECK(runtime.poll() == 0); DOCTEST_CHECK(runtime.poll() == 0); unsigned calls{}; - for (unsigned index = 0; index < 5; ++index) - { + for (unsigned index = 0; index < 5; ++index) { asio::post(runtime.get_executor(), [&] { ++calls; }); } DOCTEST_CHECK(runtime.poll(0) == 0); @@ -135,10 +131,11 @@ DOCTEST_TEST_CASE("Manual runtime rejects foreign-thread polling without executi asio::post(runtime.get_executor(), [&] { ++calls; }); bool rejected{}; std::thread foreign([&] { - try - { runtime.poll(); } - catch (const std::logic_error&) - { rejected = true; } + try { + runtime.poll(); + } catch (const std::logic_error&) { + rejected = true; + } }); foreign.join(); DOCTEST_CHECK(rejected); @@ -182,15 +179,13 @@ DOCTEST_TEST_CASE("Manual runtime resumes timer coroutines and their completion }); DOCTEST_CHECK(threads.empty()); DOCTEST_CHECK_FALSE(completed); - for (unsigned attempt = 0; attempt < 1024 && !completed; ++attempt) - { + for (unsigned attempt = 0; attempt < 1024 && !completed; ++attempt) { DOCTEST_CHECK(runtime.poll(1) <= 1); } DOCTEST_REQUIRE(completed); DOCTEST_CHECK_FALSE(bool(failure)); DOCTEST_REQUIRE(threads.size() == 3); - for (const auto thread : threads) - { + for (const auto thread : threads) { DOCTEST_CHECK(thread == owner); } } @@ -203,8 +198,7 @@ DOCTEST_TEST_CASE("Manual coroutine exceptions arrive at co_spawn completion and failure = std::move(error); completed = true; }); - for (unsigned attempt = 0; attempt < 1024 && !completed; ++attempt) - { + for (unsigned attempt = 0; attempt < 1024 && !completed; ++attempt) { runtime.poll(1); } DOCTEST_REQUIRE(completed); diff --git a/tests/mysql_auth.cpp b/tests/mysql_auth.cpp index 8fb6f74..4f119d6 100644 --- a/tests/mysql_auth.cpp +++ b/tests/mysql_auth.cpp @@ -10,8 +10,7 @@ namespace { std::string hex(std::string_view bytes) { constexpr std::string_view digits = "0123456789abcdef"; std::string result; - for (unsigned char byte : bytes) - { + for (unsigned char byte : bytes) { result.push_back(digits[byte >> 4]); result.push_back(digits[byte & 15]); } @@ -33,8 +32,7 @@ DOCTEST_TEST_CASE("MySQL authentication matches independent fixed SHA1 and SHA25 CHECK(hex(response) == "51ecd6dedbd34d5445c0a190d4f51acf0d23b94db66c91f3f789faa9193751cd"); const std::string password("a\0b\xff", 4); std::string challenge; - for (int i = 0; i < 20; ++i) - { + for (int i = 0; i < 20; ++i) { challenge.push_back(static_cast(i)); } REQUIRE(make_auth_response("mysql_native_password", password, challenge, response) == auth_status::complete); @@ -45,10 +43,8 @@ DOCTEST_TEST_CASE("MySQL authentication matches independent fixed SHA1 and SHA25 DOCTEST_TEST_CASE("MySQL auth rejects unsupported mechanisms and malformed nonce without changing output") { using namespace arknet::mysql::detail; - for (const auto plugin : {"mysql_native_password", "caching_sha2_password"}) - { - for (const auto size : {0, 1, 19, 21, 1000}) - { + for (const auto plugin : {"mysql_native_password", "caching_sha2_password"}) { + for (const auto size : {0, 1, 19, 21, 1000}) { std::string response = "previous"; CHECK(make_auth_response(plugin, "secret", std::string(size, 'x'), response) == auth_status::invalid_challenge); @@ -58,8 +54,7 @@ DOCTEST_TEST_CASE("MySQL auth rejects unsupported mechanisms and malformed nonce CHECK(make_auth_response(plugin, "", "12345678901234567890", response) == auth_status::complete); CHECK(response.empty()); } - for (const auto plugin : {"", "sha256_password", "mysql_clear_password", "unknown"}) - { + for (const auto plugin : {"", "sha256_password", "mysql_clear_password", "unknown"}) { std::string response = "previous"; CHECK( make_auth_response(plugin, "secret", "12345678901234567890", response) == auth_status::unsupported_plugin); diff --git a/tests/mysql_command.cpp b/tests/mysql_command.cpp index f10e347..f658857 100644 --- a/tests/mysql_command.cpp +++ b/tests/mysql_command.cpp @@ -29,8 +29,7 @@ column_definition column(field_type type, bool is_unsigned = false) { std::string metadata(field_type type = field_type::longlong, std::uint16_t flags = unsigned_column_flag) { std::string payload; - for (const auto text : {"def", "schema", "alias_table", "real_table", "alias_name", "real_name"}) - { + for (const auto text : {"def", "schema", "alias_table", "real_table", "alias_name", "real_name"}) { append_string(payload, text); } payload.push_back(12); @@ -45,8 +44,7 @@ std::string metadata(field_type type = field_type::longlong, std::uint16_t flags std::string text_row(std::initializer_list> values) { std::string payload; - for (const auto& value : values) - { + for (const auto& value : values) { append_string(payload, value); } return payload; @@ -73,13 +71,11 @@ std::string binary_from_execute(std::span values, std::vector const auto row_bitmap_size = (values.size() + 9) / 8; std::string payload(1 + row_bitmap_size, '\0'); columns.clear(); - for (std::size_t index = 0; index < values.size(); ++index) - { + for (std::size_t index = 0; index < values.size(); ++index) { const auto type_offset = 10 + parameter_bitmap_size + 1 + index * 2; const auto type = static_cast(static_cast(execute[type_offset])); columns.push_back(column(type, static_cast(execute[type_offset + 1]) == 0x80)); - if ((static_cast(execute[10 + index / 8]) & (1u << (index % 8))) != 0) - { + if ((static_cast(execute[10 + index / 8]) & (1u << (index % 8))) != 0) { const auto bit = index + 2; payload[1 + bit / 8] = static_cast(static_cast(payload[1 + bit / 8]) | (1u << (bit % 8))); @@ -121,8 +117,7 @@ DOCTEST_TEST_CASE("MySQL prepare OK decodes the documented twelve byte fixture a CHECK(result.column_count == 1); CHECK(result.parameter_count == 2); CHECK(result.warnings == 0); - for (std::size_t size = 0; size < payload.size(); ++size) - { + for (std::size_t size = 0; size < payload.size(); ++size) { result = {99, 98, 97, 96}; CHECK(parse_prepare_ok(std::string_view(payload).substr(0, size), result) == command_status::incomplete); CHECK(result.statement_id == 99); @@ -130,8 +125,7 @@ DOCTEST_TEST_CASE("MySQL prepare OK decodes the documented twelve byte fixture a CHECK(result.parameter_count == 97); CHECK(result.warnings == 96); } - for (const auto offset : {0, 9}) - { + for (const auto offset : {0, 9}) { auto malformed = payload; malformed[offset] = 1; CHECK(parse_prepare_ok(malformed, result) == command_status::invalid); @@ -152,22 +146,19 @@ DOCTEST_TEST_CASE("MySQL prepare OK decodes the documented twelve byte fixture a DOCTEST_TEST_CASE("MySQL column count rejects NULL zero overflow unsupported metadata and trailing bytes") { std::size_t count = 42; - for (const auto expected : {1, 250, 251, 4096}) - { + for (const auto expected : {1, 250, 251, 4096}) { std::string payload; append_length(payload, expected); REQUIRE(parse_column_count(payload, count) == command_status::complete); CHECK(count == static_cast(expected)); - for (std::size_t size = 0; size < payload.size(); ++size) - { + for (std::size_t size = 0; size < payload.size(); ++size) { count = 42; CHECK(parse_column_count(std::string_view(payload).substr(0, size), count) == command_status::incomplete); CHECK(count == 42); } } count = 42; - for (const auto byte : {'\0', '\xfb', '\xff'}) - { + for (const auto byte : {'\0', '\xfb', '\xff'}) { CHECK(parse_column_count(std::string(1, byte), count) == command_status::invalid); CHECK(count == 42); } @@ -198,8 +189,7 @@ DOCTEST_TEST_CASE("MySQL ColumnDefinition41 owns six names and reads fixed metad payload.assign(payload.size(), 'x'); CHECK(result.name == "alias_name"); payload = metadata(); - for (std::size_t size = 0; size < payload.size(); ++size) - { + for (std::size_t size = 0; size < payload.size(); ++size) { result.name = "old"; CHECK(parse_column(std::string_view(payload).substr(0, size), result) == command_status::incomplete); CHECK(result.name == "old"); @@ -232,8 +222,7 @@ DOCTEST_TEST_CASE("MySQL classic EOF distinguishes its warning and status order CHECK(result.status_flags == 0x000a); CHECK(result.affected_rows == 0); CHECK(result.last_insert_id == 0); - for (std::size_t size = 0; size < payload.size(); ++size) - { + for (std::size_t size = 0; size < payload.size(); ++size) { result = {42, 43, 44, 45, "old", "state"}; CHECK(parse_eof(std::string_view(payload).substr(0, size), result) == command_status::incomplete); CHECK(result.affected_rows == 42); @@ -274,12 +263,10 @@ DOCTEST_TEST_CASE("MySQL text numeric conversion rejects overflow incomplete num CHECK(parse_text_row(text_row({text}), columns, row) == command_status::invalid); check_old_row(row); }; - for (const auto text : {"", "1x", " 1", "+1", "9223372036854775808", "-9223372036854775809"}) - { + for (const auto text : {"", "1x", " 1", "+1", "9223372036854775808", "-9223372036854775809"}) { check_invalid(field_type::longlong, false, text); } - for (const auto text : {"-1", "18446744073709551616"}) - { + for (const auto text : {"-1", "18446744073709551616"}) { check_invalid(field_type::longlong, true, text); } check_invalid(field_type::tiny, false, "128"); @@ -289,8 +276,7 @@ DOCTEST_TEST_CASE("MySQL text numeric conversion rejects overflow incomplete num check_invalid(field_type::short_integer, true, "65536"); check_invalid(field_type::int24, false, "8388608"); check_invalid(field_type::long_integer, true, "4294967296"); - for (const auto text : {"", "1x", "nan", "inf", "-inf", "1e999", "1e-999"}) - { + for (const auto text : {"", "1x", "nan", "inf", "-inf", "1e999", "1e-999"}) { check_invalid(field_type::double_number, false, text); } check_invalid(field_type::float_number, false, "1e100"); @@ -307,24 +293,21 @@ DOCTEST_TEST_CASE("MySQL text temporal values preserve zero dates fractional pre CHECK(std::get(row[1]) == datetime_value{{2026, 2, 31}, 12, 34, 56, 123456}); CHECK(std::get(row[2]) == datetime_value{{2026, 10, 15}, 0, 0, 0, 100000}); CHECK(std::get(row[3]) == time_value{true, 34, 22, 59, 59, 999999}); - for (const auto text : {"2026-13-01", "2026-01-32", "2026-1-01", "xxxx-01-01"}) - { + for (const auto text : {"2026-13-01", "2026-01-32", "2026-1-01", "xxxx-01-01"}) { const std::array one_column = {column(field_type::date)}; row = old_row(); CHECK(parse_text_row(text_row({text}), one_column, row) == command_status::invalid); check_old_row(row); } for (const auto text : {"2026-01-01 24:00:00", "2026-01-01 00:60:00", "2026-01-01 00:00:00.", - "2026-01-01 00:00:00.1234567", "2026-01-01 00:00:00x"}) - { + "2026-01-01 00:00:00.1234567", "2026-01-01 00:00:00x"}) { const std::array one_column = {column(field_type::datetime)}; row = old_row(); CHECK(parse_text_row(text_row({text}), one_column, row) == command_status::invalid); check_old_row(row); } for (const auto text : - {"1:00:00", "00:60:00", "00:00:60", "00:00:00.", "18446744073709551616:00:00", "103079215104:00:00"}) - { + {"1:00:00", "00:60:00", "00:00:60", "00:00:00.", "18446744073709551616:00:00", "103079215104:00:00"}) { const std::array one_column = {column(field_type::time)}; row = old_row(); CHECK(parse_text_row(text_row({text}), one_column, row) == command_status::invalid); @@ -392,8 +375,7 @@ DOCTEST_TEST_CASE("MySQL result NULL bitmap uses offset two across byte boundari payload.append(6, '\1'); row_values row; REQUIRE(parse_binary_row(payload, columns, row) == command_status::complete); - for (std::size_t index = 0; index < row.size(); ++index) - { + for (std::size_t index = 0; index < row.size(); ++index) { CHECK(std::holds_alternative(row[index]) == (index == 0 || index == 6 || index == 8)); } auto malformed = payload; @@ -433,10 +415,8 @@ DOCTEST_TEST_CASE("MySQL row decoders preserve output on mandatory truncation ma std::string binary("\0\0", 2); append_integer(binary, 42, 8); append_string(binary, "binary"); - for (const auto& [payload, is_binary] : std::array{std::pair{text, false}, std::pair{binary, true}}) - { - for (std::size_t size = 0; size < payload.size(); ++size) - { + for (const auto& [payload, is_binary] : std::array{std::pair{text, false}, std::pair{binary, true}}) { + for (std::size_t size = 0; size < payload.size(); ++size) { auto row = old_row(); const auto prefix = std::string_view(payload).substr(0, size); CHECK((is_binary ? parse_binary_row(prefix, columns, row) : parse_text_row(prefix, columns, row)) == @@ -450,8 +430,7 @@ DOCTEST_TEST_CASE("MySQL row decoders preserve output on mandatory truncation ma } for (const auto type : {field_type::tiny, field_type::short_integer, field_type::long_integer, field_type::longlong, field_type::double_number, field_type::float_number, field_type::var_string, field_type::date, - field_type::datetime, field_type::time}) - { + field_type::datetime, field_type::time}) { const std::array one_column = {column(type)}; auto row = old_row(); CHECK(parse_text_row(std::string(1, '\xff'), one_column, row) == command_status::invalid); @@ -462,8 +441,7 @@ DOCTEST_TEST_CASE("MySQL row decoders preserve output on mandatory truncation ma CHECK(parse_binary_row(std::string("\0\0\xfb", 3), string_column, row) == command_status::invalid); CHECK(parse_binary_row(std::string("\1\0", 2), string_column, row) == command_status::invalid); check_old_row(row); - for (const auto type : {14, 17, 18, 19, 20, 242, 243, 244}) - { + for (const auto type : {14, 17, 18, 19, 20, 242, 243, 244}) { const std::array unknown = {column(static_cast(type))}; CHECK(parse_binary_row(std::string("\0\4", 2), unknown, row) == command_status::unsupported); CHECK(parse_text_row(std::string(1, '\xfb'), unknown, row) == command_status::unsupported); @@ -489,8 +467,7 @@ DOCTEST_TEST_CASE("MySQL malformed binary temporal numeric and float fields neve append_integer(datetime, 1000000, 4); invalid(field_type::datetime, datetime); invalid(field_type::int24, std::string("\0\0\x80\0", 4)); - for (const auto number : {std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN()}) - { + for (const auto number : {std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN()}) { std::string bytes; append_integer(bytes, std::bit_cast(number), 8); invalid(field_type::double_number, bytes); @@ -502,13 +479,11 @@ DOCTEST_TEST_CASE("MySQL binary temporal payloads reject every missing mandatory datetime_value{{2026, 10, 15}}, datetime_value{{2026, 10, 15}, 1, 2, 3}, datetime_value{{2026, 10, 15}, 1, 2, 3, 4}, time_value{}, time_value{false, 2, 1, 2, 3}, time_value{true, std::numeric_limits::max(), 23, 59, 59, 999999}}; - for (const auto& value : values) - { + for (const auto& value : values) { const std::array one_value = {value}; std::vector columns; const auto payload = binary_from_execute(one_value, columns); - for (std::size_t size = 0; size < payload.size(); ++size) - { + for (std::size_t size = 0; size < payload.size(); ++size) { auto row = old_row(); CHECK(parse_binary_row(std::string_view(payload).substr(0, size), columns, row) == command_status::incomplete); @@ -519,11 +494,10 @@ DOCTEST_TEST_CASE("MySQL binary temporal payloads reject every missing mandatory DOCTEST_TEST_CASE("MySQL byte string protocol types preserve arbitrary bytes in both row encodings") { const std::string bytes("\xff\0\xfe\xfb", 4); - for (const auto type : - {field_type::decimal, field_type::varchar, field_type::bit, field_type::json, field_type::newdecimal, - field_type::enumeration, field_type::set, field_type::tiny_blob, field_type::medium_blob, - field_type::long_blob, field_type::blob, field_type::var_string, field_type::string, field_type::geometry}) - { + for (const auto type : {field_type::decimal, field_type::varchar, field_type::bit, field_type::json, + field_type::newdecimal, field_type::enumeration, field_type::set, field_type::tiny_blob, + field_type::medium_blob, field_type::long_blob, field_type::blob, field_type::var_string, + field_type::string, field_type::geometry}) { const std::array columns = {column(type)}; auto text = text_row({bytes}); row_values row; @@ -566,11 +540,9 @@ DOCTEST_TEST_CASE("MySQL byte and count limits apply before publishing rows or e DOCTEST_TEST_CASE("MySQL large binary values cross all length encoding boundaries without changing data") { const std::array columns = {column(field_type::long_blob)}; - for (const auto size : {0, 250, 251, 255, 256, 65535, 65536, 1024 * 1024}) - { + for (const auto size : {0, 250, 251, 255, 256, 65535, 65536, 1024 * 1024}) { std::string value(size, '\0'); - for (std::size_t index = 0; index < value.size(); ++index) - { + for (std::size_t index = 0; index < value.size(); ++index) { value[index] = static_cast(index & 255); } const auto text = text_row({value}); @@ -596,20 +568,18 @@ DOCTEST_TEST_CASE("MySQL execute rejects invalid numeric and temporal parameters for (const field_value& value : {field_value(std::numeric_limits::infinity()), field_value(std::numeric_limits::quiet_NaN()), field_value(date_value{10000, 1, 1}), field_value(date_value{2026, 13, 1}), field_value(datetime_value{{2026, 1, 1}, 24}), - field_value(time_value{false, 0, 0, 60}), field_value(time_value{false, 0, 0, 0, 0, 1000000})}) - { + field_value(time_value{false, 0, 0, 60}), field_value(time_value{false, 0, 0, 0, 0, 1000000})}) { std::string output = "old"; const std::array values = {value}; CHECK(encode_execute(1, values, output) == command_status::invalid); CHECK(output == "old"); } field_value value = std::int64_t(1); - try - { value.emplace(std::string::npos, 'x'); } - catch (const std::length_error&) - {} - if (value.valueless_by_exception()) - { + try { + value.emplace(std::string::npos, 'x'); + } catch (const std::length_error&) { + } + if (value.valueless_by_exception()) { const std::array values = {value}; std::string output = "old"; CHECK(encode_execute(1, values, output) == command_status::invalid); @@ -619,13 +589,10 @@ DOCTEST_TEST_CASE("MySQL execute rejects invalid numeric and temporal parameters DOCTEST_TEST_CASE("MySQL prepared typed values roundtrip with independently shifted result NULL bitmap") { std::mt19937_64 random(0x436f6d6d616e64); - for (unsigned iteration = 0; iteration < 400; ++iteration) - { + for (unsigned iteration = 0; iteration < 400; ++iteration) { std::vector expected; - for (unsigned index = 0; index < 17; ++index) - { - switch (random() % 6) - { + for (unsigned index = 0; index < 17; ++index) { + switch (random() % 6) { case 0: expected.emplace_back(std::monostate{}); break; @@ -673,27 +640,22 @@ DOCTEST_TEST_CASE("MySQL maximum prepared parameter count stays bounded and emit DOCTEST_TEST_CASE("MySQL hostile command result payloads never partially overwrite rows or column metadata") { std::mt19937 random(0x726f7773); const std::vector columns = {column(field_type::longlong), column(field_type::blob), column(field_type::datetime)}; - for (unsigned iteration = 0; iteration < 2000; ++iteration) - { + for (unsigned iteration = 0; iteration < 2000; ++iteration) { std::string payload(random() % 96, '\0'); - for (auto& byte : payload) - { + for (auto& byte : payload) { byte = static_cast(random() & 255); } auto row = old_row(); - if (parse_text_row(payload, columns, row) != command_status::complete) - { + if (parse_text_row(payload, columns, row) != command_status::complete) { check_old_row(row); } row = old_row(); - if (parse_binary_row(payload, columns, row) != command_status::complete) - { + if (parse_binary_row(payload, columns, row) != command_status::complete) { check_old_row(row); } column_definition definition; definition.name = "old"; - if (parse_column(payload, definition) != command_status::complete) - { + if (parse_column(payload, definition) != command_status::complete) { CHECK(definition.name == "old"); } } diff --git a/tests/mysql_connection.cpp b/tests/mysql_connection.cpp index 884f30b..b51b433 100644 --- a/tests/mysql_connection.cpp +++ b/tests/mysql_connection.cpp @@ -4,8 +4,7 @@ using namespace mysql_test; DOCTEST_TEST_CASE("MySQL connection authenticates fragmented native-password exchange and supports reuse") { connection client; - for (int iteration = 0; iteration < 2; ++iteration) - { + for (int iteration = 0; iteration < 2; ++iteration) { mysql_peer peer([](mysql_peer& server) -> asio::awaitable { co_await server.send(greeting(), true); check_response(co_await server.receive(), "mysql_native_password", false); @@ -105,22 +104,21 @@ DOCTEST_TEST_CASE("MySQL TLS is required by default and plaintext full-password } DOCTEST_TEST_CASE("MySQL verifies certificate trust and DNS identity without downgrade") { - for (const bool wrong_identity : {false, true}) - { + for (const bool wrong_identity : {false, true}) { mysql_peer peer( [](mysql_peer& server) -> asio::awaitable { co_await server.send(greeting("caching_sha2_password", true)); CHECK((co_await server.receive()).size() == 32); - try - { co_await server.upgrade(); } - catch (const arknet::system_error& error) - { CHECK(bool(error.code())); } + try { + co_await server.upgrade(); + } catch (const arknet::system_error& error) { + CHECK(bool(error.code())); + } }, wrong_identity); connection client; auto options = options_for(peer, true); - if (!wrong_identity) - { + if (!wrong_identity) { // This separate client certificate cannot be a trust anchor for // the server certificate signed by tests/certs/ca.pem. options.ca_file = std::string(ARKNET_TEST_CERT_DIR) + "/client.pem"; @@ -154,24 +152,16 @@ DOCTEST_TEST_CASE("MySQL reports server authentication rejection with code and S } DOCTEST_TEST_CASE("MySQL rejects malformed greetings, wrong sequence and unsupported authentication") { - for (int scenario = 0; scenario < 4; ++scenario) - { + for (int scenario = 0; scenario < 4; ++scenario) { mysql_peer peer([scenario](mysql_peer& server) -> asio::awaitable { - if (scenario == 0) - { + if (scenario == 0) { server.sequence = 1; co_await server.send(greeting()); - } - else if (scenario == 1) - { + } else if (scenario == 1) { co_await server.send(std::string("\x0a", 1)); - } - else if (scenario == 2) - { + } else if (scenario == 2) { co_await server.send(greeting("mysql_clear_password")); - } - else - { + } else { co_await server.send(greeting()); co_await server.receive(); co_await server.send(std::string("\1\3", 2)); @@ -207,8 +197,7 @@ DOCTEST_TEST_CASE("MySQL uses a single deadline across partial handshake progres } DOCTEST_TEST_CASE("MySQL close cancels pending authentication exactly once and supports callback destruction") { - for (const bool destroy_in_callback : {false, true}) - { + for (const bool destroy_in_callback : {false, true}) { std::promise received; auto received_result = received.get_future(); mysql_peer peer([&received](mysql_peer& server) -> asio::awaitable { @@ -223,8 +212,7 @@ DOCTEST_TEST_CASE("MySQL close cancels pending authentication exactly once and s std::atomic calls{}; client->async_connect(options_for(peer), [&](connection_result value) { ++calls; - if (destroy_in_callback) - { + if (destroy_in_callback) { client.reset(); } completion.set_value(std::move(value)); @@ -233,8 +221,7 @@ DOCTEST_TEST_CASE("MySQL close cancels pending authentication exactly once and s client->close(); REQUIRE(result.wait_for(5s) == std::future_status::ready); CHECK(result.get().code == connection_errc::cancelled); - if (client) - { + if (client) { CHECK_FALSE(client->is_open()); } peer.finish(); @@ -350,8 +337,7 @@ DOCTEST_TEST_CASE("MySQL real server verified TLS and password rejection" * connection client; // The server uses its uncached/full or cached/fast SHA2 path, according to // its own authentication cache. Both paths have separate byte-level tests. - for (int index = 0; index < 2; ++index) - { + for (int index = 0; index < 2; ++index) { const auto result = connect(client, options); CHECK_MESSAGE(bool(result), result.message, " ", result.transport_error.message()); CHECK(client.is_open()); @@ -417,8 +403,7 @@ DOCTEST_TEST_CASE("MySQL server command errors leave a complete protocol boundar } DOCTEST_TEST_CASE("MySQL result bounds close the socket before an incomplete response can be reused") { - for (const bool rows_limit : {false, true}) - { + for (const bool rows_limit : {false, true}) { mysql_peer peer([](mysql_peer& server) -> asio::awaitable { co_await authenticate(server); co_await receive_command(server); @@ -427,21 +412,18 @@ DOCTEST_TEST_CASE("MySQL result bounds close the socket before an incomplete res "42", 3)}; std::string response; - for (const auto& packet : packets) - { + for (const auto& packet : packets) { std::string encoded; REQUIRE(wire::encode_message(packet, server.sequence, encoded)); response += encoded; } - try - { co_await server.write_raw(std::move(response)); } - catch (const arknet::system_error& error) - { + try { + co_await server.write_raw(std::move(response)); + } catch (const arknet::system_error& error) { // The client may reject metadata and close before the entire // response is written. Other peer failures remain test errors. if (error.code() != asio::error::broken_pipe && error.code() != asio::error::connection_reset && - error.code() != asio::error::eof) - { + error.code() != asio::error::eof) { throw; } } @@ -450,12 +432,9 @@ DOCTEST_TEST_CASE("MySQL result bounds close the socket before an incomplete res connection client; REQUIRE(bool(connect(client, options_for(peer)))); query_options options; - if (rows_limit) - { + if (rows_limit) { options.max_rows = 0; - } - else - { + } else { options.max_result_bytes = 32; } const auto result = query(client, "SELECT 42", options); @@ -475,8 +454,7 @@ DOCTEST_TEST_CASE("MySQL queued cancellation and expiry preserve the active resp co_await authenticate(server); CHECK(co_await receive_command(server) == std::string("\3first", 6)); first_received.set_value(); - while (released.wait_for(0ms) != std::future_status::ready) - { + while (released.wait_for(0ms) != std::future_status::ready) { asio::steady_timer pause(co_await asio::this_coro::executor); pause.expires_after(2ms); co_await pause.async_wait(asio::use_awaitable); @@ -514,8 +492,7 @@ DOCTEST_TEST_CASE("MySQL queued cancellation and expiry preserve the active resp } DOCTEST_TEST_CASE("MySQL active cancellation and command deadlines close transport with unknown outcome") { - for (const bool timeout : {false, true}) - { + for (const bool timeout : {false, true}) { std::promise request_received; auto received = request_received.get_future(); mysql_peer peer([&](mysql_peer& server) -> asio::awaitable { @@ -533,8 +510,7 @@ DOCTEST_TEST_CASE("MySQL active cancellation and command deadlines close transpo const auto id = client.async_commit([&](query_result value) { completion.set_value(std::move(value)); }, options); REQUIRE(received.wait_for(5s) == std::future_status::ready); - if (!timeout) - { + if (!timeout) { client.cancel(id); } REQUIRE(future.wait_for(5s) == std::future_status::ready); @@ -547,8 +523,7 @@ DOCTEST_TEST_CASE("MySQL active cancellation and command deadlines close transpo } DOCTEST_TEST_CASE("MySQL pending count and memory limits reject excess requests without sending") { - for (const bool bytes_limit : {false, true}) - { + for (const bool bytes_limit : {false, true}) { std::promise request_received; auto received = request_received.get_future(); mysql_peer peer([&](mysql_peer& server) -> asio::awaitable { @@ -678,8 +653,7 @@ DOCTEST_TEST_CASE("MySQL bounds producer admission while its IO lane is blocked co_await receive_command(server); co_await server.send(ok_packet); // Two 256KiB requests fit the 600KiB total admitted byte budget. - for (int index = 0; index < 2; ++index) - { + for (int index = 0; index < 2; ++index) { server.sequence = 0; const auto request = co_await server.receive(300 * 1024); CHECK(request.size() == 256 * 1024 + 1); @@ -699,8 +673,7 @@ DOCTEST_TEST_CASE("MySQL bounds producer admission while its IO lane is blocked }); REQUIRE(entered_result.wait_for(5s) == std::future_status::ready); std::vector> futures; - for (int index = 0; index < 16; ++index) - { + for (int index = 0; index < 16; ++index) { auto promise = std::make_shared>(); futures.push_back(promise->get_future()); client.async_query( @@ -709,16 +682,12 @@ DOCTEST_TEST_CASE("MySQL bounds producer admission while its IO lane is blocked release.set_value(); int accepted = 0; int rejected = 0; - for (auto& future : futures) - { + for (auto& future : futures) { REQUIRE(future.wait_for(5s) == std::future_status::ready); const auto result = future.get(); - if (result) - { + if (result) { ++accepted; - } - else - { + } else { CHECK(result.status.code == connection_errc::queue_full); CHECK_FALSE(result.status.outcome_unknown); ++rejected; @@ -911,8 +880,7 @@ DOCTEST_TEST_CASE("MySQL supports modern plugin auth without the obsolete secure } DOCTEST_TEST_CASE("MySQL rejects connection deadlines outside the steady-clock range") { - for (const auto timeout : {std::chrono::milliseconds::max(), -1ms, 0ms}) - { + for (const auto timeout : {std::chrono::milliseconds::max(), -1ms, 0ms}) { connection client; connect_options options; options.timeout = timeout; @@ -962,8 +930,7 @@ DOCTEST_TEST_CASE("MySQL invalid command deadlines preserve IDs callbacks and wi REQUIRE(bool(connect(client, options_for(peer)))); const auto caller = std::this_thread::get_id(); arknet::mysql::request_id previous = 0; - for (const auto timeout : {std::chrono::milliseconds::max(), -1ms, 0ms}) - { + for (const auto timeout : {std::chrono::milliseconds::max(), -1ms, 0ms}) { query_options options; options.timeout = timeout; std::atomic callbacks{}; @@ -998,8 +965,7 @@ DOCTEST_TEST_CASE("MySQL invalid command deadlines preserve IDs callbacks and wi DOCTEST_TEST_CASE("MySQL invalid connect callbacks may destroy their connection safely") { const auto caller = std::this_thread::get_id(); - for (int iteration = 0; iteration < 32; ++iteration) - { + for (int iteration = 0; iteration < 32; ++iteration) { auto client = std::make_unique(); std::promise completion; auto future = completion.get_future(); @@ -1021,17 +987,14 @@ DOCTEST_TEST_CASE("MySQL invalid connect callbacks may destroy their connection DOCTEST_TEST_CASE("MySQL rejected command callbacks may destroy their connection safely") { const auto caller = std::this_thread::get_id(); - for (const bool invalid_deadline : {false, true}) - { - for (int iteration = 0; iteration < 32; ++iteration) - { + for (const bool invalid_deadline : {false, true}) { + for (int iteration = 0; iteration < 32; ++iteration) { auto client = std::make_unique(); std::promise completion; auto future = completion.get_future(); std::atomic calls{}; query_options options; - if (invalid_deadline) - { + if (invalid_deadline) { options.timeout = 0ms; } const auto id = client->async_query( diff --git a/tests/mysql_handshake.cpp b/tests/mysql_handshake.cpp index 13cb859..29532ab 100644 --- a/tests/mysql_handshake.cpp +++ b/tests/mysql_handshake.cpp @@ -35,8 +35,7 @@ std::string make_greeting(std::string_view auth_data, std::string_view plugin = payload.push_back((capabilities & capability::plugin_auth) != 0 ? static_cast(auth_data.size()) : 0); payload.append(10, '\0'); payload.append(auth_data.substr(8)); - if ((capabilities & capability::plugin_auth) != 0) - { + if ((capabilities & capability::plugin_auth) != 0) { payload.append(plugin); payload.push_back('\0'); } @@ -47,8 +46,7 @@ std::string make_scramble(bool terminator = true) { std::string scramble("0123\0" "56789abcdefghij", 20); - if (terminator) - { + if (terminator) { scramble.push_back('\0'); } return scramble; @@ -89,10 +87,8 @@ void check_ok_unchanged(const ok_packet& packet) { } // namespace DOCTEST_TEST_CASE("MySQL protocol 10 greeting keeps capabilities and opaque binary authentication data") { - for (const auto plugin : {"caching_sha2_password", "mysql_native_password", "sha256_password", "custom_plugin"}) - { - for (const auto terminator : {false, true}) - { + for (const auto plugin : {"caching_sha2_password", "mysql_native_password", "sha256_password", "custom_plugin"}) { + for (const auto terminator : {false, true}) { const auto scramble = make_scramble(terminator); auto payload = make_greeting(scramble, plugin); server_greeting result; @@ -121,8 +117,7 @@ DOCTEST_TEST_CASE("MySQL protocol 10 greeting keeps capabilities and opaque bina DOCTEST_TEST_CASE("MySQL greetings reject every truncated prefix and preserve prior output") { const auto payload = make_greeting(make_scramble()); - for (std::size_t size = 0; size < payload.size(); ++size) - { + for (std::size_t size = 0; size < payload.size(); ++size) { CAPTURE(size); auto result = old_greeting(); CHECK(parse_greeting(std::string_view(payload).substr(0, size), result) == handshake_status::incomplete); @@ -133,8 +128,7 @@ DOCTEST_TEST_CASE("MySQL greetings reject every truncated prefix and preserve pr check_greeting_unchanged(result); // Capability and length offsets are derived independently from the fixed fields. const auto lower_flags = 1 + std::string_view("9.7.2-test").size() + 1 + 4 + 8 + 1; - for (const auto offset : {lower_flags - 1, lower_flags + 9}) - { + for (const auto offset : {lower_flags - 1, lower_flags + 9}) { auto malformed = payload; malformed[offset] = 1; CHECK(parse_greeting(malformed, result) == handshake_status::invalid); @@ -156,18 +150,15 @@ DOCTEST_TEST_CASE("MySQL greetings reject every truncated prefix and preserve pr } DOCTEST_TEST_CASE("MySQL password scramble validation never truncates opaque plugins") { - for (const auto plugin : {"mysql_native_password", "caching_sha2_password", "sha256_password"}) - { - for (const auto terminator : {false, true}) - { + for (const auto plugin : {"mysql_native_password", "caching_sha2_password", "sha256_password"}) { + for (const auto terminator : {false, true}) { const auto data = make_scramble(terminator); std::string_view scramble = "old"; REQUIRE(password_scramble(plugin, data, scramble) == handshake_status::complete); CHECK(scramble == std::string_view(data).substr(0, 20)); CHECK(scramble.data() == data.data()); } - for (const auto size : {0, 1, 19, 21, 22, 255}) - { + for (const auto size : {0, 1, 19, 21, 22, 255}) { std::string_view scramble = "old"; const std::string data(size, 'x'); CHECK(password_scramble(plugin, data, scramble) == handshake_status::invalid); @@ -181,16 +172,14 @@ DOCTEST_TEST_CASE("MySQL password scramble validation never truncates opaque plu DOCTEST_TEST_CASE("MySQL greetings bound authentication lengths without inspecting opaque plugin bytes") { const auto length_offset = 1 + std::string_view("9.7.2-test").size() + 1 + 4 + 8 + 1 + 7; - for (const auto size : {8, 9, 20, 21, 254, 255}) - { + for (const auto size : {8, 9, 20, 21, 254, 255}) { const std::string data(size, '\xff'); const auto payload = make_greeting(data, "opaque_plugin"); server_greeting result; REQUIRE(parse_greeting(payload, result) == handshake_status::complete); CHECK(result.auth_plugin_data == data); CHECK(result.auth_plugin_name == "opaque_plugin"); - for (std::size_t prefix = 0; prefix < payload.size(); ++prefix) - { + for (std::size_t prefix = 0; prefix < payload.size(); ++prefix) { result = old_greeting(); CHECK(parse_greeting(std::string_view(payload).substr(0, prefix), result) == handshake_status::incomplete); check_greeting_unchanged(result); @@ -201,8 +190,7 @@ DOCTEST_TEST_CASE("MySQL greetings bound authentication lengths without inspecti server_greeting result; CHECK(parse_greeting(payload, result) == handshake_status::complete); CHECK(result.auth_plugin_data == make_scramble()); - for (unsigned size = 1; size < 8; ++size) - { + for (unsigned size = 1; size < 8; ++size) { payload[length_offset] = static_cast(size); result = old_greeting(); CHECK(parse_greeting(payload, result) == handshake_status::invalid); @@ -274,22 +262,18 @@ DOCTEST_TEST_CASE("MySQL handshake response encodes binary auth schema plugin an } DOCTEST_TEST_CASE("MySQL auth response lengths cover one byte and length encoded boundaries") { - for (const auto size : {0, 1, 20, 250, 251, 255, 256, 65535, 65536}) - { + for (const auto size : {0, 1, 20, 250, 251, 255, 256, 65535, 65536}) { const std::string auth(size, '\xff'); handshake_response response; response.username = "u"; response.auth_response = auth; std::string payload = "old"; - if (size <= 255) - { + if (size <= 255) { REQUIRE(encode_handshake_response(response, payload) == handshake_status::complete); REQUIRE(payload.size() == 35 + auth.size()); CHECK(static_cast(payload[34]) == size); CHECK(std::string_view(payload).substr(35) == auth); - } - else - { + } else { CHECK(encode_handshake_response(response, payload) == handshake_status::invalid); CHECK(payload == "old"); } @@ -318,8 +302,7 @@ DOCTEST_TEST_CASE("MySQL response rejects contradictory flags NUL fields and uni response.capabilities = 0; check_invalid(response, handshake_status::unsupported); for (const auto flag : {capability::compress, capability::zstd_compression_algorithm, capability::local_files, - capability::query_attributes, capability::multi_factor_authentication, 1u << 29}) - { + capability::query_attributes, capability::multi_factor_authentication, 1u << 29}) { response = {}; response.capabilities |= flag; check_invalid(response, handshake_status::unsupported); @@ -354,13 +337,10 @@ DOCTEST_TEST_CASE("MySQL response rejects contradictory flags NUL fields and uni } DOCTEST_TEST_CASE("MySQL OK parsing preserves unsigned counts and supports absent or encoded info") { - for (const auto flags : {capability::protocol_41, capability::protocol_41 | capability::session_track}) - { - for (const auto& info : {std::string(), std::string("Rows matched: 5"), std::string("a\0b", 3)}) - { + for (const auto flags : {capability::protocol_41, capability::protocol_41 | capability::session_track}) { + for (const auto& info : {std::string(), std::string("Rows matched: 5"), std::string("a\0b", 3)}) { auto payload = make_ok(); - if (!info.empty()) - { + if (!info.empty()) { append_string(payload, info); } ok_packet result; @@ -392,8 +372,7 @@ DOCTEST_TEST_CASE("MySQL OK session state rejects NULL truncation invalid marker REQUIRE(parse_ok(payload, flags, result) == handshake_status::complete); CHECK(result.info == "done"); CHECK(result.session_state == state); - for (std::size_t size = 0; size < payload.size(); ++size) - { + for (std::size_t size = 0; size < payload.size(); ++size) { CAPTURE(size); result = {11, 12, 13, 14, "old info", "old state"}; CHECK(parse_ok(std::string_view(payload).substr(0, size), flags, result) == handshake_status::incomplete); @@ -422,8 +401,7 @@ DOCTEST_TEST_CASE("MySQL errors parse negotiated SQLSTATE and initial pre-negoti CHECK(result.code == 1045); CHECK(result.sql_state == "28000"); CHECK(result.message == "Access denied"); - for (std::size_t size = 0; size < 9; ++size) - { + for (std::size_t size = 0; size < 9; ++size) { result = {99, "old", "message"}; CHECK(parse_error(std::string_view(payload).substr(0, size), capability::protocol_41, result) == handshake_status::incomplete); @@ -470,16 +448,14 @@ DOCTEST_TEST_CASE("MySQL auth switch preserves plugin bytes and explicitly rejec DOCTEST_TEST_CASE("MySQL handshake payloads are independent of packet sequence and fragmentation") { const auto payload = make_greeting(make_scramble()); - for (const auto initial : std::array{0, 1, 254, 255}) - { + for (const auto initial : std::array{0, 1, 254, 255}) { auto sequence = initial; std::string wire; REQUIRE(encode_message(payload, sequence, wire)); CHECK(sequence == static_cast(initial + 1)); packet_decoder decoder(default_message_limit, initial); std::string decoded; - for (std::size_t index = 0; index < wire.size(); ++index) - { + for (std::size_t index = 0; index < wire.size(); ++index) { std::string_view input(wire.data() + index, 1); const auto status = decoder.consume(input, decoded); CHECK(status == (index + 1 == wire.size() ? packet_status::complete : packet_status::incomplete)); @@ -493,26 +469,21 @@ DOCTEST_TEST_CASE("MySQL handshake payloads are independent of packet sequence a DOCTEST_TEST_CASE("MySQL hostile handshake payloads retain outputs unless fully accepted") { std::mt19937 random(0x4d795351); - for (unsigned iteration = 0; iteration < 2000; ++iteration) - { + for (unsigned iteration = 0; iteration < 2000; ++iteration) { std::string payload(random() % 160, '\0'); - for (auto& byte : payload) - { + for (auto& byte : payload) { byte = static_cast(random() & 0xff); } auto greeting = old_greeting(); - if (parse_greeting(payload, greeting) != handshake_status::complete) - { + if (parse_greeting(payload, greeting) != handshake_status::complete) { check_greeting_unchanged(greeting); } ok_packet ok = {11, 12, 13, 14, "old info", "old state"}; - if (parse_ok(payload, modern_capabilities | capability::session_track, ok) != handshake_status::complete) - { + if (parse_ok(payload, modern_capabilities | capability::session_track, ok) != handshake_status::complete) { check_ok_unchanged(ok); } error_packet error = {99, "old", "message"}; - if (parse_error(payload, capability::protocol_41, error) != handshake_status::complete) - { + if (parse_error(payload, capability::protocol_41, error) != handshake_status::complete) { CHECK(error.code == 99); CHECK(error.sql_state == "old"); CHECK(error.message == "message"); diff --git a/tests/mysql_pool.cpp b/tests/mysql_pool.cpp index 079396f..19c4907 100644 --- a/tests/mysql_pool.cpp +++ b/tests/mysql_pool.cpp @@ -54,8 +54,7 @@ query_result execute( } asio::awaitable wait_for_signal(std::future& signal) { - while (signal.wait_for(0ms) != std::future_status::ready) - { + while (signal.wait_for(0ms) != std::future_status::ready) { asio::steady_timer pause(co_await asio::this_coro::executor); pause.expires_after(2ms); co_await pause.async_wait(asio::use_awaitable); @@ -95,8 +94,7 @@ DOCTEST_TEST_CASE("MySQL pool returns a connection only after a real server rese DOCTEST_TEST_CASE("MySQL pool waiters cancel and expire without breaking FIFO lease ownership") { mysql_peer peer([](mysql_peer& server) -> asio::awaitable { co_await authenticate(server); - for (int index = 0; index < 2; ++index) - { + for (int index = 0; index < 2; ++index) { CHECK(co_await receive_command(server) == std::string(1, '\x1f')); co_await server.send(ok_packet); CHECK(co_await receive_command(server) == std::string(1, '\3') + "USE `test-db`"); @@ -281,8 +279,7 @@ DOCTEST_TEST_CASE("MySQL pool waiter admission remains bounded before executor d }); REQUIRE(entered_result.wait_for(5s) == std::future_status::ready); std::vector> futures; - for (int index = 0; index < 16; ++index) - { + for (int index = 0; index < 16; ++index) { auto promise = std::make_shared>(); futures.push_back(promise->get_future()); pool.async_acquire([promise](auto result) { promise->set_value(std::move(result)); }); @@ -291,14 +288,12 @@ DOCTEST_TEST_CASE("MySQL pool waiter admission remains bounded before executor d REQUIRE(initial_result.wait_for(5s) == std::future_status::ready); auto active = initial_result.get(); REQUIRE(bool(active)); - for (std::size_t index = 2; index < futures.size(); ++index) - { + for (std::size_t index = 2; index < futures.size(); ++index) { REQUIRE(futures[index].wait_for(5s) == std::future_status::ready); CHECK(futures[index].get().status.code == connection_errc::queue_full); } pool.close(); - for (std::size_t index = 0; index < 2; ++index) - { + for (std::size_t index = 0; index < 2; ++index) { REQUIRE(futures[index].wait_for(5s) == std::future_status::ready); CHECK(futures[index].get().status.code == connection_errc::cancelled); } @@ -422,8 +417,7 @@ DOCTEST_TEST_CASE("MySQL pool discards connections when database restoration fai co_await server.send(ok_packet); CHECK(co_await receive_command(server) == std::string(1, '\3') + "USE `test-db`"); restoration_received.set_value(); - if (!timeout) - { + if (!timeout) { std::string error(1, '\xff'); wire::append_integer(error, 1049, 2); error.append("#42000Database no longer exists"); @@ -445,8 +439,7 @@ DOCTEST_TEST_CASE("MySQL pool discards connections when database restoration fai REQUIRE(discarded_result.wait_for(2s) == std::future_status::ready); const auto elapsed = std::chrono::steady_clock::now() - started; DOCTEST_INFO("return elapsed ms = " << std::chrono::duration_cast(elapsed).count()); - if (timeout) - { + if (timeout) { // The pool rounds remaining query time down to whole milliseconds. CHECK(elapsed >= options.reset_timeout - 1ms); } @@ -533,11 +526,9 @@ DOCTEST_TEST_CASE("MySQL real pool shares IO while preserving concurrent exclusi } DOCTEST_TEST_CASE("MySQL pool rejects invalid limits before starting IO") { - for (int setting = 0; setting < 5; ++setting) - { + for (int setting = 0; setting < 5; ++setting) { pool_options options; - switch (setting) - { + switch (setting) { case 0: options.max_connections = 0; break; @@ -559,21 +550,14 @@ DOCTEST_TEST_CASE("MySQL pool rejects invalid limits before starting IO") { } DOCTEST_TEST_CASE("MySQL pool rejects unrepresentable configured deadlines before starting IO") { - for (const auto timeout : {std::chrono::milliseconds::max(), -1ms, 0ms}) - { - for (int setting = 0; setting < 3; ++setting) - { + for (const auto timeout : {std::chrono::milliseconds::max(), -1ms, 0ms}) { + for (int setting = 0; setting < 3; ++setting) { pool_options options; - if (setting == 0) - { + if (setting == 0) { options.acquire_timeout = timeout; - } - else if (setting == 1) - { + } else if (setting == 1) { options.reset_timeout = timeout; - } - else - { + } else { options.connection.timeout = timeout; } CHECK_THROWS_AS(mysql_pool{options}, std::invalid_argument); @@ -591,8 +575,7 @@ DOCTEST_TEST_CASE("MySQL pool validates acquire overrides and preserves zero as mysql_pool pool(options); const auto caller = std::this_thread::get_id(); arknet::mysql::request_id previous = 0; - for (const auto timeout : {std::chrono::milliseconds::max(), -1ms}) - { + for (const auto timeout : {std::chrono::milliseconds::max(), -1ms}) { std::atomic callbacks{}; std::promise completion; auto future = completion.get_future(); diff --git a/tests/mysql_pool_return_failure.cpp b/tests/mysql_pool_return_failure.cpp index e369796..0131385 100644 --- a/tests/mysql_pool_return_failure.cpp +++ b/tests/mysql_pool_return_failure.cpp @@ -29,8 +29,7 @@ using arknet::mysql::pool_options; // A discarded first session must reach EOF. A different physical connection // then authenticates and acknowledges exactly one fresh command; neither RESET // nor replaying the original SQL is accepted on either session. -class replacement_peer -{ +class replacement_peer { asio::io_context context_; asio::ip::tcp::acceptor acceptor_{context_, {asio::ip::address_v4::loopback(), 0}}; asio::steady_timer deadline_{context_}; @@ -64,19 +63,15 @@ class replacement_peer co_await send(socket, ok_packet, 2); const auto command = co_await receive(socket, 0); CHECK(command == (index ? std::string("\3fresh", 6) : std::string("\3written", 8))); - if (!index) - { + if (!index) { ++original_writes; query_received_.set_value(); asio::steady_timer pause(context_); - while (reply_.wait_for(0ms) != std::future_status::ready) - { + while (reply_.wait_for(0ms) != std::future_status::ready) { pause.expires_after(1ms); co_await pause.async_wait(asio::use_awaitable); } - } - else - { + } else { ++fresh_writes; } co_await send(socket, ok_packet, 1); @@ -86,12 +81,9 @@ class replacement_peer co_await socket.async_read_some(asio::buffer(bytes), asio::redirect_error(asio::use_awaitable, error)); CHECK(size == 0); CHECK((error == asio::error::eof || error == asio::error::connection_reset)); - if (!index) - { + if (!index) { old_closed = true; - } - else - { + } else { fresh_closed = true; } } @@ -103,8 +95,7 @@ class replacement_peer replacement_peer() { deadline_.expires_after(5s); deadline_.async_wait([this](error_code error) { - if (!error) - { + if (!error) { completion_.set_exception(std::make_exception_ptr(std::runtime_error("replacement peer deadline"))); context_.stop(); } @@ -112,18 +103,14 @@ class replacement_peer asio::co_spawn( context_, [this]() -> asio::awaitable { - for (unsigned index = 0; index != 2; ++index) - { + for (unsigned index = 0; index != 2; ++index) { auto socket = co_await acceptor_.async_accept(asio::use_awaitable); ++accepted; asio::co_spawn(context_, serve(std::move(socket), index), [this](std::exception_ptr error) { - if (error) - { + if (error) { completion_.set_exception(error); context_.stop(); - } - else if (++finished_ == 2) - { + } else if (++finished_ == 2) { deadline_.cancel(); completion_.set_value(); } @@ -190,8 +177,7 @@ bool query(mysql_pool::lease& lease, const char* sql) { void wait_for_diagnostic(mysql_pool& pool, std::uint64_t count) { const auto deadline = std::chrono::steady_clock::now() + 2s; - while (pool.diagnostics().return_submission_failures < count && std::chrono::steady_clock::now() < deadline) - { + while (pool.diagnostics().return_submission_failures < count && std::chrono::steady_clock::now() < deadline) { std::this_thread::sleep_for(1ms); } const auto diagnostic = pool.diagnostics(); @@ -339,8 +325,7 @@ DOCTEST_TEST_CASE("MySQL ordinary pool shutdown retains a failed child and close wait_for_diagnostic(pool, 1); CHECK(scoped_allocation_failure::injections == before + 1); const auto deadline = std::chrono::steady_clock::now() + 2s; - while (!peer.fresh_closed && std::chrono::steady_clock::now() < deadline) - { + while (!peer.fresh_closed && std::chrono::steady_clock::now() < deadline) { std::this_thread::sleep_for(1ms); } CHECK(peer.fresh_closed); diff --git a/tests/mysql_pool_shutdown.cpp b/tests/mysql_pool_shutdown.cpp index 003de02..9f0ebef 100644 --- a/tests/mysql_pool_shutdown.cpp +++ b/tests/mysql_pool_shutdown.cpp @@ -46,8 +46,7 @@ prepare_result prepare(mysql_pool::lease& lease, std::string sql) { return ready.get(); } -struct destruction_probe -{ +struct destruction_probe { std::atomic& calls; ~destruction_probe() { @@ -55,13 +54,11 @@ struct destruction_probe } }; -struct worker_exit_probe -{ +struct worker_exit_probe { std::shared_ptr> exited; ~worker_exit_probe() { - if (exited) - { + if (exited) { *exited = true; } } @@ -81,8 +78,7 @@ std::string prepare_ok(std::uint32_t id) { // Two simultaneous physical children share one pool runtime. The minimal peer // accepts the usual native-password handshake and acknowledges text commands. -class paired_peer -{ +class paired_peer { asio::io_context context_; asio::ip::tcp::acceptor acceptor_{context_, {asio::ip::address_v4::loopback(), 0}}; asio::steady_timer deadline_{context_}; @@ -112,25 +108,22 @@ class paired_peer co_await send(socket, greeting("mysql_native_password", false), 0); co_await receive(socket, 1); co_await send(socket, ok_packet, 2); - try - { - for (;;) - { + try { + for (;;) { const auto command = co_await receive(socket, 0); CHECK(command == std::string("\3other lease", 12)); co_await send(socket, ok_packet, 1); } + } catch (const arknet::system_error& exception) { + CHECK((exception.code() == asio::error::eof || exception.code() == asio::error::connection_reset)); } - catch (const arknet::system_error& exception) - { CHECK((exception.code() == asio::error::eof || exception.code() == asio::error::connection_reset)); } } public: paired_peer() { deadline_.expires_after(5s); deadline_.async_wait([this](error_code error) { - if (!error) - { + if (!error) { completion_.set_exception(std::make_exception_ptr(std::runtime_error("paired peer deadline"))); context_.stop(); } @@ -138,17 +131,13 @@ class paired_peer asio::co_spawn( context_, [this]() -> asio::awaitable { - for (unsigned index = 0; index != 2; ++index) - { + for (unsigned index = 0; index != 2; ++index) { auto socket = co_await acceptor_.async_accept(asio::use_awaitable); asio::co_spawn(context_, serve(std::move(socket)), [this](std::exception_ptr error) { - if (error) - { + if (error) { completion_.set_exception(error); context_.stop(); - } - else if (++finished_ == 2) - { + } else if (++finished_ == 2) { deadline_.cancel(); completion_.set_value(); } @@ -196,14 +185,12 @@ DOCTEST_TEST_CASE("MySQL pool permanent shutdown rejects future acquisition sync } DOCTEST_TEST_CASE("MySQL pool permanent shutdown cancels authentication and queued waiters before joining") { - for (const bool encrypted : {false, true}) - { + for (const bool encrypted : {false, true}) { std::promise received; auto ready = received.get_future(); mysql_peer peer([&](mysql_peer& server) -> asio::awaitable { co_await server.send(greeting("mysql_native_password", encrypted)); - if (encrypted) - { + if (encrypted) { CHECK((co_await server.receive()).size() == 32); co_await server.upgrade(); } @@ -215,8 +202,7 @@ DOCTEST_TEST_CASE("MySQL pool permanent shutdown cancels authentication and queu std::atomic calls{}, destroyed{}; auto capture = std::make_shared(destroyed); auto exited = std::make_shared>(false); - for (unsigned index = 0; index != 2; ++index) - { + for (unsigned index = 0; index != 2; ++index) { pool.async_acquire([&, capture, exited](auto result) { record_worker_exit(exited); CHECK(result.status.code == connection_errc::cancelled); @@ -303,8 +289,7 @@ DOCTEST_TEST_CASE("MySQL pool IO callback cannot wait for its own runtime") { } DOCTEST_TEST_CASE("MySQL retained discarded leases and statement-only handles become inert") { - for (const bool retain_lease : {false, true}) - { + for (const bool retain_lease : {false, true}) { mysql_peer peer([](mysql_peer& server) -> asio::awaitable { co_await authenticate(server); CHECK(co_await receive_command(server) == std::string("\x16" @@ -322,8 +307,7 @@ DOCTEST_TEST_CASE("MySQL retained discarded leases and statement-only handles be // The returned entry is removed before the barrier; only the old lease // (or statement metadata alone) may still retain its child resources. peer.finish(); - if (!retain_lease) - { + if (!retain_lease) { acquired.connection = {}; } CHECK(pool->close_and_wait()); @@ -389,28 +373,24 @@ DOCTEST_TEST_CASE("MySQL ordinary pool close retires queued waiters before their std::promise queued; auto fenced = queued.get_future(); auto retire = [](mysql_pool* owner) noexcept { - try - { - if (!owner->close_and_wait()) - { + try { + if (!owner->close_and_wait()) { std::terminate(); } + } catch (...) { + std::terminate(); } - catch (...) - { std::terminate(); } }; std::unique_ptr retirement(&pool, retire); pool.async_acquire([&](auto response) { initial_completion.set_value(std::move(response)); }); REQUIRE(initial.wait_for(5s) == std::future_status::ready); auto acquired = initial.get(); REQUIRE(bool(acquired)); - for (std::size_t index = 0; index != ids.size(); ++index) - { + for (std::size_t index = 0; index != ids.size(); ++index) { ids[index] = pool.async_acquire([&, capture, index](auto result) { CHECK_FALSE(result.connection); ++calls[index]; - if (index == 0) - { + if (index == 0) { pool.close(); pool.cancel(ids[1]); pool.async_acquire([&, capture](auto rejected) { @@ -433,14 +413,12 @@ DOCTEST_TEST_CASE("MySQL ordinary pool close retires queued waiters before their CHECK(ready[1].wait_for(0ms) == std::future_status::timeout); capture.reset(); pool.close(); - for (auto& completion : ready) - { + for (auto& completion : ready) { REQUIRE(completion.wait_for(5s) == std::future_status::ready); CHECK(completion.get() == connection_errc::cancelled); } CHECK(pool.close_and_wait()); - for (const auto& count : calls) - { + for (const auto& count : calls) { CHECK(count == 1); } CHECK(destroyed == 1); @@ -476,8 +454,7 @@ DOCTEST_TEST_CASE("MySQL pool shutdown waiters observe complete captures and cal // Only one facade access is allowed to overlap its destruction. Other // concurrent-waiter coverage uses a facade kept alive by the test below. std::thread waiter([&] { - if (caller->close_and_wait()) - { + if (caller->close_and_wait()) { ++closed; } }); @@ -496,12 +473,10 @@ DOCTEST_TEST_CASE("MySQL concurrent pool publication and permanent waiters compl std::atomic calls{}, closed{}; std::latch start(1); std::vector threads; - for (unsigned producer = 0; producer != 2; ++producer) - { + for (unsigned producer = 0; producer != 2; ++producer) { threads.emplace_back([&] { start.wait(); - for (unsigned index = 0; index != 128; ++index) - { + for (unsigned index = 0; index != 128; ++index) { const auto id = pool.async_acquire( [&](auto result) { CHECK_FALSE(result.connection); @@ -512,19 +487,16 @@ DOCTEST_TEST_CASE("MySQL concurrent pool publication and permanent waiters compl } }); } - for (unsigned index = 0; index != 3; ++index) - { + for (unsigned index = 0; index != 3; ++index) { threads.emplace_back([&] { start.wait(); - if (pool.close_and_wait()) - { + if (pool.close_and_wait()) { ++closed; } }); } start.count_down(); - for (auto& thread : threads) - { + for (auto& thread : threads) { thread.join(); } CHECK(calls == 256); @@ -619,8 +591,7 @@ DOCTEST_TEST_CASE("MySQL pool waits for an executing callback and rejects work a auto complete_ready = completed.get_future(); std::thread waiter([&] { completed.set_value(pool.close_and_wait()); }); arknet::mysql::request_id id = 1; - while (id) - { + while (id) { id = pool.async_acquire( [&](auto result) { CHECK((result.status.code == connection_errc::not_connected || @@ -628,8 +599,7 @@ DOCTEST_TEST_CASE("MySQL pool waits for an executing callback and rejects work a result.status.code == connection_errc::invalid_options)); }, -1ms); - if (id) - { + if (id) { std::this_thread::sleep_for(1ms); } } @@ -661,12 +631,10 @@ DOCTEST_TEST_CASE("MySQL retained lease producers race safely with permanent poo REQUIRE(ready.wait_for(5s) == std::future_status::ready); std::latch start(1); std::vector producers; - for (unsigned producer = 0; producer != 2; ++producer) - { + for (unsigned producer = 0; producer != 2; ++producer) { producers.emplace_back([&] { start.wait(); - for (unsigned index = 0; index != 128; ++index) - { + for (unsigned index = 0; index != 128; ++index) { const auto id = acquired.connection.async_query("queued", [&](auto result) { CHECK((result.status.code == connection_errc::cancelled || result.status.code == connection_errc::not_connected)); @@ -679,8 +647,7 @@ DOCTEST_TEST_CASE("MySQL retained lease producers race safely with permanent poo } start.count_down(); CHECK(pool.close_and_wait()); - for (auto& producer : producers) - { + for (auto& producer : producers) { producer.join(); } CHECK(calls == 257); @@ -707,11 +674,9 @@ DOCTEST_TEST_CASE("MySQL concurrent pool waiters observe callback destruction an capture.reset(); REQUIRE(entered_ready.wait_for(5s) == std::future_status::ready); std::vector waiters; - for (unsigned index = 0; index != 3; ++index) - { + for (unsigned index = 0; index != 3; ++index) { waiters.emplace_back([&] { - if (pool.close_and_wait()) - { + if (pool.close_and_wait()) { CHECK(calls == 1); CHECK(destroyed == 1); CHECK(exited->load()); @@ -721,14 +686,12 @@ DOCTEST_TEST_CASE("MySQL concurrent pool waiters observe callback destruction an } // Observe the closed publication gate while the callback still owns its // capture. The finalizer and its concurrent waiters must all remain pending. - while (pool.async_acquire([](auto) {}, -1ms)) - { + while (pool.async_acquire([](auto) {}, -1ms)) { std::this_thread::sleep_for(1ms); } CHECK(closed == 0); resume.set_value(); - for (auto& waiter : waiters) - { + for (auto& waiter : waiters) { waiter.join(); } CHECK(closed == 3); diff --git a/tests/mysql_shutdown.cpp b/tests/mysql_shutdown.cpp index 871f85c..6be5eb1 100644 --- a/tests/mysql_shutdown.cpp +++ b/tests/mysql_shutdown.cpp @@ -14,8 +14,7 @@ using namespace mysql_test; namespace { -struct destruction_probe -{ +struct destruction_probe { std::atomic& calls; ~destruction_probe() { @@ -23,13 +22,11 @@ struct destruction_probe } }; -struct worker_exit_probe -{ +struct worker_exit_probe { std::shared_ptr> exited; ~worker_exit_probe() { - if (exited) - { + if (exited) { *exited = true; } } @@ -81,14 +78,12 @@ DOCTEST_TEST_CASE("MySQL permanent shutdown works before connect and rejects fut } DOCTEST_TEST_CASE("MySQL permanent shutdown retires cancelled authentication captures and joins its worker") { - for (const bool encrypted : {false, true}) - { + for (const bool encrypted : {false, true}) { std::promise received; auto ready = received.get_future(); mysql_peer peer([&](mysql_peer& server) -> asio::awaitable { co_await server.send(greeting("mysql_native_password", encrypted)); - if (encrypted) - { + if (encrypted) { CHECK((co_await server.receive()).size() == 32); co_await server.upgrade(); } @@ -180,20 +175,16 @@ DOCTEST_TEST_CASE("MySQL IO-thread permanent shutdown rejects without terminatin DOCTEST_TEST_CASE("MySQL ordinary close still permits reconnect before permanent shutdown") { connection client; - for (unsigned iteration = 0; iteration != 2; ++iteration) - { + for (unsigned iteration = 0; iteration != 2; ++iteration) { mysql_peer peer([](mysql_peer& server) -> asio::awaitable { co_await authenticate(server); co_await server.wait_for_eof(); }); REQUIRE(bool(connect(client, options_for(peer)))); CHECK(client.is_open()); - if (!iteration) - { + if (!iteration) { client.close(); - } - else - { + } else { CHECK(client.close_and_wait()); } peer.finish(); @@ -268,12 +259,10 @@ DOCTEST_TEST_CASE("MySQL concurrent submit and permanent shutdown completes each std::atomic completions{}; std::latch start(1); std::vector submitters; - for (unsigned producer = 0; producer != 2; ++producer) - { + for (unsigned producer = 0; producer != 2; ++producer) { submitters.emplace_back([&] { start.wait(); - for (unsigned request = 0; request != 128; ++request) - { + for (unsigned request = 0; request != 128; ++request) { client.async_query("not connected", [&](query_result response) { CHECK(response.status.code == connection_errc::not_connected); ++completions; @@ -284,8 +273,7 @@ DOCTEST_TEST_CASE("MySQL concurrent submit and permanent shutdown completes each } start.count_down(); CHECK(client.close_and_wait()); - for (auto& submitter : submitters) - { + for (auto& submitter : submitters) { submitter.join(); } CHECK(completions == 256); @@ -302,12 +290,10 @@ DOCTEST_TEST_CASE("MySQL concurrent permanent shutdown waiters observe complete std::latch start(1); std::atomic closed{}; std::vector waiters; - for (unsigned index = 0; index != 3; ++index) - { + for (unsigned index = 0; index != 3; ++index) { waiters.emplace_back([&] { start.wait(); - if (client.close_and_wait()) - { + if (client.close_and_wait()) { CHECK(destroyed == 1); CHECK(completions == 1); ++closed; @@ -315,8 +301,7 @@ DOCTEST_TEST_CASE("MySQL concurrent permanent shutdown waiters observe complete }); } start.count_down(); - for (auto& waiter : waiters) - { + for (auto& waiter : waiters) { waiter.join(); } CHECK(closed == 3); diff --git a/tests/mysql_test_peer.hpp b/tests/mysql_test_peer.hpp index c8cbedf..593ba71 100644 --- a/tests/mysql_test_peer.hpp +++ b/tests/mysql_test_peer.hpp @@ -33,8 +33,7 @@ using arknet::mysql::connection_result; using arknet::mysql::tls_mode; namespace wire = arknet::mysql::detail; -class mysql_peer -{ +class mysql_peer { asio::io_context context_; asio::ip::tcp::acceptor acceptor_{context_, {asio::ip::address_v4::loopback(), 0}}; asio::ssl::context tls_context_{asio::ssl::context::tls_server}; @@ -60,8 +59,7 @@ class mysql_peer stream_.native_handle(), (directory + "/server-key.pem").c_str(), SSL_FILETYPE_PEM) == 1); deadline_.expires_after(5s); deadline_.async_wait([this](error_code error) { - if (!error) - { + if (!error) { error_code ignored; acceptor_.close(ignored); stream_.next_layer().close(ignored); @@ -75,12 +73,9 @@ class mysql_peer }, [this](std::exception_ptr exception) { deadline_.cancel(); - if (exception) - { + if (exception) { completion_.set_exception(exception); - } - else - { + } else { completion_.set_value(); } }); @@ -98,15 +93,11 @@ class mysql_peer asio::awaitable write_raw(std::string bytes, bool fragment = false) { const auto stride = fragment ? 1u : bytes.size(); - for (std::size_t offset = 0; offset < bytes.size(); offset += stride) - { + for (std::size_t offset = 0; offset < bytes.size(); offset += stride) { const auto buffer = asio::buffer(bytes.data() + offset, (std::min)(stride, bytes.size() - offset)); - if (encrypted_) - { + if (encrypted_) { co_await asio::async_write(stream_, buffer, asio::use_awaitable); - } - else - { + } else { co_await asio::async_write(stream_.next_layer(), buffer, asio::use_awaitable); } } @@ -120,24 +111,18 @@ class mysql_peer asio::awaitable receive(std::size_t maximum_size = 4096) { std::array header{}; - if (encrypted_) - { + if (encrypted_) { co_await asio::async_read(stream_, asio::buffer(header), asio::use_awaitable); - } - else - { + } else { co_await asio::async_read(stream_.next_layer(), asio::buffer(header), asio::use_awaitable); } REQUIRE(header[3] == sequence++); const std::size_t size = header[0] | (std::size_t(header[1]) << 8) | (std::size_t(header[2]) << 16); REQUIRE(size <= maximum_size); std::string payload(size, '\0'); - if (encrypted_) - { + if (encrypted_) { co_await asio::async_read(stream_, asio::buffer(payload), asio::use_awaitable); - } - else - { + } else { co_await asio::async_read(stream_.next_layer(), asio::buffer(payload), asio::use_awaitable); } co_return payload; @@ -153,16 +138,12 @@ class mysql_peer asio::awaitable wait_for_eof() { std::array bytes{}; error_code error; - do - { - if (encrypted_) - { + do { + if (encrypted_) { const auto size = co_await stream_.async_read_some( asio::buffer(bytes), asio::redirect_error(asio::use_awaitable, error)); CHECK(size == 0); - } - else - { + } else { co_await stream_.next_layer().async_read_some( asio::buffer(bytes), asio::redirect_error(asio::use_awaitable, error)); } @@ -328,9 +309,8 @@ inline asio::awaitable receive_command(mysql_peer& server) { inline std::string column_packet(std::string_view name, wire::field_type type = wire::field_type::longlong) { std::string payload; - for (const auto value : - {std::string_view("def"), std::string_view("test-db"), std::string_view(""), std::string_view(""), name, name}) - { + for (const auto value : {std::string_view("def"), std::string_view("test-db"), std::string_view(""), + std::string_view(""), name, name}) { wire::append_string(payload, value); } payload.push_back('\x0c'); diff --git a/tests/mysql_wire.cpp b/tests/mysql_wire.cpp index 2f97d6d..012585d 100644 --- a/tests/mysql_wire.cpp +++ b/tests/mysql_wire.cpp @@ -25,8 +25,7 @@ std::string encode(std::string_view payload, std::uint8_t sequence = 0) { DOCTEST_TEST_CASE("MySQL integers retain all unsigned bits and distinguish NULL") { for (const auto value : std::array{0, 1, 250, 251, 255, 256, 65535, 65536, 0xffffff, 0x1000000, - std::uint64_t(1) << 63, std::numeric_limits::max()}) - { + std::uint64_t(1) << 63, std::numeric_limits::max()}) { std::string wire; append_length(wire, value); wire_reader reader(wire); @@ -35,8 +34,7 @@ DOCTEST_TEST_CASE("MySQL integers retain all unsigned bits and distinguish NULL" REQUIRE(result.has_value()); CHECK(*result == value); CHECK(reader.remaining() == 0); - for (std::size_t size = 0; size < wire.size(); ++size) - { + for (std::size_t size = 0; size < wire.size(); ++size) { wire_reader partial(std::string_view(wire).substr(0, size)); result = 42; CHECK(partial.read_length(result) == read_status::incomplete); @@ -60,8 +58,7 @@ DOCTEST_TEST_CASE("MySQL integers retain all unsigned bits and distinguish NULL" } DOCTEST_TEST_CASE("MySQL fixed integers reject invalid widths and preserve incomplete output") { - for (std::size_t width = 1; width <= 8; ++width) - { + for (std::size_t width = 1; width <= 8; ++width) { const auto maximum = width == 8 ? std::numeric_limits::max() : (std::uint64_t(1) << (width * 8)) - 1; std::string wire; @@ -103,8 +100,7 @@ DOCTEST_TEST_CASE("MySQL strings preserve binary data NULL empty and transaction wire.clear(); append_string(wire, binary); - for (std::size_t size = 0; size < wire.size(); ++size) - { + for (std::size_t size = 0; size < wire.size(); ++size) { wire_reader partial(std::string_view(wire).substr(0, size)); result = "previous"; CHECK(partial.read_string(result) == read_status::incomplete); @@ -131,8 +127,7 @@ DOCTEST_TEST_CASE("MySQL strings preserve binary data NULL empty and transaction DOCTEST_TEST_CASE("MySQL packet decoder accepts every stream split without exposing partial messages") { const std::string payload("binary\0payload\xff", 15); const auto wire = encode(payload, 7); - for (std::size_t split = 0; split < wire.size(); ++split) - { + for (std::size_t split = 0; split < wire.size(); ++split) { packet_decoder decoder(1024, 7); auto input = std::string_view(wire).substr(0, split); std::string result = "previous"; @@ -148,8 +143,7 @@ DOCTEST_TEST_CASE("MySQL packet decoder accepts every stream split without expos } packet_decoder decoder(1024, 7); std::string result; - for (std::size_t i = 0; i < wire.size(); ++i) - { + for (std::size_t i = 0; i < wire.size(); ++i) { auto input = std::string_view(wire).substr(i, 1); CHECK(decoder.consume(input, result) == (i + 1 == wire.size() ? packet_status::complete : packet_status::incomplete)); @@ -161,8 +155,7 @@ DOCTEST_TEST_CASE("MySQL packet decoder accepts every stream split without expos DOCTEST_TEST_CASE("MySQL coalesced response packets retain sequence across wrap and reset per command") { std::string wire; std::uint8_t sequence = 250; - for (int i = 0; i < 300; ++i) - { + for (int i = 0; i < 300; ++i) { std::string part; REQUIRE(encode_message(std::to_string(i), sequence, part)); wire += part; @@ -170,8 +163,7 @@ DOCTEST_TEST_CASE("MySQL coalesced response packets retain sequence across wrap auto input = std::string_view(wire); packet_decoder decoder(1024, 250); std::string result; - for (int i = 0; i < 300; ++i) - { + for (int i = 0; i < 300; ++i) { REQUIRE(decoder.consume(input, result) == packet_status::complete); CHECK(result == std::to_string(i)); } @@ -185,8 +177,7 @@ DOCTEST_TEST_CASE("MySQL coalesced response packets retain sequence across wrap } DOCTEST_TEST_CASE("MySQL maximum physical payload requires a continuation packet including an empty terminator") { - for (const auto size : {max_packet_payload - 1, max_packet_payload, max_packet_payload + 1}) - { + for (const auto size : {max_packet_payload - 1, max_packet_payload, max_packet_payload + 1}) { const std::string payload(size, 'x'); auto sequence = std::uint8_t(255); std::string wire; @@ -242,19 +233,16 @@ DOCTEST_TEST_CASE("MySQL random fragmented messages roundtrip with bounded retai std::mt19937 random(12345); packet_decoder decoder(2048); std::uint8_t sequence = 0; - for (int i = 0; i < 1000; ++i) - { + for (int i = 0; i < 1000; ++i) { std::string payload(random() % 2049, '\0'); - for (auto& byte : payload) - { + for (auto& byte : payload) { byte = static_cast(random()); } std::string wire; REQUIRE(encode_message(payload, sequence, wire, 2048)); std::size_t position = 0; std::string result = "previous"; - while (position < wire.size()) - { + while (position < wire.size()) { const auto size = std::min(random() % 127 + 1, wire.size() - position); auto input = std::string_view(wire).substr(position, size); position += size; @@ -262,8 +250,7 @@ DOCTEST_TEST_CASE("MySQL random fragmented messages roundtrip with bounded retai CHECK(status == (position == wire.size() ? packet_status::complete : packet_status::incomplete)); CHECK(input.empty()); CHECK(decoder.buffered_size() <= payload.size() + 4); - if (status == packet_status::incomplete) - { + if (status == packet_status::incomplete) { CHECK(result == "previous"); } } diff --git a/tests/owned_runtime_exit.cpp b/tests/owned_runtime_exit.cpp index 0b2de9c..041f6f8 100644 --- a/tests/owned_runtime_exit.cpp +++ b/tests/owned_runtime_exit.cpp @@ -29,17 +29,14 @@ void fail(const char* message, int status) { void verify_exit() { // Other translation units may initialize cleanup before this sentinel. // Their global-owner test is validated by ordinary, bounded process exit. - if (multi_tu_exit.load()) - { + if (multi_tu_exit.load()) { return; } const auto expected = expected_callbacks.load(); - if (finished_callbacks.load() != expected || destroyed_resources.load() != expected) - { + if (finished_callbacks.load() != expected || destroyed_resources.load() != expected) { fail("owned runtime exited before callbacks and resources were released", 41); } - if (check_late_construction.load() && late_constructions_rejected.load() != 2) - { + if (check_late_construction.load() && late_constructions_rejected.load() != 2) { fail("owned runtime accepted construction after process cleanup", 42); } } @@ -48,11 +45,9 @@ void try_late_construction(); // These callbacks are registered before the global pool is constructed. They // therefore run after both the pool destructor and the runtime cleanup. -struct exit_checks -{ +struct exit_checks { exit_checks() { - if (std::atexit(verify_exit) || std::atexit(try_late_construction)) - { + if (std::atexit(verify_exit) || std::atexit(try_late_construction)) { fail("could not register process-exit checks", 43); } } @@ -68,26 +63,26 @@ namespace { using arknet::detail::io_pool; using namespace std::chrono_literals; -class owned_endpoint : public arknet::detail::iopool_cp -{ +class owned_endpoint : public arknet::detail::iopool_cp { public: owned_endpoint() : iopool_cp(1) {} }; void try_late_construction() { - if (!check_late_construction.load()) - { + if (!check_late_construction.load()) { return; } - try - { io_pool late(1); } - catch (const std::logic_error&) - { ++late_constructions_rejected; } - try - { owned_endpoint late; } - catch (const std::logic_error&) - { ++late_constructions_rejected; } + try { + io_pool late(1); + } catch (const std::logic_error&) { + ++late_constructions_rejected; + } + try { + owned_endpoint late; + } catch (const std::logic_error&) { + ++late_constructions_rejected; + } } // Its destructor registration predates direct global_pool construction. This @@ -95,8 +90,7 @@ void try_late_construction() { std::shared_ptr indirect_owner; io_pool global_pool(1); -struct callback_resource -{ +struct callback_resource { #if defined(ARKNET_ENABLE_SSL) // Constructed in main, after runtime cleanup has already been registered. std::unique_ptr tls = std::make_unique(asio::ssl::context::tls_client); @@ -114,8 +108,7 @@ using owned_pool = std::shared_ptr; owned_pool make_pool() { owned_pool pool(new io_pool(1), arknet::detail::iopool_deleter{}); - if (!pool->start()) - { + if (!pool->start()) { fail("could not start owned IO pool", 44); } return pool; @@ -126,12 +119,10 @@ void schedule_owned(owned_pool pool, bool nested, std::chrono::milliseconds dela auto resource = std::make_shared(); ++expected_callbacks; timer->async_wait([pool = std::move(pool), timer, resource = std::move(resource), nested](auto error) mutable { - if (error) - { + if (error) { fail("owned callback was unexpectedly cancelled", 45); } - if (nested) - { + if (nested) { schedule_owned(make_pool(), false, 10ms); } ++finished_callbacks; @@ -143,8 +134,7 @@ void schedule_owned(owned_pool pool, bool nested, std::chrono::milliseconds dela #if defined(ARKNET_ENABLE_SSL) void verify_openssl_exit() { - if (destroyed_resources.load() != expected_callbacks.load()) - { + if (destroyed_resources.load() != expected_callbacks.load()) { fail("OpenSSL cleanup started before owned TLS resources were destroyed", 46); } } @@ -158,50 +148,38 @@ int main(int argc, char** argv) { // Explicit initialization keeps this direct-pool test equivalent. arknet::detail::pool_cleanup(); #if defined(ARKNET_ENABLE_SSL) - if (!OPENSSL_init_ssl(0, nullptr) || !OPENSSL_atexit(verify_openssl_exit)) - { + if (!OPENSSL_init_ssl(0, nullptr) || !OPENSSL_atexit(verify_openssl_exit)) { fail("could not initialize OpenSSL exit check", 47); } #endif - if (mode == "global") - { - if (!global_pool.start()) - { + if (mode == "global") { + if (!global_pool.start()) { return 48; } auto timer = std::make_shared(global_pool.get_context(), 1s); auto resource = std::make_shared(); ++expected_callbacks; timer->async_wait([timer, resource = std::move(resource)](auto error) { - if (error) - { + if (error) { fail("global pool callback was unexpectedly cancelled", 49); } ++finished_callbacks; }); - } - else if (mode == "callback" || mode == "nested") - { + } else if (mode == "callback" || mode == "nested") { schedule_owned(make_pool(), mode == "nested", 1s); - } - else if (mode == "late") - { + } else if (mode == "late") { check_late_construction = true; - } - else if (mode == "indirect") - { + } else if (mode == "indirect") { indirect_owner = make_pool(); } #if defined(ARKNET_TEST_MULTI_TU) - else if (mode == "external") - { + else if (mode == "external") { multi_tu_exit = true; indirect_owner = make_pool(); arknet_test_hold_external_runtime(); } #endif - else - { + else { return 50; } // Returning while the timer is pending deliberately overlaps ordinary diff --git a/tests/queued_connect_cancellation.cpp b/tests/queued_connect_cancellation.cpp index 1220d0b..e81c864 100644 --- a/tests/queued_connect_cancellation.cpp +++ b/tests/queued_connect_cancellation.cpp @@ -10,8 +10,7 @@ namespace { -struct io_runner -{ +struct io_runner { asio::io_context context; asio::executor_work_guard work{context.get_executor()}; std::thread worker{[this] { context.run(); }}; @@ -77,8 +76,7 @@ DOCTEST_TEST_CASE("TCP stop from init reports one aborted connect without reachi }); client.bind_connect([&] { const auto error = arknet::get_last_error(); - if (++connected == 1) - { + if (++connected == 1) { completion.set_value(error); } }); @@ -160,8 +158,7 @@ DOCTEST_TEST_CASE("TCP stop queued from TLS init aborts before beginning a silen client.bind_handshake([&] { ++handshakes; }); client.bind_connect([&] { const auto error = arknet::get_last_error(); - if (++connected == 1) - { + if (++connected == 1) { completion.set_value(error); } }); @@ -170,8 +167,7 @@ DOCTEST_TEST_CASE("TCP stop queued from TLS init aborts before beginning a silen // This bound detects waiting for the configured 30s handshake/stop timers. const bool cancelled = completed.wait_for(5s) == std::future_status::ready; DOCTEST_CHECK(cancelled); - if (!cancelled) - { + if (!cancelled) { // Bound the negative regression too: close the pending kernel peer // connection so old code leaves its intentionally silent TLS handshake. asio::ip::tcp::socket accepted(peer_context); diff --git a/tests/redis_child_close_failure.cpp b/tests/redis_child_close_failure.cpp index eb1af80..01ee8cf 100644 --- a/tests/redis_child_close_failure.cpp +++ b/tests/redis_child_close_failure.cpp @@ -28,16 +28,14 @@ namespace { using namespace redis_test; -struct observations -{ +struct observations { std::atomic callbacks{}, captures{}, transports{}, constructed{}, stop_entries{}, stop_failures{}; }; observations observed; thread_local unsigned inject_on_stop{}; -class observed_transport : public arknet::tcp_client -{ +class observed_transport : public arknet::tcp_client { public: template explicit observed_transport(Args&&... args) @@ -57,8 +55,7 @@ class observed_transport : public arknet::tcp_client std::lock_guard lock(callbacks_mutex); callback = std::exchange(before_connect, {}); } - if (callback) - { + if (callback) { callback(); } arknet::set_last_error(error); @@ -69,14 +66,12 @@ class observed_transport : public arknet::tcp_client void stop() { ++observed.stop_entries; - if (inject_on_stop && !--inject_on_stop) - { + if (inject_on_stop && !--inject_on_stop) { scoped_allocation_failure::arm(); } - try - { arknet::tcp_client::stop(); } - catch (...) - { + try { + arknet::tcp_client::stop(); + } catch (...) { ++observed.stop_failures; throw; } @@ -90,8 +85,7 @@ class observed_transport : public arknet::tcp_client using observed_client = arknet::redis::basic_client; using observed_pool = arknet::redis::basic_pool; -struct capture_probe -{ +struct capture_probe { ~capture_probe() { ++observed.captures; } @@ -111,15 +105,13 @@ void reset_observations() { template auto retirement_guard(Endpoint& endpoint) { auto retire = [](Endpoint* owner) noexcept { - try - { - if (!owner->close_and_wait()) - { + try { + if (!owner->close_and_wait()) { std::terminate(); } + } catch (...) { + std::terminate(); } - catch (...) - { std::terminate(); } }; return std::unique_ptr(&endpoint, retire); } @@ -146,20 +138,19 @@ void close_concurrently(Endpoint& endpoint) { std::array failures{}; std::latch start{1}; std::vector owners; - for (std::size_t index{}; index != completed.size(); ++index) - { + for (std::size_t index{}; index != completed.size(); ++index) { owners.emplace_back([&, index] { start.wait(); - try - { completed[index] = endpoint.close_and_wait(); } - catch (...) - { failures[index] = std::current_exception(); } + try { + completed[index] = endpoint.close_and_wait(); + } catch (...) { + failures[index] = std::current_exception(); + } }); } start.count_down(); owners.clear(); - for (std::size_t index{}; index != completed.size(); ++index) - { + for (std::size_t index{}; index != completed.size(); ++index) { CHECK_FALSE(failures[index]); CHECK(completed[index]); } @@ -205,14 +196,11 @@ void pending_connect(bool fault) { close_concurrently(connection); CHECK(connection.close_and_wait()); check_retirement(fault, injections); - if (ready.wait_for(0ms) == std::future_status::ready) - { + if (ready.wait_for(0ms) == std::future_status::ready) { const auto response = ready.get(); CHECK(response.error == client_errc::closed); CHECK_FALSE(response.command_may_have_run); - } - else - { + } else { FAIL_CHECK("Accepted connect callback was not terminalized before permanent close returned"); } endpoint.finish(); @@ -266,21 +254,17 @@ void pending_command(bool fault) { CHECK(connections.close_and_wait()); CHECK_FALSE(leased.connection); check_retirement(fault, injections); - if (ready.wait_for(0ms) == std::future_status::ready) - { + if (ready.wait_for(0ms) == std::future_status::ready) { const auto response = ready.get(); CHECK(response.error == client_errc::closed); CHECK(response.command_may_have_run); - } - else - { + } else { FAIL_CHECK("Accepted command callback was not terminalized before permanent close returned"); } endpoint.finish(); } -class paired_server -{ +class paired_server { asio::io_context context_; asio::ip::tcp::acceptor acceptor_{context_, {asio::ip::address_v4::loopback(), 0}}; std::promise received_; @@ -296,59 +280,45 @@ class paired_server std::vector peers; std::mutex failures_mutex; std::exception_ptr failure; - try - { - for (unsigned index{}; index != 2; ++index) - { + try { + for (unsigned index{}; index != 2; ++index) { asio::ip::tcp::socket socket(context_); const auto deadline = std::chrono::steady_clock::now() + 5s; - for (;;) - { + for (;;) { arknet::error_code error; acceptor_.accept(socket, error); - if (!error) - { + if (!error) { break; } if ((error != asio::error::would_block && error != asio::error::try_again) || - std::chrono::steady_clock::now() >= deadline) - { + std::chrono::steady_clock::now() >= deadline) { throw std::runtime_error("Two-child Redis peer accept failed"); } std::this_thread::sleep_for(1ms); } peers.emplace_back([&, socket = std::move(socket)]() mutable { - try - { + try { peer remote(socket); remote.hello(); remote.expect({"ECHO", "held"}); - if (++commands_ == 2) - { + if (++commands_ == 2) { received_.set_value(); } remote.wait_closed(); - } - catch (...) - { + } catch (...) { std::lock_guard lock(failures_mutex); failure = std::current_exception(); } }); } - } - catch (...) - { + } catch (...) { std::lock_guard lock(failures_mutex); failure = std::current_exception(); } peers.clear(); - if (failure) - { + if (failure) { completion_.set_exception(failure); - } - else - { + } else { completion_.set_value(); } }); @@ -388,15 +358,13 @@ void close_two_children(unsigned fault) { auto close_done = closed.get_future(); auto retirement = retirement_guard(connections); std::array leased; - for (std::size_t index{}; index != initial.size(); ++index) - { + for (std::size_t index{}; index != initial.size(); ++index) { connections.async_acquire([&, index](auto response) { acquired[index].set_value(std::move(response)); }); REQUIRE(initial[index].wait_for(5s) == std::future_status::ready); leased[index] = initial[index].get(); REQUIRE(leased[index]); } - for (std::size_t index{}; index != ready.size(); ++index) - { + for (std::size_t index{}; index != ready.size(); ++index) { auto capture = std::make_shared(); leased[index].connection.async_execute( {"ECHO", "held"}, @@ -423,8 +391,7 @@ void close_two_children(unsigned fault) { close_concurrently(connections); CHECK(connections.close_and_wait()); check_retirement(true, injections, 2); - for (std::size_t index{}; index != ready.size(); ++index) - { + for (std::size_t index{}; index != ready.size(); ++index) { CHECK_FALSE(leased[index].connection); REQUIRE(ready[index].wait_for(0ms) == std::future_status::ready); const auto response = ready[index].get(); @@ -454,15 +421,13 @@ void fail_native_stop_after_actor(observed_pool& connections, Submit submit) { inject_on_stop = 1; }, -1ms); - if (ready.wait_for(5s) != std::future_status::ready) - { + if (ready.wait_for(5s) != std::future_status::ready) { published->count_down(); FAIL("Pool actor did not reach native-stop arm barrier"); } - try - { submit(); } - catch (...) - { + try { + submit(); + } catch (...) { published->count_down(); throw; } @@ -508,17 +473,14 @@ void retained_retirement(bool failed_return) { actor_barrier(connections); CHECK(ready.wait_for(0ms) != std::future_status::ready); const auto injections = scoped_allocation_failure::injections.load(); - if (failed_return) - { + if (failed_return) { { scoped_allocation_failure::scope failure; held.reset(); } CHECK(scoped_allocation_failure::injections == injections + 1); fail_native_stop_after_actor(connections, [&] { connections.cancel(0); }); - } - else - { + } else { fail_native_stop_after_actor(connections, [&] { held->connection.discard(); held.reset(); diff --git a/tests/redis_client.cpp b/tests/redis_client.cpp index c0e38c3..a572b05 100644 --- a/tests/redis_client.cpp +++ b/tests/redis_client.cpp @@ -18,21 +18,14 @@ using namespace redis_test; DOCTEST_TEST_CASE("Redis configured deadlines reject nonpositive and unrepresentable durations") { - for (const auto timeout : {0ms, -1ms, std::chrono::milliseconds::max()}) - { - for (int setting = 0; setting < 3; ++setting) - { + for (const auto timeout : {0ms, -1ms, std::chrono::milliseconds::max()}) { + for (int setting = 0; setting < 3; ++setting) { arknet::redis::client_options options; - if (setting == 0) - { + if (setting == 0) { options.connect_timeout = timeout; - } - else if (setting == 1) - { + } else if (setting == 1) { options.command_timeout = timeout; - } - else - { + } else { options.drain_timeout = timeout; } DOCTEST_CHECK_THROWS_AS(client{options}, std::invalid_argument); @@ -52,8 +45,7 @@ DOCTEST_TEST_CASE("Redis invalid command deadlines reject on the IO lane without client connection(options); check(connect(connection, endpoint.port()), "connected"); const auto caller = std::this_thread::get_id(); - for (const auto timeout : {-1ms, std::chrono::milliseconds::min(), std::chrono::milliseconds::max()}) - { + for (const auto timeout : {-1ms, std::chrono::milliseconds::min(), std::chrono::milliseconds::max()}) { std::promise completed; auto response = completed.get_future(); const auto id = connection.async_execute( @@ -88,8 +80,7 @@ DOCTEST_TEST_CASE("Redis RESP3 handshake and fragmented binary pipeline keep pus remote.expect({"PING"}); const std::string replies = ">2\r\n+invalidate\r\n*1\r\n+k\r\n$5\r\n" + binary + "\r\n-WRONGTYPE expected\r\n+PONG\r\n"; - for (char byte : replies) - { + for (char byte : replies) { remote.send(std::string_view(&byte, 1)); } }); @@ -152,8 +143,7 @@ DOCTEST_TEST_CASE("Redis cancelled sent commands drain their own slots and retai remote.expect({"INCR", "first"}); remote.expect({"INCR", "second"}); received.set_value(); - if (allowed.wait_for(5s) != std::future_status::ready) - { + if (allowed.wait_for(5s) != std::future_status::ready) { throw std::runtime_error("Missing release signal"); } remote.send(":11\r\n:22\r\n"); @@ -197,8 +187,7 @@ DOCTEST_TEST_CASE("Redis timeouts drain replies without shifting later response remote.hello(); remote.expect({"GET", "slow"}); remote.expect({"GET", "following"}); - if (allowed.wait_for(5s) != std::future_status::ready) - { + if (allowed.wait_for(5s) != std::future_status::ready) { throw std::runtime_error("Missing timeout signal"); } remote.send(":10\r\n:20\r\n"); @@ -241,13 +230,11 @@ DOCTEST_TEST_CASE("Redis undrainable cancelled commands close the connection aft } DOCTEST_TEST_CASE("Redis disconnect and malformed replies fail outstanding commands exactly once") { - for (bool malformed : {false, true}) - { + for (bool malformed : {false, true}) { server endpoint([&](peer& remote) { remote.hello(); remote.expect({"SET", "key", "value"}); - if (malformed) - { + if (malformed) { remote.send("?invalid\r\n"); } }); @@ -273,8 +260,7 @@ DOCTEST_TEST_CASE("Redis failed pipelines retain complete owning replies and ret const std::string binary("a\0\r\nb", 5); const std::vector> failures{{",1.\r\n+VALID\r\n", client_errc::protocol_error}, {"*1\r\n>1\r\n+invalid\r\n+VALID\r\n", client_errc::protocol_error}, {"$3\r\nab", client_errc::disconnected}}; - for (const auto& [wire, error] : failures) - { + for (const auto& [wire, error] : failures) { server endpoint([&](peer& remote) { remote.hello(); remote.expect({"ECHO", binary}); @@ -318,8 +304,7 @@ DOCTEST_TEST_CASE("Redis cancellation preserves an owning pipeline prefix while remote.expect({"INCR", "cancelled"}); remote.expect({"GET", "following"}); remote.send("$5\r\n" + binary + "\r\n>1\r\n+prefix-ready\r\n"); - if (allowed.wait_for(5s) != std::future_status::ready) - { + if (allowed.wait_for(5s) != std::future_status::ready) { throw std::runtime_error("Missing drain signal"); } remote.send(":22\r\n:33\r\n"); @@ -404,19 +389,15 @@ DOCTEST_TEST_CASE("Redis handshake errors retain server diagnostics and never fa } DOCTEST_TEST_CASE("Redis rejects malformed non-HELLO handshake replies") { - for (auto protocol : {arknet::redis::protocol_version::resp2, arknet::redis::protocol_version::resp3}) - { + for (auto protocol : {arknet::redis::protocol_version::resp2, arknet::redis::protocol_version::resp3}) { server endpoint([&](peer& remote) { - if (protocol == arknet::redis::protocol_version::resp2) - { + if (protocol == arknet::redis::protocol_version::resp2) { remote.expect({"AUTH", "password"}); remote.expect({"CLIENT", "SETNAME", "test"}); remote.expect({"PING"}); remote.expect({"SELECT", "1"}); remote.send(":1\r\n+BOGUS\r\n+PONG\r\n+OK\r\n"); - } - else - { + } else { remote.expect({"HELLO", "3", "AUTH", "default", "password", "SETNAME", "test"}); remote.expect({"SELECT", "1"}); remote.send("%1\r\n+proto\r\n:3\r\n+BOGUS\r\n"); @@ -518,8 +499,7 @@ DOCTEST_TEST_CASE("Redis cancellation callback can release the last owner before DOCTEST_TEST_CASE("Redis single-node client interoperates with an independent authenticated server" * doctest::skip(std::getenv("ARKNET_REDIS_TEST_PORT") == nullptr)) { const char* port = std::getenv("ARKNET_REDIS_TEST_PORT"); - for (auto protocol : {arknet::redis::protocol_version::resp3, arknet::redis::protocol_version::resp2}) - { + for (auto protocol : {arknet::redis::protocol_version::resp3, arknet::redis::protocol_version::resp2}) { client connection; arknet::redis::connect_options options; options.protocol = protocol; @@ -559,8 +539,7 @@ DOCTEST_TEST_CASE("Redis ACL authentication accepts an explicitly named nopass u administrator.async_execute({"ACL", "SETUSER", "arknet-client-nopass", "on", "nopass", "~*", "+@all"}, [&](result response) { created.set_value(std::move(response)); }); check(await(created_future).replies[0].text == "OK", "create nopass test user"); - for (auto protocol : {arknet::redis::protocol_version::resp3, arknet::redis::protocol_version::resp2}) - { + for (auto protocol : {arknet::redis::protocol_version::resp3, arknet::redis::protocol_version::resp2}) { client named; arknet::redis::connect_options identity; identity.protocol = protocol; @@ -630,8 +609,7 @@ DOCTEST_TEST_CASE("Redis rejects REPLCONF locally without consuming ordered resp check(connect(connection, endpoint.port()), "connected"); const auto caller = std::this_thread::get_id(); for (const auto& arguments : - std::vector{{"REPLCONF", "ACK", "0"}, {"REPLCONF", "GETACK", "*"}, {"replconf", "capa", "psync2"}}) - { + std::vector{{"REPLCONF", "ACK", "0"}, {"REPLCONF", "GETACK", "*"}, {"replconf", "capa", "psync2"}}) { std::atomic callbacks{}; std::promise completed; auto response = completed.get_future(); diff --git a/tests/redis_close_failure.cpp b/tests/redis_close_failure.cpp index d311b5d..7bc4282 100644 --- a/tests/redis_close_failure.cpp +++ b/tests/redis_close_failure.cpp @@ -27,10 +27,11 @@ DOCTEST_TEST_CASE("Redis close retries a real Asio dispatch allocation failure b const auto before = scoped_allocation_failure::injections.load(); { scoped_allocation_failure::scope failure; - try - { connection.close(); } - catch (const std::bad_alloc&) - { threw = true; } + try { + connection.close(); + } catch (const std::bad_alloc&) { + threw = true; + } } CHECK(threw); CHECK(scoped_allocation_failure::injections == before + 1); @@ -41,8 +42,7 @@ DOCTEST_TEST_CASE("Redis close retries a real Asio dispatch allocation failure b auto response = await(future); CHECK(bool(response)); CHECK(response.replies.size() == 1); - if (response.replies.size() == 1) - { + if (response.replies.size() == 1) { CHECK(response.replies.front().text == "PONG"); } connection.close(); diff --git a/tests/redis_pool.cpp b/tests/redis_pool.cpp index c621578..0e3355d 100644 --- a/tests/redis_pool.cpp +++ b/tests/redis_pool.cpp @@ -43,17 +43,12 @@ result pipeline(Connection& connection, std::vector commands) { } // namespace DOCTEST_TEST_CASE("Redis pool configured deadlines reject nonpositive and unrepresentable durations") { - for (const auto timeout : {0ms, -1ms, std::chrono::milliseconds::max()}) - { - for (const bool acquire_timeout : {false, true}) - { + for (const auto timeout : {0ms, -1ms, std::chrono::milliseconds::max()}) { + for (const bool acquire_timeout : {false, true}) { arknet::redis::pool_options options; - if (acquire_timeout) - { + if (acquire_timeout) { options.acquire_timeout = timeout; - } - else - { + } else { options.reset_timeout = timeout; } DOCTEST_CHECK_THROWS_AS(pool{options}, std::invalid_argument); @@ -74,8 +69,7 @@ DOCTEST_TEST_CASE("Redis pool invalid acquire deadlines preserve waiter capacity options.connection.port = endpoint.port(); pool connections(options); const auto caller = std::this_thread::get_id(); - for (const auto timeout : {-1ms, std::chrono::milliseconds::min(), std::chrono::milliseconds::max()}) - { + for (const auto timeout : {-1ms, std::chrono::milliseconds::min(), std::chrono::milliseconds::max()}) { std::promise completed; auto response = completed.get_future(); const auto id = connections.async_acquire( @@ -163,8 +157,7 @@ DOCTEST_TEST_CASE("Redis lease return drains pending responses before RESET and server endpoint([&](peer& remote) { remote.hello(); remote.expect({"GET", "slow"}); - if (allowed.wait_for(5s) != std::future_status::ready) - { + if (allowed.wait_for(5s) != std::future_status::ready) { throw std::runtime_error("Missing response signal"); } remote.send(":7\r\n"); @@ -300,14 +293,11 @@ DOCTEST_TEST_CASE("Redis pool discards unsupported RESET connections before admi server endpoint( [round = 0](peer& remote) mutable { remote.hello(); - if (round++ == 0) - { + if (round++ == 0) { remote.expect({"RESET"}); remote.send("-ERR unknown command RESET\r\n"); remote.wait_closed(); - } - else - { + } else { remote.expect({"PING"}); remote.send("+PONG\r\n"); remote.wait_closed(); @@ -333,14 +323,11 @@ DOCTEST_TEST_CASE("Redis pool reset deadline includes waiting for undrained sent server endpoint( [round = 0](peer& remote) mutable { remote.hello(); - if (round++ == 0) - { + if (round++ == 0) { remote.expect({"INCR", "slow"}); remote.expect({"RESET"}); remote.wait_closed(); - } - else - { + } else { remote.expect({"PING"}); remote.send("+PONG\r\n"); remote.wait_closed(); diff --git a/tests/redis_pool_return_failure.cpp b/tests/redis_pool_return_failure.cpp index 9c0a858..411d756 100644 --- a/tests/redis_pool_return_failure.cpp +++ b/tests/redis_pool_return_failure.cpp @@ -46,8 +46,7 @@ void actor_barrier(pool& connections) { void check_diagnostics( const pool& connections, std::uint64_t expected, arknet::redis::pool_diagnostics* retained = nullptr) { const auto info = connections.diagnostics(); - if (retained) - { + if (retained) { *retained = info; } DOCTEST_CHECK(info.return_submission_failures == expected); @@ -85,15 +84,13 @@ void arm_on_actor_before(pool& connections, Submit submit) { scoped_allocation_failure::arm(); }, -1ms); - if (ready.wait_for(5s) != std::future_status::ready) - { + if (ready.wait_for(5s) != std::future_status::ready) { posted->count_down(); DOCTEST_FAIL("pool actor did not reach allocation arm barrier"); } - try - { submit(); } - catch (...) - { + try { + submit(); + } catch (...) { posted->count_down(); throw; } @@ -186,12 +183,10 @@ DOCTEST_TEST_CASE("Redis pool retains repeated close-publication failures " std::jthread observer([&](std::stop_token stop) { std::uint64_t previous{}; reader_started.count_down(); - while (!stop.stop_requested()) - { + while (!stop.stop_requested()) { const auto info = connections.diagnostics(); if (info.return_submission_failures < previous || - bool(info.last_return_submission_exception) != (info.return_submission_failures != 0)) - { + bool(info.last_return_submission_exception) != (info.return_submission_failures != 0)) { coherent = false; } previous = info.return_submission_failures; @@ -219,8 +214,7 @@ DOCTEST_TEST_CASE("Redis pool retains repeated close-publication failures " DOCTEST_CHECK(scoped_allocation_failure::injections == before + 2); check_diagnostics(connections, 2, &retained[1]); DOCTEST_CHECK(future.wait_for(0ms) != std::future_status::ready); - for (unsigned attempt = 0; attempt != 2; ++attempt) - { + for (unsigned attempt = 0; attempt != 2; ++attempt) { arm_on_actor_before(connections, [&] { connections.cancel(0); }); actor_barrier(connections); DOCTEST_CHECK(scoped_allocation_failure::injections == before + 3 + attempt); diff --git a/tests/redis_pool_shutdown.cpp b/tests/redis_pool_shutdown.cpp index f49db96..21dbc0f 100644 --- a/tests/redis_pool_shutdown.cpp +++ b/tests/redis_pool_shutdown.cpp @@ -19,14 +19,12 @@ namespace { using namespace redis_test; using namespace std::chrono_literals; -struct publication_probe -{ +struct publication_probe { std::latch entered{1}; std::latch release{1}; }; -struct tracked_transport : arknet::tcp_client -{ +struct tracked_transport : arknet::tcp_client { template explicit tracked_transport(Args&&... args) : arknet::tcp_client(std::forward(args)...) { @@ -53,12 +51,10 @@ struct tracked_transport : arknet::tcp_client std::lock_guard lock(callbacks_mutex); callback = std::exchange(before_connect, {}); } - if (callback) - { + if (callback) { callback(error); } - if (stop_requested && error) - { + if (stop_requested && error) { ++late_connect_errors; } arknet::set_last_error(error); @@ -70,8 +66,7 @@ struct tracked_transport : arknet::tcp_client template bool async_start(Host&& host, Port&& port) { const auto started = arknet::tcp_client::async_start(std::forward(host), std::forward(port)); - if (auto* current = connect_probe.exchange(nullptr)) - { + if (auto* current = connect_probe.exchange(nullptr)) { current->entered.count_down(); current->release.wait(); } @@ -84,8 +79,7 @@ struct tracked_transport : arknet::tcp_client } auto& io() { - if (auto* current = probe.exchange(nullptr)) - { + if (auto* current = probe.exchange(nullptr)) { current->entered.count_down(); current->release.wait(); } @@ -94,8 +88,7 @@ struct tracked_transport : arknet::tcp_client std::lock_guard lock(callbacks_mutex); callback = std::exchange(reenter, {}); } - if (callback) - { + if (callback) { callback(); } return arknet::tcp_client::io(); @@ -107,8 +100,7 @@ struct tracked_transport : arknet::tcp_client std::lock_guard lock(callbacks_mutex); callback = std::exchange(on_destroy, {}); } - if (callback) - { + if (callback) { callback(); } ++destroyed; @@ -134,8 +126,7 @@ result execute(Lease& connection, command arguments) { return await(future); } -struct capture_probe -{ +struct capture_probe { std::atomic& destroyed; ~capture_probe() { @@ -143,13 +134,11 @@ struct capture_probe } }; -struct worker_exit_probe -{ +struct worker_exit_probe { std::shared_ptr> exited; ~worker_exit_probe() { - if (exited) - { + if (exited) { *exited = true; } } @@ -160,8 +149,7 @@ void record_worker_exit(std::shared_ptr> exited) { probe.exited = std::move(exited); } -class paired_server -{ +class paired_server { asio::io_context context_; asio::ip::tcp::acceptor acceptor_{context_, {asio::ip::address_v4::loopback(), 0}}; std::thread worker_; @@ -175,59 +163,45 @@ class paired_server std::vector peers; std::mutex failures_mutex; std::exception_ptr failure; - try - { - for (unsigned index = 0; index < 2; ++index) - { + try { + for (unsigned index = 0; index < 2; ++index) { asio::ip::tcp::socket socket(context_); const auto deadline = std::chrono::steady_clock::now() + 5s; - for (;;) - { + for (;;) { arknet::error_code error; acceptor_.accept(socket, error); - if (!error) - { + if (!error) { break; } if ((error != asio::error::would_block && error != asio::error::try_again) || - std::chrono::steady_clock::now() >= deadline) - { + std::chrono::steady_clock::now() >= deadline) { throw std::runtime_error("Two-child Redis peer accept failed"); } std::this_thread::sleep_for(1ms); } peers.emplace_back([&, socket = std::move(socket)]() mutable { - try - { + try { peer remote(socket); remote.hello(); remote.expect({"PING"}); remote.send("+PONG\r\n"); remote.wait_closed(); - } - catch (...) - { + } catch (...) { std::lock_guard lock(failures_mutex); failure = std::current_exception(); } }); } - } - catch (...) - { + } catch (...) { std::lock_guard lock(failures_mutex); failure = std::current_exception(); } - for (auto& remote : peers) - { + for (auto& remote : peers) { remote.join(); } - if (failure) - { + if (failure) { completion_.set_exception(failure); - } - else - { + } else { completion_.set_value(); } }); @@ -398,8 +372,7 @@ DOCTEST_TEST_CASE("Redis pool retires a discarded retained child without stoppin [&](peer& remote) { ++accepted; remote.hello(); - if (accepted == 2) - { + if (accepted == 2) { remote.expect({"PING"}); remote.send("+PONG\r\n"); } @@ -583,28 +556,24 @@ DOCTEST_TEST_CASE("Redis pool ordinary close retires queued waiters before their std::promise queued; auto fenced = queued.get_future(); auto retire = [](pool* owner) noexcept { - try - { - if (!owner->close_and_wait()) - { + try { + if (!owner->close_and_wait()) { std::terminate(); } + } catch (...) { + std::terminate(); } - catch (...) - { std::terminate(); } }; std::unique_ptr retirement(&connections, retire); connections.async_acquire([&](auto response) { initial_completion.set_value(std::move(response)); }); REQUIRE(initial.wait_for(5s) == std::future_status::ready); auto leased = initial.get(); REQUIRE(leased); - for (std::size_t index = 0; index != ids.size(); ++index) - { + for (std::size_t index = 0; index != ids.size(); ++index) { ids[index] = connections.async_acquire([&, capture, index](auto response) { CHECK_FALSE(response.connection); ++calls[index]; - if (index == 0) - { + if (index == 0) { connections.close(); connections.cancel(ids[1]); connections.async_acquire([&, capture](auto rejected) { @@ -627,14 +596,12 @@ DOCTEST_TEST_CASE("Redis pool ordinary close retires queued waiters before their CHECK(ready[1].wait_for(0ms) == std::future_status::timeout); capture.reset(); connections.close(); - for (auto& completion : ready) - { + for (auto& completion : ready) { REQUIRE(completion.wait_for(5s) == std::future_status::ready); CHECK(completion.get() == client_errc::closed); } CHECK(connections.close_and_wait()); - for (const auto& count : calls) - { + for (const auto& count : calls) { CHECK(count == 1); } CHECK(destroyed == 1); @@ -668,12 +635,10 @@ DOCTEST_TEST_CASE("Redis pool concurrent finalizers wait for a running callback capture.reset(); entered.wait(); std::vector finalizers; - for (unsigned index = 0; index < 3; ++index) - { + for (unsigned index = 0; index < 3; ++index) { finalizers.emplace_back([&] { started.count_down(); - if (connections.close_and_wait()) - { + if (connections.close_and_wait()) { CHECK(completed == 1); CHECK(destroyed == 1); CHECK(*exited); @@ -685,8 +650,7 @@ DOCTEST_TEST_CASE("Redis pool concurrent finalizers wait for a running callback CHECK(finalized == 0); CHECK(destroyed == 0); release.count_down(); - for (auto& finalizer : finalizers) - { + for (auto& finalizer : finalizers) { finalizer.join(); } CHECK(finalized == 3); @@ -719,14 +683,12 @@ DOCTEST_TEST_CASE("Redis pool shutdown waits for an accepted lease publication p bool gate_seen{}; std::atomic acquisitions{}, acquired{}; const auto deadline = std::chrono::steady_clock::now() + 5s; - while (std::chrono::steady_clock::now() < deadline) - { + while (std::chrono::steady_clock::now() < deadline) { ++acquisitions; if (connections.async_acquire([&](auto response) { CHECK((response.error == client_errc::closed || response.error == client_errc::queue_full)); ++acquired; - }) == 0) - { + }) == 0) { gate_seen = true; break; } @@ -762,8 +724,7 @@ DOCTEST_TEST_CASE("Redis pool retires a late failed transport connect without du std::promise terminal; auto closed = terminal.get_future(); - struct reset_connect_observer - { + struct reset_connect_observer { ~reset_connect_observer() { std::lock_guard lock(tracked_transport::callbacks_mutex); tracked_transport::before_connect = {}; @@ -818,10 +779,8 @@ DOCTEST_TEST_CASE("Redis pool shutdown cancels a blackholed connect after a queu std::thread finalizer([&] { closed.set_value(connections.close_and_wait()); }); bool gate_seen{}; const auto deadline = std::chrono::steady_clock::now() + 5s; - while (std::chrono::steady_clock::now() < deadline) - { - if (connections.async_acquire({}) == 0) - { + while (std::chrono::steady_clock::now() < deadline) { + if (connections.async_acquire({}) == 0) { gate_seen = true; break; } @@ -874,12 +833,10 @@ DOCTEST_TEST_CASE("Redis pool gates concurrent lease and acquire publishers befo std::latch start(1); std::atomic completed{}, calls{}; std::vector publishers; - for (unsigned index = 0; index < 3; ++index) - { + for (unsigned index = 0; index < 3; ++index) { publishers.emplace_back([&] { start.wait(); - for (unsigned attempt = 0; attempt < 64; ++attempt) - { + for (unsigned attempt = 0; attempt < 64; ++attempt) { ++calls; leased.connection.async_execute({"PING"}, [&](result response) { CHECK((response.error == client_errc::closed || response.error == client_errc::not_connected || @@ -898,8 +855,7 @@ DOCTEST_TEST_CASE("Redis pool gates concurrent lease and acquire publishers befo } start.count_down(); CHECK(connections.close_and_wait()); - for (auto& publisher : publishers) - { + for (auto& publisher : publishers) { publisher.join(); } CHECK(completed == calls); diff --git a/tests/redis_pool_shutdown_tls.cpp b/tests/redis_pool_shutdown_tls.cpp index e60cbad..5bbd8fd 100644 --- a/tests/redis_pool_shutdown_tls.cpp +++ b/tests/redis_pool_shutdown_tls.cpp @@ -19,15 +19,13 @@ using namespace std::chrono_literals; using arknet::redis::client_errc; using arknet::redis::result; -struct tracked_tls_transport : arknet::tcps_client -{ +struct tracked_tls_transport : arknet::tcps_client { inline static std::atomic contexts_destroyed{}; inline static std::atomic streams_destroyed{}; inline static std::atomic transports_destroyed{}; static void record_free(void*, void* pointer, CRYPTO_EX_DATA*, int, long, void*) { - if (pointer) - { + if (pointer) { ++*static_cast*>(pointer); } } @@ -45,14 +43,12 @@ struct tracked_tls_transport : arknet::tcps_client template explicit tracked_tls_transport(construction&&... arguments) : arknet::tcps_client(std::forward(arguments)...) { - if (context_index() < 0 || SSL_CTX_set_ex_data(native_handle(), context_index(), &contexts_destroyed) != 1) - { + if (context_index() < 0 || SSL_CTX_set_ex_data(native_handle(), context_index(), &contexts_destroyed) != 1) { throw std::runtime_error("Could not register TLS context destruction probe"); } bind_handshake([this] { if (stream_index() < 0 || - SSL_set_ex_data(stream().native_handle(), stream_index(), &streams_destroyed) != 1) - { + SSL_set_ex_data(stream().native_handle(), stream_index(), &streams_destroyed) != 1) { throw std::runtime_error("Could not register TLS stream destruction probe"); } }); @@ -63,8 +59,7 @@ struct tracked_tls_transport : arknet::tcps_client } }; -class encrypted_peer -{ +class encrypted_peer { asio::ssl::stream& stream_; arknet::redis::detail::resp_decoder decoder_; @@ -76,18 +71,14 @@ class encrypted_peer arknet::redis::command read_command() { const auto deadline = std::chrono::steady_clock::now() + 5s; - while (std::chrono::steady_clock::now() < deadline) - { + while (std::chrono::steady_clock::now() < deadline) { arknet::redis::value reply; - if (decoder_.next(reply) == arknet::redis::detail::resp_status::ready) - { - if (reply.type != arknet::redis::value_type::array) - { + if (decoder_.next(reply) == arknet::redis::detail::resp_status::ready) { + if (reply.type != arknet::redis::value_type::array) { throw std::runtime_error("TLS peer expected a RESP command array"); } arknet::redis::command command; - for (auto& value : reply.elements) - { + for (auto& value : reply.elements) { command.push_back(std::move(value.text)); } return command; @@ -95,17 +86,14 @@ class encrypted_peer std::array bytes; arknet::error_code error; const auto count = stream_.read_some(asio::buffer(bytes), error); - if (error == asio::error::would_block || error == asio::error::try_again) - { + if (error == asio::error::would_block || error == asio::error::try_again) { std::this_thread::sleep_for(1ms); continue; } - if (error) - { + if (error) { throw arknet::system_error(error); } - if (!decoder_.append(std::string_view(bytes.data(), count))) - { + if (!decoder_.append(std::string_view(bytes.data(), count))) { throw std::runtime_error("TLS peer exceeded RESP decode limits"); } } @@ -114,35 +102,28 @@ class encrypted_peer void send(std::string_view bytes) { const auto deadline = std::chrono::steady_clock::now() + 5s; - while (!bytes.empty() && std::chrono::steady_clock::now() < deadline) - { + while (!bytes.empty() && std::chrono::steady_clock::now() < deadline) { arknet::error_code error; const auto count = stream_.write_some(asio::buffer(bytes), error); bytes.remove_prefix(count); - if (error == asio::error::would_block || error == asio::error::try_again) - { + if (error == asio::error::would_block || error == asio::error::try_again) { std::this_thread::sleep_for(1ms); - } - else if (error) - { + } else if (error) { throw arknet::system_error(error); } } - if (!bytes.empty()) - { + if (!bytes.empty()) { throw std::runtime_error("TLS peer send timeout"); } } void wait_closed() { const auto deadline = std::chrono::steady_clock::now() + 5s; - while (std::chrono::steady_clock::now() < deadline) - { + while (std::chrono::steady_clock::now() < deadline) { std::array bytes; arknet::error_code error; stream_.read_some(asio::buffer(bytes), error); - if (error && error != asio::error::would_block && error != asio::error::try_again) - { + if (error && error != asio::error::would_block && error != asio::error::try_again) { return; } std::this_thread::sleep_for(1ms); @@ -151,8 +132,7 @@ class encrypted_peer } }; -class encrypted_server -{ +class encrypted_server { asio::io_context context_; asio::ssl::context tls_{asio::ssl::context::tls_server}; asio::ip::tcp::acceptor acceptor_{context_, {asio::ip::address_v4::loopback(), 0}}; @@ -166,13 +146,11 @@ class encrypted_server tls_.use_certificate_chain_file(std::string(ARKNET_TEST_CERT_DIR) + "/server.pem"); tls_.use_private_key_file(std::string(ARKNET_TEST_CERT_DIR) + "/server-key.pem", asio::ssl::context::pem); worker_ = std::thread([this, script = std::move(script)]() mutable { - try - { + try { asio::ssl::stream socket(context_, tls_); asio::steady_timer deadline(context_, 5s); deadline.async_wait([&](arknet::error_code error) { - if (!error) - { + if (!error) { arknet::error_code ignored; acceptor_.cancel(ignored); socket.next_layer().close(ignored); @@ -189,19 +167,16 @@ class encrypted_server }, [&](std::exception_ptr error) { deadline.cancel(); - if (error) - { + if (error) { completion_.set_exception(error); - } - else - { + } else { completion_.set_value(); } }); context_.run(); + } catch (...) { + completion_.set_exception(std::current_exception()); } - catch (...) - { completion_.set_exception(std::current_exception()); } }); } diff --git a/tests/redis_resp.cpp b/tests/redis_resp.cpp index 4001ed4..975de5f 100644 --- a/tests/redis_resp.cpp +++ b/tests/redis_resp.cpp @@ -27,25 +27,19 @@ resp_value decode(std::string_view wire) { bool equal(const resp_value& left, const resp_value& right) { if (left.type != right.type || left.text != right.text || left.integer != right.integer || left.boolean != right.boolean || left.elements.size() != right.elements.size() || - left.attributes.size() != right.attributes.size()) - { + left.attributes.size() != right.attributes.size()) { return false; } - if (left.real != right.real && !(std::isnan(left.real) && std::isnan(right.real))) - { + if (left.real != right.real && !(std::isnan(left.real) && std::isnan(right.real))) { return false; } - for (std::size_t i = 0; i < left.elements.size(); ++i) - { - if (!equal(left.elements[i], right.elements[i])) - { + for (std::size_t i = 0; i < left.elements.size(); ++i) { + if (!equal(left.elements[i], right.elements[i])) { return false; } } - for (std::size_t i = 0; i < left.attributes.size(); ++i) - { - if (!equal(left.attributes[i], right.attributes[i])) - { + for (std::size_t i = 0; i < left.attributes.size(); ++i) { + if (!equal(left.attributes[i], right.attributes[i])) { return false; } } @@ -94,8 +88,7 @@ DOCTEST_TEST_CASE("RESP3 doubles require digits after a decimal point") { check(decode(",1.0\r\n").real == 1.0, "fractional digits"); check(decode(",-1.25e+2\r\n").real == -125.0, "fraction and exponent"); check(decode(",+1e-2\r\n").real == 0.01, "exponent without a fraction"); - for (const std::string wire : {",1.\r\n", ",-1.e+2\r\n", ",+0.E-1\r\n"}) - { + for (const std::string wire : {",1.\r\n", ",-1.e+2\r\n", ",+0.E-1\r\n"}) { DOCTEST_INFO(wire); resp_decoder decoder; resp_value value; @@ -108,19 +101,16 @@ DOCTEST_TEST_CASE("RESP3 doubles require digits after a decimal point") { } DOCTEST_TEST_CASE("RESP3 pushes cannot be nested inside another value") { - for (const std::string aggregate : {"*1", "%1", "~1", "|1", ">2\r\n+outer", "*?", "%?", "~?"}) - { + for (const std::string aggregate : {"*1", "%1", "~1", "|1", ">2\r\n+outer", "*?", "%?", "~?"}) { DOCTEST_INFO(aggregate); const auto wire = aggregate + "\r\n>1\r\n+invalid\r\n:1\r\n+VALID\r\n"; - for (std::size_t split = 0; split < wire.size(); ++split) - { + for (std::size_t split = 0; split < wire.size(); ++split) { resp_decoder decoder; resp_value value; value.text = "unchanged"; check(decoder.append(std::string_view(wire).substr(0, split)), "append prefix"); auto status = decoder.next(value); - if (status == resp_status::need_more) - { + if (status == resp_status::need_more) { check(decoder.append(std::string_view(wire).substr(split)), "append suffix"); status = decoder.next(value); } @@ -151,11 +141,9 @@ DOCTEST_TEST_CASE("Every split position and one-byte input preserve nested RESP const std::vector messages{"+OK\r\n", ":-123\r\n", "$5\r\nhello\r\n", "=7\r\ntxt:hey\r\n", ",nan\r\n", "|1\r\n+k\r\n:1\r\n*2\r\n%1\r\n+x\r\n~2\r\n#t\r\n_\r\n$?\r\n;2\r\nab\r\n;0\r\n", "*?\r\n:1\r\n%?\r\n+a\r\n$3\r\nxyz\r\n.\r\n.\r\n"}; - for (const auto& wire : messages) - { + for (const auto& wire : messages) { const auto expected = decode(wire); - for (std::size_t split = 0; split < wire.size(); ++split) - { + for (std::size_t split = 0; split < wire.size(); ++split) { resp_decoder decoder; resp_value value; value.text = "unchanged"; @@ -167,8 +155,7 @@ DOCTEST_TEST_CASE("Every split position and one-byte input preserve nested RESP } resp_decoder decoder; resp_value value; - for (std::size_t i = 0; i < wire.size(); ++i) - { + for (std::size_t i = 0; i < wire.size(); ++i) { check(decoder.append(std::string_view(wire).substr(i, 1)), "append byte"); const auto status = decoder.next(value); check( @@ -182,23 +169,20 @@ DOCTEST_TEST_CASE("Pipelined replies retain order across errors, attributes and resp_decoder decoder; check(decoder.append("+OK\r\n-ERR bad\r\n>2\r\n+invalidate\r\n*0\r\n|1\r\n+k\r\n:3\r\n:4\r\n"), "append pipeline"); resp_value value; - for (const auto type : {resp_type::simple_string, resp_type::error, resp_type::push, resp_type::integer}) - { + for (const auto type : {resp_type::simple_string, resp_type::error, resp_type::push, resp_type::integer}) { check(decoder.next(value) == resp_status::ready && value.type == type, "ordered typed reply"); } check(value.integer == 4 && value.attributes.size() == 2, "attributes do not form an extra reply"); check(decoder.next(value) == resp_status::need_more, "pipeline fully consumed"); const std::string many = [] { std::string result; - for (int i = 0; i < 10000; ++i) - { + for (int i = 0; i < 10000; ++i) { result += ':' + std::to_string(i) + "\r\n"; } return result; }(); check(decoder.append(many), "append large pipeline"); - for (int i = 0; i < 10000; ++i) - { + for (int i = 0; i < 10000; ++i) { check(decoder.next(value) == resp_status::ready && value.integer == i, "large pipeline ordering"); } } @@ -209,8 +193,7 @@ DOCTEST_TEST_CASE("Malformed RESP is sticky until reset and cannot desynchronize "*x\r\n", "%1x\r\n", "~-1\r\n", "!0\r\nxx", "_x\r\n", "#1\r\n", "(1x\r\n", ",.1\r\n", ",NaN\r\n", ",1x\r\n", ",1e999\r\n", "=3\r\ntxt\r\n", "=4\r\ntxtx\r\n", ".\r\n", "*1\r\n.\r\n", "%?\r\n+k\r\n.\r\n", "*?\r\n|1\r\n+k\r\n:1\r\n.\r\n", "$?\r\n:1\r\n", "$?\r\n;-1\r\n", "$?\r\n;1\r\nxXX", "*?\r\n.x\r\n"}; - for (const auto& wire : invalid) - { + for (const auto& wire : invalid) { resp_decoder decoder; resp_value value; check(decoder.append(wire + "+VALID\r\n"), "append invalid wire"); @@ -232,17 +215,14 @@ DOCTEST_TEST_CASE("RESP resource limits apply across fragments, nested values an limits.max_depth = 2; const std::vector excessive{"$9\r\n", "*4\r\n", "%2\r\n", "$999999999999999999999999999\r\n", "+123456789\r\n", "*1\r\n*1\r\n:1\r\n", "$?\r\n;5\r\nhello\r\n;4\r\n", "~?\r\n_\r\n_\r\n_\r\n_\r\n"}; - for (const auto& wire : excessive) - { + for (const auto& wire : excessive) { resp_decoder decoder(limits); resp_value value; resp_status status = resp_status::need_more; - for (char c : wire) - { + for (char c : wire) { check(decoder.append(std::string_view(&c, 1)), "bounded fragment append"); status = decoder.next(value); - if (status != resp_status::need_more) - { + if (status != resp_status::need_more) { break; } } @@ -254,8 +234,7 @@ DOCTEST_TEST_CASE("RESP resource limits apply across fragments, nested values an check(buffered.next(value) == resp_status::limit_exceeded, "buffer failure is sticky"); resp_decoder exact(limits); check(exact.append("$8\r\n12345678\r\n") && exact.next(value) == resp_status::ready, "exact bulk bound"); - for (int i = 0; i < 50; ++i) - { + for (int i = 0; i < 50; ++i) { check( exact.append(":1\r\n") && exact.next(value) == resp_status::ready, "limits reset between complete replies"); } @@ -279,20 +258,16 @@ DOCTEST_TEST_CASE("Redis command encoding is binary-safe and bounded") { DOCTEST_TEST_CASE("Deterministic arbitrary input stays within decoder limits") { std::mt19937 random(73612); resp_limits limits{256, 256, 128, 32, 16, 8}; - for (int round = 0; round < 5000; ++round) - { + for (int round = 0; round < 5000; ++round) { resp_decoder decoder(limits); resp_value value; std::string wire(random() % 128, '\0'); - for (char& c : wire) - { + for (char& c : wire) { c = static_cast(random() % 256); } check(decoder.append(wire), "append bounded random input"); - for (std::size_t replies = 0; replies <= wire.size(); ++replies) - { - if (decoder.next(value) != resp_status::ready) - { + for (std::size_t replies = 0; replies <= wire.size(); ++replies) { + if (decoder.next(value) != resp_status::ready) { break; } check(replies < wire.size(), "each reply consumes input"); diff --git a/tests/redis_shutdown.cpp b/tests/redis_shutdown.cpp index 80e1128..f38bc80 100644 --- a/tests/redis_shutdown.cpp +++ b/tests/redis_shutdown.cpp @@ -16,8 +16,7 @@ namespace redis_shutdown_test { -struct tracked_transport : arknet::tcp_client -{ +struct tracked_transport : arknet::tcp_client { using arknet::tcp_client::tcp_client; inline static std::atomic destroyed{}; @@ -28,31 +27,26 @@ struct tracked_transport : arknet::tcp_client // A distinct transport lets the test exercise the existing pool-friend // constructor without exposing a raw child client in the production pool API. -struct borrowed_transport : arknet::tcp_client -{ +struct borrowed_transport : arknet::tcp_client { using arknet::tcp_client::tcp_client; }; -struct publication_probe -{ +struct publication_probe { std::latch entered{1}; std::latch release{1}; }; -struct publication_transport : arknet::tcp_client -{ +struct publication_transport : arknet::tcp_client { using arknet::tcp_client::tcp_client; inline static std::atomic probe{}; inline static std::function reenter; auto& io() { - if (auto* current = probe.exchange(nullptr)) - { + if (auto* current = probe.exchange(nullptr)) { current->entered.count_down(); current->release.wait(); } - if (auto callback = std::exchange(reenter, {})) - { + if (auto callback = std::exchange(reenter, {})) { callback(); } return arknet::tcp_client::io(); @@ -64,8 +58,7 @@ struct publication_transport : arknet::tcp_client namespace arknet::redis { template<> -class basic_pool -{ +class basic_pool { public: using borrowed_client = basic_client; @@ -87,8 +80,7 @@ using namespace redis_test; using namespace std::chrono_literals; using tracked_client = arknet::redis::basic_client; -struct destruction_probe -{ +struct destruction_probe { std::atomic& destroyed; ~destruction_probe() { @@ -96,13 +88,11 @@ struct destruction_probe } }; -struct worker_exit_probe -{ +struct worker_exit_probe { std::shared_ptr> exited; ~worker_exit_probe() { - if (exited) - { + if (exited) { *exited = true; } } @@ -113,14 +103,12 @@ void record_worker_exit(std::shared_ptr> exited) { probe.exited = std::move(exited); } -struct reenter_probe -{ +struct reenter_probe { client* owner{}; std::atomic* rejected{}; ~reenter_probe() { - if (!owner->close_and_wait() && arknet::get_last_error() == asio::error::operation_not_supported) - { + if (!owner->close_and_wait() && arknet::get_last_error() == asio::error::operation_not_supported) { ++*rejected; } } @@ -279,10 +267,8 @@ DOCTEST_TEST_CASE("Redis shutdown can finish after its accepted callback destroy std::thread finalizer([&] { closed.set_value(facade->close_and_wait()); }); bool gate_seen{}; const auto deadline = std::chrono::steady_clock::now() + 5s; - while (std::chrono::steady_clock::now() < deadline) - { - if (facade->async_execute({"PING"}, {}) == 0) - { + while (std::chrono::steady_clock::now() < deadline) { + if (facade->async_execute({"PING"}, {}) == 0) { gate_seen = true; break; } @@ -361,12 +347,10 @@ DOCTEST_TEST_CASE("Redis simultaneous permanent shutdown callers observe complet std::latch start(1); std::atomic closed{}; std::vector waiters; - for (unsigned index = 0; index < 3; ++index) - { + for (unsigned index = 0; index < 3; ++index) { waiters.emplace_back([&] { start.wait(); - if (connection.close_and_wait()) - { + if (connection.close_and_wait()) { CHECK(destroyed == 1); CHECK(completions == 1); ++closed; @@ -374,8 +358,7 @@ DOCTEST_TEST_CASE("Redis simultaneous permanent shutdown callers observe complet }); } start.count_down(); - for (auto& waiter : waiters) - { + for (auto& waiter : waiters) { waiter.join(); } CHECK(closed == 3); @@ -429,10 +412,8 @@ DOCTEST_TEST_CASE("Redis permanent shutdown fences an accepted publisher paused std::thread finalizer([&] { closed.set_value(connection.close_and_wait()); }); const auto deadline = std::chrono::steady_clock::now() + 5s; bool gate_seen{}; - while (std::chrono::steady_clock::now() < deadline) - { - if (connection.async_execute({"PING"}, {}) == 0) - { + while (std::chrono::steady_clock::now() < deadline) { + if (connection.async_execute({"PING"}, {}) == 0) { gate_seen = true; break; } @@ -471,12 +452,10 @@ DOCTEST_TEST_CASE("Redis concurrent API callers are fenced before the permanent std::atomic calls{}; std::atomic completions{}; std::vector publishers; - for (unsigned lane = 0; lane < 4; ++lane) - { + for (unsigned lane = 0; lane < 4; ++lane) { publishers.emplace_back([&] { start.wait(); - for (unsigned index = 0; index < 64; ++index) - { + for (unsigned index = 0; index < 64; ++index) { ++calls; connection.async_execute({"PING"}, [&](result response) { CHECK((response.error == client_errc::not_connected || response.error == client_errc::closed)); @@ -489,8 +468,7 @@ DOCTEST_TEST_CASE("Redis concurrent API callers are fenced before the permanent } start.count_down(); CHECK(connection.close_and_wait()); - for (auto& publisher : publishers) - { + for (auto& publisher : publishers) { publisher.join(); } CHECK(completions == calls); diff --git a/tests/redis_shutdown_tls.cpp b/tests/redis_shutdown_tls.cpp index cb11b2c..8d23751 100644 --- a/tests/redis_shutdown_tls.cpp +++ b/tests/redis_shutdown_tls.cpp @@ -19,15 +19,13 @@ using namespace std::chrono_literals; using arknet::redis::client_errc; using arknet::redis::result; -struct tracked_tls_transport : arknet::tcps_client -{ +struct tracked_tls_transport : arknet::tcps_client { inline static std::atomic contexts_destroyed{}; inline static std::atomic streams_destroyed{}; inline static std::atomic transports_destroyed{}; static void record_free(void*, void* pointer, CRYPTO_EX_DATA*, int, long, void*) { - if (pointer) - { + if (pointer) { ++*static_cast*>(pointer); } } @@ -45,14 +43,12 @@ struct tracked_tls_transport : arknet::tcps_client template explicit tracked_tls_transport(construction&&... arguments) : arknet::tcps_client(std::forward(arguments)...) { - if (context_index() < 0 || SSL_CTX_set_ex_data(native_handle(), context_index(), &contexts_destroyed) != 1) - { + if (context_index() < 0 || SSL_CTX_set_ex_data(native_handle(), context_index(), &contexts_destroyed) != 1) { throw std::runtime_error("Could not register TLS context destruction probe"); } bind_handshake([this] { if (stream_index() < 0 || - SSL_set_ex_data(stream().native_handle(), stream_index(), &streams_destroyed) != 1) - { + SSL_set_ex_data(stream().native_handle(), stream_index(), &streams_destroyed) != 1) { throw std::runtime_error("Could not register TLS stream destruction probe"); } }); @@ -63,8 +59,7 @@ struct tracked_tls_transport : arknet::tcps_client } }; -class encrypted_peer -{ +class encrypted_peer { asio::ssl::stream& stream_; arknet::redis::detail::resp_decoder decoder_; @@ -76,18 +71,14 @@ class encrypted_peer arknet::redis::command read_command() { const auto deadline = std::chrono::steady_clock::now() + 5s; - while (std::chrono::steady_clock::now() < deadline) - { + while (std::chrono::steady_clock::now() < deadline) { arknet::redis::value reply; - if (decoder_.next(reply) == arknet::redis::detail::resp_status::ready) - { - if (reply.type != arknet::redis::value_type::array) - { + if (decoder_.next(reply) == arknet::redis::detail::resp_status::ready) { + if (reply.type != arknet::redis::value_type::array) { throw std::runtime_error("TLS peer expected a RESP command array"); } arknet::redis::command command; - for (auto& value : reply.elements) - { + for (auto& value : reply.elements) { command.push_back(std::move(value.text)); } return command; @@ -95,17 +86,14 @@ class encrypted_peer std::array bytes; arknet::error_code error; const auto count = stream_.read_some(asio::buffer(bytes), error); - if (error == asio::error::would_block || error == asio::error::try_again) - { + if (error == asio::error::would_block || error == asio::error::try_again) { std::this_thread::sleep_for(1ms); continue; } - if (error) - { + if (error) { throw arknet::system_error(error); } - if (!decoder_.append(std::string_view(bytes.data(), count))) - { + if (!decoder_.append(std::string_view(bytes.data(), count))) { throw std::runtime_error("TLS peer exceeded RESP decode limits"); } } @@ -114,35 +102,28 @@ class encrypted_peer void send(std::string_view bytes) { const auto deadline = std::chrono::steady_clock::now() + 5s; - while (!bytes.empty() && std::chrono::steady_clock::now() < deadline) - { + while (!bytes.empty() && std::chrono::steady_clock::now() < deadline) { arknet::error_code error; const auto count = stream_.write_some(asio::buffer(bytes), error); bytes.remove_prefix(count); - if (error == asio::error::would_block || error == asio::error::try_again) - { + if (error == asio::error::would_block || error == asio::error::try_again) { std::this_thread::sleep_for(1ms); - } - else if (error) - { + } else if (error) { throw arknet::system_error(error); } } - if (!bytes.empty()) - { + if (!bytes.empty()) { throw std::runtime_error("TLS peer send timeout"); } } void wait_closed() { const auto deadline = std::chrono::steady_clock::now() + 5s; - while (std::chrono::steady_clock::now() < deadline) - { + while (std::chrono::steady_clock::now() < deadline) { std::array bytes; arknet::error_code error; stream_.read_some(asio::buffer(bytes), error); - if (error && error != asio::error::would_block && error != asio::error::try_again) - { + if (error && error != asio::error::would_block && error != asio::error::try_again) { return; } std::this_thread::sleep_for(1ms); @@ -151,8 +132,7 @@ class encrypted_peer } }; -class encrypted_server -{ +class encrypted_server { asio::io_context context_; asio::ssl::context tls_{asio::ssl::context::tls_server}; asio::ip::tcp::acceptor acceptor_{context_, {asio::ip::address_v4::loopback(), 0}}; @@ -166,13 +146,11 @@ class encrypted_server tls_.use_certificate_chain_file(std::string(ARKNET_TEST_CERT_DIR) + "/server.pem"); tls_.use_private_key_file(std::string(ARKNET_TEST_CERT_DIR) + "/server-key.pem", asio::ssl::context::pem); worker_ = std::thread([this, script = std::move(script)]() mutable { - try - { + try { asio::ssl::stream socket(context_, tls_); asio::steady_timer deadline(context_, 5s); deadline.async_wait([&](arknet::error_code error) { - if (!error) - { + if (!error) { arknet::error_code ignored; acceptor_.cancel(ignored); socket.next_layer().close(ignored); @@ -189,19 +167,16 @@ class encrypted_server }, [&](std::exception_ptr error) { deadline.cancel(); - if (error) - { + if (error) { completion_.set_exception(error); - } - else - { + } else { completion_.set_value(); } }); context_.run(); + } catch (...) { + completion_.set_exception(std::current_exception()); } - catch (...) - { completion_.set_exception(std::current_exception()); } }); } @@ -243,8 +218,7 @@ DOCTEST_TEST_CASE( connection.async_connect(options, [&](result response) { connected.set_value(std::move(response)); }); REQUIRE(bool(await(ready))); - struct capture_probe - { + struct capture_probe { std::atomic& destroyed; ~capture_probe() { diff --git a/tests/redis_test_peer.hpp b/tests/redis_test_peer.hpp index 3595ce5..43b2a46 100644 --- a/tests/redis_test_peer.hpp +++ b/tests/redis_test_peer.hpp @@ -23,8 +23,7 @@ using arknet::redis::command; using arknet::redis::result; using arknet::redis::value_type; -class peer -{ +class peer { public: explicit peer(asio::ip::tcp::socket& socket) : socket_(socket) { @@ -33,20 +32,15 @@ class peer command read_command() { const auto deadline = std::chrono::steady_clock::now() + 5s; - while (std::chrono::steady_clock::now() < deadline) - { + while (std::chrono::steady_clock::now() < deadline) { arknet::redis::value reply; - if (decoder_.next(reply) == arknet::redis::detail::resp_status::ready) - { - if (reply.type != value_type::array) - { + if (decoder_.next(reply) == arknet::redis::detail::resp_status::ready) { + if (reply.type != value_type::array) { throw std::runtime_error("Expected a RESP command array"); } command arguments; - for (auto& element : reply.elements) - { - if (element.type != value_type::bulk_string) - { + for (auto& element : reply.elements) { + if (element.type != value_type::bulk_string) { throw std::runtime_error("Expected binary-safe command arguments"); } arguments.push_back(std::move(element.text)); @@ -56,17 +50,14 @@ class peer std::array bytes; arknet::error_code error; auto count = socket_.read_some(asio::buffer(bytes), error); - if (error == asio::error::would_block || error == asio::error::try_again) - { + if (error == asio::error::would_block || error == asio::error::try_again) { std::this_thread::sleep_for(1ms); continue; } - if (error) - { + if (error) { throw arknet::system_error(error); } - if (!decoder_.append(std::string_view(bytes.data(), count))) - { + if (!decoder_.append(std::string_view(bytes.data(), count))) { throw std::runtime_error("Command decoder limit exceeded"); } } @@ -74,8 +65,7 @@ class peer } void expect(command expected) { - if (read_command() != expected) - { + if (read_command() != expected) { throw std::runtime_error("Unexpected Redis command"); } } @@ -83,21 +73,16 @@ class peer void send(std::string_view data) { std::size_t position{}; const auto deadline = std::chrono::steady_clock::now() + 5s; - while (position < data.size() && std::chrono::steady_clock::now() < deadline) - { + while (position < data.size() && std::chrono::steady_clock::now() < deadline) { arknet::error_code error; position += socket_.write_some(asio::buffer(data.data() + position, data.size() - position), error); - if (error == asio::error::would_block || error == asio::error::try_again) - { + if (error == asio::error::would_block || error == asio::error::try_again) { std::this_thread::sleep_for(1ms); - } - else if (error) - { + } else if (error) { throw arknet::system_error(error); } } - if (position != data.size()) - { + if (position != data.size()) { throw std::runtime_error("Timed out sending a response"); } } @@ -110,12 +95,10 @@ class peer void wait_closed() { const auto deadline = std::chrono::steady_clock::now() + 5s; std::array bytes; - while (std::chrono::steady_clock::now() < deadline) - { + while (std::chrono::steady_clock::now() < deadline) { arknet::error_code error; socket_.read_some(asio::buffer(bytes), error); - if (error && error != asio::error::would_block && error != asio::error::try_again) - { + if (error && error != asio::error::would_block && error != asio::error::try_again) { return; } std::this_thread::sleep_for(1ms); @@ -128,8 +111,7 @@ class peer arknet::redis::detail::resp_decoder decoder_; }; -class server -{ +class server { public: template explicit server(Function script, std::size_t connections = 1) @@ -138,25 +120,19 @@ class server finished_ = completion_.get_future(); acceptor_.non_blocking(true); worker_ = std::thread([this, script = std::move(script), connections]() mutable { - try - { - for (std::size_t connection_index = 0; connection_index < connections; ++connection_index) - { + try { + for (std::size_t connection_index = 0; connection_index < connections; ++connection_index) { const auto deadline = std::chrono::steady_clock::now() + 5s; - for (;;) - { + for (;;) { arknet::error_code error; acceptor_.accept(socket_, error); - if (!error) - { + if (!error) { break; } - if (error != asio::error::would_block && error != asio::error::try_again) - { + if (error != asio::error::would_block && error != asio::error::try_again) { throw arknet::system_error(error); } - if (std::chrono::steady_clock::now() >= deadline) - { + if (std::chrono::steady_clock::now() >= deadline) { throw std::runtime_error("Timed out accepting Redis client"); } std::this_thread::sleep_for(1ms); @@ -166,9 +142,9 @@ class server socket_.close(); } completion_.set_value(); + } catch (...) { + completion_.set_exception(std::current_exception()); } - catch (...) - { completion_.set_exception(std::current_exception()); } }); } diff --git a/tests/router.cpp b/tests/router.cpp index bdff50f..fc1200a 100644 --- a/tests/router.cpp +++ b/tests/router.cpp @@ -88,8 +88,7 @@ DOCTEST_TEST_CASE("HTTP routing validates targets and decodes path segments exac }); for (std::string_view target : {"", "http://localhost/x", "localhost:443", "*", "/%", "/%0", "/%GG", "/%00", "/%1f", "/%7f", "/a%2Fb", "/a%5cb", "/a\\b", "/a b", "/x#fragment", "/./x", "/x/..", "/%2e", "/%2E%2e", "/x?q=%", - "/x?q=%0d", "/x?q=a b", "/x?q=#fragment"}) - { + "/x?q=%0d", "/x?q=a b", "/x?q=#fragment"}) { DOCTEST_INFO(target); check(router(incoming(verb::get, target)).result() == status::bad_request, "invalid request target rejects before route dispatch"); @@ -109,35 +108,31 @@ DOCTEST_TEST_CASE("HTTP routing validates targets and decodes path segments exac DOCTEST_TEST_CASE("HTTP routing rejects invalid configuration and duplicate resource methods") { for (std::string_view pattern : {"", "relative", "*", "/x?q=1", "/x#y", "/:", "/:9id", "/:id/:id", "/x/*tail/end", - "/x/*", "/%", "/%2f", "/../x"}) - { + "/x/*", "/%", "/%2f", "/../x"}) { arknet::http_router router; bool rejected = false; - try - { + try { router.add(verb::get, pattern, [](const auto&, auto&) {}); + } catch (const std::invalid_argument&) { + rejected = true; } - catch (const std::invalid_argument&) - { rejected = true; } check(rejected, "invalid route pattern"); } arknet::http_router router; router.add(verb::get, "/:first", [](const auto&, auto&) {}); bool duplicate = false; - try - { + try { router.add(verb::get, "/:second", [](const auto&, auto&) {}); + } catch (const std::invalid_argument&) { + duplicate = true; } - catch (const std::invalid_argument&) - { duplicate = true; } check(duplicate, "renaming a parameter does not produce a new resource"); bool unknown = false; - try - { + try { router.add(verb::unknown, "/x", [](const auto&, auto&) {}); + } catch (const std::invalid_argument&) { + unknown = true; } - catch (const std::invalid_argument&) - { unknown = true; } check(unknown, "unknown HTTP method"); } @@ -189,8 +184,7 @@ DOCTEST_TEST_CASE("HTTP routing middleware runs in order and can short circuit a router.use([&](const auto& context, auto& reply) { order += 'a'; reply.set("X-Path", context.path); - if (context.request[field::authorization].empty()) - { + if (context.request[field::authorization].empty()) { reply.result(status::unauthorized); return false; } @@ -224,12 +218,10 @@ DOCTEST_TEST_CASE("HTTP routing contains callback exceptions without exposing de arknet::http_router router; unsigned failures = 0; router.bind_error([&](std::exception_ptr error) { - try - { std::rethrow_exception(error); } - catch (const std::runtime_error& failure) - { - if (std::string_view(failure.what()) == "private detail") - { + try { + std::rethrow_exception(error); + } catch (const std::runtime_error& failure) { + if (std::string_view(failure.what()) == "private detail") { ++failures; } } @@ -266,13 +258,10 @@ DOCTEST_TEST_CASE("HTTP route responses own their data and support concurrent di check(owned.body() == "original", "response remains valid after the request changes"); std::atomic invalid{}; std::vector threads; - for (unsigned worker = 0; worker < 4; ++worker) - { + for (unsigned worker = 0; worker < 4; ++worker) { threads.emplace_back([&] { - for (unsigned iteration = 0; iteration < 100; ++iteration) - { - if (router(incoming(verb::get, "/parallel")).body() != "parallel") - { + for (unsigned iteration = 0; iteration < 100; ++iteration) { + if (router(incoming(verb::get, "/parallel")).body() != "parallel") { ++invalid; } } diff --git a/tests/rudp.cpp b/tests/rudp.cpp index 00cc381..d85e5f8 100644 --- a/tests/rudp.cpp +++ b/tests/rudp.cpp @@ -14,8 +14,7 @@ namespace { -class lossy_relay -{ +class lossy_relay { public: explicit lossy_relay(asio::ip::udp::endpoint server) : work_(asio::make_work_guard(context_)) @@ -43,12 +42,10 @@ class lossy_relay void receive() { socket_.async_receive_from(asio::buffer(buffer_), sender_, [this](arknet::error_code error, std::size_t count) { - if (error) - { + if (error) { return; } - if (sender_ != server_) - { + if (sender_ != server_) { client_ = sender_; } const auto target = sender_ == server_ ? client_ : server_; @@ -58,42 +55,30 @@ class lossy_relay arknet::detail::rudp_is_fec_control(*bytes, 4)); const bool ack = sender_ == server_ && (arknet::detail::rudp_is_control(*bytes, 2) || arknet::detail::rudp_is_fec_control(*bytes, 5)); - if (syn) - { + if (syn) { ++syn_attempts_; } // Lose the first SYN, then every initial ACK. Both initial ACKs must // be lost so another client SYN is required to recover the handshake. - if (syn && syn_attempts_ == 1) - { + if (syn && syn_attempts_ == 1) { ++dropped_syn; ++dropped; - } - else if (ack && syn_attempts_ < 3) - { + } else if (ack && syn_attempts_ < 3) { ++dropped_ack; ++dropped; - } - else if (index % 7 == 1) - { + } else if (index % 7 == 1) { ++dropped; - } - else if (index % 4 == 0) - { + } else if (index % 4 == 0) { ++reordered; auto timer = std::make_shared(context_, 25ms); timer->async_wait([this, bytes, target, timer](arknet::error_code error) { - if (!error) - { + if (!error) { send(bytes, target); } }); - } - else - { + } else { send(bytes, target); - if (index % 5 == 0) - { + if (index % 5 == 0) { ++duplicated; send(bytes, target); } @@ -113,15 +98,13 @@ class lossy_relay } // namespace DOCTEST_TEST_CASE("RUDP reassembles empty binary and fragmented messages on IPv4 and IPv6") { - for (const auto* host : {"127.0.0.1", "::1"}) - { + for (const auto* host : {"127.0.0.1", "::1"}) { DOCTEST_INFO(host); messages received; std::atomic connects{}, handshakes{}, client_handshakes{}, disconnects{}; arknet::rudp_server server; server.bind_handshake([&](auto&, arknet::error_code error) { - if (!error) - { + if (!error) { ++handshakes; } }); @@ -143,12 +126,10 @@ DOCTEST_TEST_CASE("RUDP reassembles empty binary and fragmented messages on IPv4 client.bind_recv([&](std::string_view bytes) { received.push(bytes); }); const std::vector expected{ "", std::string("a\0b", 3), std::string(4119, 'r'), std::string(16384, 'k')}; - for (unsigned round = 0; round < 2; ++round) - { + for (unsigned round = 0; round < 2; ++round) { check(server.start(host, 0), "RUDP server start/restart"); check(client.start(host, server.get_listen_port()), "RUDP start completes handshake"); - for (unsigned i = 0; i < expected.size(); ++i) - { + for (unsigned i = 0; i < expected.size(); ++i) { auto sent = client.async_send(expected[i], asio::use_future); const auto [error, count] = await(sent); check(!error && count == expected[i].size(), "RUDP send accepts a complete message"); @@ -165,8 +146,7 @@ DOCTEST_TEST_CASE("RUDP reassembles empty binary and fragmented messages on IPv4 } DOCTEST_TEST_CASE("RUDP handshake and data recover from real UDP drop duplicate and reorder") { - for (const auto mode : {arknet::fec::policy::disabled, arknet::fec::policy::fixed, arknet::fec::policy::adaptive}) - { + for (const auto mode : {arknet::fec::policy::disabled, arknet::fec::policy::fixed, arknet::fec::policy::adaptive}) { DOCTEST_INFO(static_cast(mode)); messages received; std::atomic connects{}, server_handshakes{}, client_handshakes{}; @@ -192,8 +172,7 @@ DOCTEST_TEST_CASE("RUDP handshake and data recover from real UDP drop duplicate check(client.start("127.0.0.1", relay.port()), "handshake retries dropped SYN and ACK datagrams"); check(relay.dropped_syn == 1 && relay.dropped_ack >= 2, "relay loses the SYN and every initial ACK"); std::vector expected; - for (unsigned i = 0; i < 24; ++i) - { + for (unsigned i = 0; i < 24; ++i) { auto value = std::to_string(i) + ':'; value.resize(i % 2 ? 4119 : 16384, static_cast(i)); expected.push_back(value); @@ -304,8 +283,7 @@ DOCTEST_TEST_CASE("RUDP handshake rejection drains retained sessions without a c asio::post(context, [&] { server.stop(); }); context.run_for(100ms); const bool drained = context.stopped(); - if (!drained) - { + if (!drained) { asio::post(context, [&] { retained.reset(); }); context.run_for(100ms); } @@ -321,8 +299,7 @@ DOCTEST_TEST_CASE("RUDP receive memory limits disconnect once with no buffer spa std::promise disconnected; auto completion = disconnected.get_future(); server.bind_disconnect([&](auto&) { - if (++calls == 1) - { + if (++calls == 1) { disconnected.set_value(arknet::get_last_error()); } }); @@ -339,16 +316,14 @@ DOCTEST_TEST_CASE("RUDP receive memory limits disconnect once with no buffer spa } DOCTEST_TEST_CASE("RUDP sessions work with one io_context and multiple threads") { - for (const auto* model : {"shared", "sharded"}) - { + for (const auto* model : {"shared", "sharded"}) { benchmark_runtime runtime(model, 4); arknet::rudp_server server(runtime.server_lanes(4)); server.bind_recv([](auto& session, std::string_view data) { session->async_send(data); }); check(server.start("127.0.0.1", 0), "external-context server starts"); std::vector> clients; std::array received; - for (unsigned index = 0; index < 4; ++index) - { + for (unsigned index = 0; index < 4; ++index) { auto client = std::make_unique(runtime.context_at(index + 1)); client->set_auto_reconnect(false); client->bind_recv([&, index](std::string_view data) { received[index].push(data); }); @@ -356,22 +331,18 @@ DOCTEST_TEST_CASE("RUDP sessions work with one io_context and multiple threads") clients.push_back(std::move(client)); } std::vector expected; - for (unsigned i = 0; i < 32; ++i) - { + for (unsigned i = 0; i < 32; ++i) { std::string payload = std::to_string(i) + ':'; payload.resize(4119, static_cast(i)); expected.push_back(payload); - for (const auto& client : clients) - { + for (const auto& client : clients) { check(client->async_send(payload), "concurrent-client send admission"); } } - for (auto& data : received) - { + for (auto& data : received) { check(data.wait(expected.size()) == expected, "strands preserve per-session message order"); } - for (auto& client : clients) - { + for (auto& client : clients) { client->stop(); } server.stop(); @@ -387,8 +358,7 @@ DOCTEST_TEST_CASE("RUDP automatic reconnect replaces conversations after server arknet::rudp_client client; client.set_auto_reconnect(true, 50ms).set_connect_timeout(200ms); client.bind_connect([&] { - if (!arknet::get_last_error()) - { + if (!arknet::get_last_error()) { connections.push("connected"); } }); @@ -434,13 +404,11 @@ DOCTEST_TEST_CASE("RUDP unacknowledged messages exhaust retries and disconnect") std::function receive; receive = [&] { peer.async_receive_from(asio::buffer(storage), sender, [&](arknet::error_code error, std::size_t count) { - if (error) - { + if (error) { return; } const std::string_view bytes(storage.data(), count); - if (arknet::detail::rudp_is_control(bytes, 1)) - { + if (arknet::detail::rudp_is_control(bytes, 1)) { auto ack = std::make_shared( arknet::detail::rudp_control(2, arknet::detail::rudp_read_id(bytes.data() + 1), 42)); peer.async_send_to(asio::buffer(*ack), sender, [ack](arknet::error_code, std::size_t) {}); diff --git a/tests/rudp_fec.cpp b/tests/rudp_fec.cpp index 61e43d1..7baf504 100644 --- a/tests/rudp_fec.cpp +++ b/tests/rudp_fec.cpp @@ -18,8 +18,7 @@ namespace { -class fec_relay -{ +class fec_relay { public: explicit fec_relay(asio::ip::udp::endpoint server) : work_(asio::make_work_guard(context_)) @@ -55,12 +54,10 @@ class fec_relay void receive() { socket_.async_receive_from(asio::buffer(buffer_), sender_, [this](arknet::error_code error, std::size_t count) { - if (error) - { + if (error) { return; } - if (sender_ != server_) - { + if (sender_ != server_) { client_ = sender_; } const auto target = sender_ == server_ ? client_ : server_; @@ -70,25 +67,19 @@ class fec_relay const auto kind = framed ? static_cast((*bytes)[5]) : 255; const auto data_count = framed ? static_cast((*bytes)[6]) : 0; const auto index = framed ? static_cast((*bytes)[8]) : 0; - if (kind == 0 && data_count > 1 && index == 1) - { + if (kind == 0 && data_count > 1 && index == 1) { ++dropped; - } - else if (kind == 1) - { + } else if (kind == 1) { ++reordered; auto timer = std::make_shared(context_, 5ms); timer->async_wait([this, bytes, target, timer](arknet::error_code error) { - if (!error) - { + if (!error) { send(bytes, target); send(bytes, target); ++duplicated; } }); - } - else - { + } else { send(bytes, target); } receive(); @@ -105,10 +96,8 @@ class fec_relay } // namespace DOCTEST_TEST_CASE("RUDP negotiates fixed and adaptive FEC on IPv4 and IPv6") { - for (const auto* host : {"127.0.0.1", "::1"}) - { - for (const auto mode : {arknet::fec::policy::fixed, arknet::fec::policy::adaptive}) - { + for (const auto* host : {"127.0.0.1", "::1"}) { + for (const auto mode : {arknet::fec::policy::fixed, arknet::fec::policy::adaptive}) { DOCTEST_INFO(host); DOCTEST_INFO(static_cast(mode)); auto fec = arknet::fec::options::fixed(); @@ -136,8 +125,7 @@ DOCTEST_TEST_CASE("RUDP negotiates fixed and adaptive FEC on IPv4 and IPv6") { check(accepted->get_rudp_options().mtu == 1000, "negotiation selects the smaller wire MTU"); check(accepted->get_fec_options().mode == mode, "session exposes negotiated FEC policy"); const std::vector expected{"", std::string("a\0b", 3), std::string(16384, 'f')}; - for (const auto& bytes : expected) - { + for (const auto& bytes : expected) { check(client.async_send(bytes), "FEC accepts complete binary messages"); } check(received.wait(expected.size()) == expected, "FEC preserves message contents and order"); @@ -189,11 +177,9 @@ DOCTEST_TEST_CASE("RUDP rejects FEC mismatch and malformed negotiation without a asio::io_context context; asio::ip::udp::socket peer(context, asio::ip::udp::v4()); peer.connect(server.acceptor().local_endpoint()); - for (const auto invalid_version : {true, false}) - { + for (const auto invalid_version : {true, false}) { auto request = arknet::detail::rudp_fec_control(4, 321, 0, invalid_version ? 1200 : 50); - if (invalid_version) - { + if (invalid_version) { request[5] = 2; } peer.send(asio::buffer(request)); @@ -224,20 +210,17 @@ DOCTEST_TEST_CASE("RUDP FEC refreshes liveness only for matching negotiated hand peer.send(asio::buffer(request)); std::array storage{}; const auto receive_ack = [&] { - for (unsigned i = 0; i < 8; ++i) - { + for (unsigned i = 0; i < 8; ++i) { const auto size = peer.receive(asio::buffer(storage)); const std::string_view bytes(storage.data(), size); - if (arknet::detail::rudp_is_fec_control(bytes, 5)) - { + if (arknet::detail::rudp_is_fec_control(bytes, 5)) { return true; } check(bytes.starts_with("AFEC"), "only FEC feedback may precede repeated ACK"); } return false; }; - for (unsigned i = 0; i < 2; ++i) - { + for (unsigned i = 0; i < 2; ++i) { check(receive_ack(), "initial and repeated SYN return FEC ACK"); } await(connection); @@ -248,8 +231,7 @@ DOCTEST_TEST_CASE("RUDP FEC refreshes liveness only for matching negotiated hand const std::array invalid_requests{invalid_version, arknet::detail::rudp_fec_control(4, 124, 0, 1400), arknet::detail::rudp_fec_control(4, 123, 0, 1399), arknet::detail::rudp_control(1, 123)}; - for (const auto& bytes : invalid_requests) - { + for (const auto& bytes : invalid_requests) { peer.send(asio::buffer(bytes)); } auto rejected = accepted->post([&] { return accepted->last_alive_time(); }, 50ms, asio::use_future); @@ -268,8 +250,7 @@ DOCTEST_TEST_CASE("RUDP FEC refreshes liveness only for matching negotiated hand } DOCTEST_TEST_CASE("RUDP rejects invalid FEC ACK version conversation and MTU") { - for (const unsigned invalid_field : {0U, 1U, 2U, 3U}) - { + for (const unsigned invalid_field : {0U, 1U, 2U, 3U}) { DOCTEST_INFO(invalid_field); asio::io_context context; asio::ip::udp::socket peer(context, {asio::ip::make_address("127.0.0.1"), 0}); @@ -278,8 +259,7 @@ DOCTEST_TEST_CASE("RUDP rejects invalid FEC ACK version conversation and MTU") { std::shared_ptr response; bool recognized{}; peer.async_receive_from(asio::buffer(request), remote, [&](arknet::error_code error, std::size_t count) { - if (error || !arknet::detail::rudp_is_fec_control({request.data(), count}, 4)) - { + if (error || !arknet::detail::rudp_is_fec_control({request.data(), count}, 4)) { return; } recognized = true; @@ -288,8 +268,7 @@ DOCTEST_TEST_CASE("RUDP rejects invalid FEC ACK version conversation and MTU") { invalid_field == 2 ? 2000 : invalid_field == 3 ? 50 : 1200)); - if (invalid_field == 0) - { + if (invalid_field == 0) { (*response)[9] = 2; } peer.async_send_to(asio::buffer(*response), remote, [](arknet::error_code, std::size_t) {}); @@ -326,23 +305,17 @@ DOCTEST_TEST_CASE("RUDP FEC configuration validates limits before start") { } DOCTEST_TEST_CASE("RUDP FEC safely stops built-in client session and server inside receive callbacks") { - for (const unsigned stop_target : {0U, 1U, 2U}) - { + for (const unsigned stop_target : {0U, 1U, 2U}) { DOCTEST_INFO(stop_target); messages disconnected; arknet::rudp_server server; server.set_fec_options(arknet::fec::options::fixed()); server.bind_recv([&](auto& session, std::string_view bytes) { - if (stop_target == 1) - { + if (stop_target == 1) { session->stop(); - } - else if (stop_target == 2) - { + } else if (stop_target == 2) { server.stop(); - } - else - { + } else { session->async_send(bytes); } }); @@ -363,12 +336,9 @@ DOCTEST_TEST_CASE("RUDP FEC safely stops built-in client session and server insi } DOCTEST_TEST_CASE("RUDP FEC safely stops client session and server with pending sends and statistics") { - for (const auto* model : {"shared", "sharded"}) - { - for (const auto mode : {arknet::fec::policy::fixed, arknet::fec::policy::adaptive}) - { - for (const unsigned stop_target : {0U, 1U, 2U}) - { + for (const auto* model : {"shared", "sharded"}) { + for (const auto mode : {arknet::fec::policy::fixed, arknet::fec::policy::adaptive}) { + for (const unsigned stop_target : {0U, 1U, 2U}) { DOCTEST_INFO(std::string_view(model)); DOCTEST_INFO(static_cast(mode)); DOCTEST_INFO(stop_target); @@ -380,8 +350,7 @@ DOCTEST_TEST_CASE("RUDP FEC safely stops client session and server with pending auto client_fec = server_fec; client_fec.mode = mode; - struct shutdown_result - { + struct shutdown_result { arknet::error_code error; std::size_t bytes; std::future snapshot; @@ -394,20 +363,14 @@ DOCTEST_TEST_CASE("RUDP FEC safely stops client session and server with pending arknet::rudp_server server(runtime.server_lanes(4)); server.set_rudp_options(arq).set_fec_options(server_fec); server.bind_recv([&](auto& session, std::string_view bytes) { - if (stop_target == 0) - { + if (stop_target == 0) { session->async_send(bytes); - } - else - { + } else { session->async_send("partial group", [&, session](arknet::error_code error, std::size_t count) { stopped.set_value({error, count, session->get_rudp_statistics()}); - if (stop_target == 1) - { + if (stop_target == 1) { session->stop(); - } - else - { + } else { server.stop(); } }); @@ -416,8 +379,7 @@ DOCTEST_TEST_CASE("RUDP FEC safely stops client session and server with pending arknet::rudp_client client(runtime.context_at(2)); client.set_rudp_options(arq).set_fec_options(client_fec).set_auto_reconnect(false); client.bind_recv([&](std::string_view) { - if (stop_target == 0) - { + if (stop_target == 0) { client.async_send("partial group", [&](arknet::error_code error, std::size_t count) { stopped.set_value({error, count, client.get_rudp_statistics()}); client.stop(); @@ -439,12 +401,12 @@ DOCTEST_TEST_CASE("RUDP FEC safely stops client session and server with pending server.stop(); check(result.snapshot.wait_for(5s) == std::future_status::ready, "statistics requested before callback shutdown complete"); - try - { result.snapshot.get(); } - catch (const std::future_error&) - { check(false, "statistics callback retains its promise through shutdown"); } - catch (const std::logic_error&) - {} + try { + result.snapshot.get(); + } catch (const std::future_error&) { + check(false, "statistics callback retains its promise through shutdown"); + } catch (const std::logic_error&) { + } check(client.is_stopped() && server.is_stopped(), "receive callback shutdown finishes"); check(server.get_session_count() == 0 && disconnect_count == 1, "callback shutdown releases sessions and disconnects once"); @@ -455,10 +417,8 @@ DOCTEST_TEST_CASE("RUDP FEC safely stops client session and server with pending } DOCTEST_TEST_CASE("RUDP FEC serializes concurrent sends and statistics on shared and sharded IO") { - for (const auto* model : {"shared", "sharded"}) - { - for (const auto mode : {arknet::fec::policy::fixed, arknet::fec::policy::adaptive}) - { + for (const auto* model : {"shared", "sharded"}) { + for (const auto mode : {arknet::fec::policy::fixed, arknet::fec::policy::adaptive}) { DOCTEST_INFO(std::string_view(model)); DOCTEST_INFO(static_cast(mode)); benchmark_runtime runtime(model, 4); @@ -484,8 +444,7 @@ DOCTEST_TEST_CASE("RUDP FEC serializes concurrent sends and statistics on shared std::array, 4> deliveries; std::array, 4> relays; std::array, 4> clients; - for (unsigned i = 0; i < clients.size(); ++i) - { + for (unsigned i = 0; i < clients.size(); ++i) { relays[i] = std::make_unique(server.acceptor().local_endpoint()); clients[i] = std::make_unique(runtime.context_at(i + 1)); clients[i]->set_rudp_options(arq).set_fec_options(client_fec).set_auto_reconnect(false); @@ -494,8 +453,7 @@ DOCTEST_TEST_CASE("RUDP FEC serializes concurrent sends and statistics on shared deliveries[i] = delivered[i].get_future(); clients[i]->bind_recv([&, i](std::string_view bytes) { received[i].push(bytes); - if (++received_count[i] == 32) - { + if (++received_count[i] == 32) { delivered[i].set_value(); } }); @@ -507,12 +465,9 @@ DOCTEST_TEST_CASE("RUDP FEC serializes concurrent sends and statistics on shared std::array>, 4> snapshots; std::array errors; std::array, 4>, 4> expected; - for (unsigned client = 0; client < clients.size(); ++client) - { - for (unsigned writer = 0; writer < sends.size(); ++writer) - { - for (unsigned sequence = 0; sequence < 8; ++sequence) - { + for (unsigned client = 0; client < clients.size(); ++client) { + for (unsigned writer = 0; writer < sends.size(); ++writer) { + for (unsigned sequence = 0; sequence < 8; ++sequence) { auto bytes = std::to_string(writer) + ':' + std::to_string(client) + ':' + std::to_string(sequence) + ':'; bytes.resize(sequence % 2 ? 2048 : 4119, static_cast(writer)); @@ -522,35 +477,28 @@ DOCTEST_TEST_CASE("RUDP FEC serializes concurrent sends and statistics on shared } std::barrier start(4); std::array writers; - for (unsigned writer = 0; writer < writers.size(); ++writer) - { + for (unsigned writer = 0; writer < writers.size(); ++writer) { writers[writer] = std::thread([&, writer] { start.arrive_and_wait(); - try - { - for (unsigned sequence = 0; sequence < 8; ++sequence) - { - for (unsigned client = 0; client < clients.size(); ++client) - { + try { + for (unsigned sequence = 0; sequence < 8; ++sequence) { + for (unsigned client = 0; client < clients.size(); ++client) { sends[writer].push_back( clients[client]->async_send(expected[client][writer][sequence], asio::use_future)); snapshots[writer].push_back(clients[client]->get_rudp_statistics()); } } + } catch (...) { + errors[writer] = std::current_exception(); } - catch (...) - { errors[writer] = std::current_exception(); } }); } - for (auto& writer : writers) - { + for (auto& writer : writers) { writer.join(); } - for (unsigned writer = 0; writer < writers.size(); ++writer) - { + for (unsigned writer = 0; writer < writers.size(); ++writer) { check(!errors[writer], "concurrent endpoint admission and statistics queries do not throw"); - for (unsigned i = 0; i < sends[writer].size(); ++i) - { + for (unsigned i = 0; i < sends[writer].size(); ++i) { const auto result = await(sends[writer][i]); check(!result.first && result.second == expected[i % 4][writer][i / 4].size(), "every concurrent send completes with its exact message size"); @@ -559,24 +507,18 @@ DOCTEST_TEST_CASE("RUDP FEC serializes concurrent sends and statistics on shared "concurrent snapshots respect the configured FEC memory bound"); } } - for (unsigned client = 0; client < clients.size(); ++client) - { + for (unsigned client = 0; client < clients.size(); ++client) { const auto complete = deliveries[client].wait_for(5s) == std::future_status::ready; - if (!complete) - { + if (!complete) { const auto snapshot = [](auto& endpoint) { - if (endpoint.is_started()) - { - try - { + if (endpoint.is_started()) { + try { auto future = endpoint.get_rudp_statistics(); - if (future.wait_for(1s) == std::future_status::ready) - { + if (future.wait_for(1s) == std::future_status::ready) { return future.get(); } + } catch (const std::logic_error&) { } - catch (const std::logic_error&) - {} } return arknet::rudp::statistics{}; }; @@ -608,14 +550,11 @@ DOCTEST_TEST_CASE("RUDP FEC serializes concurrent sends and statistics on shared check(complete, "all concurrent messages arrive before the receive deadline"); const auto actual = received[client].wait(32); check(actual.size() == 32, "concurrent senders deliver each complete message once"); - for (unsigned writer = 0; writer < writers.size(); ++writer) - { + for (unsigned writer = 0; writer < writers.size(); ++writer) { std::vector ordered; const auto prefix = std::to_string(writer) + ':'; - for (const auto& bytes : actual) - { - if (bytes.starts_with(prefix)) - { + for (const auto& bytes : actual) { + if (bytes.starts_with(prefix)) { ordered.push_back(bytes); } } @@ -627,8 +566,7 @@ DOCTEST_TEST_CASE("RUDP FEC serializes concurrent sends and statistics on shared std::chrono::steady_clock::now() + std::chrono::milliseconds(server_fec.group_lifetime_ms); auto snapshot = clients[client]->get_rudp_statistics(); auto stats = await(snapshot); - while (stats.fec.recovered_shards == 0 && std::chrono::steady_clock::now() < recovery_deadline) - { + while (stats.fec.recovered_shards == 0 && std::chrono::steady_clock::now() < recovery_deadline) { std::this_thread::sleep_for(1ms); snapshot = clients[client]->get_rudp_statistics(); stats = await(snapshot); @@ -663,8 +601,7 @@ DOCTEST_TEST_CASE("RUDP fixed FEC recovers missing datagrams with duplicates and client.bind_disconnect([&] { failure.store(arknet::get_last_error().value()); }); check(client.start("127.0.0.1", relay.port()), "FEC handshake passes relay"); std::vector expected; - for (unsigned i = 0; i < 24; ++i) - { + for (unsigned i = 0; i < 24; ++i) { auto bytes = std::to_string(i) + ':'; bytes.resize(i % 2 ? 4119 : 16384, static_cast(i)); expected.push_back(bytes); @@ -696,8 +633,7 @@ DOCTEST_TEST_CASE("RUDP FEC renegotiates after automatic reconnect") { arknet::rudp_client client; client.set_fec_options(arknet::fec::options::adaptive()).set_auto_reconnect(true, 50ms).set_connect_timeout(200ms); client.bind_connect([&] { - if (!arknet::get_last_error()) - { + if (!arknet::get_last_error()) { connections.push("connected"); } }); diff --git a/tests/rudp_stability.cpp b/tests/rudp_stability.cpp index 94f1b65..161321a 100644 --- a/tests/rudp_stability.cpp +++ b/tests/rudp_stability.cpp @@ -14,22 +14,19 @@ namespace { -struct clock_probe : arknet::detail::alive_time_cp -{ +struct clock_probe : arknet::detail::alive_time_cp { void move_wall_clock(std::chrono::system_clock::duration change) { last_alive_time_ += change; } }; template -struct polled_server_t -{ +struct polled_server_t { asio::io_context context; Server server{context}; ~polled_server_t() { - if (context.stopped()) - { + if (context.stopped()) { context.restart(); } asio::post(context, [this] { server.stop(); }); @@ -45,14 +42,11 @@ struct polled_server_t template void until(Predicate done) { const auto deadline = std::chrono::steady_clock::now() + 5s; - while (std::chrono::steady_clock::now() < deadline) - { - if (done()) - { + while (std::chrono::steady_clock::now() < deadline) { + if (done()) { return; } - if (context.stopped()) - { + if (context.stopped()) { context.restart(); } context.run_one_for(5ms); @@ -63,8 +57,7 @@ struct polled_server_t using polled_server = polled_server_t; -class raw_peer -{ +class raw_peer { public: explicit raw_peer(asio::ip::udp::endpoint destination) : socket_(context_, asio::ip::udp::v4()) { @@ -84,17 +77,14 @@ class raw_peer std::optional conversation; std::array bytes{}; arknet::error_code error; - for (;;) - { + for (;;) { const auto count = socket_.receive(asio::buffer(bytes), 0, error); - if (error == asio::error::would_block || error == asio::error::try_again) - { + if (error == asio::error::would_block || error == asio::error::try_again) { return conversation; } check(!error, "raw UDP peer receives without a transport error"); const std::string_view packet(bytes.data(), count); - if (arknet::detail::rudp_is_control(packet, 2) && arknet::detail::rudp_read_id(packet.data() + 1) == sync) - { + if (arknet::detail::rudp_is_control(packet, 2) && arknet::detail::rudp_read_id(packet.data() + 1) == sync) { conversation = arknet::detail::rudp_read_id(packet.data() + 5); } } @@ -105,8 +95,7 @@ class raw_peer asio::ip::udp::socket socket_; }; -struct observed_rudp_server : arknet::rudp_server_impl_t -{ +struct observed_rudp_server : arknet::rudp_server_impl_t { using base = arknet::rudp_server_impl_t; using base::base; @@ -183,8 +172,7 @@ DOCTEST_TEST_CASE("RUDP admission is bounded and freed sessions admit new peers" DOCTEST_CHECK_THROWS_AS(runtime.server.set_max_sessions(3), std::logic_error); DOCTEST_CHECK(runtime.server.get_max_sessions() == 2); std::vector> peers; - for (unsigned i = 0; i < 8; ++i) - { + for (unsigned i = 0; i < 8; ++i) { peers.push_back(std::make_unique(runtime.server.acceptor().local_endpoint())); } // Queue both handshakes before polling, then observe their ACKs rather than @@ -201,16 +189,14 @@ DOCTEST_TEST_CASE("RUDP admission is bounded and freed sessions admit new peers" check(first_session && second_session, "both peer endpoints have admitted sessions"); DOCTEST_CHECK(first_session->conversation() == first); DOCTEST_CHECK(second_session->conversation() == second); - for (unsigned i = 2; i < peers.size(); ++i) - { + for (unsigned i = 2; i < peers.size(); ++i) { peers[i]->send(arknet::detail::rudp_control(1, i + 1)); } // A missing ACK proves rejection only after every excess SYN was handled. runtime.until([&] { return runtime.server.received_datagrams == peers.size(); }); DOCTEST_CHECK(runtime.server.get_session_count() == 2); DOCTEST_CHECK(handshakes == 2); - for (unsigned i = 2; i < peers.size(); ++i) - { + for (unsigned i = 2; i < peers.size(); ++i) { DOCTEST_CHECK_FALSE(peers[i]->acknowledged(i + 1).has_value()); } peers[1]->send(arknet::detail::rudp_control(1, 2)); @@ -250,8 +236,7 @@ DOCTEST_TEST_CASE("RUDP invalid datagrams cannot refresh session liveness") { const std::vector invalid{"junk", arq_message(conversation + 1), bad_length, bad_command, arknet::detail::rudp_control(1, 122), arknet::detail::rudp_control(3, 123, conversation + 1), arknet::detail::rudp_control(3, 122, conversation)}; - for (const auto& packet : invalid) - { + for (const auto& packet : invalid) { const auto received = runtime.server.received_datagrams; peer.send(packet); runtime.until([&] { return runtime.server.received_datagrams == received + 1; }); @@ -272,8 +257,7 @@ DOCTEST_TEST_CASE("RUDP invalid datagrams cannot refresh session liveness") { } DOCTEST_TEST_CASE("RUDP invalid traffic cannot postpone the silence timeout") { - for (unsigned mode = 0; mode < 4; ++mode) - { + for (unsigned mode = 0; mode < 4; ++mode) { DOCTEST_INFO(mode); unsigned disconnected{}; arknet::rudp_session* original{}; @@ -283,8 +267,7 @@ DOCTEST_TEST_CASE("RUDP invalid traffic cannot postpone the silence timeout") { polled_server runtime; runtime.server.bind_connect([](auto& session) { session->set_silence_timeout(60ms); }); runtime.server.bind_disconnect([&](auto& session) { - if (session.get() == original) - { + if (session.get() == original) { ++disconnected; disconnect_error = arknet::get_last_error(); } @@ -297,8 +280,7 @@ DOCTEST_TEST_CASE("RUDP invalid traffic cannot postpone the silence timeout") { check(session, "timeout test retains its original session"); original = session.get(); std::string invalid; - switch (mode) - { + switch (mode) { case 0: invalid = "junk"; break; @@ -313,8 +295,7 @@ DOCTEST_TEST_CASE("RUDP invalid traffic cannot postpone the silence timeout") { break; } const auto deadline = std::chrono::steady_clock::now() + 5s; - while (!session->is_stopped() && std::chrono::steady_clock::now() < deadline) - { + while (!session->is_stopped() && std::chrono::steady_clock::now() < deadline) { peer.send(invalid); runtime.context.run_one_for(5ms); } diff --git a/tests/scoped_allocation_failure.cpp b/tests/scoped_allocation_failure.cpp index 7bedcec..39727cf 100644 --- a/tests/scoped_allocation_failure.cpp +++ b/tests/scoped_allocation_failure.cpp @@ -12,8 +12,7 @@ namespace { void before_allocation() { - if (scoped_allocation_failure::armed) - { + if (scoped_allocation_failure::armed) { scoped_allocation_failure::disarm(); ++scoped_allocation_failure::injections; throw std::bad_alloc(); @@ -22,8 +21,7 @@ void before_allocation() { void* allocate(std::size_t size) { before_allocation(); - if (auto* pointer = std::malloc(size ? size : 1)) - { + if (auto* pointer = std::malloc(size ? size : 1)) { return pointer; } throw std::bad_alloc(); @@ -36,13 +34,11 @@ void* allocate_aligned(std::size_t size, std::align_val_t alignment) { auto* pointer = _aligned_malloc(size ? size : 1, value); #else void* pointer{}; - if (posix_memalign(&pointer, value < sizeof(void*) ? sizeof(void*) : value, size ? size : 1)) - { + if (posix_memalign(&pointer, value < sizeof(void*) ? sizeof(void*) : value, size ? size : 1)) { pointer = nullptr; } #endif - if (pointer) - { + if (pointer) { return pointer; } throw std::bad_alloc(); @@ -67,10 +63,11 @@ void* operator new[](std::size_t size) { } void* operator new(std::size_t size, const std::nothrow_t&) noexcept { - try - { return allocate(size); } - catch (...) - { return nullptr; } + try { + return allocate(size); + } catch (...) { + return nullptr; + } } void* operator new[](std::size_t size, const std::nothrow_t&) noexcept { @@ -110,10 +107,11 @@ void* operator new[](std::size_t size, std::align_val_t alignment) { } void* operator new(std::size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept { - try - { return allocate_aligned(size, alignment); } - catch (...) - { return nullptr; } + try { + return allocate_aligned(size, alignment); + } catch (...) { + return nullptr; + } } void* operator new[](std::size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept { diff --git a/tests/scoped_allocation_failure.hpp b/tests/scoped_allocation_failure.hpp index eea5fe8..fb7af08 100644 --- a/tests/scoped_allocation_failure.hpp +++ b/tests/scoped_allocation_failure.hpp @@ -17,8 +17,7 @@ inline void disarm() noexcept { armed = false; } -class scope -{ +class scope { bool previous_; public: diff --git a/tests/scoped_asio_allocation_failure.hpp b/tests/scoped_asio_allocation_failure.hpp index b6ef894..e0f0604 100644 --- a/tests/scoped_asio_allocation_failure.hpp +++ b/tests/scoped_asio_allocation_failure.hpp @@ -31,8 +31,7 @@ namespace scoped_allocation_failure { inline void* asio_aligned_malloc(std::size_t size, std::size_t alignment) noexcept { - if (armed) - { + if (armed) { disarm(); ++injections; // Let Asio's actual allocation path throw its normal bad_alloc. diff --git a/tests/socket_metadata.cpp b/tests/socket_metadata.cpp index 4f7c96d..6396a2b 100644 --- a/tests/socket_metadata.cpp +++ b/tests/socket_metadata.cpp @@ -65,8 +65,7 @@ void lifecycle() { const auto result = arknet::get_last_error(); check_native_snapshot(server); const auto port = server.get_listen_port(); - if (!result) - { + if (!result) { CHECK(port != 0); CHECK(server.get_listen_address() == "127.0.0.1"); } @@ -76,16 +75,12 @@ void lifecycle() { }); server.bind_stop([&] { ++stopped; - if constexpr (std::is_same_v) - { - if (bound_port) - { + if constexpr (std::is_same_v) { + if (bound_port) { CHECK(server.get_listen_port() == bound_port); CHECK(server.get_listen_address() == "127.0.0.1"); } - } - else - { + } else { check_closed(server); } }); @@ -131,23 +126,20 @@ void lifecycle() { check_closed(server); } -struct external_runtime -{ +struct external_runtime { asio::io_context context; asio::executor_work_guard work{context.get_executor()}; std::array workers; external_runtime() { - for (auto& worker : workers) - { + for (auto& worker : workers) { worker = std::thread([this] { context.run(); }); } } ~external_runtime() { context.stop(); - for (auto& worker : workers) - { + for (auto& worker : workers) { worker.join(); } } @@ -172,13 +164,10 @@ void callback_metadata() { server.stop(); }); server.bind_stop([&] { - if constexpr (std::is_same_v) - { + if constexpr (std::is_same_v) { CHECK(server.get_listen_port() == port); CHECK(server.get_listen_address() == "127.0.0.1"); - } - else - { + } else { check_closed(server); } stopped.set_value(); @@ -201,18 +190,15 @@ void concurrent_stop() { std::atomic invalid{}; std::atomic reads{}; std::array readers; - for (auto& reader : readers) - { + for (auto& reader : readers) { reader = std::thread([&] { ready.arrive_and_wait(); - while (!finished.load()) - { + while (!finished.load()) { const auto observed_port = server.get_listen_port(); const auto port_error = arknet::get_last_error(); if ((port_error && port_error != asio::error::bad_descriptor && port_error != asio::error::invalid_argument) || - (port_error && observed_port != 0) || (observed_port != 0 && observed_port != port)) - { + (port_error && observed_port != 0) || (observed_port != 0 && observed_port != port)) { ++invalid; } const auto address = server.get_listen_address(); @@ -220,8 +206,7 @@ void concurrent_stop() { if ((address_error && address_error != asio::error::bad_descriptor && address_error != asio::error::invalid_argument) || (address_error && !address.empty()) || - (!address_error && address != "127.0.0.1" && address != "0.0.0.0")) - { + (!address_error && address != "127.0.0.1" && address != "0.0.0.0")) { ++invalid; } ++reads; @@ -230,18 +215,15 @@ void concurrent_stop() { } ready.arrive_and_wait(); unsigned successful_restarts = 0; - for (unsigned iteration = 0; iteration < 8; ++iteration) - { + for (unsigned iteration = 0; iteration < 8; ++iteration) { server.stop(); - if (server.start("127.0.0.1", port)) - { + if (server.start("127.0.0.1", port)) { ++successful_restarts; } } server.stop(); finished = true; - for (auto& reader : readers) - { + for (auto& reader : readers) { reader.join(); } CHECK(successful_restarts == 8); diff --git a/tests/stress.cpp b/tests/stress.cpp index 969a7f4..8163154 100644 --- a/tests/stress.cpp +++ b/tests/stress.cpp @@ -13,16 +13,14 @@ namespace { -enum class protocol -{ +enum class protocol { tcp, udp, websocket, http }; -struct options -{ +struct options { std::string selected = "all"; #if defined(ARKNET_TEST_CERT_DIR) std::string certs = ARKNET_TEST_CERT_DIR; @@ -43,8 +41,7 @@ options stress_config; std::size_t number(std::string_view value) { std::size_t result = 0; const auto parsed = std::from_chars(value.data(), value.data() + value.size(), result); - if (parsed.ec != std::errc{} || parsed.ptr != value.data() + value.size()) - { + if (parsed.ec != std::errc{} || parsed.ptr != value.data() + value.size()) { throw std::invalid_argument("invalid stress number"); } return result; @@ -52,79 +49,53 @@ std::size_t number(std::string_view value) { options parse(int argc, char** argv) { options result; - for (int i = 1; i < argc; ++i) - { + for (int i = 1; i < argc; ++i) { const std::string_view name(argv[i]); - if (name == "--list-test-cases") - { + if (name == "--list-test-cases") { result.list_test_cases = true; continue; } - if (name.starts_with("--test-case=")) - { + if (name.starts_with("--test-case=")) { result.test_case = name.substr(12); continue; } - if (i + 1 >= argc) - { + if (i + 1 >= argc) { throw std::invalid_argument("stress option requires a value"); } const std::string_view value(argv[++i]); - if (name == "--protocol") - { + if (name == "--protocol") { result.selected = value; - } - else if (name == "--certs") - { + } else if (name == "--certs") { result.certs = value; - } - else if (name == "--clients") - { + } else if (name == "--clients") { result.clients = number(value); - } - else if (name == "--rounds") - { + } else if (name == "--rounds") { result.rounds = number(value); - } - else if (name == "--window") - { + } else if (name == "--window") { result.window = number(value); - } - else if (name == "--messages") - { + } else if (name == "--messages") { result.messages = number(value); - } - else if (name == "--seconds") - { + } else if (name == "--seconds") { result.seconds = number(value); - } - else if (name == "--test-case") - { + } else if (name == "--test-case") { result.test_case = value; - } - else - { + } else { throw std::invalid_argument("unknown stress option"); } } - if (result.clients < 16 || result.clients > 64) - { + if (result.clients < 16 || result.clients > 64) { throw std::invalid_argument("stress clients must be between 16 and 64"); } - if (result.rounds < 2 || result.rounds > 10000) - { + if (result.rounds < 2 || result.rounds > 10000) { throw std::invalid_argument("stress rounds must be between 2 and 10000"); } - if (result.window == 0 || result.window > 16) - { + if (result.window == 0 || result.window > 16) { throw std::invalid_argument("stress window must be between 1 and 16"); } - if (result.messages < result.window || result.messages > 4096) - { + if (result.messages < result.window || result.messages > 4096) { throw std::invalid_argument("invalid stress messages"); } - if (result.seconds > 3600) - { + if (result.seconds > 3600) { throw std::invalid_argument("stress seconds exceeds one hour"); } bool selected = result.selected == "all" || result.selected == "tcp" || result.selected == "udp"; @@ -137,15 +108,13 @@ options parse(int argc, char** argv) { selected = selected || result.selected == "wss" || result.selected == "https"; #endif #endif - if (!selected) - { + if (!selected) { throw std::invalid_argument("unknown or disabled stress protocol"); } return result; } -struct io_runner -{ +struct io_runner { asio::io_context context; asio::executor_work_guard work{context.get_executor()}; std::thread thread{[this] { context.run(); }}; @@ -157,13 +126,11 @@ struct io_runner } }; -struct cleanup -{ +struct cleanup { std::function stop; ~cleanup() { - if (stop) - { + if (stop) { stop(); } } @@ -172,15 +139,12 @@ struct cleanup bool accept_peer(asio::ip::tcp::acceptor& listener, asio::ip::tcp::socket& peer, const std::atomic& abort) { const auto deadline = std::chrono::steady_clock::now() + 5s; arknet::error_code ec; - while (!abort && std::chrono::steady_clock::now() < deadline) - { + while (!abort && std::chrono::steady_clock::now() < deadline) { listener.accept(peer, ec); - if (!ec) - { + if (!ec) { return true; } - if (ec != asio::error::would_block && ec != asio::error::try_again) - { + if (ec != asio::error::would_block && ec != asio::error::try_again) { return false; } std::this_thread::sleep_for(1ms); @@ -192,20 +156,16 @@ bool read_peer(asio::ip::tcp::socket& peer, std::string& data, const std::atomic const auto deadline = std::chrono::steady_clock::now() + 20s; arknet::error_code ec; peer.non_blocking(true, ec); - if (ec) - { + if (ec) { return false; } std::size_t offset = 0; - while (!abort && offset < data.size() && std::chrono::steady_clock::now() < deadline) - { + while (!abort && offset < data.size() && std::chrono::steady_clock::now() < deadline) { offset += peer.read_some(asio::buffer(data.data() + offset, data.size() - offset), ec); - if (ec && ec != asio::error::would_block && ec != asio::error::try_again) - { + if (ec && ec != asio::error::would_block && ec != asio::error::try_again) { return false; } - if (ec) - { + if (ec) { std::this_thread::sleep_for(1ms); } } @@ -216,24 +176,21 @@ std::string packet(std::uint32_t round, std::uint32_t client, std::uint32_t sequ const std::array header{round, client, sequence}; std::string data(sizeof(header) + 32 + sequence % 8 * 113, '\0'); std::memcpy(data.data(), header.data(), sizeof(header)); - for (std::size_t i = sizeof(header); i < data.size(); ++i) - { + for (std::size_t i = sizeof(header); i < data.size(); ++i) { data[i] = static_cast((round * 7 + client * 19 + sequence * 31 + i) % 251); } return data; } bool header(std::string_view data, std::array& value) { - if (data.size() < sizeof(value)) - { + if (data.size() < sizeof(value)) { return false; } std::memcpy(value.data(), data.data(), sizeof(value)); return true; } -struct batch -{ +struct batch { explicit batch(std::size_t size, std::uint32_t generation) : echo_calls(size) , seen(size) @@ -270,32 +227,28 @@ struct batch template bool stress_send(Sender& sender, Data&& data, Callback&& callback) { #if ARKNET_USE_BOOST_ASIO - if constexpr (Kind == protocol::http) - { - if constexpr (Sender::is_client()) - { + if constexpr (Kind == protocol::http) { + if constexpr (Sender::is_client()) { http::request request(http::verb::post, "/stress", 11); request.set(http::field::host, "localhost"); request.body() = std::forward(data); request.prepare_payload(); return sender.async_send(std::move(request), std::forward(callback)); - } - else - { + } else { http::response response(http::status::ok, 11); response.body() = std::forward(data); response.prepare_payload(); return sender.async_send(std::move(response), std::forward(callback)); } - } - else + } else #endif - { return sender.async_send(std::forward(data), std::forward(callback)); } + { + return sender.async_send(std::forward(data), std::forward(callback)); + } } template -struct flow -{ +struct flow { flow(std::shared_ptr owner, std::size_t identity, std::size_t count, std::size_t pipeline, std::unique_ptr client) : monitor(std::move(owner)) @@ -318,8 +271,7 @@ struct flow void begin(std::shared_ptr owner) { monitor = std::move(owner); - for (auto& count : calls) - { + for (auto& count : calls) { count = 0; } std::fill(seen.begin(), seen.end(), false); @@ -328,23 +280,20 @@ struct flow } void pump() { - while (!monitor->failed && next < total && next - received < window) - { + while (!monitor->failed && next < total && next - received < window) { const auto sequence = next++; auto data = packet(monitor->round, static_cast(id), static_cast(sequence)); const auto bytes = data.size(); const bool accepted = stress_send(*peer, std::move(data), [this, current = monitor, sequence, bytes](const arknet::error_code& ec, std::size_t count) { if (calls[sequence].fetch_add(1) != 0 || ec || - (Kind == protocol::http ? count <= bytes : count != bytes)) - { + (Kind == protocol::http ? count <= bytes : count != bytes)) { current->fail(); } ++current->completed; current->notify(); }); - if (!accepted) - { + if (!accepted) { monitor->fail(); } } @@ -354,8 +303,7 @@ struct flow std::array value{}; if (!header(data, value) || value[0] != monitor->round || value[1] != id || value[2] >= total || seen[value[2]] || data != packet(value[0], value[1], value[2]) || - (Kind != protocol::udp && value[2] != received)) - { + (Kind != protocol::udp && value[2] != received)) { monitor->fail(); return; } @@ -369,12 +317,9 @@ struct flow template bool listen(Server& server) { - if constexpr (Kind == protocol::tcp) - { + if constexpr (Kind == protocol::tcp) { return server.start("127.0.0.1", 0, arknet::use_dgram); - } - else - { + } else { return server.start("127.0.0.1", 0); } } @@ -382,16 +327,11 @@ bool listen(Server& server) { template bool connect(Client& client, std::uint16_t port) { const char* host = "127.0.0.1"; - if constexpr (Kind == protocol::tcp) - { + if constexpr (Kind == protocol::tcp) { return client.start(host, port, arknet::use_dgram); - } - else if constexpr (Kind == protocol::websocket) - { + } else if constexpr (Kind == protocol::websocket) { return client.start(host, port, "/stress"); - } - else - { + } else { return client.start(host, port); } } @@ -405,70 +345,58 @@ void load(const options& config, const char* name, std::string_view model, std:: std::shared_ptr active; auto server = external ? std::make_unique(2048, 1024 * 1024, runner->server_lanes(config.clients)) : std::make_unique(2048, 1024 * 1024, 4); - if constexpr (requires { server->set_cert_file("", "", "", ""); }) - { + if constexpr (requires { server->set_cert_file("", "", "", ""); }) { check(!config.certs.empty(), "TLS stress requires --certs"); server->set_cert_file("", config.certs + "/server.pem", config.certs + "/server-key.pem", ""); check(!arknet::get_last_error(), "stress server identity"); } server->bind_recv([&](auto& session, const auto& incoming) { const std::string_view data = [&]() -> std::string_view { - if constexpr (Kind == protocol::http) - { + if constexpr (Kind == protocol::http) { return incoming.body(); - } - else - { + } else { return incoming; } }(); auto current = std::atomic_load(&active); std::array value{}; if (!header(data, value) || value[0] != current->round || value[1] >= config.clients || - value[2] >= config.messages || data != packet(value[0], value[1], value[2])) - { + value[2] >= config.messages || data != packet(value[0], value[1], value[2])) { current->fail(); return; } const auto index = value[1] * config.messages + value[2]; - if (current->seen[index].fetch_add(1) != 0) - { + if (current->seen[index].fetch_add(1) != 0) { current->fail(); } - if constexpr (Kind == protocol::websocket) - { + if constexpr (Kind == protocol::websocket) { session->ws_stream().binary(true); } const auto bytes = data.size(); if (!stress_send( *session, std::string(data), [current, index, bytes](const arknet::error_code& ec, std::size_t count) { if (current->echo_calls[index].fetch_add(1) != 0 || ec || - (Kind == protocol::http ? count <= bytes : count != bytes)) - { + (Kind == protocol::http ? count <= bytes : count != bytes)) { current->fail(); } ++current->completed; current->notify(); - })) - { + })) { current->fail(); } }); std::vector> clients; - for (std::size_t i = 0; i < config.clients; ++i) - { + for (std::size_t i = 0; i < config.clients; ++i) { auto client = external ? std::make_unique(2048, 1024 * 1024, runner->context_at(i)) : std::make_unique(2048, 1024 * 1024, 1); client->set_auto_reconnect(false); client->set_connect_timeout(10s); client->set_max_send_buffer_size(config.window * 2048); - if constexpr (requires { client->load_verify_file(std::string{}); }) - { + if constexpr (requires { client->load_verify_file(std::string{}); }) { client->load_verify_file(config.certs + "/ca.pem"); } - if constexpr (Kind == protocol::websocket) - { + if constexpr (Kind == protocol::websocket) { client->bind_connect([ptr = client.get()] { ptr->ws_stream().binary(true); }); } clients.push_back(std::move(client)); @@ -476,49 +404,39 @@ void load(const options& config, const char* name, std::string_view model, std:: const auto duration = std::chrono::milliseconds(config.seconds * 250) / config.rounds; std::size_t batches = 0; - for (std::size_t round = 0; round < config.rounds; ++round) - { + for (std::size_t round = 0; round < config.rounds; ++round) { auto monitor = std::make_shared(config.clients * config.messages, static_cast(batches)); std::atomic_store(&active, monitor); std::vector>> runs; - for (std::size_t i = 0; i < config.clients; ++i) - { + for (std::size_t i = 0; i < config.clients; ++i) { runs.push_back(std::make_unique>( monitor, i, config.messages, config.window, std::move(clients[i]))); } cleanup stop_all{[&] { - for (auto& run : runs) - { + for (auto& run : runs) { run->peer->request_stop(); } - for (auto& run : runs) - { + for (auto& run : runs) { run->peer->wait_stopped(); } server->request_stop(); server->wait_stopped(); }}; - if (!listen(*server)) - { + if (!listen(*server)) { const auto ec = arknet::get_last_error(); std::cerr << name << label << " listen round=" << round << ": " << ec.category().name() << ':' << ec.value() << " " << ec.message() << std::endl; check(false, "stress server start/restart"); } - for (auto& run : runs) - { + for (auto& run : runs) { run->peer->bind_recv([ptr = run.get()](const auto& data) { - if constexpr (Kind == protocol::http) - { + if constexpr (Kind == protocol::http) { ptr->receive(data.body()); - } - else - { + } else { ptr->receive(data); } }); - if (!connect(*run->peer, server->get_listen_port())) - { + if (!connect(*run->peer, server->get_listen_port())) { const auto ec = arknet::get_last_error(); std::cerr << name << label << " connect round=" << round << " client=" << run->id << " port=" << server->get_listen_port() << ": " << ec.category().name() << ':' << ec.value() @@ -527,35 +445,28 @@ void load(const options& config, const char* name, std::string_view model, std:: } } const auto deadline = std::chrono::steady_clock::now() + duration; - do - { + do { std::atomic_store(&active, monitor); - for (auto& run : runs) - { + for (auto& run : runs) { run->peer->post([ptr = run.get(), monitor] { ptr->begin(monitor); }); } const bool finished = monitor->wait(); - if (!finished) - { + if (!finished) { std::cerr << name << label << " batch round=" << round << " generation=" << batches << " received=" << monitor->received << '/' << monitor->total << " completions=" << monitor->completed << '/' << monitor->total * 2 << std::endl; check(false, (monitor->failed ? "stress content/sequence/completion failure" : "stress batch timeout")); } - for (auto& run : runs) - { - for (const auto& count : run->calls) - { + for (auto& run : runs) { + for (const auto& count : run->calls) { check(count == 1, "stress client completion is not exactly once"); } } - for (std::size_t i = 0; i < monitor->total; ++i) - { + for (std::size_t i = 0; i < monitor->total; ++i) { check(monitor->echo_calls[i] == 1 && monitor->seen[i] == 1, "stress server echo is not exactly once"); } ++batches; - if (std::chrono::steady_clock::now() >= deadline) - { + if (std::chrono::steady_clock::now() >= deadline) { break; } monitor = std::make_shared(config.clients * config.messages, static_cast(batches)); @@ -563,14 +474,12 @@ void load(const options& config, const char* name, std::string_view model, std:: stop_all.stop(); stop_all.stop = {}; check(server->get_session_count() == 0, "stress stop retains server sessions"); - for (auto& run : runs) - { + for (auto& run : runs) { check(run->peer->is_stopped() && run->peer->get_queued_send_buffer_size() == 0, "stress stop retains client work"); clients[run->id] = std::move(run->peer); } - if (runner) - { + if (runner) { check(!runner->context_at().stopped(), "stress stops shared context"); std::promise alive; auto future = alive.get_future(); @@ -599,8 +508,7 @@ void slow_receiver() { std::atomic abort{}; std::thread reader([&] { arknet::error_code ec; - if (!accept_peer(listener, socket, abort)) - { + if (!accept_peer(listener, socket, abort)) { integrity = false; accepted.set_value(); return; @@ -608,17 +516,14 @@ void slow_receiver() { socket.set_option(asio::socket_base::receive_buffer_size(4096), ec); accepted.set_value(); std::unique_lock lock(mutex); - if (!ready.wait_for(lock, 20s, [&] { return release; })) - { + if (!ready.wait_for(lock, 20s, [&] { return release; })) { integrity = false; return; } lock.unlock(); std::string data(size, '\0'); - for (const auto index : expected) - { - if (!read_peer(socket, data, abort) || data != std::string(size, static_cast('a' + index))) - { + for (const auto index : expected) { + if (!read_peer(socket, data, abort) || data != std::string(size, static_cast('a' + index))) { integrity = false; break; } @@ -636,8 +541,7 @@ void slow_receiver() { release = true; } ready.notify_all(); - if (reader.joinable()) - { + if (reader.joinable()) { reader.join(); } arknet::error_code ec; @@ -649,28 +553,22 @@ void slow_receiver() { client.bind_init([&] { client.set_sndbuf_size(4096); }); check(client.start("127.0.0.1", listener.local_endpoint().port()), "slow receiver connect"); await(connected); - for (std::size_t i = 0; i < attempts; ++i) - { + for (std::size_t i = 0; i < attempts; ++i) { const bool queued = client.async_send( std::string(size, static_cast('a' + i)), [&, i](const arknet::error_code& ec, std::size_t bytes) { - if (calls[i].fetch_add(1) != 0) - { + if (calls[i].fetch_add(1) != 0) { valid = false; } - if (ec == asio::error::no_buffer_space && bytes == 0) - { + if (ec == asio::error::no_buffer_space && bytes == 0) { ++rejected; - } - else if (ec || bytes != size) - { + } else if (ec || bytes != size) { valid = false; } ++completed; std::lock_guard lock(mutex); ready.notify_all(); }); - if (queued) - { + if (queued) { expected.push_back(i); } } @@ -694,8 +592,7 @@ void slow_receiver() { check(read_peer(socket, recovered, abort) && recovered == "recovered", "slow receiver recovery payload mismatch"); client.stop(); finish.stop = {}; - for (const auto& count : calls) - { + for (const auto& count : calls) { check(count == 1, "slow receiver completion is not exactly once"); } std::cout << "TCP slow receiver: " << expected.size() << " accepted, " << rejected << " rejected, queue drained" @@ -717,8 +614,7 @@ void blocked_stop() { std::atomic abort{}; std::thread peer([&] { arknet::error_code ec; - if (!accept_peer(listener, socket, abort)) - { + if (!accept_peer(listener, socket, abort)) { watchdog = true; accepted.set_value(); return; @@ -739,8 +635,7 @@ void blocked_stop() { stopped = true; } ready.notify_all(); - if (peer.joinable()) - { + if (peer.joinable()) { peer.join(); } client.stop(); @@ -750,12 +645,10 @@ void blocked_stop() { client.bind_init([&] { client.set_sndbuf_size(4096); }); check(client.start("127.0.0.1", listener.local_endpoint().port()), "blocked stop connect"); await(connected); - for (std::size_t i = 0; i < calls.size(); ++i) - { + for (std::size_t i = 0; i < calls.size(); ++i) { check(client.async_send(std::string(size, 'x'), [&, i](const arknet::error_code&, std::size_t bytes) { - if (calls[i].fetch_add(1) != 0 || bytes > size) - { + if (calls[i].fetch_add(1) != 0 || bytes > size) { valid = false; } }), @@ -790,23 +683,19 @@ void blocked_server_stop(bool external) { auto server = external ? std::make_unique(runner->context) : std::make_unique(2048, 1024 * 1024, 2); server->bind_connect([&](auto& connected) { - if (notified.exchange(true)) - { + if (notified.exchange(true)) { valid = false; return; } session = connected; connected->set_disconnect_timeout(200ms); connected->set_sndbuf_size(4096); - for (std::size_t i = 0; i < calls.size(); ++i) - { + for (std::size_t i = 0; i < calls.size(); ++i) { if (!connected->async_send(std::string(size, 's'), [&, i](const arknet::error_code&, std::size_t bytes) { - if (calls[i].fetch_add(1) != 0 || bytes > size) - { + if (calls[i].fetch_add(1) != 0 || bytes > size) { valid = false; } - })) - { + })) { valid = false; } } @@ -835,8 +724,7 @@ void blocked_server_stop(bool external) { stopped = true; } ready.notify_all(); - if (peer.joinable()) - { + if (peer.joinable()) { peer.join(); } server->stop(); @@ -857,12 +745,10 @@ void blocked_server_stop(bool external) { check(!watchdog, "server stop waits for blocked session writes until the peer watchdog closes it"); check(valid && calls[0] == 1 && calls[1] == 1 && server->get_session_count() == 0, "blocked server stop leaves sends or sessions"); - if (const auto remaining = session.lock()) - { + if (const auto remaining = session.lock()) { check(remaining->get_queued_send_buffer_size() == 0, "blocked server stop retains session queued bytes"); } - if (runner) - { + if (runner) { check(!runner->context.stopped(), "blocked server stops host context"); } } @@ -886,8 +772,7 @@ void stop_during_connect(bool external) { server->bind_connect([&](auto& session) { const auto previous = connections.fetch_add(1); session->stop(); - if (previous == 0) - { + if (previous == 0) { connected.set_value(); } }); @@ -899,8 +784,7 @@ void stop_during_connect(bool external) { finish.stop = {}; check(connections == 1 && disconnections == 0 && server->get_session_count() == 0, "stop-in-connect leaves a session or emits disconnect for an unregistered session"); - if (runner) - { + if (runner) { check(!runner->context.stopped(), "stop-in-connect stops host context"); std::promise alive; auto completed = alive.get_future(); @@ -974,25 +858,21 @@ DOCTEST_TEST_CASE("stress.https.load" * doctest::test_suite("stress.https")) { int main(int argc, char** argv) { std::cout << std::unitbuf; - try - { stress_config = parse(argc, argv); } - catch (const std::exception& error) - { + try { + stress_config = parse(argc, argv); + } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return EXIT_FAILURE; } doctest::Context context; - if (stress_config.list_test_cases) - { + if (stress_config.list_test_cases) { const char* argument = "--list-test-cases"; context.applyCommandLine(1, &argument); } - if (stress_config.selected != "all") - { + if (stress_config.selected != "all") { context.addFilter("test-suite", ("stress." + stress_config.selected).c_str()); } - if (!stress_config.test_case.empty()) - { + if (!stress_config.test_case.empty()) { context.addFilter("test-case", stress_config.test_case.c_str()); } return context.run(); diff --git a/tests/tcp.cpp b/tests/tcp.cpp index 825fe47..74f07d4 100644 --- a/tests/tcp.cpp +++ b/tests/tcp.cpp @@ -9,14 +9,12 @@ #include #include "check.hpp" -class custom_session : public arknet::tcp_session_t -{ +class custom_session : public arknet::tcp_session_t { public: using arknet::tcp_session_t::tcp_session_t; }; -class guarded_client : public arknet::tcp_client_t -{ +class guarded_client : public arknet::tcp_client_t { public: using arknet::tcp_client_t::tcp_client_t; std::atomic filter_calls{}; @@ -65,8 +63,7 @@ DOCTEST_TEST_CASE("TCP raw sends own buffers and guarded temporary payloads") { check(!guarded_result.first && guarded_result.second == 8190, "guarded temporary payload completion"); const auto chunks = received.wait(1366); std::string reconstructed; - for (std::size_t i = 1; i < chunks.size(); ++i) - { + for (std::size_t i = 1; i < chunks.size(); ++i) { reconstructed += chunks[i]; } check(reconstructed == std::string(8190, 'g') && client.get_queued_send_buffer_size() == 0, @@ -104,8 +101,7 @@ DOCTEST_TEST_CASE("TCP datagram framing covers each length prefix boundary") { client.bind_recv([&](std::string_view data) { received.push(data); }); check(client.start("127.0.0.1", server.get_listen_port(), arknet::use_dgram), "boundary connect"); std::vector expected; - for (const auto length : {0u, 1u, 253u, 254u, 65535u, 65536u}) - { + for (const auto length : {0u, 1u, 253u, 254u, 65535u, 65536u}) { expected.emplace_back(length, static_cast('a' + expected.size())); client.async_send(expected.back()); } @@ -128,8 +124,7 @@ DOCTEST_TEST_CASE("TCP datagram receives retain split prefixes and consecutive f const std::array prefix{254, 254, 0}; // A native peer bypasses arknet's sender so receiver and sender cannot share // a framing bug. Separate writes also allow incomplete prefix/body reads. - for (const auto byte : prefix) - { + for (const auto byte : prefix) { asio::write(peer, asio::buffer(&byte, 1)); } asio::write(peer, asio::buffer(first.data(), 17)); @@ -204,8 +199,7 @@ DOCTEST_TEST_CASE("Stopped TCP clients release ownership after their external co client->async_send(std::string("stop")); await(received); const auto deadline = std::chrono::steady_clock::now() + 5s; - while (!client->is_stopped() && std::chrono::steady_clock::now() < deadline) - { + while (!client->is_stopped() && std::chrono::steady_clock::now() < deadline) { std::this_thread::sleep_for(1ms); } check(client->is_stopped(), "callback stop completed"); diff --git a/tests/tls.cpp b/tests/tls.cpp index f6da9df..54cfae2 100644 --- a/tests/tls.cpp +++ b/tests/tls.cpp @@ -150,12 +150,9 @@ void reject_missing_client_certificate() { trust(client); client.bind_disconnect([&] { refused.set_value(); }); // TLS 1.3 may finish the client handshake before the server's certificate-required alert. - if (client.start("localhost", server.get_listen_port())) - { + if (client.start("localhost", server.get_listen_port())) { await(refused_result); - } - else - { + } else { const auto error = arknet::get_last_error(); check(error != asio::error::timed_out && error != asio::error::operation_aborted, "mTLS rejection must not be a timeout or cancellation"); @@ -268,8 +265,7 @@ void secure_reconnect() { trust(client); client.set_auto_reconnect(true, 20ms); client.bind_connect([&] { - if (!arknet::get_last_error()) - { + if (!arknet::get_last_error()) { connected.push("connected"); } }); diff --git a/tests/udp.cpp b/tests/udp.cpp index 2f5c732..e29162a 100644 --- a/tests/udp.cpp +++ b/tests/udp.cpp @@ -12,10 +12,8 @@ namespace { -struct udp_send_probe : arknet::detail::udp_send_op -{ - struct test_io - { +struct udp_send_probe : arknet::detail::udp_send_op { + struct test_io { bool running_in_this_thread() const { return true; } @@ -23,8 +21,7 @@ struct udp_send_probe : arknet::detail::udp_send_op test_io* io_ = &context; - struct test_stream - { + struct test_stream { arknet::error_code error; std::size_t count{}; @@ -58,10 +55,8 @@ DOCTEST_TEST_CASE("UDP send completion reports zero bytes on failure") { const std::array, 5> results{ {{asio::error::message_size, 65508}, {arknet::error_code(1784, asio::error::get_system_category()), 65508}, {asio::error::operation_aborted, 8}, {{}, 8}, {{}, 0}}}; - for (const auto& [error, count] : results) - { - for (const bool connected : {false, true}) - { + for (const auto& [error, count] : results) { + for (const bool connected : {false, true}) { DOCTEST_INFO("connected=", connected, ", error=", error.value(), ", bytes=", count); udp_send_probe sender; sender.socket.error = error; @@ -83,8 +78,7 @@ DOCTEST_TEST_CASE("UDP send completion reports zero bytes on failure") { } } -struct udp_io_runner -{ +struct udp_io_runner { asio::io_context context; asio::executor_work_guard work{context.get_executor()}; std::thread thread{[this] { context.run(); }}; @@ -92,8 +86,7 @@ struct udp_io_runner void finish() { work.reset(); context.stop(); - if (thread.joinable()) - { + if (thread.joinable()) { thread.join(); } } @@ -104,8 +97,7 @@ struct udp_io_runner }; DOCTEST_TEST_CASE("UDP clients do not opt into source port reuse by default") { - for (const auto* host : {"127.0.0.1", "::1"}) - { + for (const auto* host : {"127.0.0.1", "::1"}) { DOCTEST_INFO("host=", std::string(host)); asio::io_context context; const asio::ip::udp::endpoint destination(asio::ip::make_address(host), 0); @@ -126,8 +118,7 @@ DOCTEST_TEST_CASE("UDP clients do not opt into source port reuse by default") { competing.set_option(asio::socket_base::reuse_address(reuse)); arknet::error_code error; competing.bind(local, error); - if (!error) - { + if (!error) { competing.connect(peer.local_endpoint(), error); } DOCTEST_CHECK_MESSAGE( @@ -153,8 +144,7 @@ DOCTEST_TEST_CASE("UDP sessions preserve first, empty and binary datagrams acros arknet::udp_server server(1024, 65536); server.bind_connect([&](auto&) { ++connects; }); server.bind_recv([&](auto& session, std::string_view data) { - if (data.size() == 8192) - { + if (data.size() == 8192) { second_endpoint = session->get_remote_port(); } session->async_send(data); @@ -166,16 +156,14 @@ DOCTEST_TEST_CASE("UDP sessions preserve first, empty and binary datagrams acros second.set_auto_reconnect(false); second.bind_recv([&](std::string_view data) { second_received.push(data); }); const std::vector expected{"", std::string("a\0b", 3), "third"}; - for (std::size_t round = 0; round < 2; ++round) - { + for (std::size_t round = 0; round < 2; ++round) { check(server.start("127.0.0.1", 0), "UDP server start/restart"); check(client.start("127.0.0.1", server.get_listen_port()), "UDP connect/reconnect"); check(second.start("127.0.0.1", server.get_listen_port()), "second UDP connect"); const auto first_port = client.get_local_port(); const auto second_port = second.get_local_port(); check(first_port != second_port, "UDP clients need distinct endpoints"); - for (std::size_t i = 0; i < expected.size(); ++i) - { + for (std::size_t i = 0; i < expected.size(); ++i) { client.async_send(expected[i]); const auto count = round * expected.size() + i + 1; check(received.wait(count)[count - 1] == expected[i], "UDP first/empty/binary datagrams survive"); @@ -235,8 +223,7 @@ DOCTEST_TEST_CASE("UDP cast owns payloads and completes rejected and resolved se std::atomic rejection_calls{}; const bool accepted = sender.async_send(target, std::string("x"), [&](const arknet::error_code& ec, std::size_t count) { - if (ec == asio::error::no_buffer_space && count == 0) - { + if (ec == asio::error::no_buffer_space && count == 0) { ++rejection_calls; } }); @@ -312,8 +299,7 @@ DOCTEST_TEST_CASE("UDP callbacks stop and restart without stopping an external I const bool caller_accepted = shared_cast.async_send(target, std::string("overflow"), [&](const arknet::error_code& ec, std::size_t count) { shared_cast.stop(); - if (ec == asio::error::no_buffer_space && count == 0) - { + if (ec == asio::error::no_buffer_space && count == 0) { ++caller_stop_callbacks; } }); @@ -335,8 +321,7 @@ DOCTEST_TEST_CASE("UDP cast concurrent admission and stop complete every submiss constexpr std::size_t race_offset = admission_attempts + 1; constexpr std::size_t attempts = race_offset + threads * race_rows; - struct result - { + struct result { bool submitted{}; bool accepted{}; bool after_stop{}; @@ -362,8 +347,7 @@ DOCTEST_TEST_CASE("UDP cast concurrent admission and stop complete every submiss std::size_t limit = 4 * payload_size; sender.set_max_send_buffer_size(limit); const auto submit = [&](std::size_t id, bool after_stop = false) { - try - { + try { std::string payload(payload_size, static_cast('A' + id % 26)); const bool accepted = sender.async_send(target, asio::const_buffer(payload.data(), payload.size()), [&, id](const arknet::error_code& error, std::size_t bytes) { @@ -384,13 +368,12 @@ DOCTEST_TEST_CASE("UDP cast concurrent admission and stop complete every submiss results[id].accepted = accepted; results[id].after_stop = after_stop; } - if (sender.get_queued_send_buffer_size() > limit) - { + if (sender.get_queued_send_buffer_size() > limit) { queue_overflow = true; } + } catch (...) { + ++submission_exceptions; } - catch (...) - { ++submission_exceptions; } }; const auto wait_completions = [&](std::size_t count) { std::unique_lock lock(result_mutex); @@ -406,32 +389,27 @@ DOCTEST_TEST_CASE("UDP cast concurrent admission and stop complete every submiss released.wait(); }); const bool io_paused = entered.wait_for(5s) == std::future_status::ready; - if (!io_paused) - { + if (!io_paused) { release->set_value(); } check(io_paused, "cast IO admission gate entered"); std::barrier start(static_cast(threads)); std::vector senders; - for (std::size_t thread = 0; thread < threads; ++thread) - { + for (std::size_t thread = 0; thread < threads; ++thread) { senders.emplace_back([&, thread] { start.arrive_and_wait(); - for (std::size_t row = 0; row < admission_rows; ++row) - { + for (std::size_t row = 0; row < admission_rows; ++row) { submit(thread * admission_rows + row); } }); } - for (auto& thread : senders) - { + for (auto& thread : senders) { thread.join(); } std::size_t admitted{}; { std::lock_guard lock(result_mutex); - for (std::size_t id = 0; id < admission_attempts; ++id) - { + for (std::size_t id = 0; id < admission_attempts; ++id) { admitted += results[id].accepted; } } @@ -463,8 +441,7 @@ DOCTEST_TEST_CASE("UDP cast concurrent admission and stop complete every submiss released.wait(); }); const bool race_paused = entered.wait_for(5s) == std::future_status::ready; - if (!race_paused) - { + if (!race_paused) { release->set_value(); } check(race_paused, "cast IO stop-race gate entered"); @@ -472,15 +449,13 @@ DOCTEST_TEST_CASE("UDP cast concurrent admission and stop complete every submiss std::latch stopped(1); std::barrier race(static_cast(threads + 1)); senders.clear(); - for (std::size_t thread = 0; thread < threads; ++thread) - { + for (std::size_t thread = 0; thread < threads; ++thread) { senders.emplace_back([&, thread] { const auto base = race_offset + thread * race_rows; submit(base); seeded.count_down(); race.arrive_and_wait(); - for (std::size_t row = 1; row + 1 < race_rows; ++row) - { + for (std::size_t row = 1; row + 1 < race_rows; ++row) { submit(base + row); } stopped.wait(); @@ -491,15 +466,15 @@ DOCTEST_TEST_CASE("UDP cast concurrent admission and stop complete every submiss const auto seeded_bytes = sender.get_queued_send_buffer_size(); std::jthread stopper([&] { race.arrive_and_wait(); - try - { sender.stop(); } - catch (...) - { stop_exception = true; } + try { + sender.stop(); + } catch (...) { + stop_exception = true; + } stopped.count_down(); }); release->set_value(); - for (auto& thread : senders) - { + for (auto& thread : senders) { thread.join(); } stopper.join(); @@ -517,25 +492,19 @@ DOCTEST_TEST_CASE("UDP cast concurrent admission and stop complete every submiss check(submission_exceptions == 0 && !queue_overflow, "cast concurrent submissions do not throw or exceed the byte limit"); std::lock_guard lock(result_mutex); - for (std::size_t id = 0; id < attempts; ++id) - { + for (std::size_t id = 0; id < attempts; ++id) { DOCTEST_CAPTURE(id); const auto& value = results[id]; DOCTEST_INFO("accepted=", value.accepted, ", callbacks=", value.callbacks, ", error=", value.error.value(), " ", value.error.message(), ", bytes=", value.bytes); check(value.submitted && value.callbacks == 1, "every cast submission completes exactly once"); - if (!value.accepted) - { + if (!value.accepted) { check(value.bytes == 0 && (value.error == asio::error::no_buffer_space || (id >= race_offset && value.error == asio::error::not_connected)), "cast rejection reports a queue or stopped-state error without bytes"); - } - else if (!value.error) - { + } else if (!value.error) { check(value.bytes == payload_size, "successful cast send reports the full payload size"); - } - else - { + } else { check( id >= race_offset && value.bytes <= payload_size && (value.error == asio::error::operation_aborted || value.error == asio::error::not_connected || @@ -543,8 +512,7 @@ DOCTEST_TEST_CASE("UDP cast concurrent admission and stop complete every submiss value.error == asio::error::connection_reset || value.error == asio::error::broken_pipe), "accepted cast send reports only a valid stop cancellation"); } - if (value.after_stop) - { + if (value.after_stop) { check(!value.accepted && value.error == asio::error::not_connected, "cast sends after completed stop reject as not_connected"); } @@ -561,8 +529,7 @@ DOCTEST_TEST_CASE("UDP cast preserves empty, binary, and maximum IPv4 payloads") check(sender.start("127.0.0.1", 0), "datagram sender start"); auto endpoint = asio::ip::udp::endpoint(asio::ip::make_address("127.0.0.1"), receiver.get_local_port()); const std::vector payloads{"", std::string("a\0z", 3), std::string(65507, 'm')}; - for (std::size_t index = 0; index < payloads.size(); ++index) - { + for (std::size_t index = 0; index < payloads.size(); ++index) { auto completion = sender.async_send(endpoint, payloads[index], asio::use_future); const auto [error, count] = await(completion); check(!error && count == payloads[index].size(), "UDP send reports every payload byte"); @@ -586,16 +553,14 @@ DOCTEST_TEST_CASE("UDP cast preserves empty, binary, and maximum IPv4 payloads") } DOCTEST_TEST_CASE("UDP cast rejects a send copied before callback stop and restart") { - class filtered_cast : public arknet::udp_cast_t - { + class filtered_cast : public arknet::udp_cast_t { public: using arknet::udp_cast_t::udp_cast_t; std::promise entered; std::latch release{1}; std::string_view data_filter_before_send(std::string_view bytes) { - if (bytes == "previous") - { + if (bytes == "previous") { entered.set_value(); release.wait(); } @@ -611,8 +576,7 @@ DOCTEST_TEST_CASE("UDP cast rejects a send copied before callback stop and resta check(receiver.start("127.0.0.1", 0), "generation receiver start"); filtered_cast sender(runner.context); sender.bind_start([&] { - if (!arknet::get_last_error()) - { + if (!arknet::get_last_error()) { started.push("started"); } }); @@ -626,14 +590,12 @@ DOCTEST_TEST_CASE("UDP cast rejects a send copied before callback stop and resta [&](const arknet::error_code& error, std::size_t count) { sent.set_value({error, count}); }); }); - struct unlock - { + struct unlock { std::latch& latch; bool armed = true; void open() { - if (std::exchange(armed, false)) - { + if (std::exchange(armed, false)) { latch.count_down(); } } @@ -663,8 +625,7 @@ DOCTEST_TEST_CASE("UDP cast rejects a send copied before callback stop and resta } DOCTEST_TEST_CASE("UDP custom session can stop during connect without delivering its first datagram") { - class custom_session : public arknet::udp_session_t - { + class custom_session : public arknet::udp_session_t { public: using arknet::udp_session_t::udp_session_t; }; @@ -675,8 +636,7 @@ DOCTEST_TEST_CASE("UDP custom session can stop during connect without delivering auto disconnected = rejected.get_future(); arknet::udp_server_t server; server.bind_connect([&](auto& session) { - if (++connect_count == 1) - { + if (++connect_count == 1) { session->stop(); rejected.set_value(); } @@ -700,8 +660,7 @@ DOCTEST_TEST_CASE("UDP custom session can stop during connect without delivering server.stop(); } -class udp_delayed_session : public arknet::udp_session_t -{ +class udp_delayed_session : public arknet::udp_session_t { public: using arknet::udp_session_t::udp_session_t; @@ -741,13 +700,11 @@ DOCTEST_TEST_CASE("UDP session routing does not wait for a connect callback send } DOCTEST_TEST_CASE("UDP cast request_stop does not retain an already stopped shared object") { - for (const bool was_started : {false, true}) - { + for (const bool was_started : {false, true}) { std::weak_ptr lifetime; { auto endpoint = std::make_shared(); - if (was_started) - { + if (was_started) { check(endpoint->start("127.0.0.1", 0), "shared UDP cast start"); endpoint->stop(); } @@ -813,12 +770,9 @@ DOCTEST_TEST_CASE("UDP cast accepts generic move-only send completions") { auto direct = direct_done.get_future(); check(sender.async_send(target, "direct", [state = std::make_unique(6), &direct_done](const auto& error, auto count) mutable { - if (static_cast(*state) == count) - { + if (static_cast(*state) == count) { direct_done.set_value({error, count}); - } - else - { + } else { direct_done.set_value({asio::error::fault, count}); } }), diff --git a/tests/websocket.cpp b/tests/websocket.cpp index ce78021..9804d11 100644 --- a/tests/websocket.cpp +++ b/tests/websocket.cpp @@ -2,14 +2,12 @@ #include #include "check.hpp" -class custom_ws_client : public arknet::ws_client_t -{ +class custom_ws_client : public arknet::ws_client_t { public: using arknet::ws_client_t::ws_client_t; }; -class custom_ws_session : public arknet::ws_session_t -{ +class custom_ws_session : public arknet::ws_session_t { public: using arknet::ws_session_t::ws_session_t; }; @@ -56,8 +54,7 @@ DOCTEST_TEST_CASE("WebSocket custom endpoints preserve upgrade decorators and ta [](websocket::response_type& response) { response.set(http::field::server, "arknet-custom"); })); }); server.bind_upgrade([&](auto& session) { - if (!arknet::get_last_error()) - { + if (!arknet::get_last_error()) { requests.push(session->get_upgrade_request().target()); } });