/* Copyright (c) 2008-2009, 2013-2021, Arvid Norberg Copyright (c) 2018, Alden Torres All rights reserved. You may use, distribute and modify this code under the terms of the BSD license, see LICENSE file. */ #ifndef TORRENT_DISK_BUFFER_HOLDER_HPP_INCLUDED #define TORRENT_DISK_BUFFER_HOLDER_HPP_INCLUDED #include "libtorrent/config.hpp" #include "libtorrent/assert.hpp" #include "libtorrent/span.hpp" #include #include #include #ifndef TORRENT_DEBUG_BUFFER_POOL #define TORRENT_DEBUG_BUFFER_POOL 0 #endif namespace libtorrent { // the interface for freeing disk buffers, used by the disk_buffer_holder. // when implementing disk_interface, this must also be implemented in order // to return disk buffers back to libtorrent struct TORRENT_EXPORT buffer_allocator_interface { // returns one or more disk buffers (previously handed out by // the disk subsystem) back to the allocator. The bulk // overload, ``free_multiple_buffers()``, lets callers free a // batch of buffers under a single mutex acquisition. virtual void free_disk_buffer(char* b) = 0; virtual void free_multiple_buffers(span bufs) = 0; #if TORRENT_DEBUG_BUFFER_POOL virtual void rename_buffer(char* buf, char const* category) = 0; #endif protected: ~buffer_allocator_interface() = default; }; struct bulk_free_buffer; struct disk_buffer_ref; namespace aux { struct disk_cache; } // The disk buffer holder acts like a ``unique_ptr`` that frees a disk buffer // when it's destructed // // If this buffer holder is moved-from, default constructed or reset, // ``data()`` will return nullptr. struct TORRENT_EXPORT disk_buffer_holder { disk_buffer_holder& operator=(disk_buffer_holder&&) & noexcept; disk_buffer_holder(disk_buffer_holder&&) noexcept; disk_buffer_holder& operator=(disk_buffer_holder const&) = delete; disk_buffer_holder(disk_buffer_holder const&) = delete; // construct a buffer holder that will free the held buffer // using a disk buffer pool directly (there's only one // disk_buffer_pool per session) disk_buffer_holder(buffer_allocator_interface& alloc, char* buf) noexcept; // default construct a holder that does not own any buffer disk_buffer_holder() noexcept = default; // frees disk buffer held by this object ~disk_buffer_holder(); // return a pointer to the held buffer, if any. Otherwise returns nullptr. char* data() const noexcept { return m_buf; } // free the held disk buffer, if any, and clear the holder. This sets the // holder object to a default-constructed state void reset(); // swap pointers of two disk buffer holders. void swap(disk_buffer_holder& h) noexcept { using std::swap; swap(h.m_allocator, m_allocator); swap(h.m_buf, m_buf); } // if this returns true, the buffer may not be modified in place bool is_mutable() const noexcept { return false; } // implicitly convertible to true if the object is currently holding a // buffer explicit operator bool() const noexcept { return m_buf != nullptr; } #if TORRENT_DEBUG_BUFFER_POOL void rename(char const* category); #endif private: friend struct bulk_free_buffer; friend struct disk_buffer_ref; friend struct aux::disk_cache; buffer_allocator_interface* m_allocator = nullptr; char* m_buf = nullptr; }; // Pointer-only disk buffer reference. Holds a char* but neither the allocator // nor the size. Has unique_ptr-like move semantics. The destructor asserts that // the pointer is null. The buffer must be transferred to a bulk_free_buffer // before this goes out of scope. Since it has no allocator, it cannot free the // buffer itself. struct TORRENT_EXPORT disk_buffer_ref { disk_buffer_ref() noexcept = default; // take ownership of the buffer held by ``h``, leaving ``h`` // empty. The buffer must subsequently be transferred to a // ``bulk_free_buffer`` for release; this type cannot free the // buffer itself. explicit disk_buffer_ref(disk_buffer_holder&& h) noexcept; disk_buffer_ref(disk_buffer_ref&&) noexcept; disk_buffer_ref& operator=(disk_buffer_ref&&) noexcept; disk_buffer_ref(disk_buffer_ref const&) = delete; disk_buffer_ref& operator=(disk_buffer_ref const&) = delete; ~disk_buffer_ref() { TORRENT_ASSERT(m_buf == nullptr); if (m_buf != nullptr) std::abort(); } // returns a pointer to the held buffer, or nullptr if this // reference is empty. ``operator bool()`` returns true iff a // buffer is held. char* data() const noexcept { return m_buf; } explicit operator bool() const noexcept { return m_buf != nullptr; } private: friend struct bulk_free_buffer; char* m_buf = nullptr; }; // Accumulates disk buffers to be freed in a single batch call, reducing // allocator mutex acquisitions from one per buffer to one per batch. // All buffers added must share the same allocator. // Freeing happens in the destructor. struct TORRENT_EXPORT bulk_free_buffer { // construct an empty batch tied to the given allocator. All // buffers added via ``add()`` must have come from this same // allocator. The batch is freed in the destructor. explicit bulk_free_buffer(buffer_allocator_interface& alloc) : m_allocator(&alloc) {} // hidden bulk_free_buffer(bulk_free_buffer const&) = delete; bulk_free_buffer& operator=(bulk_free_buffer const&) = delete; // transfer ownership of h's buffer into this batch. void add(disk_buffer_ref r); ~bulk_free_buffer(); private: buffer_allocator_interface* m_allocator = nullptr; std::vector m_bufs; }; } #endif