diff --git a/core/deleter.hh b/core/deleter.hh index 00e0ac6665..250de361d1 100644 --- a/core/deleter.hh +++ b/core/deleter.hh @@ -27,29 +27,62 @@ #include #include +/// \addtogroup memory-module +/// @{ + +/// Provides a mechanism for managing the lifetime of a buffer. +/// +/// A \c deleter is an object that is used to inform the consumer +/// of some buffer (not referenced by the deleter itself) how to +/// delete the buffer. This can be by calling an arbitrary function +/// or destroying an object carried by the deleter. Examples of +/// a deleter's encapsulated actions are: +/// +/// - calling \c std::free(p) on some captured pointer, p +/// - calling \c delete \c p on some captured pointer, p +/// - decrementing a reference count somewhere +/// +/// A deleter performs its action from its destructor. class deleter final { public: + /// \cond internal struct impl; struct raw_object_tag {}; + /// \endcond private: // if bit 0 set, point to object to be freed directly. impl* _impl = nullptr; public: + /// Constructs an empty deleter that does nothing in its destructor. deleter() = default; deleter(const deleter&) = delete; + /// Moves a deleter. deleter(deleter&& x) : _impl(x._impl) { x._impl = nullptr; } + /// \cond internal explicit deleter(impl* i) : _impl(i) {} deleter(raw_object_tag tag, void* object) : _impl(from_raw_object(object)) {} + /// \endcond + /// Destroys the deleter and carries out the encapsulated action. ~deleter(); deleter& operator=(deleter&& x); deleter& operator=(deleter&) = delete; + /// Performs a sharing operation. The encapsulated action will only + /// be carried out after both the original deleter and the returned + /// deleter are both destroyed. + /// + /// \return a deleter with the same encapsulated action as this one. deleter share(); + /// Checks whether the deleter has an associated action. explicit operator bool() const { return bool(_impl); } + /// \cond internal void reset(impl* i) { this->~deleter(); new (this) deleter(i); } + /// \endcond + /// Appends another deleter to this deleter. When this deleter is + /// destroyed, both encapsulated actions will be carried out. void append(deleter d); private: static bool is_raw_object(impl* i) { @@ -72,12 +105,14 @@ private: } }; +/// \cond internal struct deleter::impl { unsigned refs = 1; deleter next; impl(deleter next) : next(std::move(next)) {} virtual ~impl() {} }; +/// \endcond inline deleter::~deleter() { @@ -99,6 +134,7 @@ deleter& deleter::operator=(deleter&& x) { return *this; } +/// \cond internal template struct lambda_deleter_impl final : deleter::impl { Deleter del; @@ -119,24 +155,39 @@ inline object_deleter_impl* make_object_deleter_impl(deleter next, Object obj) { return new object_deleter_impl(std::move(next), std::move(obj)); } +/// \endcond -template +/// Makes a \ref deleter that encapsulates the action of +/// destroying an object, as well as running another deleter. The input +/// object is moved to the deleter, and destroyed when the deleter is destroyed. +/// +/// \param d deleter that will become part of the new deleter's encapsulated action +/// \param o object whose destructor becomes part of the new deleter's encapsulated action +/// \related deleter +template deleter -make_deleter(deleter next, Deleter d) { - return deleter(new lambda_deleter_impl(std::move(next), std::move(d))); +make_deleter(deleter next, Object o) { + return deleter(new lambda_deleter_impl(std::move(next), std::move(o))); } -template +/// Makes a \ref deleter that encapsulates the action of destroying an object. The input +/// object is moved to the deleter, and destroyed when the deleter is destroyed. +/// +/// \param o object whose destructor becomes the new deleter's encapsulated action +/// \related deleter +template deleter -make_deleter(Deleter d) { - return make_deleter(deleter(), std::move(d)); +make_deleter(Object o) { + return make_deleter(deleter(), std::move(o)); } +/// \cond internal struct free_deleter_impl final : deleter::impl { void* obj; free_deleter_impl(void* obj) : impl(deleter()), obj(obj) {} virtual ~free_deleter_impl() override { std::free(obj); } }; +/// \endcond inline deleter @@ -176,6 +227,10 @@ void deleter::append(deleter d) { d._impl = nullptr; } +/// Makes a deleter that calls \c std::free() when it is destroyed. +/// +/// \param obj object to free. +/// \related deleter inline deleter make_free_deleter(void* obj) { @@ -185,12 +240,20 @@ make_free_deleter(void* obj) { return deleter(deleter::raw_object_tag(), obj); } +/// Makes a deleter that calls \c std::free() when it is destroyed, as well +/// as invoking the encapsulated action of another deleter. +/// +/// \param d deleter to invoke. +/// \param obj object to free. +/// \related deleter inline deleter make_free_deleter(deleter next, void* obj) { return make_deleter(std::move(next), [obj] () mutable { std::free(obj); }); } +/// \see make_deleter(Object) +/// \related deleter template inline deleter @@ -198,6 +261,8 @@ make_object_deleter(T&& obj) { return deleter{make_object_deleter_impl(deleter(), std::move(obj))}; } +/// \see make_deleter(deleter, Object) +/// \related deleter template inline deleter @@ -205,4 +270,6 @@ make_object_deleter(deleter d, T&& obj) { return deleter{make_object_deleter_impl(std::move(d), std::move(obj))}; } +/// @} + #endif /* DELETER_HH_ */ diff --git a/core/future.hh b/core/future.hh index 79a742f1e4..4b404d0b18 100644 --- a/core/future.hh +++ b/core/future.hh @@ -225,6 +225,10 @@ struct future_state { } return std::move(_u.value); } + using get0_return_type = std::tuple_element_t<0, std::tuple>; + static get0_return_type get0(std::tuple&& x) { + return std::get<0>(x); + } void forward_to(promise& pr) noexcept { assert(_state != state::future); if (_state == state::exception) { @@ -308,6 +312,10 @@ struct future_state<> { } return {}; } + using get0_return_type = void; + static get0_return_type get0(std::tuple<>&&) { + return; + } std::exception_ptr get_exception() noexcept { assert(_u.st >= state::exception_min); // Move ex out so future::~future() knows we've handled it @@ -677,6 +685,18 @@ public: return state()->get(); } + /// Gets the value returned by the computation. + /// + /// Similar to \ref get(), but instead of returning a + /// tuple, returns the first value of the tuple. This is + /// useful for the common case of a \c future with exactly + /// one type parameter. + /// + /// Equivalent to: \c std::get<0>(f.get()). + typename future_state::get0_return_type get0() { + return future_state::get0(get()); + } + /// \cond internal void wait() { auto thread = seastar::thread_impl::get(); diff --git a/core/temporary_buffer.hh b/core/temporary_buffer.hh index 8ced95229e..790883cf06 100644 --- a/core/temporary_buffer.hh +++ b/core/temporary_buffer.hh @@ -26,8 +26,34 @@ #include "util/eclipse.hh" #include -// A temporary_buffer either points inside a larger buffer, or, if the requested size -// is too large, or if the larger buffer is scattered, contains its own storage. +/// \addtogroup memory-module +/// @{ + +/// Temporary, self-managed byte buffer. +/// +/// A \c temporary_buffer is similar to an \c std::string or a \c std::unique_ptr, +/// but provides more flexible memory management. A \c temporary_buffer can own the memory +/// it points to, or it can be shared with another \c temporary_buffer, or point at a substring +/// of a buffer. It uses a \ref deleter to manage its memory. +/// +/// A \c temporary_buffer should not be held indefinitely. It can be held while a request +/// is processed, or for a similar duration, but not longer, as it can tie up more memory +/// that its size indicates. +/// +/// A buffer can be shared: two \c temporary_buffer objects will point to the same data, +/// or a subset of it. See the \ref temporary_buffer::share() method. +/// +/// Unless you created a \c temporary_buffer yourself, do not modify its contents, as they +/// may be shared with another user that does not expect the data to change. +/// +/// Use cases for a \c temporary_buffer include: +/// - passing a substring of a tcp packet for the user to consume (zero-copy +/// tcp input) +/// - passing a refcounted blob held in memory to tcp, ensuring that when the TCP ACK +/// is received, the blob is released (by decrementing its reference count) (zero-copy +/// tcp output) +/// +/// \tparam CharType underlying character type (must be a variant of \c char). template class temporary_buffer { static_assert(sizeof(CharType) == 1, "must buffer stream of bytes"); @@ -35,6 +61,10 @@ class temporary_buffer { size_t _size; deleter _deleter; public: + /// Creates a \c temporary_buffer of a specified size. The buffer is not shared + /// with anyone, and is not initialized. + /// + /// \param size buffer size, in bytes explicit temporary_buffer(size_t size) : _buffer(static_cast(malloc(size * sizeof(CharType)))), _size(size) , _deleter(make_free_deleter(_buffer)) { @@ -43,17 +73,26 @@ public: } } //explicit temporary_buffer(CharType* borrow, size_t size) : _buffer(borrow), _size(size) {} + /// Creates an empty \c temporary_buffer that does not point at anything. temporary_buffer() : _buffer(nullptr) , _size(0) {} temporary_buffer(const temporary_buffer&) = delete; + /// Moves a \c temporary_buffer. temporary_buffer(temporary_buffer&& x) : _buffer(x._buffer), _size(x._size), _deleter(std::move(x._deleter)) { x._buffer = nullptr; x._size = 0; } + /// Creates a \c temporary_buffer with a specific deleter. + /// + /// \param buf beginning of the buffer held by this \c temporary_buffer + /// \param size size of the buffer + /// \param d deleter controlling destruction of the buffer. The deleter + /// will be destroyed when there are no longer any users for the buffer. temporary_buffer(CharType* buf, size_t size, deleter d) : _buffer(buf), _size(size), _deleter(std::move(d)) {} void operator=(const temporary_buffer&) = delete; + /// Moves a \c temporary_buffer. temporary_buffer& operator=(temporary_buffer&& x) { if (this != &x) { _buffer = x._buffer; @@ -64,40 +103,88 @@ public: } return *this; } + /// Gets a pointer to the beginning of the buffer. const CharType* get() const { return _buffer; } + /// Gets a writable pointer to the beginning of the buffer. Use only + /// when you are certain no user expects the buffer data not to change. CharType* get_write() { return _buffer; } + /// Gets the buffer size. size_t size() const { return _size; } + /// Gets a pointer to the beginning of the buffer. const CharType* begin() { return _buffer; } + /// Gets a pointer to the end of the buffer. const CharType* end() { return _buffer + _size; } + /// Returns the buffer, but with a reduced size. The original + /// buffer is consumed by this call and can no longer be used. + /// + /// \param size New size; must be smaller than current size. + /// \return the same buffer, with a prefix removed. temporary_buffer prefix(size_t size) && { auto ret = std::move(*this); ret._size = size; return ret; } + /// Reads a character from a specific position in the buffer. + /// + /// \param pos position to read character from; must be less than size. CharType operator[](size_t pos) const { return _buffer[pos]; } + /// Checks whether the buffer is empty. bool empty() const { return !size(); } + /// Checks whether the buffer is not empty. operator bool() { return size(); } + /// Create a new \c temporary_buffer object referring to the same + /// underlying data. The underlying \ref deleter will not be destroyed + /// until both the original and the clone have been destroyed. + /// + /// \return a clone of the buffer object. temporary_buffer share() { return temporary_buffer(_buffer, _size, _deleter.share()); } + /// Create a new \c temporary_buffer object referring to a substring of the + /// same underlying data. The underlying \ref deleter will not be destroyed + /// until both the original and the clone have been destroyed. + /// + /// \param pos Position of the first character to share. + /// \param len Length of substring to share. + /// \return a clone of the buffer object, referring to a substring. temporary_buffer share(size_t pos, size_t len) { auto ret = share(); ret._buffer += pos; ret._size = len; return ret; } + /// Remove a prefix from the buffer. The underlying data + /// is not modified. + /// + /// \param pos Position of first character to retain. void trim_front(size_t pos) { _buffer += pos; _size -= pos; } + /// Remove a suffix from the buffer. The underlying data + /// is not modified. + /// + /// \param pos Position of first character to drop. void trim(size_t pos) { _size = pos; } + /// Stops automatic memory management. When the \c temporary_buffer + /// object is destroyed, the underlying \ref deleter will not be called. + /// Instead, it is the caller's responsibility to destroy the deleter object + /// when the data is no longer needed. + /// + /// \return \ref deleter object managing the data's lifetime. deleter release() { return std::move(_deleter); } + /// Creates a \c temporary_buffer object with a specified size, with + /// memory aligned to a specific boundary. + /// + /// \param alignment Required alignment; must be a power of two. + /// \param size Required size. + /// \return a new \c temporary_buffer object. static temporary_buffer aligned(size_t alignment, size_t size) { void *ptr = nullptr; auto ret = ::posix_memalign(&ptr, alignment, size * sizeof(CharType)); @@ -109,4 +196,6 @@ public: } }; +/// @} + #endif /* TEMPORARY_BUFFER_HH_ */