aboutsummaryrefslogtreecommitdiffstats
path: root/vespalib/src/vespa/vespalib/util/generationhandler.h
blob: 24dce7e00208a2f4d5ffed565ccc3415dd7fa9fa (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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.

#pragma once

#include <cstdint>
#include <atomic>

namespace vespalib {

/**
 * Class used to keep track of the current generation of a component
 * (changed by a single writer), and previous generations still
 * occupied by multiple readers.  Readers will take a generation guard
 * by calling takeGuard().
 **/
class GenerationHandler {
public:
    using generation_t = uint64_t;
    using sgeneration_t = int64_t ;

    /*
     * This must be type stable memory, and cannot be freed before the
     * GenerationHandler is freed (i.e. when external methods ensure that
     * no readers are still active).
     */
    class GenerationHold
    {
        // least significant bit is invalid flag
        std::atomic<uint32_t> _refCount;

        static bool valid(uint32_t refCount) noexcept { return (refCount & 1) == 0u; }
    public:
        std::atomic<generation_t> _generation;
        GenerationHold *_next;	// next free element or next newer element.

        GenerationHold() noexcept;
        ~GenerationHold();

        void setValid() noexcept;
        bool setInvalid() noexcept;
        void release() noexcept {
            _refCount.fetch_sub(2, std::memory_order_release);
        }
        GenerationHold *acquire() noexcept;
        static GenerationHold *copy(GenerationHold *self) noexcept;
        uint32_t getRefCount() const noexcept {
            return _refCount.load(std::memory_order_relaxed) / 2;
        }
        uint32_t getRefCountAcqRel() noexcept {
            return _refCount.fetch_add(0, std::memory_order_acq_rel) / 2;
        }
    };

    /**
     * Class that keeps a reference to a generation until destroyed.
     **/
    class Guard {
    private:
        GenerationHold *_hold;
        void cleanup() noexcept {
            if (_hold != nullptr) {
                _hold->release();
                _hold = nullptr;
            }
        }
    public:
        Guard() noexcept : _hold(nullptr) { }
        Guard(GenerationHold *hold) noexcept : _hold(hold->acquire()) { } // hold is never nullptr
        ~Guard() { cleanup(); }
        Guard(const Guard & rhs) noexcept : _hold(GenerationHold::copy(rhs._hold)) { }
        Guard(Guard &&rhs) noexcept
            : _hold(rhs._hold)
        {
            rhs._hold = nullptr;
        }
        Guard & operator=(const Guard & rhs) noexcept;
        Guard & operator=(Guard &&rhs) noexcept;

        bool valid() const noexcept {
            return _hold != nullptr;
        }
        generation_t getGeneration() const { return _hold->_generation.load(std::memory_order_relaxed); }
    };

private:
    std::atomic<generation_t>     _generation;
    std::atomic<generation_t>     _oldest_used_generation;
    std::atomic<GenerationHold *> _last;      // Points to "current generation" entry
    GenerationHold               *_first;     // Points to "firstUsedGeneration" entry
    GenerationHold               *_free;      // List of free entries
    uint32_t                      _numHolds;  // Number of allocated generation hold entries

    void set_generation(generation_t generation) noexcept { _generation.store(generation, std::memory_order_relaxed); }

public:
    /**
     * Creates a new generation handler.
     **/
    GenerationHandler();
    ~GenerationHandler();

    /**
     * Take a generation guard on the current generation.
     * Should be called by reader threads.
     **/
    Guard takeGuard() const;

    /**
     * Increases the current generation by 1.
     * Should be called by the writer thread.
     **/
    void incGeneration();

    /**
     * Update the oldest used generation.
     * Should be called by the writer thread.
     */
    void update_oldest_used_generation();

    /**
     * Returns the oldest generation guarded by a reader.
     * It might be too low if writer hasn't updated oldest used generation after last reader left.
     */
    generation_t get_oldest_used_generation() const noexcept {
        return _oldest_used_generation.load(std::memory_order_relaxed);
    }

    /**
     * Returns the current generation.
     **/
    generation_t getCurrentGeneration() const noexcept {
        return _generation.load(std::memory_order_relaxed);
    }

    generation_t getNextGeneration() const noexcept {
        return getCurrentGeneration() + 1;
    }

    /**
     * Returns the number of readers holding a generation guard on the
     * given generation.  Should be called by the writer thread.
     */
    uint32_t getGenerationRefCount(generation_t gen) const;

    /**
     * Returns the number of readers holding a generation guard.
     * Should be called by the writer thread.
     */
    uint64_t getGenerationRefCount() const;
};

}