| /* |
| * Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license |
| * that can be found in the LICENSE file. |
| */ |
| |
| #pragma once |
| |
| #include <atomic> |
| #include <list> |
| #include <mutex> |
| |
| #include "ExternalRCRef.hpp" |
| #include "Memory.h" |
| #include "RawPtr.hpp" |
| #include "concurrent/Mutex.hpp" |
| |
| namespace kotlin::mm { |
| |
| // Registry for all externally-reference-counted references to objects. Examples: |
| // * stable references (i.e. always part of the root set) |
| // * weak references |
| // * ObjC back references. A mix between stable and weak references |
| // created for ObjC part of Kotlin objects. Have a count of external |
| // references. When > 0 - stable reference. When = 0 - weak reference. |
| // |
| // Consists of 2 global lists and 1 thread local list of `ExternalRCRefImpl`s. |
| // Each `ExternalRCRefImpl` has a reference `obj_` to an object and an external references counter `rc_`. |
| // Invariants: |
| // * `rc_ > 0` -> alive externally referenced object, must be in the root set. |
| // * `rc_ == 0` -> alive externally unreferenced object, must eventually be out of the root set. |
| // * `rc_ == disposedMarker` -> the `ExternalRCRefImpl` itself is no longer externally referenced and can be eventually deleted. |
| // * `rc_` can be increased and decreased by any thread in any state. |
| // * In practice 0 -> 1 only happens in mutator threads in runnable state, but the implementation of `ExternalRCRefRegistry` |
| // does not depend on it. |
| // * `ExternalRCRefImpl`s are owned either by a global `std::list` or by thread local `std::list`s. Global list is protected by a mutex. |
| // * Insertion into the global list happens by moving from thread local list during STW and when a thread gets destroyed. |
| // * Only the GC thread traverses and removes elements from the global list. Removal happens only if `ExternalRCRefImpl` |
| // has `rc_ == disposedMarker` and it's not in the roots list. |
| // * During global list traversal `ExternalRCRefImpl`s `obj_` referenced may get nulled out by the GC. |
| // * Insertion into thread local lists happens in runnable state. |
| // * Removal from thread local list happens during STW and thread destruction. |
| // * `ExternalRCRefImpl`s are additionally linked into an intrusive global roots list. |
| // * Any thread in any state can insert into the roots list. Insertion only happens into the head. |
| // * Only the GC thread can remove from the roots list during root scanning. If after removal |
| // the `rc_` of the `ExternalRCRefImpl` is `> 0`, the GC thread will make sure the ref is inserted |
| // into the head |
| // * During roots list traversal all refs to the left are either marked or inserted into the mark queue. |
| class ExternalRCRefRegistry : private Pinned { |
| // TODO: Consider using a real mutex. |
| using Mutex = SpinLock; |
| |
| public: |
| class ThreadQueue : private Pinned { |
| public: |
| explicit ThreadQueue(ExternalRCRefRegistry& registry) : owner_(registry) {} |
| |
| ~ThreadQueue() { publish(); } |
| |
| void publish() noexcept { |
| std::unique_lock guard(owner_.mutex_); |
| RuntimeAssert(owner_.all_.get_allocator() == queue_.get_allocator(), "allocators must match"); |
| owner_.all_.splice(owner_.all_.end(), std::move(queue_)); |
| } |
| |
| void clearForTests() noexcept { |
| for (auto& externalRCRef : queue_) { |
| externalRCRef.dispose(); |
| } |
| queue_.clear(); |
| } |
| |
| [[nodiscard("must be manually disposed")]] ExternalRCRefImpl& createExternalRCRefImpl(KRef obj, ExternalRCRefImpl::Rc rc) noexcept { |
| AssertThreadState(ThreadState::kRunnable); |
| queue_.emplace_back(owner_, obj, rc); |
| return queue_.back(); |
| } |
| |
| private: |
| ExternalRCRefRegistry& owner_; |
| std::list<ExternalRCRefImpl> queue_; |
| }; |
| |
| class RootsIterator { |
| public: |
| KRef operator*() const noexcept { |
| // Ignoring rc here. If someone nulls out rc during root |
| // scanning, it's okay to be conservative and still make it a root. |
| return ref_->objAtomic().load(std::memory_order_relaxed); |
| } |
| |
| RootsIterator& operator++() noexcept { |
| ref_ = owner_->nextRoot(ref_); |
| return *this; |
| } |
| |
| bool operator==(const RootsIterator& rhs) const noexcept { return ref_ == rhs.ref_; } |
| |
| bool operator!=(const RootsIterator& rhs) const noexcept { return !(*this == rhs); } |
| |
| private: |
| friend class ExternalRCRefRegistry; |
| |
| RootsIterator(ExternalRCRefRegistry& owner, ExternalRCRefImpl* ref) noexcept : owner_(&owner), ref_(ref) {} |
| |
| ExternalRCRefRegistry* owner_; |
| ExternalRCRefImpl* ref_; |
| }; |
| |
| class RootsIterable : private MoveOnly { |
| public: |
| RootsIterator begin() const noexcept { return RootsIterator(*owner_, owner_->nextRoot(&owner_->rootsHead_)); } |
| |
| RootsIterator end() const noexcept { return RootsIterator(*owner_, &owner_->rootsTail_); } |
| |
| private: |
| friend class ExternalRCRefRegistry; |
| |
| explicit RootsIterable(ExternalRCRefRegistry& owner) noexcept : owner_(&owner) {} |
| |
| raw_ptr<ExternalRCRefRegistry> owner_; |
| }; |
| |
| class Iterator { |
| public: |
| std_support::atomic_ref<KRef> operator*() noexcept { return iterator_->objAtomic(); } |
| |
| Iterator& operator++() noexcept { |
| iterator_ = owner_->nextAlive(std::next(iterator_)); |
| return *this; |
| } |
| |
| bool operator==(const Iterator& rhs) const noexcept { return iterator_ == rhs.iterator_; } |
| |
| bool operator!=(const Iterator& rhs) const noexcept { return iterator_ != rhs.iterator_; } |
| |
| private: |
| friend class ExternalRCRefRegistry; |
| friend class ExternalRCRefRegistryTest; |
| |
| Iterator(ExternalRCRefRegistry& owner, std::list<ExternalRCRefImpl>::iterator iterator) noexcept : |
| owner_(&owner), iterator_(iterator) {} |
| |
| ExternalRCRefRegistry* owner_; |
| std::list<ExternalRCRefImpl>::iterator iterator_; |
| }; |
| |
| class Iterable : private MoveOnly { |
| public: |
| Iterator begin() noexcept { return Iterator(owner_, owner_.nextAlive(owner_.all_.begin())); } |
| Iterator end() noexcept { return Iterator(owner_, owner_.all_.end()); } |
| |
| private: |
| friend class ExternalRCRefRegistry; |
| |
| Iterable(ExternalRCRefRegistry& owner) noexcept : owner_(owner), guard_(owner_.mutex_) {} |
| |
| ExternalRCRefRegistry& owner_; |
| std::unique_lock<Mutex> guard_; |
| }; |
| |
| ExternalRCRefRegistry() noexcept { rootsHead_.nextRoot_.store(&rootsTail_, std::memory_order_relaxed); } |
| |
| ~ExternalRCRefRegistry() = default; |
| |
| static ExternalRCRefRegistry& instance() noexcept; |
| |
| void clearForTests() noexcept { |
| rootsHead_.nextRoot_ = &rootsTail_; |
| for (auto& ref : all_) { |
| // Allow the tests not to run the finalizers for weaks. |
| ref.rc_ = ExternalRCRefImpl::disposedMarker; |
| } |
| all_.clear(); |
| } |
| |
| // Should be called on the GC thread after all threads have published. |
| RootsIterable roots() noexcept { return RootsIterable(*this); } |
| |
| // Should be called on the GC thread after marking is complete. |
| // Locks the registry and allows safe iteration over it. |
| Iterable lockForIter() noexcept { return Iterable(*this); } |
| |
| private: |
| friend class ExternalRCRefImpl; |
| friend class ExternalRCRefRegistryTest; |
| |
| ExternalRCRefImpl* nextRoot(ExternalRCRefImpl* current) noexcept; |
| // Erase `ref` from the roots list. `prev` is the current guess of the ref |
| // previous to `ref`. Returns two refs between which `ref` was deleted. |
| std::pair<ExternalRCRefImpl*, ExternalRCRefImpl*> eraseFromRoots(ExternalRCRefImpl* prev, ExternalRCRefImpl* ref) noexcept; |
| void insertIntoRootsHead(ExternalRCRefImpl& ref) noexcept; |
| // Return the next undisposed ref, cleaning up the disposed in the process. |
| std::list<ExternalRCRefImpl>::iterator nextAlive(std::list<ExternalRCRefImpl>::iterator it) noexcept; |
| |
| // TODO: Iteration over `all_` will be slow, because it's `std::list` |
| // collected at different times from different threads, and so the refs |
| // are all over the memory. Consider using custom allocator for that. |
| std::list<ExternalRCRefImpl> all_; |
| Mutex mutex_; |
| ExternalRCRefImpl rootsHead_{}; |
| static inline ExternalRCRefImpl rootsTail_{}; |
| }; |
| |
| } // namespace kotlin::mm |