| /** @file | |
| A brief file description | |
| @section license License | |
| Licensed to the Apache Software Foundation (ASF) under one | |
| or more contributor license agreements. See the NOTICE file | |
| distributed with this work for additional information | |
| regarding copyright ownership. The ASF licenses this file | |
| to you 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. | |
| */ | |
| /************************************************************************** | |
| UIOBuffer.cc | |
| **************************************************************************/ | |
| #include "iocore/eventsystem/Thread.h" | |
| #include "tscore/Allocator.h" | |
| #include "iocore/eventsystem/IOBuffer.h" | |
| #include "swoc/Lexicon.h" | |
| #include "tscore/Diags.h" | |
| #include "tscore/ink_memory.h" | |
| #include <optional> | |
| // TODO: I think we're overly aggressive here on making MIOBuffer 64-bit | |
| // but not sure it's worthwhile changing anything to 32-bit honestly. | |
| IOBufferBlock * | |
| iobufferblock_clone(IOBufferBlock *src, int64_t offset, int64_t len) | |
| { | |
| IOBufferBlock *start_buf = nullptr; | |
| IOBufferBlock *current_buf = nullptr; | |
| while (src && len >= 0) { | |
| char *start = src->_start; | |
| char *end = src->_end; | |
| int64_t max_bytes = end - start; | |
| max_bytes -= offset; | |
| if (max_bytes <= 0) { | |
| offset = -max_bytes; | |
| src = src->next.get(); | |
| continue; | |
| } | |
| int64_t bytes = len; | |
| if (bytes >= max_bytes) { | |
| bytes = max_bytes; | |
| } | |
| IOBufferBlock *new_buf = src->clone(); | |
| new_buf->_start += offset; | |
| new_buf->_buf_end = new_buf->_end = new_buf->_start + bytes; | |
| if (!start_buf) { | |
| start_buf = new_buf; | |
| current_buf = start_buf; | |
| } else { | |
| current_buf->next = new_buf; | |
| current_buf = new_buf; | |
| } | |
| len -= bytes; | |
| src = src->next.get(); | |
| offset = 0; | |
| } | |
| return start_buf; | |
| } | |
| IOBufferBlock * | |
| iobufferblock_skip(IOBufferBlock *b, int64_t *poffset, int64_t *plen, int64_t write) | |
| { | |
| int64_t offset = *poffset; | |
| int64_t len = write; | |
| while (b && len >= 0) { | |
| int64_t max_bytes = b->read_avail(); | |
| // If this block ends before the start offset, skip it | |
| // and adjust the offset to consume its length. | |
| max_bytes -= offset; | |
| if (max_bytes <= 0) { | |
| offset = -max_bytes; | |
| b = b->next.get(); | |
| continue; | |
| } | |
| if (len >= max_bytes) { | |
| b = b->next.get(); | |
| len -= max_bytes; | |
| offset = 0; | |
| } else { | |
| offset = offset + len; | |
| break; | |
| } | |
| } | |
| *poffset = offset; | |
| *plen -= write; | |
| return b; | |
| } | |
| void | |
| iobuffer_mem_inc(const char *_loc, int64_t _size_index) | |
| { | |
| if (!res_track_memory) { | |
| return; | |
| } | |
| if (!BUFFER_SIZE_INDEX_IS_FAST_ALLOCATED(_size_index)) { | |
| return; | |
| } | |
| if (!_loc) { | |
| _loc = "memory/IOBuffer/UNKNOWN-LOCATION"; | |
| } | |
| ResourceTracker::increment(_loc, index_to_buffer_size(_size_index)); | |
| } | |
| void | |
| iobuffer_mem_dec(const char *_loc, int64_t _size_index) | |
| { | |
| if (!res_track_memory) { | |
| return; | |
| } | |
| if (!BUFFER_SIZE_INDEX_IS_FAST_ALLOCATED(_size_index)) { | |
| return; | |
| } | |
| if (!_loc) { | |
| _loc = "memory/IOBuffer/UNKNOWN-LOCATION"; | |
| } | |
| ResourceTracker::increment(_loc, -index_to_buffer_size(_size_index)); | |
| } | |
| ////////////////////////////////////////////////////////////////// | |
| // | |
| // inline functions definitions | |
| // | |
| ////////////////////////////////////////////////////////////////// | |
| ////////////////////////////////////////////////////////////////// | |
| // | |
| // class IOBufferData -- | |
| // inline functions definitions | |
| // | |
| ////////////////////////////////////////////////////////////////// | |
| int64_t | |
| IOBufferData::block_size() | |
| { | |
| return index_to_buffer_size(_size_index); | |
| } | |
| IOBufferData * | |
| new_IOBufferData_internal(const char *location, void *b, int64_t size, int64_t asize_index) | |
| { | |
| (void)size; | |
| IOBufferData *d = THREAD_ALLOC(ioDataAllocator, this_thread()); | |
| d->_size_index = asize_index; | |
| ink_assert(BUFFER_SIZE_INDEX_IS_CONSTANT(asize_index) || size <= d->block_size()); | |
| d->_location = location; | |
| d->_data = static_cast<char *>(b); | |
| return d; | |
| } | |
| IOBufferData * | |
| new_xmalloc_IOBufferData_internal(const char *location, void *b, int64_t size) | |
| { | |
| return new_IOBufferData_internal(location, b, size, BUFFER_SIZE_INDEX_FOR_XMALLOC_SIZE(size)); | |
| } | |
| IOBufferData * | |
| new_IOBufferData_internal(const char *loc, int64_t size_index, AllocType type) | |
| { | |
| IOBufferData *d = THREAD_ALLOC(ioDataAllocator, this_thread()); | |
| d->_location = loc; | |
| d->alloc(size_index, type); | |
| return d; | |
| } | |
| // IRIX has a compiler bug which prevents this function | |
| // from being compiled correctly at -O3 | |
| // so it is DUPLICATED in IOBuffer.cc | |
| // ****** IF YOU CHANGE THIS FUNCTION change that one as well. | |
| void | |
| IOBufferData::alloc(int64_t size_index, AllocType type) | |
| { | |
| if (_data) { | |
| dealloc(); | |
| } | |
| _size_index = size_index; | |
| _mem_type = type; | |
| iobuffer_mem_inc(_location, size_index); | |
| switch (type) { | |
| case MEMALIGNED: | |
| if (BUFFER_SIZE_INDEX_IS_FAST_ALLOCATED(size_index)) { | |
| _data = static_cast<char *>(ioBufAllocator[size_index].alloc_void()); | |
| // coverity[dead_error_condition] | |
| } else if (BUFFER_SIZE_INDEX_IS_XMALLOCED(size_index)) { | |
| _data = static_cast<char *>(ats_memalign(ats_pagesize(), index_to_buffer_size(size_index))); | |
| } | |
| break; | |
| default: | |
| case DEFAULT_ALLOC: | |
| if (BUFFER_SIZE_INDEX_IS_FAST_ALLOCATED(size_index)) { | |
| _data = static_cast<char *>(ioBufAllocator[size_index].alloc_void()); | |
| } else if (BUFFER_SIZE_INDEX_IS_XMALLOCED(size_index)) { | |
| _data = static_cast<char *>(ats_malloc(BUFFER_SIZE_FOR_XMALLOC(size_index))); | |
| } | |
| break; | |
| } | |
| } | |
| // ****** IF YOU CHANGE THIS FUNCTION change that one as well. | |
| void | |
| IOBufferData::dealloc() | |
| { | |
| iobuffer_mem_dec(_location, _size_index); | |
| switch (_mem_type) { | |
| case MEMALIGNED: | |
| if (BUFFER_SIZE_INDEX_IS_FAST_ALLOCATED(_size_index)) { | |
| ioBufAllocator[_size_index].free_void(_data); | |
| } else if (BUFFER_SIZE_INDEX_IS_XMALLOCED(_size_index)) { | |
| ::free(_data); | |
| } | |
| break; | |
| default: | |
| case DEFAULT_ALLOC: | |
| if (BUFFER_SIZE_INDEX_IS_FAST_ALLOCATED(_size_index)) { | |
| ioBufAllocator[_size_index].free_void(_data); | |
| } else if (BUFFER_SIZE_INDEX_IS_XMALLOCED(_size_index)) { | |
| ats_free(_data); | |
| } | |
| break; | |
| } | |
| _data = nullptr; | |
| _size_index = BUFFER_SIZE_NOT_ALLOCATED; | |
| _mem_type = NO_ALLOC; | |
| } | |
| void | |
| IOBufferData::free() | |
| { | |
| dealloc(); | |
| THREAD_FREE(this, ioDataAllocator, this_thread()); | |
| } | |
| ////////////////////////////////////////////////////////////////// | |
| // | |
| // class IOBufferBlock -- | |
| // inline functions definitions | |
| // | |
| ////////////////////////////////////////////////////////////////// | |
| IOBufferBlock * | |
| new_IOBufferBlock_internal(const char *location) | |
| { | |
| IOBufferBlock *b = THREAD_ALLOC(ioBlockAllocator, this_thread()); | |
| b->_location = location; | |
| return b; | |
| } | |
| IOBufferBlock * | |
| new_IOBufferBlock_internal(const char *location, IOBufferData *d, int64_t len, int64_t offset) | |
| { | |
| IOBufferBlock *b = THREAD_ALLOC(ioBlockAllocator, this_thread()); | |
| b->_location = location; | |
| b->set(d, len, offset); | |
| return b; | |
| } | |
| IOBufferBlock::IOBufferBlock() | |
| { | |
| return; | |
| } | |
| void | |
| IOBufferBlock::consume(int64_t len) | |
| { | |
| _start += len; | |
| ink_assert(_start <= _end); | |
| } | |
| void | |
| IOBufferBlock::fill(int64_t len) | |
| { | |
| _end += len; | |
| ink_assert(_end <= _buf_end); | |
| } | |
| void | |
| IOBufferBlock::reset() | |
| { | |
| _end = _start = buf(); | |
| _buf_end = buf() + data->block_size(); | |
| } | |
| void | |
| IOBufferBlock::alloc(int64_t i) | |
| { | |
| ink_assert(BUFFER_SIZE_ALLOCATED(i)); | |
| data = new_IOBufferData_internal(_location, i); | |
| reset(); | |
| } | |
| void | |
| IOBufferBlock::clear() | |
| { | |
| data = nullptr; | |
| IOBufferBlock *p = next.get(); | |
| while (p) { | |
| // If our block pointer refcount dropped to zero, | |
| // recursively free the list. | |
| if (p->refcount_dec() == 0) { | |
| IOBufferBlock *n = p->next.detach(); | |
| p->free(); | |
| p = n; | |
| } else { | |
| // We don't hold the last refcount, so we are done. | |
| break; | |
| } | |
| } | |
| // Nuke the next pointer without dropping the refcount | |
| // because we already manually did that. | |
| next.detach(); | |
| _buf_end = _end = _start = nullptr; | |
| } | |
| IOBufferBlock * | |
| IOBufferBlock::clone() const | |
| { | |
| IOBufferBlock *b = new_IOBufferBlock_internal(_location); | |
| b->data = data; | |
| b->_start = _start; | |
| b->_end = _end; | |
| b->_buf_end = _end; | |
| b->_location = _location; | |
| return b; | |
| } | |
| void | |
| IOBufferBlock::dealloc() | |
| { | |
| clear(); | |
| } | |
| void | |
| IOBufferBlock::free() | |
| { | |
| dealloc(); | |
| THREAD_FREE(this, ioBlockAllocator, this_thread()); | |
| } | |
| void | |
| IOBufferBlock::set_internal(void *b, int64_t len, int64_t asize_index) | |
| { | |
| data = new_IOBufferData_internal(_location, BUFFER_SIZE_NOT_ALLOCATED); | |
| data->_data = static_cast<char *>(b); | |
| iobuffer_mem_inc(_location, asize_index); | |
| data->_size_index = asize_index; | |
| reset(); | |
| _end = _start + len; | |
| } | |
| void | |
| IOBufferBlock::set(IOBufferData *d, int64_t len, int64_t offset) | |
| { | |
| data = d; | |
| const int64_t block_sz = d->block_size(); | |
| const int64_t safe_offset = std::clamp<int64_t>(offset, 0, block_sz); | |
| const int64_t safe_len = std::clamp<int64_t>(len, 0, block_sz - safe_offset); | |
| _start = buf() + safe_offset; | |
| _buf_end = buf() + block_sz; | |
| _end = _start + safe_len; | |
| } | |
| ////////////////////////////////////////////////////////////////// | |
| // | |
| // class IOBufferReader -- | |
| // inline functions definitions | |
| // | |
| ////////////////////////////////////////////////////////////////// | |
| void | |
| IOBufferReader::skip_empty_blocks() | |
| { | |
| while (block->next && block->next->read_avail() && start_offset >= block->size()) { | |
| start_offset -= block->size(); | |
| block = block->next; | |
| } | |
| } | |
| bool | |
| IOBufferReader::low_water() | |
| { | |
| return mbuf->low_water(); | |
| } | |
| bool | |
| IOBufferReader::high_water() | |
| { | |
| return read_avail() >= mbuf->water_mark; | |
| } | |
| bool | |
| IOBufferReader::current_low_water() | |
| { | |
| return mbuf->current_low_water(); | |
| } | |
| IOBufferBlock * | |
| IOBufferReader::get_current_block() | |
| { | |
| return block.get(); | |
| } | |
| char * | |
| IOBufferReader::start() | |
| { | |
| if (!block) { | |
| return nullptr; | |
| } | |
| skip_empty_blocks(); | |
| return block->start() + start_offset; | |
| } | |
| char * | |
| IOBufferReader::end() | |
| { | |
| if (!block) { | |
| return nullptr; | |
| } | |
| skip_empty_blocks(); | |
| return block->end(); | |
| } | |
| int64_t | |
| IOBufferReader::block_read_avail() | |
| { | |
| if (!block) { | |
| return 0; | |
| } | |
| skip_empty_blocks(); | |
| return static_cast<int64_t>(block->end() - (block->start() + start_offset)); | |
| } | |
| std::string_view | |
| IOBufferReader::block_read_view() | |
| { | |
| const char *start = this->start(); // empty blocks are skipped in here. | |
| return start ? std::string_view{start, static_cast<size_t>(block->end() - start)} : std::string_view{}; | |
| } | |
| int | |
| IOBufferReader::block_count() | |
| { | |
| int count = 0; | |
| IOBufferBlock *b = block.get(); | |
| while (b) { | |
| count++; | |
| b = b->next.get(); | |
| } | |
| return count; | |
| } | |
| int64_t | |
| IOBufferReader::read_avail() | |
| { | |
| int64_t t = 0; | |
| IOBufferBlock *b = block.get(); | |
| while (b) { | |
| t += b->read_avail(); | |
| b = b->next.get(); | |
| } | |
| t -= start_offset; | |
| if (size_limit != INT64_MAX && t > size_limit) { | |
| t = size_limit; | |
| } | |
| return t; | |
| } | |
| bool | |
| IOBufferReader::is_read_avail_more_than(int64_t size) | |
| { | |
| int64_t t = -start_offset; | |
| IOBufferBlock *b = block.get(); | |
| while (b) { | |
| t += b->read_avail(); | |
| if (t > size) { | |
| return true; | |
| } | |
| b = b->next.get(); | |
| } | |
| return false; | |
| } | |
| void | |
| IOBufferReader::consume(int64_t n) | |
| { | |
| ink_release_assert(n == 0 || is_read_avail_more_than(n - 1)); | |
| start_offset += n; | |
| if (size_limit != INT64_MAX) { | |
| size_limit -= n; | |
| } | |
| ink_assert(size_limit >= 0); | |
| if (!block) { | |
| return; | |
| } | |
| int64_t r = block->read_avail(); | |
| int64_t s = start_offset; | |
| while (r <= s && block->next && block->next->read_avail()) { | |
| s -= r; | |
| start_offset = s; | |
| block = block->next; | |
| r = block->read_avail(); | |
| } | |
| } | |
| char & | |
| IOBufferReader::operator[](int64_t i) | |
| { | |
| static char default_ret = '\0'; // This is just to avoid compiler warnings... | |
| IOBufferBlock *b = block.get(); | |
| i += start_offset; | |
| while (b) { | |
| int64_t bytes = b->read_avail(); | |
| if (bytes > i) { | |
| return b->start()[i]; | |
| } | |
| i -= bytes; | |
| b = b->next.get(); | |
| } | |
| ink_release_assert(!"out of range"); | |
| // Never used, just to satisfy compilers not understanding the fatality of ink_release_assert(). | |
| return default_ret; | |
| } | |
| void | |
| IOBufferReader::clear() | |
| { | |
| accessor = nullptr; | |
| block = nullptr; | |
| mbuf = nullptr; | |
| start_offset = 0; | |
| size_limit = INT64_MAX; | |
| } | |
| void | |
| IOBufferReader::reset() | |
| { | |
| block = mbuf->_writer; | |
| start_offset = 0; | |
| size_limit = INT64_MAX; | |
| } | |
| //////////////////////////////////////////////////////////////// | |
| // | |
| // class MIOBuffer -- | |
| // inline functions definitions | |
| // | |
| //////////////////////////////////////////////////////////////// | |
| extern ClassAllocator<MIOBuffer, false> ioAllocator; | |
| MIOBuffer::MIOBuffer(int64_t default_size_index) | |
| { | |
| clear(); | |
| size_index = default_size_index; | |
| _location = nullptr; | |
| return; | |
| } | |
| MIOBuffer::MIOBuffer() | |
| { | |
| clear(); | |
| _location = nullptr; | |
| return; | |
| } | |
| MIOBuffer::~MIOBuffer() | |
| { | |
| _writer = nullptr; | |
| dealloc_all_readers(); | |
| } | |
| MIOBuffer * | |
| new_MIOBuffer_internal(const char *location, int64_t size_index) | |
| { | |
| MIOBuffer *b = THREAD_ALLOC(ioAllocator, this_thread()); | |
| b->_location = location; | |
| b->alloc(size_index); | |
| b->water_mark = 0; | |
| return b; | |
| } | |
| void | |
| free_MIOBuffer(MIOBuffer *mio) | |
| { | |
| mio->_writer = nullptr; | |
| mio->dealloc_all_readers(); | |
| THREAD_FREE(mio, ioAllocator, this_thread()); | |
| } | |
| MIOBuffer * | |
| new_empty_MIOBuffer_internal(const char *location, int64_t size_index) | |
| { | |
| MIOBuffer *b = THREAD_ALLOC(ioAllocator, this_thread()); | |
| b->size_index = size_index; | |
| b->water_mark = 0; | |
| b->_location = location; | |
| return b; | |
| } | |
| void | |
| free_empty_MIOBuffer(MIOBuffer *mio) | |
| { | |
| THREAD_FREE(mio, ioAllocator, this_thread()); | |
| } | |
| IOBufferReader * | |
| MIOBuffer::alloc_accessor(MIOBufferAccessor *anAccessor) | |
| { | |
| int i; | |
| for (i = 0; i < MAX_MIOBUFFER_READERS; i++) { | |
| if (!readers[i].allocated()) { | |
| break; | |
| } | |
| } | |
| // TODO refactor code to return nullptr at some point | |
| ink_release_assert(i < MAX_MIOBUFFER_READERS); | |
| IOBufferReader *e = &readers[i]; | |
| e->mbuf = this; | |
| e->reset(); | |
| e->accessor = anAccessor; | |
| return e; | |
| } | |
| IOBufferReader * | |
| MIOBuffer::alloc_reader() | |
| { | |
| int i; | |
| for (i = 0; i < MAX_MIOBUFFER_READERS; i++) { | |
| if (!readers[i].allocated()) { | |
| break; | |
| } | |
| } | |
| // TODO refactor code to return nullptr at some point | |
| ink_release_assert(i < MAX_MIOBUFFER_READERS); | |
| IOBufferReader *e = &readers[i]; | |
| e->mbuf = this; | |
| e->reset(); | |
| e->accessor = nullptr; | |
| return e; | |
| } | |
| int64_t | |
| MIOBuffer::block_size() | |
| { | |
| return index_to_buffer_size(size_index); | |
| } | |
| IOBufferReader * | |
| MIOBuffer::clone_reader(IOBufferReader *r) | |
| { | |
| int i; | |
| for (i = 0; i < MAX_MIOBUFFER_READERS; i++) { | |
| if (!readers[i].allocated()) { | |
| break; | |
| } | |
| } | |
| // TODO refactor code to return nullptr at some point | |
| ink_release_assert(i < MAX_MIOBUFFER_READERS); | |
| IOBufferReader *e = &readers[i]; | |
| e->mbuf = this; | |
| e->accessor = nullptr; | |
| e->block = r->block; | |
| e->start_offset = r->start_offset; | |
| e->size_limit = r->size_limit; | |
| ink_assert(e->size_limit >= 0); | |
| return e; | |
| } | |
| int64_t | |
| MIOBuffer::block_write_avail() | |
| { | |
| IOBufferBlock *b = first_write_block(); | |
| return b ? b->write_avail() : 0; | |
| } | |
| //////////////////////////////////////////////////////////////// | |
| // | |
| // MIOBuffer::append_block() | |
| // | |
| // Appends a block to writer->next and make it the current | |
| // block. | |
| // Note that the block is not appended to the end of the list. | |
| // That means that if writer->next was not null before this | |
| // call then the block that writer->next was pointing to will | |
| // have its reference count decremented and writer->next | |
| // will have a new value which is the new block. | |
| // In any case the new appended block becomes the current | |
| // block. | |
| // | |
| //////////////////////////////////////////////////////////////// | |
| void | |
| MIOBuffer::append_block_internal(IOBufferBlock *b) | |
| { | |
| // It would be nice to remove an empty buffer at the beginning, | |
| // but this breaks HTTP. | |
| if (!_writer) { | |
| _writer = b; | |
| init_readers(); | |
| } else { | |
| ink_assert(!_writer->next || !_writer->next->read_avail()); | |
| _writer->next = b; | |
| while (b->read_avail()) { | |
| _writer = b; | |
| b = b->next.get(); | |
| if (!b) { | |
| break; | |
| } | |
| } | |
| } | |
| while (_writer->next && !_writer->write_avail() && _writer->next->read_avail()) { | |
| _writer = _writer->next; | |
| } | |
| } | |
| void | |
| MIOBuffer::append_block(IOBufferBlock *b) | |
| { | |
| ink_assert(b->read_avail()); | |
| append_block_internal(b); | |
| } | |
| //////////////////////////////////////////////////////////////// | |
| // | |
| // MIOBuffer::append_block() | |
| // | |
| // Allocate a block, appends it to current->next | |
| // and make the new block the current block (writer). | |
| // | |
| //////////////////////////////////////////////////////////////// | |
| void | |
| MIOBuffer::append_block(int64_t asize_index) | |
| { | |
| ink_assert(BUFFER_SIZE_ALLOCATED(asize_index)); | |
| IOBufferBlock *b = new_IOBufferBlock_internal(_location); | |
| b->alloc(asize_index); | |
| append_block_internal(b); | |
| return; | |
| } | |
| void | |
| MIOBuffer::add_block() | |
| { | |
| if (this->_writer == nullptr || this->_writer->next == nullptr) { | |
| append_block(size_index); | |
| } | |
| } | |
| void | |
| MIOBuffer::check_add_block() | |
| { | |
| if (!high_water() && current_low_water()) { | |
| add_block(); | |
| } | |
| } | |
| IOBufferBlock * | |
| MIOBuffer::get_current_block() | |
| { | |
| return first_write_block(); | |
| } | |
| ////////////////////////////////////////////////////////////////// | |
| // | |
| // MIOBuffer::current_write_avail() | |
| // | |
| // returns the total space available in all blocks. | |
| // This function is different than write_avail() because | |
| // it will not append a new block if there is no space | |
| // or below the watermark space available. | |
| // | |
| ////////////////////////////////////////////////////////////////// | |
| int64_t | |
| MIOBuffer::current_write_avail() | |
| { | |
| int64_t t = 0; | |
| IOBufferBlock *b = _writer.get(); | |
| while (b) { | |
| t += b->write_avail(); | |
| b = b->next.get(); | |
| } | |
| return t; | |
| } | |
| ////////////////////////////////////////////////////////////////// | |
| // | |
| // MIOBuffer::write_avail() | |
| // | |
| // returns the number of bytes available in the current block. | |
| // If there is no current block or not enough free space in | |
| // the current block then a new block is appended. | |
| // | |
| ////////////////////////////////////////////////////////////////// | |
| int64_t | |
| MIOBuffer::write_avail() | |
| { | |
| check_add_block(); | |
| return current_write_avail(); | |
| } | |
| void | |
| MIOBuffer::fill(int64_t len) | |
| { | |
| int64_t f = _writer->write_avail(); | |
| while (f < len) { | |
| _writer->fill(f); | |
| len -= f; | |
| if (len > 0) { | |
| _writer = _writer->next; | |
| ink_release_assert(_writer); | |
| } | |
| f = _writer->write_avail(); | |
| } | |
| _writer->fill(len); | |
| } | |
| int | |
| MIOBuffer::max_block_count() | |
| { | |
| int maxb = 0; | |
| for (auto &reader : readers) { | |
| if (reader.allocated()) { | |
| int c = reader.block_count(); | |
| if (c > maxb) { | |
| maxb = c; | |
| } | |
| } | |
| } | |
| return maxb; | |
| } | |
| int64_t | |
| MIOBuffer::max_read_avail() | |
| { | |
| int64_t s = 0; | |
| int found = 0; | |
| for (auto &reader : readers) { | |
| if (reader.allocated()) { | |
| int64_t ss = reader.read_avail(); | |
| if (ss > s) { | |
| s = ss; | |
| } | |
| found = 1; | |
| } | |
| } | |
| if (!found && _writer) { | |
| return _writer->read_avail(); | |
| } | |
| return s; | |
| } | |
| void | |
| MIOBuffer::set(void *b, int64_t len) | |
| { | |
| _writer = new_IOBufferBlock_internal(_location); | |
| _writer->set_internal(b, len, BUFFER_SIZE_INDEX_FOR_CONSTANT_SIZE(len)); | |
| init_readers(); | |
| } | |
| void | |
| MIOBuffer::append_xmalloced(void *b, int64_t len) | |
| { | |
| if (len == 0) { | |
| return; | |
| } | |
| IOBufferBlock *x = new_IOBufferBlock_internal(_location); | |
| x->set_internal(b, len, BUFFER_SIZE_INDEX_FOR_XMALLOC_SIZE(len)); | |
| append_block_internal(x); | |
| } | |
| void | |
| MIOBuffer::append_fast_allocated(void *b, int64_t len, int64_t fast_size_index) | |
| { | |
| IOBufferBlock *x = new_IOBufferBlock_internal(_location); | |
| x->set_internal(b, len, fast_size_index); | |
| append_block_internal(x); | |
| } | |
| void | |
| MIOBuffer::alloc(int64_t i) | |
| { | |
| _writer = new_IOBufferBlock_internal(_location); | |
| _writer->alloc(i); | |
| size_index = i; | |
| init_readers(); | |
| } | |
| void | |
| MIOBuffer::dealloc_reader(IOBufferReader *e) | |
| { | |
| if (e->accessor) { | |
| ink_assert(e->accessor->writer() == this); | |
| ink_assert(e->accessor->reader() == e); | |
| e->accessor->clear(); | |
| } | |
| e->clear(); | |
| } | |
| IOBufferReader * | |
| IOBufferReader::clone() | |
| { | |
| return mbuf->clone_reader(this); | |
| } | |
| void | |
| IOBufferReader::dealloc() | |
| { | |
| mbuf->dealloc_reader(this); | |
| } | |
| void | |
| MIOBuffer::dealloc_all_readers() | |
| { | |
| for (auto &reader : readers) { | |
| if (reader.allocated()) { | |
| dealloc_reader(&reader); | |
| } | |
| } | |
| } | |
| void | |
| MIOBufferAccessor::reader_for(MIOBuffer *abuf) | |
| { | |
| mbuf = abuf; | |
| if (abuf) { | |
| entry = mbuf->alloc_accessor(this); | |
| } else { | |
| entry = nullptr; | |
| } | |
| } | |
| void | |
| MIOBufferAccessor::reader_for(IOBufferReader *areader) | |
| { | |
| if (entry == areader) { | |
| return; | |
| } | |
| mbuf = areader->mbuf; | |
| entry = areader; | |
| ink_assert(mbuf); | |
| } | |
| void | |
| MIOBufferAccessor::writer_for(MIOBuffer *abuf) | |
| { | |
| mbuf = abuf; | |
| entry = nullptr; | |
| } | |
| MIOBufferAccessor::~MIOBufferAccessor() {} | |
| // | |
| // General Buffer Allocator | |
| // | |
| #if TS_USE_ALLOCATOR_METRICS | |
| MeteredAllocator<FreelistAllocator> ioBufAllocator[DEFAULT_BUFFER_SIZES]; | |
| #else | |
| FreelistAllocator ioBufAllocator[DEFAULT_BUFFER_SIZES]; | |
| #endif | |
| ClassAllocator<MIOBuffer, false> ioAllocator("ioAllocator", DEFAULT_BUFFER_NUMBER); | |
| ClassAllocator<IOBufferData, false> ioDataAllocator("ioDataAllocator", DEFAULT_BUFFER_NUMBER); | |
| ClassAllocator<IOBufferBlock, false> ioBlockAllocator("ioBlockAllocator", DEFAULT_BUFFER_NUMBER); | |
| int64_t default_large_iobuffer_size = DEFAULT_LARGE_BUFFER_SIZE; | |
| int64_t default_small_iobuffer_size = DEFAULT_SMALL_BUFFER_SIZE; | |
| int64_t max_iobuffer_size = DEFAULT_BUFFER_SIZES - 1; | |
| // | |
| // Initialization | |
| // | |
| void | |
| init_buffer_allocators(int iobuffer_advice, int chunk_sizes[DEFAULT_BUFFER_SIZES], bool use_hugepages) | |
| { | |
| for (int i = 0; i < DEFAULT_BUFFER_SIZES; i++) { | |
| int64_t s = DEFAULT_BUFFER_BASE_SIZE * ((static_cast<int64_t>(1)) << i); | |
| int64_t a = DEFAULT_BUFFER_ALIGNMENT; | |
| int n = chunk_sizes[i]; | |
| if (n == 0) { | |
| n = i <= default_large_iobuffer_size ? DEFAULT_BUFFER_NUMBER : DEFAULT_HUGE_BUFFER_NUMBER; | |
| } | |
| if (s < a) { | |
| a = s; | |
| } | |
| auto name = new char[64]; | |
| if (use_hugepages) { | |
| snprintf(name, 64, "ioBufAllocatorHP[%d]", i); | |
| } else { | |
| snprintf(name, 64, "ioBufAllocator[%d]", i); | |
| } | |
| ioBufAllocator[i].re_init(name, s, n, a, use_hugepages, iobuffer_advice); | |
| } | |
| } | |
| void | |
| init_buffer_allocators(int iobuffer_advice) | |
| { | |
| int chunk_sizes[DEFAULT_BUFFER_SIZES] = {0}; | |
| init_buffer_allocators(iobuffer_advice, chunk_sizes, false); | |
| } | |
| auto | |
| make_buffer_size_parser() | |
| { | |
| using L = swoc::Lexicon<int>; | |
| static const L lexicon{ | |
| L::with_multi{{0, {"128"}}, | |
| {1, {"256"}}, | |
| {2, {"512"}}, | |
| {3, {"1k", "1024"}}, | |
| {4, {"2k", "2048"}}, | |
| {5, {"4k", "4096"}}, | |
| {6, {"8k", "8192"}}, | |
| {7, {"16k"}}, | |
| {8, {"32k"}}, | |
| {9, {"64k"}}, | |
| {10, {"128k"}}, | |
| {11, {"256k"}}, | |
| {12, {"512k"}}, | |
| {13, {"1M", "1024k"}}, | |
| {14, {"2M", "2048k"}}}, | |
| -1 | |
| }; | |
| return [](swoc::TextView esize) -> std::optional<int> { | |
| int result = lexicon[esize]; | |
| if (result == -1) { | |
| return std::nullopt; | |
| } | |
| return result; | |
| }; | |
| } | |
| bool | |
| parse_buffer_chunk_sizes(const char *chunk_sizes_string, int chunk_sizes[DEFAULT_BUFFER_SIZES]) | |
| { | |
| const swoc::TextView delimiters(", "); | |
| if (chunk_sizes_string != nullptr) { | |
| swoc::TextView src(chunk_sizes_string, swoc::TextView::npos); | |
| auto parser = make_buffer_size_parser(); | |
| int n = 0; | |
| while (!src.empty()) { | |
| swoc::TextView token{src.take_prefix_at(delimiters)}; | |
| swoc::TextView esize = token.take_prefix_at(':'); | |
| if (!token.empty()) { | |
| auto parsed = parser(esize); | |
| if (parsed) { | |
| n = *parsed; | |
| } else { | |
| Error("Failed to parse size for %.*s", static_cast<int>(esize.size()), esize.data()); | |
| return false; | |
| } | |
| } else { | |
| // element didn't have a colon so its just a chunk size | |
| token = esize; | |
| } | |
| // Check if n goes out of bounds | |
| if (n >= DEFAULT_BUFFER_SIZES) { | |
| Error("Invalid IO buffer chunk sizes string"); | |
| return false; | |
| } | |
| auto x = swoc::svto_radix<10>(token); | |
| if (token.empty() && x != std::numeric_limits<decltype(x)>::max()) { | |
| chunk_sizes[n++] = x; | |
| } else { | |
| Error("Failed to parse chunk size"); | |
| return false; | |
| } | |
| src.ltrim(delimiters); | |
| } | |
| } | |
| return true; | |
| } | |
| // | |
| // MIOBuffer | |
| // | |
| int64_t | |
| MIOBuffer::write(const void *abuf, int64_t alen) | |
| { | |
| const char *buf = static_cast<const char *>(abuf); | |
| int64_t len = alen; | |
| while (len) { | |
| if (!_writer) { | |
| add_block(); | |
| } | |
| int64_t f = _writer->write_avail(); | |
| f = f < len ? f : len; | |
| if (f > 0) { | |
| ::memcpy(_writer->end(), buf, f); | |
| _writer->fill(f); | |
| buf += f; | |
| len -= f; | |
| } | |
| if (len) { | |
| if (!_writer->next) { | |
| add_block(); | |
| } else { | |
| _writer = _writer->next; | |
| } | |
| } | |
| } | |
| return alen; | |
| } | |
| int64_t | |
| MIOBuffer::write(IOBufferReader *r, int64_t len, int64_t offset) | |
| { | |
| return this->write(r->block.get(), len, offset + r->start_offset); | |
| } | |
| int64_t | |
| MIOBuffer::write(IOBufferChain const *chain, int64_t len, int64_t offset) | |
| { | |
| return this->write(chain->head(), std::min(len, chain->length()), offset); | |
| } | |
| int64_t | |
| MIOBuffer::write(IOBufferBlock const *b, int64_t alen, int64_t offset) | |
| { | |
| int64_t len = alen; | |
| while (b && len > 0) { | |
| int64_t max_bytes = b->read_avail(); | |
| max_bytes -= offset; | |
| if (max_bytes <= 0) { | |
| offset = -max_bytes; | |
| b = b->next.get(); | |
| continue; | |
| } | |
| int64_t bytes; | |
| if (len >= max_bytes) { | |
| bytes = max_bytes; | |
| } else { | |
| bytes = len; | |
| } | |
| IOBufferBlock *bb = b->clone(); | |
| bb->_start += offset; | |
| bb->_buf_end = bb->_end = bb->_start + bytes; | |
| append_block(bb); | |
| offset = 0; | |
| len -= bytes; | |
| b = b->next.get(); | |
| } | |
| return alen - len; | |
| } | |
| bool | |
| MIOBuffer::is_max_read_avail_more_than(int64_t size) | |
| { | |
| bool no_reader = true; | |
| for (auto &reader : this->readers) { | |
| if (reader.allocated()) { | |
| if (reader.is_read_avail_more_than(size)) { | |
| return true; | |
| } | |
| no_reader = false; | |
| } | |
| } | |
| if (no_reader && this->_writer) { | |
| return (this->_writer->read_avail() > size); | |
| } | |
| return false; | |
| } | |
| // | |
| // IOBufferReader | |
| // | |
| int64_t | |
| IOBufferReader::read(void *ab, int64_t len) | |
| { | |
| char *b = static_cast<char *>(ab); | |
| int64_t n = len; | |
| int64_t l = block_read_avail(); | |
| int64_t bytes = 0; | |
| while (n && l) { | |
| if (n < l) { | |
| l = n; | |
| } | |
| ::memcpy(b, start(), l); | |
| consume(l); | |
| b += l; | |
| n -= l; | |
| bytes += l; | |
| l = block_read_avail(); | |
| } | |
| return bytes; | |
| } | |
| // TODO: I don't think this method is used anywhere, so perhaps get rid of it ? | |
| int64_t | |
| IOBufferReader::memchr(char c, int64_t len, int64_t offset) | |
| { | |
| IOBufferBlock *b = block.get(); | |
| offset += start_offset; | |
| int64_t o = offset; | |
| while (b && len) { | |
| int64_t max_bytes = b->read_avail(); | |
| max_bytes -= offset; | |
| if (max_bytes <= 0) { | |
| offset = -max_bytes; | |
| b = b->next.get(); | |
| continue; | |
| } | |
| int64_t bytes; | |
| if (len < 0 || len >= max_bytes) { | |
| bytes = max_bytes; | |
| } else { | |
| bytes = len; | |
| } | |
| char *s = b->start() + offset; | |
| char *p = static_cast<char *>(::memchr(s, c, bytes)); | |
| if (p) { | |
| return static_cast<int64_t>(o - start_offset + p - s); | |
| } | |
| o += bytes; | |
| len -= bytes; | |
| b = b->next.get(); | |
| offset = 0; | |
| } | |
| return -1; | |
| } | |
| char * | |
| IOBufferReader::memcpy(void *ap, int64_t len, int64_t offset) | |
| { | |
| char *p = static_cast<char *>(ap); | |
| IOBufferBlock *b = block.get(); | |
| offset += start_offset; | |
| while (b && len) { | |
| int64_t max_bytes = b->read_avail(); | |
| max_bytes -= offset; | |
| if (max_bytes <= 0) { | |
| offset = -max_bytes; | |
| b = b->next.get(); | |
| continue; | |
| } | |
| int64_t bytes; | |
| if (len < 0 || len >= max_bytes) { | |
| bytes = max_bytes; | |
| } else { | |
| bytes = len; | |
| } | |
| ::memcpy(p, b->start() + offset, bytes); | |
| p += bytes; | |
| len -= bytes; | |
| b = b->next.get(); | |
| offset = 0; | |
| } | |
| return p; | |
| } | |
| // | |
| // IOBufferChain | |
| // | |
| int64_t | |
| IOBufferChain::write(IOBufferBlock *blocks, int64_t length, int64_t offset) | |
| { | |
| int64_t n = length; | |
| while (blocks && n > 0) { | |
| int64_t block_bytes = blocks->read_avail(); | |
| if (block_bytes <= offset) { // skip the entire block | |
| offset -= block_bytes; | |
| } else { | |
| int64_t bytes = std::min(n, block_bytes - offset); | |
| IOBufferBlock *bb = blocks->clone(); | |
| if (offset) { | |
| bb->consume(offset); | |
| block_bytes -= offset; // bytes really available to use. | |
| offset = 0; | |
| } | |
| if (block_bytes > n) { | |
| bb->_end -= (block_bytes - n); | |
| } | |
| // Attach the cloned block since its data will be kept. | |
| this->append(bb); | |
| n -= bytes; | |
| } | |
| blocks = blocks->next.get(); | |
| } | |
| length -= n; // actual bytes written to chain. | |
| _len += length; | |
| return length; | |
| } | |
| int64_t | |
| IOBufferChain::write(IOBufferData *data, int64_t length, int64_t offset) | |
| { | |
| int64_t zret = 0; | |
| IOBufferBlock *b = new_IOBufferBlock(); | |
| if (length < 0) { | |
| length = 0; | |
| } | |
| b->set(data, length, offset); | |
| this->append(b); | |
| zret = b->read_avail(); | |
| _len += zret; | |
| return zret; | |
| } | |
| void | |
| IOBufferChain::append(IOBufferBlock *block) | |
| { | |
| if (nullptr == _tail) { | |
| _head = block; | |
| _tail = block; | |
| } else { | |
| _tail->next = block; | |
| _tail = block; | |
| } | |
| } | |
| int64_t | |
| IOBufferChain::consume(int64_t size) | |
| { | |
| int64_t zret = 0; | |
| int64_t bytes; | |
| size = std::min(size, _len); | |
| while (_head != nullptr && size > 0 && (bytes = _head->read_avail()) > 0) { | |
| if (size >= bytes) { | |
| _head = _head->next; | |
| zret += bytes; | |
| size -= bytes; | |
| } else { | |
| _head->consume(size); | |
| zret += size; | |
| size = 0; | |
| } | |
| } | |
| _len -= zret; | |
| if (_head == nullptr || _len == 0) { | |
| _head = nullptr, _tail = nullptr, _len = 0; | |
| } | |
| return zret; | |
| } |