cpp-common/bt2: add `bt2::ConstMessageArray`
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Thu, 26 Oct 2023 14:43:05 +0000 (10:43 -0400)
committerSimon Marchi <simon.marchi@efficios.com>
Thu, 14 Dec 2023 15:57:04 +0000 (10:57 -0500)
A `bt2::ConstMessageArray` instance wraps an array of `const` messages
(`bt_message_array_const`) which is used in everything related to "next"
message iterator methods.

There are two ways to build such a wrapper:

1. Use bt2::ConstMessageArray::wrapExisting() to wrap an existing
   library message array with a given length:

       bt_message_array_const libMsgs;
       uint64_t count;

       if (bt_message_iterator_next(myIter, &libMsgs, &count) !=
               BT_MESSAGE_ITERATOR_NEXT_STATUS_OK) {
           // Handle special status
       }

       const auto msgs = bt2::ConstMessageArray::wrapExisting(libMsgs,
                                                              count);

       ⭐

       if (!msgs[0].isEvent()) {
           // ...
       }

       for (const auto msg : msgs) {
           // Handle `msg`
       }

   At ⭐, `msgs` is now the owner of the messages of `libMsgs`,
   meaning:

   a) Its destructor puts all the contained message references.

   b) Do NOT put the message references of `libMsgs` yourself with
      bt_message_put_ref().

   c) Do NOT make another `ConstMessageArray` wrap `libMsgs`.

2. Use bt2::ConstMessageArray::wrapEmpty() to wrap an empty library
   message array with an explicit capacity:

       bt_message_iterator_class_next_method_status myNext(
               bt_self_message_iterator * const libSelfMsgIter,
               const bt_message_array_const libMsgs,
               const uint64_t capacity, uint64_t * const count)
       {
           auto msgs = bt2::ConstMessageArray::wrapEmpty(libMsgs,
                                                         capacity);

           // Create messages and append with `msgs.append(...)`

           *count = msgs.release();
           return BT_MESSAGE_ITERATOR_CLASS_NEXT_METHOD_STATUS_OK;
       }

   If there's any thrown exception during the message appending part,
   the destructor of `msgs` will put the references of its messages.

   Then when you call ConstMessageArray::release(), you become the owner
   of the library array again. The returned value is its length before
   the release.

As you can see, this wrapper is a bit fragile and really on the "you
know what you're doing" territory, but I believe it can be useful and
safer when used as intended.

Conceptually, only one `ConstMessageArray` at a time may manage the
underlying library array. Therefore, copy operations are disabled, but
move operations are implemented.

Because the copy constructor isn't implemented, we can't use
`CommonIterator`, so `ConstMessageArrayIterator` is a dedicated,
lightweight version of the latter.

Get a message array length and capacity with the length() and
capacity() methods.

Check if a message array is empty or full with the isEmpty() and
isFull() methods.

Append a message to a message array with the append() method.

Release a message array with the release() method.

Borrow a message from a message array with the `[]` operator.

Iterate a message array with the begin() and end() methods.

Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
Change-Id: Iac8bea4195836f6f0d157eb08bec27b3d24ca506
Reviewed-on: https://review.lttng.org/c/babeltrace/+/11146
Tested-by: jenkins <jenkins@lttng.org>
Reviewed-by: Simon Marchi <simon.marchi@efficios.com>
CI-Build: Simon Marchi <simon.marchi@efficios.com>

src/Makefile.am
src/cpp-common/bt2/message-array.hpp [new file with mode: 0644]

index ee539f8e026cba5f6713370e1d42f2a66e3222de..d2559c3382de4f06b7d7ea763c57f2fd1b46aeb4 100644 (file)
@@ -25,6 +25,7 @@ noinst_HEADERS = \
        cpp-common/bt2/integer-range-set.hpp \
        cpp-common/bt2/internal/utils.hpp \
        cpp-common/bt2/logging.hpp \
+       cpp-common/bt2/message-array.hpp \
        cpp-common/bt2/message.hpp \
        cpp-common/bt2/shared-object.hpp \
        cpp-common/bt2/raw-value-proxy.hpp \
