diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da8f4804..ea2448f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -304,10 +304,7 @@ jobs: lint: name: lint - # Held on 24.04: 26.04's clang-tidy (21+) enables checks that flag ~180 - # sites (modernize-use-scoped-lock, -use-designated-initializers, ...). - # Move with that cleanup, not with a runner bump. - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 steps: - uses: actions/checkout@v7 # Boost headers so clang-tidy can process the Beast transport. libboost-dev diff --git a/examples/simplerestjson/serve_main.cc b/examples/simplerestjson/serve_main.cc index 3b71206b..01ffe0c4 100644 --- a/examples/simplerestjson/serve_main.cc +++ b/examples/simplerestjson/serve_main.cc @@ -36,14 +36,14 @@ class InMemoryBookstore final : public BookstoreHandler { public: opal::Outcome AddBook(const AddBookInput& input, const opal::server::RequestContext& /*context*/) override { - const std::lock_guard lock(mu_); + const std::scoped_lock lock(mu_); titles_[input.isbn] = input.title; return AddBookOutput{.status = 201, .isbn = input.isbn}; } opal::Outcome GetBook(const GetBookInput& input, const opal::server::RequestContext& /*context*/) override { - const std::lock_guard lock(mu_); + const std::scoped_lock lock(mu_); const auto it = titles_.find(input.isbn); if (it == titles_.end()) { const std::string message = "no book: " + input.isbn; diff --git a/examples/weather/handwritten/weather_types.cc b/examples/weather/handwritten/weather_types.cc index 349a70de..1eebfffe 100644 --- a/examples/weather/handwritten/weather_types.cc +++ b/examples/weather/handwritten/weather_types.cc @@ -82,7 +82,7 @@ Outcome DeserializeListCitiesOutput(const Document& doc) { const Document* name = entry.Find("name"); if (city_id == nullptr || name == nullptr) return MissingMember("items[].*"); if (!city_id->is_string() || !name->is_string()) return WrongType("items[].*"); - out.items.push_back(CitySummary{city_id->as_string(), name->as_string()}); + out.items.push_back(CitySummary{.cityId = city_id->as_string(), .name = name->as_string()}); } return out; } diff --git a/runtime/include/opal/core/document.h b/runtime/include/opal/core/document.h index b3ab61fa..585c3288 100644 --- a/runtime/include/opal/core/document.h +++ b/runtime/include/opal/core/document.h @@ -58,7 +58,7 @@ class Document { Document(DocumentMap value) : value_(std::move(value)) {} // NOLINT static Document FromTimestamp(Timestamp ts, TimestampFormat format) { - return Document(TimestampValue{ts, format}); + return Document(TimestampValue{.value = ts, .format = format}); } bool is_null() const { return std::holds_alternative(value_); } diff --git a/runtime/include/opal/core/exception_guard.h b/runtime/include/opal/core/exception_guard.h index a8becce7..37a9a1ce 100644 --- a/runtime/include/opal/core/exception_guard.h +++ b/runtime/include/opal/core/exception_guard.h @@ -3,7 +3,7 @@ #include -#if defined(__cpp_exceptions) +#ifdef __cpp_exceptions #include #endif @@ -29,7 +29,7 @@ namespace opal::internal { // `on_throw` must return the same type as `body` (or both must be void). template auto Contain(Body&& body, OnThrow&& on_throw) -> decltype(std::forward(body)()) { -#if defined(__cpp_exceptions) +#ifdef __cpp_exceptions try { return std::forward(body)(); } catch (const std::exception& e) { diff --git a/runtime/include/opal/eventstream/async_event_stream.h b/runtime/include/opal/eventstream/async_event_stream.h index 53e5741b..16b6bbc3 100644 --- a/runtime/include/opal/eventstream/async_event_stream.h +++ b/runtime/include/opal/eventstream/async_event_stream.h @@ -40,7 +40,7 @@ struct Detached { // A coroutine promise must declare unhandled_exception(), but under // -fno-exceptions it is unreachable — nothing can throw — so the // containment body compiles only when exceptions are enabled. -#if defined(__cpp_exceptions) +#ifdef __cpp_exceptions try { std::rethrow_exception(std::current_exception()); } catch (const std::exception& e) { @@ -90,7 +90,7 @@ class [[nodiscard]] StreamTask { void unhandled_exception() noexcept { // Required by the coroutine machinery, but unreachable under // -fno-exceptions; the containment body compiles only with exceptions. -#if defined(__cpp_exceptions) +#ifdef __cpp_exceptions try { std::rethrow_exception(std::current_exception()); } catch (const std::exception& e) { diff --git a/runtime/include/opal/eventstream/event_stream.h b/runtime/include/opal/eventstream/event_stream.h index ee27d2c8..cb31a701 100644 --- a/runtime/include/opal/eventstream/event_stream.h +++ b/runtime/include/opal/eventstream/event_stream.h @@ -59,7 +59,7 @@ struct SharedSessionState { // Drops one pin; the revoking stream's drain wait wakes at zero. Free so // async completions can release with only the state in hand (ADR-0019). inline void ReleasePin(SharedSessionState& state) { - const std::lock_guard lock(state.mutex); + const std::scoped_lock lock(state.mutex); if (--state.active == 0) state.idle.notify_all(); } @@ -103,7 +103,7 @@ class SharedViewOwner { if (state_ == nullptr) return; const std::shared_ptr state = std::move(state_); { - const std::lock_guard lock(state->mutex); + const std::scoped_lock lock(state->mutex); state->socket = nullptr; } socket_->Close(); @@ -219,7 +219,7 @@ class EventStreamHandle { // session ended (or this handle was moved from). http::WebSocket* Acquire() const { if (state_ == nullptr) return nullptr; - const std::lock_guard lock(state_->mutex); + const std::scoped_lock lock(state_->mutex); if (state_->socket == nullptr) return nullptr; ++state_->active; return state_->socket; diff --git a/runtime/include/opal/server/session_registry.h b/runtime/include/opal/server/session_registry.h index 9ae00512..b0637134 100644 --- a/runtime/include/opal/server/session_registry.h +++ b/runtime/include/opal/server/session_registry.h @@ -183,7 +183,7 @@ class SessionRegistry { ExpireDetachedNow(); std::vector> all; { - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); for (auto& [id, entry] : sessions_) all.push_back(std::move(entry)); sessions_.clear(); all.insert(all.end(), std::make_move_iterator(retired_.begin()), @@ -214,7 +214,7 @@ class SessionRegistry { const bool async_mode = options_.async_delivery && handle.SupportsAsync(); auto entry = std::make_shared(std::move(handle)); entry->async_mode = async_mode; - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); ReapLocked(); if (!sessions_.emplace(std::move(id), entry).second) return false; // Async entries drain through completion chains and hold no thread; @@ -236,7 +236,7 @@ class SessionRegistry { bool Remove(const Id& id) { std::shared_ptr entry; { - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); const auto it = sessions_.find(id); if (it == sessions_.end()) return false; entry = std::move(it->second); @@ -250,7 +250,7 @@ class SessionRegistry { // its own shared_ptr. bool has_writer = false; { - const std::lock_guard entry_lock(entry->mutex); + const std::scoped_lock entry_lock(entry->mutex); entry->stopping = true; entry->queue.clear(); has_writer = entry->writer.joinable(); @@ -275,12 +275,12 @@ class SessionRegistry { std::shared_ptr entry; std::optional old_handle; // a copy to close outside the locks { - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); const auto it = sessions_.find(id); if (it == sessions_.end()) return false; entry = it->second; { - const std::lock_guard entry_lock(entry->mutex); + const std::scoped_lock entry_lock(entry->mutex); if (entry->detached) return false; entry->detached = true; entry->deadline = std::chrono::steady_clock::now() + options_.grace_period; @@ -313,7 +313,7 @@ class SessionRegistry { std::shared_ptr entry; std::optional claimed; { - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); const auto it = sessions_.find(id); if (it == sessions_.end()) return false; entry = it->second; @@ -404,7 +404,7 @@ class SessionRegistry { bool SendTo(const Id& id, Tx event, DeliveryClass delivery = {}) { std::shared_ptr entry; { - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); const auto it = sessions_.find(id); if (it == sessions_.end()) return false; entry = it->second; @@ -422,7 +422,7 @@ class SessionRegistry { std::vector>> targets; targets.reserve(ids.size()); { - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); for (const Id& id : ids) { if (const auto it = sessions_.find(id); it != sessions_.end()) { targets.emplace_back(&id, it->second); @@ -448,7 +448,7 @@ class SessionRegistry { const DeliveryClass& delivery = {}) { std::vector>> targets; { - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); targets.reserve(sessions_.size()); for (const auto& [id, entry] : sessions_) targets.emplace_back(id, entry); } @@ -478,10 +478,10 @@ class SessionRegistry { bool Close(const Id& id) { std::optional doomed; { - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); const auto it = sessions_.find(id); if (it == sessions_.end()) return false; - const std::lock_guard entry_lock(it->second->mutex); + const std::scoped_lock entry_lock(it->second->mutex); doomed.emplace(it->second->handle); } doomed->Close(); @@ -496,7 +496,7 @@ class SessionRegistry { // Resume's handle swap. The closes still run outside every lock. std::vector handles; { - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); handles.reserve(sessions_.size()); for (const auto& [id, entry] : sessions_) handles.push_back(entry->handle); } @@ -519,7 +519,7 @@ class SessionRegistry { // A snapshot of the registered ids — the "everyone currently here" input // to the Broadcast(ids, ...) overloads. std::vector Ids() const { - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); std::vector ids; ids.reserve(sessions_.size()); for (const auto& [id, entry] : sessions_) ids.push_back(id); @@ -527,7 +527,7 @@ class SessionRegistry { } std::size_t size() const { - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); return sessions_.size(); } @@ -590,7 +590,7 @@ class SessionRegistry { entry.queue.pop_front(); } if (!entry.handle.Send(event).ok()) { - const std::lock_guard lock(entry.mutex); + const std::scoped_lock lock(entry.mutex); entry.stopping = true; // A detached entry may be retaining its queue for the resume // (the failed send is the dying old connection's, not the tail's). @@ -599,7 +599,7 @@ class SessionRegistry { } } { - const std::lock_guard lock(entry.mutex); + const std::scoped_lock lock(entry.mutex); entry.done = true; } entry.wake.notify_all(); // Resume waits out this exit before swapping @@ -609,7 +609,7 @@ class SessionRegistry { // own failure path sets the same state inline, under its held lock.) static void RequestStop(Entry& entry) { { - const std::lock_guard lock(entry.mutex); + const std::scoped_lock lock(entry.mutex); entry.stopping = true; entry.queue.clear(); } @@ -656,7 +656,7 @@ class SessionRegistry { bool queued = false; std::optional claimed; { - const std::lock_guard lock(entry->mutex); + const std::scoped_lock lock(entry->mutex); if (entry->detached) { // Tested before stopping: detached implies stopping, and the // retention branch must win over the refuse-on-stopping one. @@ -699,7 +699,7 @@ class SessionRegistry { // id has as the drop is charged; that is close-on-full's contract. std::optional doomed; { - const std::lock_guard lock(entry->mutex); + const std::scoped_lock lock(entry->mutex); doomed.emplace(entry->handle); } doomed->Close(); @@ -743,7 +743,7 @@ class SessionRegistry { entry->handle.SendAsync(event, [entry](const Outcome& sent) { std::optional next; { - const std::lock_guard lock(entry->mutex); + const std::scoped_lock lock(entry->mutex); if (sent.ok()) { // Delivered: retire the event. (RequestStop may have cleared the // queue mid-flight, so the pop is guarded.) @@ -796,7 +796,7 @@ class SessionRegistry { } bool done = false; { - const std::lock_guard lock((*it)->mutex); + const std::scoped_lock lock((*it)->mutex); done = (*it)->done; } if (done) { @@ -825,7 +825,7 @@ class SessionRegistry { void StopExpiry() { { - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); expiry_stop_ = true; } expiry_wake_.notify_all(); @@ -837,7 +837,7 @@ class SessionRegistry { while (!expiry_stop_) { std::optional next; for (const auto& [id, entry] : sessions_) { - const std::lock_guard entry_lock(entry->mutex); + const std::scoped_lock entry_lock(entry->mutex); if (!entry->detached) continue; if (!next || entry->deadline < *next) next = entry->deadline; } @@ -869,7 +869,7 @@ class SessionRegistry { bool claim = false; bool has_writer = false; { - const std::lock_guard entry_lock(it->second->mutex); + const std::scoped_lock entry_lock(it->second->mutex); claim = it->second->detached && now >= it->second->deadline; has_writer = claim && it->second->writer.joinable(); } @@ -905,7 +905,7 @@ class SessionRegistry { void ExpireDetachedNow() { std::vector>> expired; { - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); expired = TakeExpiredLocked(std::chrono::steady_clock::time_point::max()); } FireExpiries(expired); diff --git a/runtime/src/cbor/cbor.cc b/runtime/src/cbor/cbor.cc index 0af00bf6..58419cb8 100644 --- a/runtime/src/cbor/cbor.cc +++ b/runtime/src/cbor/cbor.cc @@ -293,12 +293,12 @@ class Decoder { if (inner->is_int()) { auto ts = Timestamp::FromEpochSecondsChecked(static_cast(inner->as_int())); if (!ts) return std::move(ts).error(); - return Document(TimestampValue{*ts, TimestampFormat::kEpochSeconds}); + return Document(TimestampValue{.value = *ts, .format = TimestampFormat::kEpochSeconds}); } if (inner->is_double()) { auto ts = Timestamp::FromEpochSecondsChecked(inner->as_double()); if (!ts) return std::move(ts).error(); - return Document(TimestampValue{*ts, TimestampFormat::kEpochSeconds}); + return Document(TimestampValue{.value = *ts, .format = TimestampFormat::kEpochSeconds}); } return Fail("tag 1 content is not a number"); } diff --git a/runtime/src/core/regex.cc b/runtime/src/core/regex.cc index 3a88b45f..6231225b 100644 --- a/runtime/src/core/regex.cc +++ b/runtime/src/core/regex.cc @@ -208,7 +208,7 @@ class Parser { if (AtEnd() || Peek() < '0' || Peek() > '9') return false; long value = 0; while (!AtEnd() && Peek() >= '0' && Peek() <= '9') { - value = value * 10 + (Take() - '0'); + value = (value * 10) + (Take() - '0'); if (value > kMaxRepeatCount) return false; } *out = static_cast(value); @@ -429,7 +429,7 @@ class Parser { else if (c >= 'A' && c <= 'F') d = c - 'A' + 10; if (d < 0) return -1; - value = value * 16 + d; + value = (value * 16) + d; } return value; } @@ -469,7 +469,7 @@ class RegexCompiler { if (auto emitted = compiler.Emit(**tree); !emitted.ok()) { return std::move(emitted).error(); } - if (auto added = compiler.Add({Regex::Inst::Op::kMatch}); !added.ok()) { + if (auto added = compiler.Add({.op = Regex::Inst::Op::kMatch}); !added.ok()) { return std::move(added).error(); } return re; @@ -503,17 +503,17 @@ class RegexCompiler { case Node::Kind::kEmpty: return Unit{}; case Node::Kind::kByte: { - Inst inst{Inst::Op::kByte}; + Inst inst{.op = Inst::Op::kByte}; inst.byte = node.byte; return Add(inst); } case Node::Kind::kClass: { - Inst inst{Inst::Op::kClass}; + Inst inst{.op = Inst::Op::kClass}; inst.arg = AddClass(node.cls); return Add(inst); } case Node::Kind::kAssert: { - Inst inst{Inst::Op::kAssert}; + Inst inst{.op = Inst::Op::kAssert}; inst.assert_kind = node.assert_kind; return Add(inst); } @@ -530,12 +530,12 @@ class RegexCompiler { std::uint32_t split_pc = 0; if (!last) { split_pc = Here(); - if (auto added = Add({Inst::Op::kSplit}); !added.ok()) return added; + if (auto added = Add({.op = Inst::Op::kSplit}); !added.ok()) return added; } if (auto emitted = Emit(*node.children[i]); !emitted.ok()) return emitted; if (!last) { jumps_to_end.push_back(Here()); - if (auto added = Add({Inst::Op::kJmp}); !added.ok()) return added; + if (auto added = Add({.op = Inst::Op::kJmp}); !added.ok()) return added; re_->program_[split_pc].arg = Here(); } } @@ -556,9 +556,9 @@ class RegexCompiler { if (node.max < 0) { // body{min,} — one looping optional copy: L: split(end); body; jmp L std::uint32_t loop = Here(); - if (auto added = Add({Inst::Op::kSplit}); !added.ok()) return added; + if (auto added = Add({.op = Inst::Op::kSplit}); !added.ok()) return added; if (auto emitted = Emit(body); !emitted.ok()) return emitted; - Inst jmp{Inst::Op::kJmp}; + Inst jmp{.op = Inst::Op::kJmp}; jmp.arg = loop; if (auto added = Add(jmp); !added.ok()) return added; re_->program_[loop].arg = Here(); @@ -569,7 +569,7 @@ class RegexCompiler { std::vector splits; for (int i = node.min; i < node.max; ++i) { splits.push_back(Here()); - if (auto added = Add({Inst::Op::kSplit}); !added.ok()) return added; + if (auto added = Add({.op = Inst::Op::kSplit}); !added.ok()) return added; if (auto emitted = Emit(body); !emitted.ok()) return emitted; } for (std::uint32_t pc : splits) re_->program_[pc].arg = Here(); diff --git a/runtime/src/core/timestamp.cc b/runtime/src/core/timestamp.cc index d362afc8..931f31b0 100644 --- a/runtime/src/core/timestamp.cc +++ b/runtime/src/core/timestamp.cc @@ -48,7 +48,7 @@ struct CivilTime { }; std::int64_t FloorDiv(std::int64_t a, std::int64_t b) { - return a / b - ((a % b != 0 && (a < 0) != (b < 0)) ? 1 : 0); + return (a / b) - ((a % b != 0 && (a < 0) != (b < 0)) ? 1 : 0); } std::int64_t EpochDays(const CivilTime& c) { @@ -57,7 +57,7 @@ std::int64_t EpochDays(const CivilTime& c) { CivilTime Decompose(std::int64_t ms) { const std::int64_t days = FloorDiv(ms, kMsPerDay); - std::int64_t ms_of_day = ms - days * kMsPerDay; + std::int64_t ms_of_day = ms - (days * kMsPerDay); const std::chrono::year_month_day ymd{std::chrono::sys_days{std::chrono::days{days}}}; CivilTime civil; @@ -74,10 +74,10 @@ CivilTime Decompose(std::int64_t ms) { } std::int64_t Compose(const CivilTime& c) { - return EpochDays(c) * kMsPerDay + - (static_cast(c.hour) * 3600 + static_cast(c.minute) * 60 + - c.second) * - kMsPerSecond + + return (EpochDays(c) * kMsPerDay) + + (((static_cast(c.hour) * 3600) + (static_cast(c.minute) * 60) + + c.second) * + kMsPerSecond) + c.millisecond; } @@ -102,7 +102,7 @@ bool ParseDigits(std::string_view text, std::size_t pos, std::size_t count, int* for (std::size_t i = 0; i < count; ++i) { const char c = text[pos + i]; if (c < '0' || c > '9') return false; - value = value * 10 + (c - '0'); + value = (value * 10) + (c - '0'); } *out = value; return true; @@ -155,8 +155,8 @@ Outcome ParseDateTime(std::string_view text) { !ParseDigits(text, pos + 4, 2, &om) || oh > 23 || om > 59) { return invalid(); } - offset_ms = - (static_cast(oh) * 3600 + static_cast(om) * 60) * kMsPerSecond; + offset_ms = ((static_cast(oh) * 3600) + (static_cast(om) * 60)) * + kMsPerSecond; if (negative) offset_ms = -offset_ms; pos += 6; } else { diff --git a/runtime/src/eventstream/envelope.cc b/runtime/src/eventstream/envelope.cc index 2f5d4dbf..2f1ec5bd 100644 --- a/runtime/src/eventstream/envelope.cc +++ b/runtime/src/eventstream/envelope.cc @@ -24,10 +24,12 @@ Error Malformed(std::string what) { Message MakeMessage(std::string_view message_type, std::string_view type_header, std::string_view type, std::string_view content_type, Blob payload) { Message message; - message.headers.push_back({std::string(kMessageTypeHeader), std::string(message_type)}); - message.headers.push_back({std::string(type_header), std::string(type)}); + message.headers.push_back( + {.name = std::string(kMessageTypeHeader), .value = std::string(message_type)}); + message.headers.push_back({.name = std::string(type_header), .value = std::string(type)}); if (!content_type.empty()) { - message.headers.push_back({std::string(kContentTypeHeader), std::string(content_type)}); + message.headers.push_back( + {.name = std::string(kContentTypeHeader), .value = std::string(content_type)}); } message.payload = std::move(payload); return message; diff --git a/runtime/src/eventstream/frame.cc b/runtime/src/eventstream/frame.cc index 2aff46cb..aae97a25 100644 --- a/runtime/src/eventstream/frame.cc +++ b/runtime/src/eventstream/frame.cc @@ -282,14 +282,15 @@ Outcome> DecodeMessage(std::string_view buffer) { if (!value.has_value()) { return Malformed("malformed header value"); } - message.headers.push_back(Header{std::move(name), std::move(*value)}); + message.headers.push_back(Header{.name = std::move(name), .value = std::move(*value)}); } // The bounds checks above keep cursor <= block.size() at every step, so // the loop exits exactly at the block boundary. message.payload = Blob::FromString( buffer.substr(kPreludeBytes + headers_length, total - kFrameOverheadBytes - headers_length)); - return std::optional(DecodedFrame{std::move(message), total}); + return std::optional( + DecodedFrame{.message = std::move(message), .bytes_consumed = total}); } } // namespace opal::eventstream diff --git a/runtime/src/eventstream/jsonrpc_frame.cc b/runtime/src/eventstream/jsonrpc_frame.cc index e50a4b39..1c74d4a5 100644 --- a/runtime/src/eventstream/jsonrpc_frame.cc +++ b/runtime/src/eventstream/jsonrpc_frame.cc @@ -155,8 +155,7 @@ JsonRpcStreamFrame DecodeResponse(const Document& envelope, const Document& id) DocumentMap payload = data != nullptr ? data->as_map() : DocumentMap(); // The unary client's fallback, mirrored: an error message member fills a // data object that carries none. - if (message != nullptr && !message->as_string().empty() && - payload.find(kMessageMember) == payload.end()) { + if (message != nullptr && !message->as_string().empty() && !payload.contains(kMessageMember)) { payload.emplace(kMessageMember, *message); } const Document* type = data != nullptr ? data->Find(kTypeMember) : nullptr; diff --git a/runtime/src/eventstream/jsonrpc_stream_socket.cc b/runtime/src/eventstream/jsonrpc_stream_socket.cc index 9ef8047f..199dc225 100644 --- a/runtime/src/eventstream/jsonrpc_stream_socket.cc +++ b/runtime/src/eventstream/jsonrpc_stream_socket.cc @@ -46,9 +46,10 @@ Inbound FromViolation(int code, const std::string& reason, const Document& id, R } Inbound ClassifyInbound(Outcome> raw, const Document& id, Role role) { - if (!raw.ok()) return {std::move(raw), std::nullopt, false}; + if (!raw.ok()) return {.result = std::move(raw), .violation_text = std::nullopt, .close = false}; const std::optional& maybe = *raw; - if (!maybe.has_value()) return {std::move(raw), std::nullopt, false}; + if (!maybe.has_value()) + return {.result = std::move(raw), .violation_text = std::nullopt, .close = false}; const Message& message = *maybe; if (!message.headers.empty()) { // A peer speaking the eventstream envelope wire into a JSON-RPC stream @@ -63,14 +64,18 @@ Inbound ClassifyInbound(Outcome> raw, const Document& id, id); switch (frame.kind) { case JsonRpcStreamFrame::Kind::kEvent: - return {std::optional(std::move(frame.message)), std::nullopt, false}; + return {.result = std::optional(std::move(frame.message)), + .violation_text = std::nullopt, + .close = false}; case JsonRpcStreamFrame::Kind::kException: if (role == Role::kServer) { return FromViolation(-32600, "a response envelope from the client", id, role); } // The client's terminal error: exactly the exception Message the // downstream decoder turns into the modeled error (ADR-0016). - return {std::optional(std::move(frame.message)), std::nullopt, false}; + return {.result = std::optional(std::move(frame.message)), + .violation_text = std::nullopt, + .close = false}; case JsonRpcStreamFrame::Kind::kResult: if (role == Role::kServer) { // Terminal response envelopes are server-minted; a client-sent @@ -79,7 +84,7 @@ Inbound ClassifyInbound(Outcome> raw, const Document& id, } // The clean end — the server closes right behind it, so nothing // meaningful can follow. - return {std::optional(), std::nullopt, false}; + return {.result = std::optional(), .violation_text = std::nullopt, .close = false}; case JsonRpcStreamFrame::Kind::kViolation: return FromViolation(frame.code, frame.reason, id, role); } diff --git a/runtime/src/http/.clang-tidy b/runtime/src/http/.clang-tidy index be31f189..de4c96cd 100644 --- a/runtime/src/http/.clang-tidy +++ b/runtime/src/http/.clang-tidy @@ -1,8 +1,12 @@ -# Scoped exclusion: boost::asio::thread_pool's destructor chain trips a -# clang-analyzer false positive (the analyzer models the execution_context -# base destructor before the derived body, then reports use-after-free on -# service_registry_ inside boost's own ipp). thread_pool.hpp is included -# only by beast_transport.cc, so the check stays enabled everywhere else — -# see the root .clang-tidy's convention note. +# Scoped exclusions: Boost.Asio internals reached only through +# beast_transport.cc trip clang-analyzer false positives inside boost's own +# ipp files, so the checks stay enabled everywhere else — see the root +# .clang-tidy's convention note. +# - cplusplus.NewDelete: boost::asio::thread_pool's destructor chain (the +# analyzer models the execution_context base destructor before the derived +# body, then reports use-after-free on service_registry_). +# - optin.cplusplus.VirtualCall: resolver_thread_pool's destructor calls its +# own shutdown(); newer Boost (1.88+) exposes it to the analyzer via the +# tcp::resolver in Dial(). InheritParentConfig: true -Checks: "-clang-analyzer-cplusplus.NewDelete" +Checks: "-clang-analyzer-cplusplus.NewDelete,-clang-analyzer-optin.cplusplus.VirtualCall" diff --git a/runtime/src/http/beast_transport.cc b/runtime/src/http/beast_transport.cc index e8359cf2..b568e781 100644 --- a/runtime/src/http/beast_transport.cc +++ b/runtime/src/http/beast_transport.cc @@ -155,6 +155,9 @@ bhttp::response ToHeadWireResponse( wire.insert(field.name_string(), field.value()); } wire.keep_alive(keep_alive); + // fields::contains is Boost 1.88+; docs/development.md builds this TU + // against distro Boost (1.83 on Ubuntu 24.04). + // NOLINTNEXTLINE(readability-container-contains) if (full.find(bhttp::field::content_length) != full.end()) { wire.content_length(full.body().size()); } @@ -464,7 +467,7 @@ class WsSession final : public WebSocketSessionBase, void OnReceiveDeadline(std::uint64_t generation) { WebSocket::ReceiveCallback expired; { - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); if (!pending_receive_ || receive_park_generation_ != generation) { return; // the park this deadline bounded already completed } @@ -486,7 +489,7 @@ class WsSession final : public WebSocketSessionBase, return std::move(frame).error(); // the codec's Validation, verbatim } // Serializes concurrent senders; the wire itself allows one write op. - const std::lock_guard send_turn(send_mutex_); + const std::scoped_lock send_turn(send_mutex_); std::unique_lock lock(mutex_); // An async send may be in flight (send_mutex_ only serializes blocking // callers); wait it out — serialize-by-waiting, per ADR-0019. @@ -550,7 +553,7 @@ class WsSession final : public WebSocketSessionBase, } catch (...) { WebSocket::SendCallback cb; { - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); cb = std::exchange(pending_send_, nullptr); write_complete_ = true; wake_.notify_all(); @@ -600,7 +603,7 @@ class WsSession final : public WebSocketSessionBase, void PumpRead() { { - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); if (failed_ || peer_closed_ || close_started_) { return; // ended, or the close op owns the read side now } @@ -620,7 +623,7 @@ class WsSession final : public WebSocketSessionBase, if (ec == bws::error::closed) { AsyncWaiters waiters; { - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); peer_closed_ = true; waiters = TakeAsyncWaitersLocked(); wake_.notify_all(); @@ -635,7 +638,7 @@ class WsSession final : public WebSocketSessionBase, // and a Close that escalated past an in-flight write cancelled the // socket's ops outright (RequestCloseLocked). Neither abort is an // outcome — the close/write completions decide clean-versus-error. - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); if (close_requested_ && ec == asio::error::operation_aborted) { return; } @@ -701,7 +704,7 @@ class WsSession final : public WebSocketSessionBase, WebSocket::ReceiveCallback receive; std::optional handoff; { - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); if (pending_receive_) { receive = std::exchange(pending_receive_, nullptr); // The park is over, so its deadline has nothing left to bound. @@ -854,7 +857,7 @@ class WsSession final : public WebSocketSessionBase, bool clean = false; std::string why; { - const std::lock_guard lock(self->mutex_); + const std::scoped_lock lock(self->mutex_); if (!self->failed_ && !self->peer_closed_) { if (ec) { self->failed_ = true; @@ -876,7 +879,7 @@ class WsSession final : public WebSocketSessionBase, AsyncWaiters waiters; std::string why; { - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); if (!failed_ && !peer_closed_) { failed_ = true; error_ = std::move(reason); @@ -1095,7 +1098,7 @@ struct BeastServerTransport::State : std::enable_shared_from_this { void RegisterWebSocket(const std::shared_ptr& session) { std::string abort_reason; { - const std::lock_guard lock(websockets_mutex); + const std::scoped_lock lock(websockets_mutex); if (!websockets_aborted) { std::erase_if(websockets, [](const auto& weak) { return weak.expired(); }); websockets.push_back(session); @@ -1109,7 +1112,7 @@ struct BeastServerTransport::State : std::enable_shared_from_this { void AbortWebSockets(const std::string& reason) { std::vector> live; { - const std::lock_guard lock(websockets_mutex); + const std::scoped_lock lock(websockets_mutex); websockets_aborted = true; websockets_abort_reason = reason; for (const auto& weak : websockets) { @@ -1199,8 +1202,9 @@ struct BeastServerTransport::State : std::enable_shared_from_this { ReadNext(std::shared_ptr(session, &session->stream)); return; } + auto& ssl_ctx = *ssl; auto session = std::make_shared>>( - weak_from_this(), std::move(socket), *ssl); + weak_from_this(), std::move(socket), ssl_ctx); std::shared_ptr> stream(session, &session->stream); beast::get_lowest_layer(*stream).expires_after( std::chrono::seconds(opts.request_timeout_seconds)); @@ -1544,7 +1548,7 @@ struct BeastServerTransport::State : std::enable_shared_from_this { // arm — drop accounting, keep-alive teardown, the next read — is the same // whether or not a body follows the headers. template - void WriteWire(const std::shared_ptr& stream, std::shared_ptr wire, + void WriteWire(const std::shared_ptr& stream, const std::shared_ptr& wire, bool keep_alive, std::string peer) { // Each wire phase gets its own request_timeout_seconds budget: Beast // expiries are absolute and outlive the op, so without a re-arm the @@ -1556,26 +1560,26 @@ struct BeastServerTransport::State : std::enable_shared_from_this { std::chrono::seconds(opts.request_timeout_seconds)); auto& wire_stream = *stream; auto& wire_ref = *wire; - bhttp::async_write(wire_stream, wire_ref, - [weak = weak_from_this(), stream, wire, keep_alive, - phase = PhaseStart{std::move(peer), std::chrono::steady_clock::now()}]( - beast::error_code write_ec, std::size_t) mutable { - auto self = weak.lock(); - if (self != nullptr) { - self->active.fetch_sub(1); - } - if (self == nullptr || write_ec || !keep_alive) { - if (self != nullptr && write_ec) { - // The peer vanished mid-response (ADR-0013). - using Kind = BeastServerTransport::ConnectionEvent::Kind; - self->NotifyConnectionEvent(Kind::kDropped, write_ec, - std::move(phase)); - } - CloseStream(*stream); - return; - } - self->ReadNext(stream); - }); + bhttp::async_write( + wire_stream, wire_ref, + [weak = weak_from_this(), stream, wire, keep_alive, + phase = PhaseStart{.peer = std::move(peer), .at = std::chrono::steady_clock::now()}]( + beast::error_code write_ec, std::size_t) mutable { + auto self = weak.lock(); + if (self != nullptr) { + self->active.fetch_sub(1); + } + if (self == nullptr || write_ec || !keep_alive) { + if (self != nullptr && write_ec) { + // The peer vanished mid-response (ADR-0013). + using Kind = BeastServerTransport::ConnectionEvent::Kind; + self->NotifyConnectionEvent(Kind::kDropped, write_ec, std::move(phase)); + } + CloseStream(*stream); + return; + } + self->ReadNext(stream); + }); } // The WebSocket upgrade path (ADR-0015), entered from ReadNext's success @@ -2039,7 +2043,7 @@ struct BeastHttpClient::State { } std::unique_ptr TakeIdle() { - const std::lock_guard lock(mutex); + const std::scoped_lock lock(mutex); if (idle.empty()) { return nullptr; } @@ -2049,7 +2053,7 @@ struct BeastHttpClient::State { } void ReturnIdle(std::unique_ptr connection) { - const std::lock_guard lock(mutex); + const std::scoped_lock lock(mutex); if (idle.size() < opts.max_idle_connections) { idle.push_back(std::move(connection)); } diff --git a/runtime/src/http/forwarded.cc b/runtime/src/http/forwarded.cc index c5026b45..e232b435 100644 --- a/runtime/src/http/forwarded.cc +++ b/runtime/src/http/forwarded.cc @@ -62,7 +62,7 @@ std::optional
ParseAddress(std::string_view text) { if (tail.find('.') != std::string_view::npos && !StrictDottedOctets(tail)) { return std::nullopt; } - std::copy(text.begin(), text.end(), terminated.begin()); + std::ranges::copy(text, terminated.begin()); Address address; if (inet_pton(AF_INET, terminated.data(), address.bytes.data()) == 1) { address.family = AF_INET; @@ -90,8 +90,7 @@ std::string FormatAddress(const Address& address) { } bool AllDigits(std::string_view text) { - return !text.empty() && - std::all_of(text.begin(), text.end(), [](char c) { return c >= '0' && c <= '9'; }); + return !text.empty() && std::ranges::all_of(text, [](char c) { return c >= '0' && c <= '9'; }); } // RFC 4007 zone suffix ("%"), dropped from IPv6 text before @@ -178,7 +177,7 @@ Outcome TrustedProxies::Parse(const std::vector& ci } prefix_bits = 0; for (const char c : digits) { - prefix_bits = prefix_bits * 10 + (c - '0'); + prefix_bits = (prefix_bits * 10) + (c - '0'); } // A base written as IPv4-mapped IPv6 was normalized to the embedded // IPv4, so its prefix shifts across the /96 mapping range with it; @@ -191,7 +190,8 @@ Outcome TrustedProxies::Parse(const std::vector& ci return Error::Validation("TrustedProxies: prefix out of range in \"" + cidr + "\""); } } - result.networks_.push_back(Network{base->bytes, base->family, prefix_bits}); + result.networks_.push_back( + Network{.bytes = base->bytes, .family = base->family, .prefix_bits = prefix_bits}); } return result; } @@ -202,7 +202,7 @@ bool TrustedProxies::Contains(std::string_view address) const { } bool TrustedProxies::ContainsBytes(const std::array& bytes, int family) const { - return std::any_of(networks_.begin(), networks_.end(), [&](const Network& network) { + return std::ranges::any_of(networks_, [&](const Network& network) { return network.family == family && PrefixMatch(network.bytes, bytes, network.prefix_bits); }); } @@ -214,9 +214,10 @@ DerivedClient DeriveClient(const HttpRequest& request, const TrustedProxies& tru } Address client = *peer; if (!trusted.ContainsBytes(client.bytes, client.family)) { - return {FormatAddress(client), request.headers.Has("x-forwarded-for") - ? DerivedClient::Source::kUntrustedHeaderIgnored - : DerivedClient::Source::kDirectPeer}; + return {.address = FormatAddress(client), + .source = request.headers.Has("x-forwarded-for") + ? DerivedClient::Source::kUntrustedHeaderIgnored + : DerivedClient::Source::kDirectPeer}; } // The walk half of the forwarded.h contract. client is assigned before // each trust test, so a malformed-entry stop and exhaustion both leave @@ -235,10 +236,10 @@ DerivedClient DeriveClient(const HttpRequest& request, const TrustedProxies& tru } client = *entry; if (!trusted.ContainsBytes(client.bytes, client.family)) { - return {FormatAddress(client), DerivedClient::Source::kForwarded}; + return {.address = FormatAddress(client), .source = DerivedClient::Source::kForwarded}; } } - return {FormatAddress(client), DerivedClient::Source::kTrustedTier}; + return {.address = FormatAddress(client), .source = DerivedClient::Source::kTrustedTier}; } std::string ClientAddress(const HttpRequest& request, const TrustedProxies& trusted) { diff --git a/runtime/src/http/headers.cc b/runtime/src/http/headers.cc index 301a3abc..ebbc97a7 100644 --- a/runtime/src/http/headers.cc +++ b/runtime/src/http/headers.cc @@ -212,10 +212,7 @@ void Headers::Add(std::string_view name, std::string_view value) { } void Headers::Remove(std::string_view name) { - entries_.erase( - std::remove_if(entries_.begin(), entries_.end(), - [&](const auto& entry) { return HeaderNameEquals(entry.first, name); }), - entries_.end()); + std::erase_if(entries_, [&](const auto& entry) { return HeaderNameEquals(entry.first, name); }); } std::vector SplitHeaderListValues(std::string_view value) { diff --git a/runtime/src/http/http1.cc b/runtime/src/http/http1.cc index 39398121..782ab6ea 100644 --- a/runtime/src/http/http1.cc +++ b/runtime/src/http/http1.cc @@ -128,7 +128,7 @@ bool ParseRequestLine(std::string_view line, std::string* method, std::string* t Outcome ParseStatusLine(std::string_view line) { const auto space = line.find(' '); - if (space == std::string_view::npos || line.size() < space + 4 || line.substr(0, 5) != "HTTP/") { + if (space == std::string_view::npos || line.size() < space + 4 || !line.starts_with("HTTP/")) { return Error::Transport("http: malformed status line: " + std::string(line)); } const int status = std::atoi(std::string(line.substr(space + 1)).c_str()); diff --git a/runtime/src/http/server_dispatch.cc b/runtime/src/http/server_dispatch.cc index 3ab62210..dd97e0ea 100644 --- a/runtime/src/http/server_dispatch.cc +++ b/runtime/src/http/server_dispatch.cc @@ -75,7 +75,7 @@ std::string FormatPeerAddress(const sockaddr* address, socklen_t length) { HttpResponse InvokeHandlerGuarded(const RequestHandler& handler, HttpRequest request) { if (!handler) { - return HttpResponse{503, {}, "", ""}; + return HttpResponse{.status = 503, .headers = {}, .body = "", .operation = ""}; } EnsureInboundTraceIdentity(request); // Contain any handler exception as a correlated 500 — otherwise it unwinds diff --git a/runtime/src/http/socket_transport.cc b/runtime/src/http/socket_transport.cc index d0d61759..a9b593b3 100644 --- a/runtime/src/http/socket_transport.cc +++ b/runtime/src/http/socket_transport.cc @@ -203,7 +203,7 @@ void SocketHttpServer::AcceptLoop() { // Request line: "GET /target HTTP/1.1". HttpRequest request; if (!ParseRequestLine(message->start_line, &request.method, &request.target)) { - response = HttpResponse{400, {}, "malformed request line"}; + response = HttpResponse{.status = 400, .headers = {}, .body = "malformed request line"}; } else { // Exact: the method token is case-sensitive (RFC 9110 §9.1), and the // router matches it by exact string. @@ -215,7 +215,7 @@ void SocketHttpServer::AcceptLoop() { response = InvokeHandlerGuarded(handler_, std::move(request)); } } else { - response = HttpResponse{400, {}, message.error().message()}; + response = HttpResponse{.status = 400, .headers = {}, .body = message.error().message()}; } // The transport is authoritative for framing, so drop any copies a handler diff --git a/runtime/src/http/uri.cc b/runtime/src/http/uri.cc index fd47dc0c..53c002dc 100644 --- a/runtime/src/http/uri.cc +++ b/runtime/src/http/uri.cc @@ -68,17 +68,17 @@ Outcome PercentDecode(std::string_view text) { } void QueryString::Add(std::string_view key, std::string_view value) { - params_.push_back({EncodeQueryComponent(key), EncodeQueryComponent(value), false}); + params_.push_back( + {.key = EncodeQueryComponent(key), .value = EncodeQueryComponent(value), .flag = false}); } void QueryString::AddFlag(std::string_view key) { - params_.push_back({EncodeQueryComponent(key), "", true}); + params_.push_back({.key = EncodeQueryComponent(key), .value = "", .flag = true}); } bool QueryString::Has(std::string_view key) const { const std::string encoded = EncodeQueryComponent(key); - return std::any_of(params_.begin(), params_.end(), - [&](const Param& param) { return param.key == encoded; }); + return std::ranges::any_of(params_, [&](const Param& param) { return param.key == encoded; }); } std::string QueryString::ToString() const { @@ -144,11 +144,11 @@ Outcome ParseEndpoint(std::string_view url) { constexpr std::string_view kHttp = "http://"; constexpr std::string_view kHttps = "https://"; std::string_view rest; - if (url.substr(0, kHttp.size()) == kHttp) { + if (url.starts_with(kHttp)) { endpoint.scheme = "http"; endpoint.port = 80; rest = url.substr(kHttp.size()); - } else if (url.substr(0, kHttps.size()) == kHttps) { + } else if (url.starts_with(kHttps)) { endpoint.scheme = "https"; endpoint.port = 443; rest = url.substr(kHttps.size()); @@ -169,7 +169,7 @@ Outcome ParseEndpoint(std::string_view url) { int port = 0; for (const char c : authority.substr(colon + 1)) { if (c < '0' || c > '9') return Error::Validation("endpoint: invalid port"); - port = port * 10 + (c - '0'); + port = (port * 10) + (c - '0'); if (port > 65535) return Error::Validation("endpoint: invalid port"); } if (port == 0) return Error::Validation("endpoint: invalid port"); diff --git a/runtime/src/http/websocket_pair.cc b/runtime/src/http/websocket_pair.cc index 10c023a7..211be218 100644 --- a/runtime/src/http/websocket_pair.cc +++ b/runtime/src/http/websocket_pair.cc @@ -109,7 +109,7 @@ class PairEnd final : public WebSocket { // completion can tear down waits only on that end's pins. std::array waiters; { - const std::lock_guard lock(state_->mutex); + const std::scoped_lock lock(state_->mutex); state_->closed = true; for (std::size_t end = 0; end < 2; ++end) { WebSocket::SendCallback send; @@ -157,7 +157,7 @@ class PairEnd final : public WebSocket { WebSocket::ReceiveCallback deliver; eventstream::Message delivered; { - const std::lock_guard lock(state_->mutex); + const std::scoped_lock lock(state_->mutex); if (state_->closed) { callback(Error::Transport("websocket pair: session is closed")); return; @@ -170,7 +170,8 @@ class PairEnd final : public WebSocket { std::deque& outbound = state_->queues[send_index_]; if (outbound.size() >= kQueueDepth) { // Backpressure without blocking: park until the receiver drains. - state_->pending_send[send_index_] = PendingSend{message, std::move(callback)}; + state_->pending_send[send_index_] = + PendingSend{.message = message, .callback = std::move(callback)}; return; } deliver = TakePeerReceiverLocked(message, delivered); @@ -206,7 +207,7 @@ class PairEnd final : public WebSocket { WebSocket::ReceiveCallback deliver; std::uint64_t parked_generation = 0; { - const std::lock_guard lock(state_->mutex); + const std::scoped_lock lock(state_->mutex); if (state_->pending_receive[send_index_] || state_->blocked_receivers[send_index_] > 0) { callback(Error::Validation("websocket pair: a receive is already outstanding")); return; @@ -273,13 +274,13 @@ class PairEnd final : public WebSocket { // Under -fno-exceptions a failed thread spawn terminates (nothing can // throw), which is the fail-fast posture; with exceptions on, contain // it here so the caller never sees a throw beside a still-armed park. -#if defined(__cpp_exceptions) +#ifdef __cpp_exceptions WebSocket::ReceiveCallback refused; try { std::thread(watchdog).detach(); return; } catch (...) { - const std::lock_guard lock(state_->mutex); + const std::scoped_lock lock(state_->mutex); if (state_->pending_receive[send_index_] && state_->receive_park_generation[send_index_] == generation) { refused = std::exchange(state_->pending_receive[send_index_], nullptr); diff --git a/runtime/src/server/metrics.cc b/runtime/src/server/metrics.cc index 331ed659..452d2eab 100644 --- a/runtime/src/server/metrics.cc +++ b/runtime/src/server/metrics.cc @@ -235,7 +235,7 @@ namespace internal { void MetricFamily::Add(const MetricLabels& labels, double amount, bool set) { const std::string key = RenderLabels(labels, kind == Kind::kHistogram); - const std::lock_guard lock(mutex); + const std::scoped_lock lock(mutex); if (auto found = samples.find(key); found != samples.end()) { if (set) { found->second.value = amount; @@ -253,7 +253,7 @@ void MetricFamily::Add(const MetricLabels& labels, double amount, bool set) { void MetricFamily::Observe(const MetricLabels& labels, double value) { const std::string key = RenderLabels(labels, kind == Kind::kHistogram); - const std::lock_guard lock(mutex); + const std::scoped_lock lock(mutex); auto found = samples.find(key); if (found == samples.end()) { if (samples.size() >= max_series) { @@ -275,7 +275,7 @@ void MetricFamily::Observe(const MetricLabels& labels, double value) { void MetricFamily::Declare(const MetricLabels& labels) { const std::string key = RenderLabels(labels, kind == Kind::kHistogram); - const std::lock_guard lock(mutex); + const std::scoped_lock lock(mutex); if (samples.contains(key)) { return; // idempotent, and never disturbs a series already carrying events } @@ -332,7 +332,7 @@ void MetricsRegistry::RecordStart(const RequestStart& start) { // always keyed by method — the unlabeled form is the sum over these keys — // and the key set is bounded by the method vocabulary. std::string method = NormalizeMethod(start.method); - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); ++in_flight_[std::move(method)]; } @@ -364,7 +364,7 @@ void MetricsRegistry::Record(const RequestObservation& observation) { .route = observation.operation.empty() ? std::string(kUnmatchedRoute) : observation.operation}; - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); // Before the cap check: a request that started must bring the gauge back // down whether or not its route survives admission, or a refused route // leaks in-flight forever. Only decrement one that was incremented — @@ -410,7 +410,7 @@ void MetricsRegistry::RecordRejection(std::string_view method, int status) { .method = method.empty() ? std::string(kUnparsedMethod) : NormalizeMethod(method), .route = std::string(kUnmatchedRoute)}; - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); RouteStats* stats = AdmitRoute(key); if (stats == nullptr) { return; @@ -446,7 +446,7 @@ std::shared_ptr MetricsRegistry::Register(std::string na "' is one of the built-in families"); } } - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); if (auto found = families_.find(name); found != families_.end()) { // Idempotent for an identical re-registration; a mismatch is the case // that would corrupt the scrape, so it aborts rather than picking one. @@ -516,7 +516,7 @@ std::string MetricsRegistry::Expose() const { return {}; } std::string out; - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); // Families are emitted whole and in order. The format requires every line // of a family to be contiguous, so each family is written in one pass — @@ -526,8 +526,9 @@ std::string MetricsRegistry::Expose() const { return BuiltInLabels( {{std::string(kMethodLabel), key.method}, {std::string(kRouteLabel), key.route}}); }; + using RouteCounter = std::uint64_t RouteStats::*; const auto counter_family = [&](std::string_view name, std::string_view help, - std::uint64_t RouteStats::*field) { + RouteCounter field) { AppendFamilyHeader(out, name, "counter", help); for (const auto& [key, stats] : routes_) { AppendSample(out, name, "", route_labels(key), std::to_string(stats.*field)); @@ -580,7 +581,7 @@ std::string MetricsRegistry::Expose() const { AppendSample(out, kObservationsDropped, "", BuiltInLabels({}), std::to_string(observations_dropped_)); for (const auto& [name, family] : families_) { - const std::lock_guard family_lock(family->mutex); + const std::scoped_lock family_lock(family->mutex); if (family->dropped != 0) { AppendSample(out, kObservationsDropped, "", BuiltInLabels({{"metric", name}}), std::to_string(family->dropped)); @@ -591,7 +592,7 @@ std::string MetricsRegistry::Expose() const { // are already keyed by rendered labels, so a family's series are // contiguous the way the format requires. for (const auto& [name, family] : families_) { - const std::lock_guard family_lock(family->mutex); + const std::scoped_lock family_lock(family->mutex); const char* type = "counter"; if (family->kind == internal::MetricFamily::Kind::kGauge) type = "gauge"; if (family->kind == internal::MetricFamily::Kind::kHistogram) type = "histogram"; diff --git a/runtime/src/server/middleware.cc b/runtime/src/server/middleware.cc index d1f2b43f..9c700547 100644 --- a/runtime/src/server/middleware.cc +++ b/runtime/src/server/middleware.cc @@ -180,8 +180,10 @@ Middleware Observe(std::function on_complete, request.headers, "Observe labeler"); } if (on_start != nullptr) { - CallContained(on_start, RequestStart{request.method, request.target, labels}, - "Observe on_start"); + CallContained( + on_start, + RequestStart{.method = request.method, .target = request.target, .labels = labels}, + "Observe on_start"); } RequestObservation observation; observation.labels = std::move(labels); @@ -199,7 +201,7 @@ Middleware Observe(std::function on_complete, } const auto start = now(); http::HttpResponse response; -#if defined(__cpp_exceptions) +#ifdef __cpp_exceptions try { response = next(request); } catch (...) { diff --git a/runtime/src/server/router.cc b/runtime/src/server/router.cc index f4164d65..32ec2bc2 100644 --- a/runtime/src/server/router.cc +++ b/runtime/src/server/router.cc @@ -151,7 +151,9 @@ Outcome Router::Add(std::string_view method, std::string_view pattern, Rou std::string(pattern)); } } - bucket.push_back(RouteEntry{std::move(*segments), std::move(handler), std::string(operation)}); + bucket.push_back(RouteEntry{.segments = std::move(*segments), + .handler = std::move(handler), + .operation = std::string(operation)}); return Unit{}; } @@ -175,10 +177,9 @@ http::HttpResponse Router::Route(const http::HttpRequest& request) const { std::string allow; for (const auto& [method, bucket] : routes_) { if (method == request.method) continue; - const bool any_match = - std::any_of(bucket.begin(), bucket.end(), [&segments](const RouteEntry& route) { - return internal::MatchSegments(route.segments, segments, nullptr); - }); + const bool any_match = std::ranges::any_of(bucket, [&segments](const RouteEntry& route) { + return internal::MatchSegments(route.segments, segments, nullptr); + }); if (!any_match) continue; if (!allow.empty()) allow += ", "; allow += method; diff --git a/runtime/src/server/websocket_router.cc b/runtime/src/server/websocket_router.cc index a7b43c5e..0ebf5e86 100644 --- a/runtime/src/server/websocket_router.cc +++ b/runtime/src/server/websocket_router.cc @@ -89,10 +89,9 @@ std::function(const http::HttpRequest&)> WebSo std::string allow; for (const auto& [method, bucket] : routes_) { if (method == request.method) continue; - const bool any_match = - std::any_of(bucket.begin(), bucket.end(), [&target](const StreamRoute& route) { - return internal::MatchSegments(route.segments, target->path_segments, nullptr); - }); + const bool any_match = std::ranges::any_of(bucket, [&target](const StreamRoute& route) { + return internal::MatchSegments(route.segments, target->path_segments, nullptr); + }); if (!any_match) continue; if (!allow.empty()) allow += ", "; allow += method; diff --git a/runtime/testing/include/opal/testing/connection_event_recorder.h b/runtime/testing/include/opal/testing/connection_event_recorder.h index 5449c4a0..a95afed2 100644 --- a/runtime/testing/include/opal/testing/connection_event_recorder.h +++ b/runtime/testing/include/opal/testing/connection_event_recorder.h @@ -23,7 +23,7 @@ struct ConnectionEventRecorder { std::function Hook() { return [this](const http::BeastServerTransport::ConnectionEvent& event) { - const std::lock_guard lock(mutex); + const std::scoped_lock lock(mutex); events.push_back(event); }; } @@ -33,12 +33,12 @@ struct ConnectionEventRecorder { const auto deadline = std::chrono::steady_clock::now() + budget; while (std::chrono::steady_clock::now() < deadline) { { - const std::lock_guard lock(mutex); + const std::scoped_lock lock(mutex); if (events.size() >= count) return true; } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } - const std::lock_guard lock(mutex); + const std::scoped_lock lock(mutex); return events.size() >= count; } }; diff --git a/runtime/testing/include/opal/testing/websocket_contract_test.h b/runtime/testing/include/opal/testing/websocket_contract_test.h index 1d8215e5..8e256ce4 100644 --- a/runtime/testing/include/opal/testing/websocket_contract_test.h +++ b/runtime/testing/include/opal/testing/websocket_contract_test.h @@ -73,7 +73,7 @@ class ContractMailbox { // the frame unwinds, and ~condition_variable runs while the poster is // still inside notify_all. Holding the lock keeps the waiter parked on // the mutex until the notify is done. - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); ASSERT_FALSE(value_.has_value()) << "completion fired twice"; value_.emplace(std::move(value)); ready_.notify_all(); @@ -89,14 +89,14 @@ class ContractMailbox { ADD_FAILURE() << "contract mailbox: no completion arrived within the deadline"; std::abort(); // T (an Outcome) has no default value to limp on with } - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); T value = std::move(*value_); value_.reset(); return value; } bool Empty() { - const std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); return !value_.has_value(); }