diff --git a/tdutils/CMakeLists.txt b/tdutils/CMakeLists.txt index d57d93c8f..80351d13a 100644 --- a/tdutils/CMakeLists.txt +++ b/tdutils/CMakeLists.txt @@ -247,6 +247,7 @@ set(TDUTILS_SOURCE td/utils/tl_storers.h td/utils/translit.h td/utils/TsFileLog.h + td/utils/TsList.h td/utils/type_traits.h td/utils/UInt.h td/utils/uint128.h @@ -268,6 +269,7 @@ set(TDUTILS_TEST_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/test/HazardPointers.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/heap.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/json.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test/List.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/log.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/misc.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/MpmcQueue.cpp diff --git a/tdutils/td/utils/TsList.h b/tdutils/td/utils/TsList.h new file mode 100644 index 000000000..3769d8b29 --- /dev/null +++ b/tdutils/td/utils/TsList.h @@ -0,0 +1,199 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/List.h" + +#include + +namespace td { + +template +struct TsList; + +template +struct TsListNode : protected ListNode { + TsListNode() { + clear(); + } + explicit TsListNode(DataT &&data) : data_(std::move(data)) { + clear(); + } + + ~TsListNode() { + remove(); + } + + std::unique_lock lock() TD_WARN_UNUSED_RESULT; + + TsListNode(const TsListNode &) = delete; + TsListNode &operator=(const TsListNode &) = delete; + + TsListNode(TsListNode &&other) { + other.validate(); + if (other.empty()) { + data_ = std::move(other.data_); + clear(); + } else { + auto guard = other.lock(); + init_from(std::move(other)); + } + validate(); + other.validate(); + } + + TsListNode &operator=(TsListNode &&other) { + validate(); + if (this == &other) { + return *this; + } + other.validate(); + remove(); + + if (other.empty()) { + data_ = std::move(other.data_); + } else { + auto guard = other.lock(); + init_from(std::move(other)); + } + + validate(); + other.validate(); + return *this; + } + + void validate() { + CHECK(empty() || !ListNode::empty() || is_root); + CHECK(!empty() || ListNode::empty()); + } + + void remove() { + validate(); + if (is_root) { + CHECK(ListNode::empty()); + return; + } + if (empty()) { + CHECK(ListNode::empty()); + return; + } + { + auto guard = lock(); + ListNode::remove(); + if (!is_root) { + parent = nullptr; + } + } + validate(); + } + + void put(TsListNode *other) { + validate(); + other->validate(); + DCHECK(other->empty()); + DCHECK(!empty()); + DCHECK(!other->is_root); + { + auto guard = lock(); + ListNode::put(other); + other->parent = parent; + } + validate(); + other->validate(); + } + + void put_back(TsListNode *other) { + DCHECK(other->empty()); + DCHECK(!empty()); + DCHECK(!other->is_root); + auto guard = lock(); + ListNode::put_back(other); + other->parent = parent; + } + + bool empty() const { + return parent == nullptr; + } + + TsListNode *get_next() { + return static_cast(next); + } + TsListNode *get_prev() { + return static_cast(prev); + } + + DataT &get_data_unsafe() { + return data_; + } + + private: + TsList *parent; + bool is_root{false}; + DataT data_; + + friend struct TsList; + + void clear() { + ListNode::clear(); + if (!is_root) { + parent = nullptr; + } + } + + void init_from(TsListNode &&other) { + ListNode::init_from(std::move(other)); + parent = other.parent; + other.parent = nullptr; + data_ = std::move(other.data_); + } +}; + +template +struct TsList : public TsListNode { + public: + TsList() { + this->parent = this; + this->is_root = true; + } + TsList(const TsList &) = delete; + TsList &operator=(const TsList &) = delete; + TsList(TsList &&) = delete; + TsList &operator=(TsList &&) = delete; + ~TsList() { + CHECK(ListNode::empty()); + this->parent = nullptr; + } + std::unique_lock lock() TD_WARN_UNUSED_RESULT { + return std::unique_lock(mutex_); + } + TsListNode *begin() { + return this->get_next(); + } + TsListNode *end() { + return this; + } + TsListNode *get() { + auto guard = lock(); + auto res = static_cast *>(ListNode::get()); + if (res) { + res->parent = nullptr; + } + return res; + } + + private: + std::mutex mutex_; +}; + +template +std::unique_lock TsListNode::lock() { + CHECK(parent != nullptr); + return parent->lock(); +} + +} // namespace td diff --git a/tdutils/test/List.cpp b/tdutils/test/List.cpp new file mode 100644 index 000000000..f39ce98dc --- /dev/null +++ b/tdutils/test/List.cpp @@ -0,0 +1,174 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/common.h" +#include "td/utils/List.h" +#include "td/utils/Random.h" +#include "td/utils/tests.h" +#include "td/utils/TsList.h" + +#include +#include + +struct Data { + td::uint64 value{0}; + bool in_list{false}; + + Data() = default; + Data(td::uint64 value, bool in_list) : value(value), in_list(in_list) { + } + Data(const Data &from) = delete; + Data &operator=(const Data &from) = delete; + Data(Data &&from) { + *this = std::move(from); + } + Data &operator=(Data &&from) { + if (this == &from) { + return *this; + } + value = from.value; + in_list = from.in_list; + from.value = 0; + from.in_list = false; + return *this; + } + ~Data() = default; +}; + +struct Node : public td::ListNode { + Node() = default; + explicit Node(Data data) : data(std::move(data)) { + } + + Data data; +}; + +Data &get_data(Node &node) { + return node.data; +} + +Data &get_data(td::TsListNode &node) { + return node.get_data_unsafe(); +} + +std::unique_lock lock(td::ListNode &node) { + return {}; +} + +std::unique_lock lock(td::TsListNode &node) { + return node.lock(); +} + +template +void do_run_list_test(ListRootT &root, std::atomic &id) { + td::vector nodes; + + td::Random::Xorshift128plus rnd(123); + + auto next_id = [&] { + return ++id; + }; + auto add_node = [&] { + if (nodes.size() >= 20) { + return; + } + nodes.emplace_back(NodeT({next_id(), false})); + }; + auto pop_node = [&] { + if (nodes.size() == 0) { + return; + } + nodes.pop_back(); + }; + + auto link_node = [&] { + if (nodes.empty()) { + return; + } + auto i = rnd.fast(0, (int)nodes.size() - 1); + nodes[i].remove(); + get_data(nodes[i]) = Data(next_id(), true); + root.put(&nodes[i]); + }; + auto unlink_node = [&] { + if (nodes.empty()) { + return; + } + auto i = rnd.fast(0, (int)nodes.size() - 1); + nodes[i].remove(); + get_data(nodes[i]).in_list = false; + }; + auto swap_nodes = [&] { + if (nodes.empty()) { + return; + } + auto i = rnd.fast(0, (int)nodes.size() - 1); + auto j = rnd.fast(0, (int)nodes.size() - 1); + std::swap(nodes[i], nodes[j]); + }; + auto set_node = [&] { + if (nodes.empty()) { + return; + } + auto i = rnd.fast(0, (int)nodes.size() - 1); + auto j = rnd.fast(0, (int)nodes.size() - 1); + nodes[i] = std::move(nodes[j]); + }; + auto validate = [&] { + std::multiset in_list, not_in_list; + for (auto &node : nodes) { + if (get_data(node).in_list) { + in_list.insert(get_data(node).value); + } else { + not_in_list.insert(get_data(node).value); + } + } + auto guard = lock(root); + for (auto *begin = root.begin(), *end = root.end(); begin != end; begin = begin->get_next()) { + auto &data = get_data(*static_cast(begin)); + CHECK(data.in_list); + CHECK(data.value != 0); + auto it = in_list.find(data.value); + if (it != in_list.end()) { + in_list.erase(it); + } else { + ASSERT_EQ(0u, not_in_list.count(data.value)); + } + } + ASSERT_EQ(0u, in_list.size()); + }; + td::RandomSteps steps( + {{add_node, 3}, {pop_node, 1}, {unlink_node, 1}, {link_node, 3}, {swap_nodes, 1}, {set_node, 1}, {validate, 1}}); + for (int i = 0; i < 10000; i++) { + steps.step(rnd); + } +} + +TEST(Misc, List) { + td::ListNode root; + std::atomic id{0}; + for (std::size_t i = 0; i < 4; i++) { + do_run_list_test(root, id); + } +} + +TEST(Misc, TsList) { + td::TsList root; + std::atomic id{0}; + for (std::size_t i = 0; i < 4; i++) { + do_run_list_test, td::TsList, td::TsListNode>(root, id); + } +} + +TEST(Misc, TsListConcurrent) { + td::TsList root; + td::vector threads; + std::atomic id{0}; + for (std::size_t i = 0; i < 4; i++) { + threads.emplace_back( + [&] { do_run_list_test, td::TsList, td::TsListNode>(root, id); }); + } +}