aboutsummaryrefslogtreecommitdiffstats
path: root/src/LifecycleManager.cpp
blob: 78e112daff89ae5a8e2f38f24a398382da2c647e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#include "LifecycleManager.h"

#include "Merkle.h"
#include "Types.h"

#include <vector>

namespace nt
{
    LifecycleManager::LifecycleManager(ObjectManager& objects,
                                        IStorageBackend& storage)
        : objects_(objects), storage_(storage)
    {}

    void LifecycleManager::Monitor(ObjectManager::registry* object)
    {
        if (object != nullptr)
            ++object->head->handle_count;
    }

    void LifecycleManager::Unmonitor(ObjectManager::registry* object)
    {
        if (object == nullptr || object->head->handle_count == 0) return;
        --object->head->handle_count;
        TryCollect(object);
    }

    void LifecycleManager::Pin(ObjectManager::registry* object)
    {
        if (object != nullptr)
            ++object->head->reference_count;
    }

    void LifecycleManager::Unpin(ObjectManager::registry* object)
    {
        if (object == nullptr || object->head->reference_count == 0) return;
        --object->head->reference_count;
        TryCollect(object);
    }

    bool LifecycleManager::IsEligibleForGC(ObjectManager::registry* object) const
    {
        if (object == nullptr || object->head == nullptr) return false;
        return object->head->handle_count == 0
            && object->head->reference_count == 0;
    }

    const bool LifecycleManager::Contention(ObjectManager::registry* object)
    {
        return object != nullptr;
    }

    void LifecycleManager::TryCollect(ObjectManager::registry* object)
    {
        if (!IsEligibleForGC(object)) return;
        if (object->head == nullptr || object->head->type == nullptr) return;

        // Named-lifetime types are managed explicitly and must not auto-collect:
        //   BRANCH  — named branches persist until a future explicit-delete API
        //             tears them down. Their counters going to zero just means
        //             nobody currently holds them open.
        //   SESSION — rnt_session_close is the sole removal path.
        // Everything else (MULTIGROUP, BRANCH_TREE, RELATION,
        // EPHEMERAL_RELATION, TRANSACTION) is purely structurally referenced
        // and is GC-eligible by counters alone.
        const auto label = object->head->type->label;
        if (label == BRANCH || label == SESSION) return;

        if (label == MULTIGROUP)   CascadeMultigroup(object);
        if (label == BRANCH_TREE)  CascadeBranchTree(object);

        objects_.Unregister(object->head->path);
    }

    void LifecycleManager::CascadeMultigroup(ObjectManager::registry* multigroup)
    {
        if (multigroup == nullptr || multigroup->head == nullptr) return;
        const auto& mg_path = multigroup->head->path;
        if (mg_path.size() < 3) return;

        // /system/snapshots/<hash>/relations/<n> entries are the relation
        // children pinned by this multigroup. Collect first, mutate after —
        // Unpin can Unregister the entry we're standing on if its counters
        // were already zero, and iteration must outlive that mutation.
        std::vector<ObjectManager::registry*> children;
        for (auto* cur = objects_.entries.get(); cur != nullptr; cur = cur->next.get())
        {
            const auto& p = cur->head->path;
            if (p.size() != mg_path.size() + 2) continue;

            bool prefix_match = true;
            for (size_t i = 0; i < mg_path.size(); ++i)
                if (p[i] != mg_path[i]) { prefix_match = false; break; }
            if (!prefix_match) continue;
            if (p[mg_path.size()] != "relations") continue;

            children.push_back(cur);
        }

        for (auto* child : children) Unpin(child);
    }

    void LifecycleManager::CascadeBranchTree(ObjectManager::registry* branch_tree)
    {
        if (branch_tree == nullptr || branch_tree->head == nullptr) return;

        auto* bt = dynamic_cast<ObjectManager::BranchTree*>(branch_tree->object.get());
        if (bt == nullptr || bt->merkle_root.empty()) return;

        // Page through the (mg_name, mg_hash) tree and collect each referenced
        // multigroup before unpinning. Decoupling iteration from mutation
        // keeps us safe against Unpin cascading further GC under our feet.
        std::vector<ObjectManager::registry*> mgs;
        constexpr size_t kPageSize = 1024;
        size_t offset = 0;
        while (true)
        {
            auto page = Merkle<std::string>::Page(storage_, bt->merkle_root,
                                                   offset, kPageSize);
            if (page.empty()) break;
            for (const auto& entry : page)
            {
                static const Hash32 zero{};
                if (entry.payload == zero) continue;
                const std::string mg_hash = bin_to_hex(entry.payload);
                auto* mg_entry = objects_.Find(
                    {"system", "snapshots", mg_hash});
                if (mg_entry) mgs.push_back(mg_entry);
            }
            if (page.size() < kPageSize) break;
            offset += page.size();
        }

        for (auto* mg : mgs) Unpin(mg);
    }
}