2 * Copyright 2017 Facebook, Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include <folly/io/IOBufQueue.h>
25 using std::unique_ptr;
31 const size_t MIN_ALLOC_SIZE = 2000;
32 const size_t MAX_ALLOC_SIZE = 8000;
33 const size_t MAX_PACK_COPY = 4096;
36 * Convenience function to append chain src to chain dst.
39 appendToChain(unique_ptr<IOBuf>& dst, unique_ptr<IOBuf>&& src, bool pack) {
43 IOBuf* tail = dst->prev();
45 // Copy up to MAX_PACK_COPY bytes if we can free buffers; this helps
46 // reduce wastage (the tail's tailroom and the head's headroom) when
47 // joining two IOBufQueues together.
48 size_t copyRemaining = MAX_PACK_COPY;
51 (n = src->length()) < copyRemaining &&
52 n < tail->tailroom()) {
53 memcpy(tail->writableTail(), src->data(), n);
60 tail->appendChain(std::move(src));
65 } // anonymous namespace
69 IOBufQueue::IOBufQueue(const Options& options)
74 IOBufQueue::IOBufQueue(IOBufQueue&& other) noexcept
75 : options_(other.options_),
76 chainLength_(other.chainLength_),
77 head_(std::move(other.head_)) {
78 other.chainLength_ = 0;
81 IOBufQueue& IOBufQueue::operator=(IOBufQueue&& other) {
83 options_ = other.options_;
84 chainLength_ = other.chainLength_;
85 head_ = std::move(other.head_);
86 other.chainLength_ = 0;
91 std::pair<void*, uint64_t>
92 IOBufQueue::headroom() {
94 return std::make_pair(head_->writableBuffer(), head_->headroom());
96 return std::make_pair(nullptr, 0);
101 IOBufQueue::markPrepended(uint64_t n) {
111 IOBufQueue::prepend(const void* buf, uint64_t n) {
114 throw std::overflow_error("Not enough room to prepend");
116 memcpy(static_cast<char*>(p.first) + p.second - n, buf, n);
121 IOBufQueue::append(unique_ptr<IOBuf>&& buf, bool pack) {
125 if (options_.cacheChainLength) {
126 chainLength_ += buf->computeChainDataLength();
128 appendToChain(head_, std::move(buf), pack);
132 IOBufQueue::append(IOBufQueue& other, bool pack) {
136 if (options_.cacheChainLength) {
137 if (other.options_.cacheChainLength) {
138 chainLength_ += other.chainLength_;
140 chainLength_ += other.head_->computeChainDataLength();
143 appendToChain(head_, std::move(other.head_), pack);
144 other.chainLength_ = 0;
148 IOBufQueue::append(const void* buf, size_t len) {
149 auto src = static_cast<const uint8_t*>(buf);
151 if ((head_ == nullptr) || head_->prev()->isSharedOne() ||
152 (head_->prev()->tailroom() == 0)) {
154 IOBuf::create(std::max(MIN_ALLOC_SIZE,
155 std::min(len, MAX_ALLOC_SIZE))),
158 IOBuf* last = head_->prev();
159 uint64_t copyLen = std::min(len, (size_t)last->tailroom());
160 memcpy(last->writableTail(), src, copyLen);
162 last->append(copyLen);
163 chainLength_ += copyLen;
169 IOBufQueue::wrapBuffer(const void* buf, size_t len, uint64_t blockSize) {
170 auto src = static_cast<const uint8_t*>(buf);
172 size_t n = std::min(len, size_t(blockSize));
173 append(IOBuf::wrapBuffer(src, n));
180 IOBufQueue::preallocateSlow(uint64_t min, uint64_t newAllocationSize,
182 // Allocate a new buffer of the requested max size.
183 unique_ptr<IOBuf> newBuf(IOBuf::create(std::max(min, newAllocationSize)));
184 appendToChain(head_, std::move(newBuf), false);
185 IOBuf* last = head_->prev();
186 return make_pair(last->writableTail(),
187 std::min(max, last->tailroom()));
190 unique_ptr<IOBuf> IOBufQueue::split(size_t n, bool throwOnUnderflow) {
191 unique_ptr<IOBuf> result;
193 if (head_ == nullptr) {
194 if (throwOnUnderflow) {
195 throw std::underflow_error(
196 "Attempt to remove more bytes than are present in IOBufQueue");
200 } else if (head_->length() <= n) {
201 n -= head_->length();
202 chainLength_ -= head_->length();
203 unique_ptr<IOBuf> remainder = head_->pop();
204 appendToChain(result, std::move(head_), false);
205 head_ = std::move(remainder);
207 unique_ptr<IOBuf> clone = head_->cloneOne();
208 clone->trimEnd(clone->length() - n);
209 appendToChain(result, std::move(clone), false);
215 if (UNLIKELY(result == nullptr)) {
216 return IOBuf::create(0);
221 void IOBufQueue::trimStart(size_t amount) {
222 auto trimmed = trimStartAtMost(amount);
223 if (trimmed != amount) {
224 throw std::underflow_error(
225 "Attempt to trim more bytes than are present in IOBufQueue");
229 size_t IOBufQueue::trimStartAtMost(size_t amount) {
230 auto original = amount;
235 if (head_->length() > amount) {
236 head_->trimStart(amount);
237 chainLength_ -= amount;
241 amount -= head_->length();
242 chainLength_ -= head_->length();
243 head_ = head_->pop();
245 return original - amount;
248 void IOBufQueue::trimEnd(size_t amount) {
249 auto trimmed = trimEndAtMost(amount);
250 if (trimmed != amount) {
251 throw std::underflow_error(
252 "Attempt to trim more bytes than are present in IOBufQueue");
256 size_t IOBufQueue::trimEndAtMost(size_t amount) {
257 auto original = amount;
262 if (head_->prev()->length() > amount) {
263 head_->prev()->trimEnd(amount);
264 chainLength_ -= amount;
268 amount -= head_->prev()->length();
269 chainLength_ -= head_->prev()->length();
271 if (head_->isChained()) {
272 head_->prev()->unlink();
277 return original - amount;
280 std::unique_ptr<folly::IOBuf> IOBufQueue::pop_front() {
284 chainLength_ -= head_->length();
285 std::unique_ptr<folly::IOBuf> retBuf = std::move(head_);
286 head_ = retBuf->pop();
290 void IOBufQueue::clear() {
294 IOBuf* buf = head_.get();
298 } while (buf != head_.get());
302 void IOBufQueue::appendToString(std::string& out) const {
307 options_.cacheChainLength ? chainLength_ : head_->computeChainDataLength();
308 out.reserve(out.size() + len);
310 for (auto range : *head_) {
311 out.append(reinterpret_cast<const char*>(range.data()), range.size());
315 void IOBufQueue::gather(uint64_t maxLength) {
316 if (head_ != nullptr) {
317 head_->gather(maxLength);