| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromecast/net/io_buffer_pool.h" |
| |
| #include <new> |
| |
| #include "base/bits.h" |
| #include "base/memory/aligned_memory.h" |
| #include "base/synchronization/lock.h" |
| |
| namespace chromecast { |
| |
| // The IOBufferPool allocates IOBuffers and the associated data as a single |
| // contiguous buffer. The buffer is laid out like this: |
| // |------------Wrapper----------|---data area---| |
| // |--IOBuffer--|--Internal ptr--|---data area---| |
| // |
| // The contiguous buffer is allocated as a character array, and then a Wrapper |
| // instance is placement-newed into it. We return a pointer to the IOBuffer |
| // within the Wrapper. |
| // |
| // When the IOBuffer is deleted (in operator delete), we get a pointer to the |
| // beginning of storage for the IOBuffer, which is the same memory location |
| // as the Wrapper instance (since the Wrapper has no vtable or base class, this |
| // should be true for any compiler). We can therefore cast the "deleted" |
| // pointer to a Wrapper* and then reclaim the buffer. |
| // |
| // All the actual data and logic for the buffer pool is held in the Internal |
| // class, which is refcounted with 1 ref for the IOBufferPool owner, and 1 ref |
| // for each buffer currently in use (ie, not in the free list). The Internal |
| // instance is only deleted when its internal refcount drops to 0; this allows |
| // buffers allocated from the pool to be safely used and deleted even after the |
| // pool has been destroyed. |
| // |
| // Note that locking in the Internal methods is optional since it is only needed |
| // when threadsafe operation is requested. |
| |
| class IOBufferPool::Internal { |
| public: |
| Internal(size_t data_area_size, size_t max_buffers, bool threadsafe); |
| |
| Internal(const Internal&) = delete; |
| Internal& operator=(const Internal&) = delete; |
| |
| size_t num_allocated() const { |
| base::AutoLockMaybe lock(lock_ptr_); |
| return num_allocated_; |
| } |
| |
| size_t num_free() const { |
| base::AutoLockMaybe lock(lock_ptr_); |
| return num_free_; |
| } |
| |
| void Preallocate(size_t num_buffers); |
| |
| void OwnerDestroyed(); |
| |
| scoped_refptr<net::IOBuffer> GetBuffer(); |
| |
| private: |
| class Buffer; |
| class Wrapper; |
| union Storage; |
| |
| static constexpr size_t kAlignment = 16; |
| |
| static Storage* AllocateStorageUnionAndDataArea(size_t data_area_size); |
| static char* DataAreaFromStorageUnion(Storage* ptr); |
| |
| ~Internal(); |
| |
| void Reclaim(Wrapper* wrapper); |
| |
| const size_t data_area_size_; |
| const size_t max_buffers_; |
| |
| mutable base::Lock lock_; |
| base::Lock* const lock_ptr_; |
| |
| Storage* free_buffers_; |
| size_t num_allocated_; |
| size_t num_free_; |
| |
| int refs_; |
| }; |
| |
| class IOBufferPool::Internal::Buffer : public net::IOBuffer { |
| public: |
| Buffer(char* data, size_t size) |
| : net::IOBuffer(base::make_span(data, size)) {} |
| |
| Buffer(const Buffer&) = delete; |
| Buffer& operator=(const Buffer&) = delete; |
| |
| private: |
| friend class Wrapper; |
| |
| ~Buffer() override = default; |
| static void operator delete(void* ptr); |
| }; |
| |
| class IOBufferPool::Internal::Wrapper { |
| public: |
| Wrapper(char* data, size_t size, IOBufferPool::Internal* pool) |
| : buffer_(data, size), pool_(pool) {} |
| |
| Wrapper(const Wrapper&) = delete; |
| Wrapper& operator=(const Wrapper&) = delete; |
| |
| ~Wrapper() = delete; |
| static void operator delete(void*) = delete; |
| |
| Buffer* buffer() { return &buffer_; } |
| |
| void Reclaim() { pool_->Reclaim(this); } |
| |
| private: |
| Buffer buffer_; |
| IOBufferPool::Internal* const pool_; |
| }; |
| |
| union IOBufferPool::Internal::Storage { |
| Storage* next; // Pointer to next free buffer. |
| Wrapper wrapper; |
| }; |
| |
| void IOBufferPool::Internal::Buffer::operator delete(void* ptr) { |
| Wrapper* wrapper = reinterpret_cast<Wrapper*>(ptr); |
| wrapper->Reclaim(); |
| } |
| |
| IOBufferPool::Internal::Internal(size_t data_area_size, |
| size_t max_buffers, |
| bool threadsafe) |
| : data_area_size_(data_area_size), |
| max_buffers_(max_buffers), |
| lock_ptr_(threadsafe ? &lock_ : nullptr), |
| free_buffers_(nullptr), |
| num_allocated_(0), |
| num_free_(0), |
| refs_(1) { // 1 ref for the owner. |
| } |
| |
| IOBufferPool::Internal::~Internal() { |
| while (free_buffers_) { |
| char* data = reinterpret_cast<char*>(free_buffers_); |
| free_buffers_ = free_buffers_->next; |
| base::AlignedFree(data); |
| } |
| } |
| |
| // Allocates aligned space for a `union Storage` plus an additional data |
| // area of `data_area_size` bytes with the same alignment. |
| // static |
| IOBufferPool::Internal::Storage* |
| IOBufferPool::Internal::AllocateStorageUnionAndDataArea(size_t data_area_size) { |
| size_t kAlignedStorageSize = base::bits::AlignUp(sizeof(Storage), kAlignment); |
| return reinterpret_cast<Storage*>( |
| base::AlignedAlloc(kAlignedStorageSize + data_area_size, kAlignment)); |
| } |
| |
| // Returns a pointer to the data area that follows a `union Storage`. |
| // static |
| char* IOBufferPool::Internal::DataAreaFromStorageUnion( |
| IOBufferPool::Internal::Storage* ptr) { |
| size_t kAlignedStorageSize = base::bits::AlignUp(sizeof(Storage), kAlignment); |
| return reinterpret_cast<char*>(ptr) + kAlignedStorageSize; |
| } |
| |
| void IOBufferPool::Internal::Preallocate(size_t num_buffers) { |
| // We assume that this is uncontended in normal usage, so just lock for the |
| // entire method. |
| base::AutoLockMaybe lock(lock_ptr_); |
| if (num_buffers > max_buffers_) { |
| num_buffers = max_buffers_; |
| } |
| if (num_allocated_ >= num_buffers) { |
| return; |
| } |
| size_t num_extra_buffers = num_buffers - num_allocated_; |
| num_free_ += num_extra_buffers; |
| num_allocated_ += num_extra_buffers; |
| while (num_extra_buffers > 0) { |
| Storage* storage = AllocateStorageUnionAndDataArea(data_area_size_); |
| storage->next = free_buffers_; |
| free_buffers_ = storage; |
| |
| --num_extra_buffers; |
| } |
| // No need to add refs here, since the newly allocated buffers are not in use. |
| } |
| |
| void IOBufferPool::Internal::OwnerDestroyed() { |
| bool deletable; |
| { |
| base::AutoLockMaybe lock(lock_ptr_); |
| --refs_; // Remove the owner's ref. |
| deletable = (refs_ == 0); |
| } |
| |
| if (deletable) { |
| delete this; |
| } |
| } |
| |
| scoped_refptr<net::IOBuffer> IOBufferPool::Internal::GetBuffer() { |
| Storage* ptr = nullptr; |
| |
| { |
| base::AutoLockMaybe lock(lock_ptr_); |
| if (free_buffers_) { |
| ptr = free_buffers_; |
| free_buffers_ = free_buffers_->next; |
| --num_free_; |
| } else { |
| if (num_allocated_ == max_buffers_) |
| return nullptr; |
| ++num_allocated_; |
| } |
| ++refs_; // Add a ref for the now in-use buffer. |
| } |
| |
| if (!ptr) { |
| ptr = AllocateStorageUnionAndDataArea(data_area_size_); |
| } |
| |
| char* data_area = DataAreaFromStorageUnion(ptr); |
| Wrapper* wrapper = |
| new (static_cast<void*>(ptr)) Wrapper(data_area, data_area_size_, this); |
| return scoped_refptr<net::IOBuffer>(wrapper->buffer()); |
| } |
| |
| void IOBufferPool::Internal::Reclaim(Wrapper* wrapper) { |
| Storage* storage = reinterpret_cast<Storage*>(wrapper); |
| bool deletable; |
| { |
| base::AutoLockMaybe lock(lock_ptr_); |
| storage->next = free_buffers_; |
| free_buffers_ = storage; |
| ++num_free_; |
| --refs_; // Remove a ref since this buffer is no longer in use. |
| deletable = (refs_ == 0); |
| } |
| |
| if (deletable) { |
| delete this; |
| } |
| } |
| |
| IOBufferPool::IOBufferPool(size_t buffer_size, |
| size_t max_buffers, |
| bool threadsafe) |
| : buffer_size_(buffer_size), |
| max_buffers_(max_buffers), |
| threadsafe_(threadsafe), |
| internal_(new Internal(buffer_size_, max_buffers_, threadsafe_)) {} |
| |
| IOBufferPool::IOBufferPool(size_t buffer_size) |
| : IOBufferPool(buffer_size, static_cast<size_t>(-1)) {} |
| |
| IOBufferPool::~IOBufferPool() { |
| internal_->OwnerDestroyed(); |
| } |
| |
| size_t IOBufferPool::NumAllocatedForTesting() const { |
| return internal_->num_allocated(); |
| } |
| |
| size_t IOBufferPool::NumFreeForTesting() const { |
| return internal_->num_free(); |
| } |
| |
| void IOBufferPool::Preallocate(size_t num_buffers) { |
| internal_->Preallocate(num_buffers); |
| } |
| |
| scoped_refptr<net::IOBuffer> IOBufferPool::GetBuffer() { |
| return internal_->GetBuffer(); |
| } |
| |
| } // namespace chromecast |