/**************************************************************************/1/* safe_list.h */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#pragma once3132#include "core/os/memory.h"33#include "core/typedefs.h"3435#include <atomic>36#include <functional>37#include <initializer_list>3839// Design goals for these classes:40// - Accessing this list with an iterator will never result in a use-after free,41// even if the element being accessed has been logically removed from the list on42// another thread.43// - Logical deletion from the list will not result in deallocation at that time,44// instead the node will be deallocated at a later time when it is safe to do so.45// - No blocking synchronization primitives will be used.4647// This is used in very specific areas of the engine where it's critical that these guarantees are held.4849template <typename T, typename A = DefaultAllocator>50class SafeList {51struct SafeListNode {52std::atomic<SafeListNode *> next = nullptr;5354// If the node is logically deleted, this pointer will typically point55// to the previous list item in time that was also logically deleted.56std::atomic<SafeListNode *> graveyard_next = nullptr;5758std::function<void(T)> deletion_fn = [](T t) { return; };5960T val;61};6263static_assert(std::atomic<T>::is_always_lock_free);6465std::atomic<SafeListNode *> head = nullptr;66std::atomic<SafeListNode *> graveyard_head = nullptr;6768std::atomic_uint active_iterator_count = 0;6970public:71class Iterator {72friend class SafeList;7374SafeListNode *cursor = nullptr;75SafeList *list = nullptr;7677Iterator(SafeListNode *p_cursor, SafeList *p_list) :78cursor(p_cursor), list(p_list) {79list->active_iterator_count++;80}8182public:83Iterator(const Iterator &p_other) :84cursor(p_other.cursor), list(p_other.list) {85list->active_iterator_count++;86}8788~Iterator() {89list->active_iterator_count--;90}9192public:93T &operator*() {94return cursor->val;95}9697Iterator &operator++() {98cursor = cursor->next;99return *this;100}101102// These two operators are mostly useful for comparisons to nullptr.103bool operator==(const void *p_other) const {104return cursor == p_other;105}106107bool operator!=(const void *p_other) const {108return cursor != p_other;109}110111// These two allow easy range-based for loops.112bool operator==(const Iterator &p_other) const {113return cursor == p_other.cursor;114}115116bool operator!=(const Iterator &p_other) const {117return cursor != p_other.cursor;118}119};120121public:122// Calling this will cause an allocation.123void insert(T p_value) {124SafeListNode *new_node = memnew_allocator(SafeListNode, A);125new_node->val = p_value;126SafeListNode *expected_head = nullptr;127do {128expected_head = head.load();129new_node->next.store(expected_head);130} while (!head.compare_exchange_strong(/* expected= */ expected_head, /* new= */ new_node));131}132133Iterator find(T p_value) {134for (Iterator it = begin(); it != end(); ++it) {135if (*it == p_value) {136return it;137}138}139return end();140}141142void erase(T p_value, std::function<void(T)> p_deletion_fn) {143Iterator tmp = find(p_value);144erase(tmp, p_deletion_fn);145}146147void erase(T p_value) {148Iterator tmp = find(p_value);149erase(tmp, [](T t) { return; });150}151152void erase(Iterator &p_iterator, std::function<void(T)> p_deletion_fn) {153p_iterator.cursor->deletion_fn = p_deletion_fn;154erase(p_iterator);155}156157void erase(Iterator &p_iterator) {158if (find(p_iterator.cursor->val) == nullptr) {159// Not in the list, nothing to do.160return;161}162// First, remove the node from the list.163while (true) {164Iterator prev = begin();165SafeListNode *expected_head = prev.cursor;166for (; prev != end(); ++prev) {167if (prev.cursor && prev.cursor->next == p_iterator.cursor) {168break;169}170}171if (prev != end()) {172// There exists a node before this.173prev.cursor->next.store(p_iterator.cursor->next.load());174// Done.175break;176} else {177if (head.compare_exchange_strong(/* expected= */ expected_head, /* new= */ p_iterator.cursor->next.load())) {178// Successfully reassigned the head pointer before another thread changed it to something else.179break;180}181// Fall through upon failure, try again.182}183}184// Then queue it for deletion by putting it in the node graveyard.185// Don't touch `next` because an iterator might still be pointing at this node.186SafeListNode *expected_head = nullptr;187do {188expected_head = graveyard_head.load();189p_iterator.cursor->graveyard_next.store(expected_head);190} while (!graveyard_head.compare_exchange_strong(/* expected= */ expected_head, /* new= */ p_iterator.cursor));191}192193Iterator begin() {194return Iterator(head.load(), this);195}196197Iterator end() {198return Iterator(nullptr, this);199}200201// Calling this will cause zero to many deallocations.202bool maybe_cleanup() {203SafeListNode *cursor = nullptr;204SafeListNode *new_graveyard_head = nullptr;205do {206// The access order here is theoretically important.207cursor = graveyard_head.load();208if (active_iterator_count.load() != 0) {209// It's not safe to clean up with an active iterator, because that iterator210// could be pointing to an element that we want to delete.211return false;212}213// Any iterator created after this point will never point to a deleted node.214// Swap it out with the current graveyard head.215} while (!graveyard_head.compare_exchange_strong(/* expected= */ cursor, /* new= */ new_graveyard_head));216// Our graveyard list is now unreachable by any active iterators,217// detached from the main graveyard head and ready for deletion.218while (cursor) {219SafeListNode *tmp = cursor;220cursor = cursor->graveyard_next;221tmp->deletion_fn(tmp->val);222memdelete_allocator<SafeListNode, A>(tmp);223}224return true;225}226227_FORCE_INLINE_ SafeList() {}228_FORCE_INLINE_ SafeList(std::initializer_list<T> p_init) {229for (const T &E : p_init) {230insert(E);231}232}233234~SafeList() {235#ifdef DEBUG_ENABLED236if (!maybe_cleanup()) {237ERR_PRINT("There are still iterators around when destructing a SafeList. Memory will be leaked. This is a bug.");238}239#else240maybe_cleanup();241#endif242}243};244245246