// Copyright 2023 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_CLIENT_RING_BUFFER_ANNOTATION_H_ #define CRASHPAD_CLIENT_RING_BUFFER_ANNOTATION_H_ #include #include "client/annotation.h" #include "client/length_delimited_ring_buffer.h" namespace crashpad { //! \brief Capacity of `RingBufferAnnotation`, in bytes. using RingBufferAnnotationCapacity = RingBufferCapacity; namespace internal { //! \brief Default capacity of `RingBufferAnnotation`, in bytes. inline constexpr RingBufferAnnotationCapacity kDefaultRingBufferAnnotationCapacity = 8192; } // namespace internal //! \brief An `Annotation` which wraps a `LengthDelimitedRingBuffer` //! of up to `Capacity` bytes in length. //! //! Supports writing variable-length data via `Push()`. When the ring buffer is //! full, it will drop old data items in FIFO order until enough space is //! available for the write. //! //! Supports guarding concurrent reads from writes via `ScopedSpinGuard`, so //! writing to this object is thread-safe. //! //! Clients which read this `Annotation`'s memory can optionally invoke //! `TryCreateScopedSpinGuard()` on this object to ensure any pending write //! finishes before the memory is read. //! //! Each item in this ring buffer is delimited by its length encoded in //! little-endian Base 128 varint encoding. //! //! `RingBufferAnnotation` uses varint-encoded delimiters to enable //! zero-copy deserialization of the ringbuffer's contents when storing //! protobufs inside the ringbuffer, e.g. via //! `google::protobuf::util::ParseDelimitedFromZeroCopyStream()` or similar. //! //! \sa //! https://github.com/protocolbuffers/protobuf/blob/3202b9da88ceb75b65bbabaf4033c95e872f828d/src/google/protobuf/util/delimited_message_util.h#L85 //! \sa //! https://github.com/protocolbuffers/protobuf/blob/8bd49dea5e167a389d94b71d24c981d8f9fa0c99/src/google/protobuf/io/zero_copy_stream_impl_lite.h#L68 //! \sa //! https://github.com/protocolbuffers/protobuf/blob/8bd49dea5e167a389d94b71d24c981d8f9fa0c99/src/google/protobuf/io/coded_stream.h#L171 //! //! To deserialize the items stored in this annotation, use //! `LengthDelimitedRingBufferReader`. template class RingBufferAnnotation final : public Annotation { public: //! \brief Constructs a `RingBufferAnnotation`. //! \param[in] type A unique identifier for the type of data in the ring //! buffer. //! \param[in] name The name of the annotation. constexpr RingBufferAnnotation(Annotation::Type type, const char name[]) : Annotation(type, name, reinterpret_cast(&ring_buffer_data_), ConcurrentAccessGuardMode::kScopedSpinGuard), ring_buffer_data_(), ring_buffer_writer_(ring_buffer_data_) {} RingBufferAnnotation(const RingBufferAnnotation&) = delete; RingBufferAnnotation& operator=(const RingBufferAnnotation&) = delete; RingBufferAnnotation(RingBufferAnnotation&&) = default; RingBufferAnnotation& operator=(RingBufferAnnotation&&) = default; //! \brief Pushes data onto this annotation's ring buffer. //! //! If the ring buffer does not have enough space to store `buffer_length` //! bytes of data, old data items are dropped in FIFO order until //! enough space is available to store the new data. bool Push(const void* const buffer, RingBufferAnnotationCapacity buffer_length) { // Use a zero timeout so the operation immediately fails if another thread // or process is currently reading this Annotation. constexpr uint64_t kSpinGuardTimeoutNanoseconds = 0; auto spin_guard = TryCreateScopedSpinGuard(kSpinGuardTimeoutNanoseconds); if (!spin_guard) { return false; } bool success = ring_buffer_writer_.Push(buffer, buffer_length); if (success) { SetSize(ring_buffer_data_.GetRingBufferLength()); } return success; } //! \brief Reset the annotation (e.g., for testing). //! This method is not thread-safe. void ResetForTesting() { ring_buffer_data_.ResetForTesting(); ring_buffer_writer_.ResetForTesting(); } private: using RingBufferWriter = LengthDelimitedRingBufferWriter>; //! \brief The ring buffer data stored in this Anotation. RingBufferData ring_buffer_data_; //! \brief The writer which wraps `ring_buffer_data_`. RingBufferWriter ring_buffer_writer_; }; // Allow just `RingBufferAnnotation foo;` to be declared without template // arguments using C++17 class template argument deduction. template RingBufferAnnotation(Annotation::Type type, const char name[]) -> RingBufferAnnotation; } // namespace crashpad #endif // CRASHPAD_CLIENT_RING_BUFFER_ANNOTATION_H_