#ifndef hazard_pointer_h #define hazard_pointer_h #include #include #include #include #include #include #include #include namespace as { namespace detail { struct retired_node { void *ptr; std::function deleter; std::atomic next{nullptr}; retired_node(void *p, std::function d) : ptr(p), deleter(std::move(d)) { } }; struct hazard_pointer_record { std::atomic pointer{nullptr}; std::atomic allocated{false}; }; struct thread_hazards { static constexpr int MAX_RECORDS = 64; hazard_pointer_record records[MAX_RECORDS]; int max_hazard; std::atomic next{nullptr}; explicit thread_hazards(int max_haz): max_hazard(max_haz) { assert(max_haz <= MAX_RECORDS); } }; } // namespace detail // --- Dominio de Hazard Pointers --- class hazard_pointer_domain { public: static constexpr int default_hazard_pointers_per_thread = 16; explicit hazard_pointer_domain( int max_hazard_per_thread = default_hazard_pointers_per_thread) : _max_hazard_per_thread(max_hazard_per_thread), _retired_threshold(64 * max_hazard_per_thread) { assert(max_hazard_per_thread > 0); } ~hazard_pointer_domain() { // Reclamar toda la memoria pendiente reclaim_all(); // Limpiar los thread hazards auto *thread_hps = _threads_head.load(std::memory_order_acquire); while (thread_hps != nullptr) { auto *next = thread_hps->next.load(std::memory_order_acquire); delete thread_hps; thread_hps = next; } } hazard_pointer_domain(const hazard_pointer_domain &) = delete; hazard_pointer_domain & operator=(const hazard_pointer_domain &) = delete; // Adquirir un slot de hazard pointer (lock-free) std::atomic *acquire_slot() { auto &hps = _get_thread_hazard_pointers(); // Buscar un slot no asignado (lock-free) for (int i = 0; i < hps.max_hazard; ++i) { bool expected = false; if (hps.records[i].allocated.compare_exchange_strong( expected, true, std::memory_order_acq_rel)) { return &hps.records[i].pointer; } } // Si llegamos aquí, no hay slots disponibles (todos están // asignados) throw std::runtime_error( "Sin slots de Hazard Pointers disponibles."); } // Liberar un slot de hazard pointer void release_slot(std::atomic *slot) { slot->store(nullptr, std::memory_order_release); // Buscar el record correspondiente y marcarlo como no asignado auto &hps = _get_thread_hazard_pointers(); for (int i = 0; i < hps.max_hazard; ++i) { if (&hps.records[i].pointer == slot) { hps.records[i].allocated.store( false, std::memory_order_release); break; } } } // Obtener todos los hazard pointers activos (ordenados) std::vector get_active_hps() { std::vector active; active.reserve(_max_hazard_per_thread * 4); // Estimación inicial // Recorrer todos los threads (lock-free) auto *thread_hps = _threads_head.load(std::memory_order_acquire); while (thread_hps != nullptr) { for (int i = 0; i < thread_hps->max_hazard; ++i) { void *ptr = thread_hps->records[i].pointer.load( std::memory_order_acquire); if (ptr != nullptr) { active.push_back(ptr); } } thread_hps = thread_hps->next.load(std::memory_order_acquire); } std::sort(active.begin(), active.end()); return active; } // Añadir un puntero a la lista de retirados (lock-free con // protección ABA) void retire_ptr(void *ptr, std::function deleter) { if (ptr == nullptr) return; auto retired_count = _retired_count.fetch_add(1, std::memory_order_relaxed); // Crear nodo y añadir a la lista lock-free auto *node = new detail::retired_node(ptr, deleter); // Retry loop con CAS para evitar ABA while (true) { auto *head = _retired_head.load(std::memory_order_acquire); node->next.store(head, std::memory_order_relaxed); if (_retired_head.compare_exchange_strong( head, node, std::memory_order_release, std::memory_order_acquire)) { // Incrementar versión para prevenir ABA _retire_version.fetch_add(1, std::memory_order_release); break; } } // Trigger reclamación si se alcanza el threshold if (retired_count >= _retired_threshold - 1) { reclaim(); } } // Reclamar memoria de punteros retirados pero seguros (lock-free) void reclaim() { // Intentar tomar la lista de retirados atómicamente auto *head = _retired_head.exchange(nullptr, std::memory_order_acquire); if (head == nullptr) return; // Otro thread ya lo procesó // Incrementar versión al limpiar la lista _retire_version.fetch_add(1, std::memory_order_release); auto active = get_active_hps(); std::vector hazardous_nodes; // Escanear y reclamar nodos no peligrosos while (head != nullptr) { auto *next = head->next.load(std::memory_order_relaxed); // Búsqueda binaria para verificar si es seguro eliminar if (!std::binary_search(active.begin(), active.end(), head->ptr)) { head->deleter(head->ptr); delete head; _retired_count.fetch_sub(1, std::memory_order_relaxed); } else { // Guardar para reinsertar hazardous_nodes.push_back(head); } head = next; } // Reinsertar nodos peligrosos de vuelta a la lista if (!hazardous_nodes.empty()) { // Enlazar los nodos peligrosos juntos for (size_t i = 0; i < hazardous_nodes.size() - 1; ++i) { hazardous_nodes[i]->next.store( hazardous_nodes[i + 1], std::memory_order_relaxed); } // Reinsertar todos a la vez auto *old_head = _retired_head.load(std::memory_order_acquire); while (true) { hazardous_nodes.back()->next.store( old_head, std::memory_order_relaxed); if (_retired_head.compare_exchange_strong( old_head, hazardous_nodes.front(), std::memory_order_release, std::memory_order_acquire)) { break; } } } } private: int _max_hazard_per_thread; int _retired_threshold; // Lista lock-free de nodos retirados con protección ABA std::atomic _retired_head{nullptr}; std::atomic _retire_version{ 0}; // Contador de versión para ABA std::atomic _retired_count{0}; // Lista lock-free de todos los thread_hazards std::atomic _threads_head{nullptr}; // Obtener los hazard pointers del thread actual (por dominio) detail::thread_hazards &_get_thread_hazard_pointers() { thread_local std::unordered_map local_map; auto &entry = local_map[this]; if (entry == nullptr) { entry = new detail::thread_hazards(_max_hazard_per_thread); // Registrar en la lista global de este dominio (lock-free) auto *old_head = _threads_head.load(std::memory_order_acquire); do { entry->next.store(old_head, std::memory_order_relaxed); } while (!_threads_head.compare_exchange_strong( old_head, entry, std::memory_order_release, std::memory_order_acquire)); } return *entry; } // Reclamar todos los nodos pendientes (usado en destructor) void reclaim_all() { // Seguir reclamando hasta que no queden nodos while (_retired_head.load(std::memory_order_acquire) != nullptr) { reclaim(); } } }; // Dominio por defecto inline hazard_pointer_domain &default_domain() { static hazard_pointer_domain dom; return dom; } // --- API Pública conforme a C++26 --- // 1. Clase Base para objetos protegidos por Hazard Pointers template> class hazard_pointer_obj_base { public: void retire(D deleter = D(), hazard_pointer_domain &domain = default_domain()) { domain.retire_ptr(this, [d = std::move(deleter)](void *p) mutable { d(static_cast(p)); }); } protected: hazard_pointer_obj_base() = default; hazard_pointer_obj_base(const hazard_pointer_obj_base &) = default; hazard_pointer_obj_base(hazard_pointer_obj_base &&) = default; hazard_pointer_obj_base & operator=(const hazard_pointer_obj_base &) = default; hazard_pointer_obj_base & operator=(hazard_pointer_obj_base &&) = default; ~hazard_pointer_obj_base() = default; }; // 2. El Hazard Pointer (RAII) class hazard_pointer { std::atomic *slot; hazard_pointer_domain *domain; public: hazard_pointer(hazard_pointer_domain &dom = default_domain()) : slot(dom.acquire_slot()), domain(&dom) { } // Prohibir copia hazard_pointer(const hazard_pointer &) = delete; hazard_pointer &operator=(const hazard_pointer &) = delete; // Permitir movimiento hazard_pointer(hazard_pointer &&other) noexcept : slot(other.slot), domain(other.domain) { other.slot = nullptr; other.domain = nullptr; } hazard_pointer &operator=(hazard_pointer &&other) noexcept { if (this != &other) { if (slot && domain) domain->release_slot(slot); slot = other.slot; domain = other.domain; other.slot = nullptr; other.domain = nullptr; } return *this; } ~hazard_pointer() { if (slot && domain) domain->release_slot(slot); } // Proteger un puntero atómico template [[nodiscard]] T *protect(const std::atomic &src) noexcept { T *ptr = src.load(std::memory_order_relaxed); while (true) { slot->store(ptr, std::memory_order_release); T *temp = src.load(std::memory_order_acquire); if (ptr == temp) return ptr; ptr = temp; } } // Resetear la protección void reset() noexcept { if (slot) slot->store(nullptr, std::memory_order_release); } // Intercambiar con otro hazard_pointer void swap(hazard_pointer &other) noexcept { std::swap(slot, other.slot); std::swap(domain, other.domain); } // Obtener el dominio hazard_pointer_domain &get_domain() const noexcept { return *domain; } }; // Función de conveniencia inline hazard_pointer make_hazard_pointer(hazard_pointer_domain &domain = default_domain()) { return hazard_pointer(domain); } // Intercambio no-miembro inline void swap(hazard_pointer &a, hazard_pointer &b) noexcept { a.swap(b); } } // namespace as #endif // hazard_pointer_h