diff --git a/src/cpp-common/bt2/message-array.hpp b/src/cpp-common/bt2/message-array.hpp
new file mode 100644 (file)
index 0000000..a286bc9
--- /dev/null
@@ -0,0 +1,300 @@
+/*
+ * Copyright (c) 2023 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef BABELTRACE_CPP_COMMON_BT2_MESSAGE_ARRAY_HPP
+#define BABELTRACE_CPP_COMMON_BT2_MESSAGE_ARRAY_HPP
+
+#include <algorithm>
+
+#include <babeltrace2/babeltrace.h>
+
+#include "common/assert.h"
+
+#include "message.hpp"
+
+namespace bt2 {
+
+class ConstMessageArray;
+
+class ConstMessageArrayIterator final
+{
+    friend class ConstMessageArray;
+
+public:
+    using difference_type = std::ptrdiff_t;
+    using value_type = ConstMessage;
+    using pointer = value_type *;
+    using reference = value_type&;
+    using iterator_category = std::input_iterator_tag;
+
+private:
+    explicit ConstMessageArrayIterator(const ConstMessageArray& msgArray,
+                                       const uint64_t idx) noexcept :
+        _mMsgArray {&msgArray},
+        _mIdx {idx}
+    {
+    }
+
+public:
+    ConstMessageArrayIterator& operator++() noexcept
+    {
+        ++_mIdx;
+        return *this;
+    }
+
+    ConstMessageArrayIterator operator++(int) noexcept
+    {
+        const auto tmp = *this;
+
+        ++(*this);
+        return tmp;
+    }
+
+    bool operator==(const ConstMessageArrayIterator& other) const noexcept
+    {
+        BT_ASSERT_DBG(_mMsgArray == other._mMsgArray);
+        return _mIdx == other._mIdx;
+    }
+
+    bool operator!=(const ConstMessageArrayIterator& other) const noexcept
+    {
+        return !(*this == other);
+    }
+
+    ConstMessage operator*() noexcept;
+
+private:
+    const ConstMessageArray *_mMsgArray;
+    uint64_t _mIdx;
+};
+
+/*
+ * A wrapper of `bt_message_array_const`, either:
+ *
+ * Containing existing messages:
+ *     Use ConstMessageArray::wrapExisting().
+ *
+ *     Example:
+ *
+ *         bt_message_array_const libMsgs;
+ *         uint64_t count;
+ *
+ *         if (bt_message_iterator_next(myIter, &libMsgs, &count) !=
+ *                 BT_MESSAGE_ITERATOR_NEXT_STATUS_OK) {
+ *             // Handle special status
+ *         }
+ *
+ *         const auto msgs = bt2::ConstMessageArray::wrapExisting(libMsgs,
+ *                                                                count);
+ *
+ *         // At this point `msgs` manages `libMsgs`
+ *
+ * An empty one which you need to fill:
+ *     Use ConstMessageArray::wrapEmpty().
+ *
+ *     Use the release() method to move the ownership of the wrapped
+ *     library array to you.
+ *
+ *     Example:
+ *
+ *         bt_message_iterator_class_next_method_status myNext(
+ *                 bt_self_message_iterator * const libSelfMsgIter,
+ *                 const bt_message_array_const libMsgs,
+ *                 const uint64_t capacity, uint64_t * const count)
+ *         {
+ *             auto msgs = bt2::ConstMessageArray::wrapEmpty(libMsgs,
+ *                                                           capacity);
+ *
+ *             // Create messages and append with `msgs.append(...)`
+ *
+ *             *count = msgs.release();
+ *             return BT_MESSAGE_ITERATOR_CLASS_NEXT_METHOD_STATUS_OK;
+ *         }
+ *
+ * In both cases, the returned message array wrapper is always the sole
+ * owner of the wrapped library array: it's not a simple passive view.
+ * The destructor puts the references of the contained messages.
+ */
+class ConstMessageArray final
+{
+private:
+    explicit ConstMessageArray(const bt_message_array_const libArrayPtr, const std::uint64_t length,
+                               const std::uint64_t capacity) noexcept :
+        _mLibArrayPtr {libArrayPtr},
+        _mLen {length}, _mCap {capacity}
+    {
+        BT_ASSERT_DBG(length <= capacity);
+        BT_ASSERT_DBG(capacity > 0);
+    }
+
+public:
+    using Iterator = ConstMessageArrayIterator;
+
+    ~ConstMessageArray()
+    {
+        this->_putMsgRefs();
+    }
+
+    /*
+     * Not available because there's no underlying message array to
+     * copy to.
+     */
+    ConstMessageArray(const ConstMessageArray&) = delete;
+
+    /*
+     * Not available because we don't need it yet.
+     */
+    ConstMessageArray& operator=(const ConstMessageArray&) = delete;
+
+    ConstMessageArray(ConstMessageArray&& other) noexcept :
+        ConstMessageArray {other._mLibArrayPtr, other._mLen, other._mCap}
+    {
+        other._reset();
+    }
+
+    ConstMessageArray& operator=(ConstMessageArray&& other) noexcept
+    {
+        /* Put existing message references */
+        this->_putMsgRefs();
+
+        /* Move other members to this message array */
+        _mLibArrayPtr = other._mLibArrayPtr;
+        _mLen = other._mLen;
+        _mCap = other._mCap;
+
+        /* Reset other message array */
+        other._reset();
+
+        return *this;
+    }
+
+    /*
+     * Wraps an existing library array `libArrayPtr`, known to contain
+     * `length` messages.
+     *
+     * CAUTION: The ownership of the existing messages contained in
+     * `libArrayPtr` is _moved_ to the returned `ConstMessageArray`
+     * instance.
+     *
+     * This is similar to what the constructor of `std::shared_ptr`
+     * does. Do NOT wrap the same library array twice.
+     */
+    static ConstMessageArray wrapExisting(const bt_message_array_const libArrayPtr,
+                                          const std::uint64_t length) noexcept
+    {
+        return ConstMessageArray {libArrayPtr, length, length};
+    }
+
+    /*
+     * Wraps an existing library array `libArrayPtr`, known to be empty,
+     * with a capacity of `capacity` messages.
+     */
+    static ConstMessageArray wrapEmpty(const bt_message_array_const libArrayPtr,
+                                       const std::uint64_t capacity) noexcept
+    {
+        return ConstMessageArray {libArrayPtr, 0, capacity};
+    }
+
+    std::uint64_t length() const noexcept
+    {
+        return _mLen;
+    }
+
+    std::uint64_t capacity() const noexcept
+    {
+        return _mCap;
+    }
+
+    bool isEmpty() const noexcept
+    {
+        return _mLen == 0;
+    }
+
+    bool isFull() const noexcept
+    {
+        return _mLen == _mCap;
+    }
+
+    void append(ConstMessage::Shared message) noexcept
+    {
+        BT_ASSERT_DBG(!this->isFull());
+
+        /* Move reference to underlying array */
+        _mLibArrayPtr[_mLen] = message.release().libObjPtr();
+        ++_mLen;
+    }
+
+    /*
+     * Transfers the ownership of the wrapped library array to the
+     * caller, returning the number of contained messages (array
+     * length).
+     */
+    std::uint64_t release() noexcept
+    {
+        const auto len = _mLen;
+
+        this->_reset();
+        return len;
+    }
+
+    ConstMessage operator[](const std::uint64_t index) const noexcept
+    {
+        BT_ASSERT_DBG(index < _mLen);
+        return ConstMessage {_mLibArrayPtr[index]};
+    }
+
+    Iterator begin() const noexcept
+    {
+        return Iterator {*this, 0};
+    }
+
+    Iterator end() const noexcept
+    {
+        return Iterator {*this, this->length()};
+    }
+
+private:
+    void _reset() noexcept
+    {
+        /*
+         * This means this array is pretty much dead, and any call to
+         * append() or operator[]() will make assertions fail.
+         *
+         * That being said, you may still move another array to this
+         * one.
+         */
+        _mLen = 0;
+        _mCap = 0;
+    }
+
+    /*
+     * Decrements the reference count of all the contained messages.
+     */
+    void _putMsgRefs() noexcept
+    {
+        std::for_each(&_mLibArrayPtr[0], &_mLibArrayPtr[_mLen], [](const bt_message * const msg) {
+            bt_message_put_ref(msg);
+        });
+    }
+
+    /* Underlying array which is generally owned by the library */
+    bt_message_array_const _mLibArrayPtr;
+
+    /* Length (count of contained messages) */
+    std::uint64_t _mLen;
+
+    /* Capacity (maximum length) */
+    std::uint64_t _mCap;
+};
+
+inline ConstMessage ConstMessageArrayIterator::operator*() noexcept
+{
+    return (*_mMsgArray)[_mIdx];
+}
+
+} /* namespace bt2 */
+
+#endif /* BABELTRACE_CPP_COMMON_BT2_MESSAGE_ARRAY_HPP */
This page took 0.028272 seconds and 4 git commands to generate.