Seastar
High performance C++ framework for concurrent servers
circular_buffer.hh
1 /*
2  * This file is open source software, licensed to you under the terms
3  * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
4  * distributed with this work for additional information regarding copyright
5  * ownership. You may not use this file except in compliance with the License.
6  *
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing,
12  * software distributed under the License is distributed on an
13  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14  * KIND, either express or implied. See the License for the
15  * specific language governing permissions and limitations
16  * under the License.
17  */
18 /*
19  * Copyright (C) 2014 Cloudius Systems, Ltd.
20  */
21 
22 #pragma once
23 
24 #include <seastar/core/transfer.hh>
25 #include <seastar/core/bitops.hh>
26 #include <seastar/util/concepts.hh>
27 #include <seastar/util/modules.hh>
28 #ifndef SEASTAR_MODULE
29 #include <memory>
30 #include <algorithm>
31 #endif
32 
33 namespace seastar {
34 
61 SEASTAR_MODULE_EXPORT
62 template <typename T, typename Alloc = std::allocator<T>>
64  struct impl : Alloc {
65  T* storage = nullptr;
66  // begin, end interpreted (mod capacity)
67  size_t begin = 0;
68  size_t end = 0;
69  size_t capacity = 0;
70 
71  impl(Alloc a) noexcept : Alloc(std::move(a)) { }
72  void reset() {
73  storage = {};
74  begin = 0;
75  end = 0;
76  capacity = 0;
77  }
78  };
79  static_assert(!std::is_default_constructible_v<Alloc>
80  || std::is_nothrow_default_constructible_v<Alloc>);
81  static_assert(std::is_nothrow_move_constructible_v<Alloc>);
82  impl _impl;
83 public:
84  using value_type = T;
85  using size_type = size_t;
86  using reference = T&;
87  using pointer = T*;
88  using const_reference = const T&;
89  using const_pointer = const T*;
90 public:
91  circular_buffer() noexcept SEASTAR_CONCEPT(requires std::default_initializable<Alloc>) : circular_buffer(Alloc()) {}
92  circular_buffer(Alloc alloc) noexcept;
93  circular_buffer(circular_buffer&& X) noexcept;
94  circular_buffer(const circular_buffer& X) = delete;
95  ~circular_buffer();
96  circular_buffer& operator=(const circular_buffer&) = delete;
97  circular_buffer& operator=(circular_buffer&& b) noexcept;
98  void push_front(const T& data);
99  void push_front(T&& data);
100  template <typename... A>
101  void emplace_front(A&&... args);
102  void push_back(const T& data);
103  void push_back(T&& data);
104  template <typename... A>
105  void emplace_back(A&&... args);
106  T& front() noexcept;
107  const T& front() const noexcept;
108  T& back() noexcept;
109  const T& back() const noexcept;
110  void pop_front() noexcept;
111  void pop_back() noexcept;
112  bool empty() const noexcept;
113  size_t size() const noexcept;
114  size_t capacity() const noexcept;
115  void reserve(size_t);
116  void clear() noexcept;
117  T& operator[](size_t idx) noexcept;
118  const T& operator[](size_t idx) const noexcept;
119  template <typename Func>
120  void for_each(Func func);
121  // access an element, may return wrong or destroyed element
122  // only useful if you do not rely on data accuracy (e.g. prefetch)
123  T& access_element_unsafe(size_t idx) noexcept;
124 private:
125  void expand();
126  void expand(size_t);
127  void maybe_expand(size_t nr = 1);
128  size_t mask(size_t idx) const;
129 
130  template<typename CB, typename ValueType>
131  struct cbiterator {
132  using iterator_category = std::random_access_iterator_tag;
133  using value_type = ValueType;
134  using difference_type = std::ptrdiff_t;
135  using pointer = ValueType*;
136  using reference = ValueType&;
137 
138  ValueType& operator*() const noexcept { return cb->_impl.storage[cb->mask(idx)]; }
139  ValueType* operator->() const noexcept { return &cb->_impl.storage[cb->mask(idx)]; }
140  // prefix
141  cbiterator<CB, ValueType>& operator++() noexcept {
142  idx++;
143  return *this;
144  }
145  // postfix
146  cbiterator<CB, ValueType> operator++(int) noexcept {
147  auto v = *this;
148  idx++;
149  return v;
150  }
151  // prefix
152  cbiterator<CB, ValueType>& operator--() noexcept {
153  idx--;
154  return *this;
155  }
156  // postfix
157  cbiterator<CB, ValueType> operator--(int) noexcept {
158  auto v = *this;
159  idx--;
160  return v;
161  }
162  cbiterator<CB, ValueType> operator+(difference_type n) const noexcept {
163  return cbiterator<CB, ValueType>(cb, idx + n);
164  }
165  cbiterator<CB, ValueType> operator-(difference_type n) const noexcept {
166  return cbiterator<CB, ValueType>(cb, idx - n);
167  }
168  cbiterator<CB, ValueType>& operator+=(difference_type n) noexcept {
169  idx += n;
170  return *this;
171  }
172  cbiterator<CB, ValueType>& operator-=(difference_type n) noexcept {
173  idx -= n;
174  return *this;
175  }
176  bool operator==(const cbiterator<CB, ValueType>& rhs) const noexcept {
177  return idx == rhs.idx;
178  }
179  bool operator!=(const cbiterator<CB, ValueType>& rhs) const noexcept {
180  return idx != rhs.idx;
181  }
182  bool operator<(const cbiterator<CB, ValueType>& rhs) const noexcept {
183  return *this - rhs < 0;
184  }
185  bool operator>(const cbiterator<CB, ValueType>& rhs) const noexcept {
186  return *this - rhs > 0;
187  }
188  bool operator>=(const cbiterator<CB, ValueType>& rhs) const noexcept {
189  return *this - rhs >= 0;
190  }
191  bool operator<=(const cbiterator<CB, ValueType>& rhs) const noexcept {
192  return *this - rhs <= 0;
193  }
194  difference_type operator-(const cbiterator<CB, ValueType>& rhs) const noexcept {
195  return idx - rhs.idx;
196  }
197  private:
198  CB* cb;
199  size_t idx;
200  cbiterator(CB* b, size_t i) noexcept : cb(b), idx(i) {}
201  friend class circular_buffer;
202  };
203  friend class iterator;
204 
205 public:
206  typedef cbiterator<circular_buffer, T> iterator;
207  typedef cbiterator<const circular_buffer, const T> const_iterator;
208 
209  iterator begin() noexcept {
210  return iterator(this, _impl.begin);
211  }
212  const_iterator begin() const noexcept {
213  return const_iterator(this, _impl.begin);
214  }
215  iterator end() noexcept {
216  return iterator(this, _impl.end);
217  }
218  const_iterator end() const noexcept {
219  return const_iterator(this, _impl.end);
220  }
221  const_iterator cbegin() const noexcept {
222  return const_iterator(this, _impl.begin);
223  }
224  const_iterator cend() const noexcept {
225  return const_iterator(this, _impl.end);
226  }
227  iterator erase(iterator first, iterator last) noexcept;
228 };
229 
230 template <typename T, typename Alloc>
231 inline
232 size_t
233 circular_buffer<T, Alloc>::mask(size_t idx) const {
234  return idx & (_impl.capacity - 1);
235 }
236 
237 template <typename T, typename Alloc>
238 inline
239 bool
240 circular_buffer<T, Alloc>::empty() const noexcept {
241  return _impl.begin == _impl.end;
242 }
243 
244 template <typename T, typename Alloc>
245 inline
246 size_t
247 circular_buffer<T, Alloc>::size() const noexcept {
248  return _impl.end - _impl.begin;
249 }
250 
251 template <typename T, typename Alloc>
252 inline
253 size_t
254 circular_buffer<T, Alloc>::capacity() const noexcept {
255  return _impl.capacity;
256 }
257 
258 template <typename T, typename Alloc>
259 inline
260 void
261 circular_buffer<T, Alloc>::reserve(size_t size) {
262  if (capacity() < size) {
263  // Make sure that the new capacity is a power of two.
264  expand(size_t(1) << log2ceil(size));
265  }
266 }
267 
268 template <typename T, typename Alloc>
269 inline
270 void
271 circular_buffer<T, Alloc>::clear() noexcept {
272  erase(begin(), end());
273 }
274 
275 template <typename T, typename Alloc>
276 inline
277 circular_buffer<T, Alloc>::circular_buffer(Alloc alloc) noexcept
278  : _impl(std::move(alloc)) {
279 }
280 
281 template <typename T, typename Alloc>
282 inline
283 circular_buffer<T, Alloc>::circular_buffer(circular_buffer&& x) noexcept
284  : _impl(std::move(x._impl)) {
285  x._impl.reset();
286 }
287 
288 template <typename T, typename Alloc>
289 inline
290 circular_buffer<T, Alloc>& circular_buffer<T, Alloc>::operator=(circular_buffer&& x) noexcept {
291  if (this != &x) {
292  this->~circular_buffer();
293  new (this) circular_buffer(std::move(x));
294  }
295  return *this;
296 }
297 
298 template <typename T, typename Alloc>
299 template <typename Func>
300 inline
301 void
302 circular_buffer<T, Alloc>::for_each(Func func) {
303  auto s = _impl.storage;
304  auto m = _impl.capacity - 1;
305  for (auto i = _impl.begin; i != _impl.end; ++i) {
306  func(s[i & m]);
307  }
308 }
309 
310 template <typename T, typename Alloc>
311 inline
312 circular_buffer<T, Alloc>::~circular_buffer() {
313  for_each([this] (T& obj) {
314  std::allocator_traits<Alloc>::destroy(_impl, &obj);
315  });
316  _impl.deallocate(_impl.storage, _impl.capacity);
317 }
318 
319 template <typename T, typename Alloc>
320 void
321 circular_buffer<T, Alloc>::expand() {
322  expand(std::max<size_t>(_impl.capacity * 2, 1));
323 }
324 
325 template <typename T, typename Alloc>
326 void
327 circular_buffer<T, Alloc>::expand(size_t new_cap) {
328  auto new_storage = _impl.allocate(new_cap);
329  auto p = new_storage;
330  try {
331  for_each([this, &p] (T& obj) {
332  transfer_pass1(_impl, &obj, p);
333  p++;
334  });
335  } catch (...) {
336  while (p != new_storage) {
337  std::allocator_traits<Alloc>::destroy(_impl, --p);
338  }
339  _impl.deallocate(new_storage, new_cap);
340  throw;
341  }
342  p = new_storage;
343  for_each([this, &p] (T& obj) {
344  transfer_pass2(_impl, &obj, p++);
345  });
346  std::swap(_impl.storage, new_storage);
347  std::swap(_impl.capacity, new_cap);
348  _impl.begin = 0;
349  _impl.end = p - _impl.storage;
350  _impl.deallocate(new_storage, new_cap);
351 }
352 
353 template <typename T, typename Alloc>
354 inline
355 void
356 circular_buffer<T, Alloc>::maybe_expand(size_t nr) {
357  if (_impl.end - _impl.begin + nr > _impl.capacity) {
358  expand();
359  }
360 }
361 
362 template <typename T, typename Alloc>
363 inline
364 void
365 circular_buffer<T, Alloc>::push_front(const T& data) {
366  maybe_expand();
367  auto p = &_impl.storage[mask(_impl.begin - 1)];
368  std::allocator_traits<Alloc>::construct(_impl, p, data);
369  --_impl.begin;
370 }
371 
372 template <typename T, typename Alloc>
373 inline
374 void
375 circular_buffer<T, Alloc>::push_front(T&& data) {
376  maybe_expand();
377  auto p = &_impl.storage[mask(_impl.begin - 1)];
378  std::allocator_traits<Alloc>::construct(_impl, p, std::move(data));
379  --_impl.begin;
380 }
381 
382 template <typename T, typename Alloc>
383 template <typename... Args>
384 inline
385 void
386 circular_buffer<T, Alloc>::emplace_front(Args&&... args) {
387  maybe_expand();
388  auto p = &_impl.storage[mask(_impl.begin - 1)];
389  std::allocator_traits<Alloc>::construct(_impl, p, std::forward<Args>(args)...);
390  --_impl.begin;
391 }
392 
393 template <typename T, typename Alloc>
394 inline
395 void
396 circular_buffer<T, Alloc>::push_back(const T& data) {
397  maybe_expand();
398  auto p = &_impl.storage[mask(_impl.end)];
399  std::allocator_traits<Alloc>::construct(_impl, p, data);
400  ++_impl.end;
401 }
402 
403 template <typename T, typename Alloc>
404 inline
405 void
406 circular_buffer<T, Alloc>::push_back(T&& data) {
407  maybe_expand();
408  auto p = &_impl.storage[mask(_impl.end)];
409  std::allocator_traits<Alloc>::construct(_impl, p, std::move(data));
410  ++_impl.end;
411 }
412 
413 template <typename T, typename Alloc>
414 template <typename... Args>
415 inline
416 void
417 circular_buffer<T, Alloc>::emplace_back(Args&&... args) {
418  maybe_expand();
419  auto p = &_impl.storage[mask(_impl.end)];
420  std::allocator_traits<Alloc>::construct(_impl, p, std::forward<Args>(args)...);
421  ++_impl.end;
422 }
423 
424 template <typename T, typename Alloc>
425 inline
426 T&
427 circular_buffer<T, Alloc>::front() noexcept {
428  return _impl.storage[mask(_impl.begin)];
429 }
430 
431 template <typename T, typename Alloc>
432 inline
433 const T&
434 circular_buffer<T, Alloc>::front() const noexcept {
435  return _impl.storage[mask(_impl.begin)];
436 }
437 
438 template <typename T, typename Alloc>
439 inline
440 T&
441 circular_buffer<T, Alloc>::back() noexcept {
442  return _impl.storage[mask(_impl.end - 1)];
443 }
444 
445 template <typename T, typename Alloc>
446 inline
447 const T&
448 circular_buffer<T, Alloc>::back() const noexcept {
449  return _impl.storage[mask(_impl.end - 1)];
450 }
451 
452 template <typename T, typename Alloc>
453 inline
454 void
455 circular_buffer<T, Alloc>::pop_front() noexcept {
456  std::allocator_traits<Alloc>::destroy(_impl, &front());
457  ++_impl.begin;
458 }
459 
460 template <typename T, typename Alloc>
461 inline
462 void
463 circular_buffer<T, Alloc>::pop_back() noexcept {
464  std::allocator_traits<Alloc>::destroy(_impl, &back());
465  --_impl.end;
466 }
467 
468 template <typename T, typename Alloc>
469 inline
470 T&
471 circular_buffer<T, Alloc>::operator[](size_t idx) noexcept {
472  return _impl.storage[mask(_impl.begin + idx)];
473 }
474 
475 template <typename T, typename Alloc>
476 inline
477 const T&
478 circular_buffer<T, Alloc>::operator[](size_t idx) const noexcept {
479  return _impl.storage[mask(_impl.begin + idx)];
480 }
481 
482 template <typename T, typename Alloc>
483 inline
484 T&
485 circular_buffer<T, Alloc>::access_element_unsafe(size_t idx) noexcept {
486  return _impl.storage[mask(_impl.begin + idx)];
487 }
488 
489 template <typename T, typename Alloc>
490 inline
491 typename circular_buffer<T, Alloc>::iterator
492 circular_buffer<T, Alloc>::erase(iterator first, iterator last) noexcept {
493  static_assert(std::is_nothrow_move_assignable_v<T>, "erase() assumes move assignment does not throw");
494  if (first == last) {
495  return last;
496  }
497  // Move to the left or right depending on which would result in least amount of moves.
498  // This also guarantees that iterators will be stable when removing from either front or back.
499  if (std::distance(begin(), first) < std::distance(last, end())) {
500  auto new_start = std::move_backward(begin(), first, last);
501  for (auto i = begin(); i < new_start; ++i) {
502  std::allocator_traits<Alloc>::destroy(_impl, &*i);
503  }
504  _impl.begin = new_start.idx;
505  return last;
506  } else {
507  auto new_end = std::move(last, end(), first);
508  for (auto i = new_end, e = end(); i < e; ++i) {
509  std::allocator_traits<Alloc>::destroy(_impl, &*i);
510  }
511  _impl.end = new_end.idx;
512  return first;
513  }
514 }
515 
516 }
Definition: circular_buffer.hh:63
holds the implementation parts of the metrics layer, do not use directly.
Seastar API namespace.
Definition: abort_on_ebadf.hh:26