aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/yt/threading/rw_spin_lock.h
blob: 5dfefac6862c6059293d5a24efb47371edccbd0c (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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#pragma once

#include "public.h"
#include "spin_lock_base.h"

#include <library/cpp/yt/memory/public.h>

#include <util/system/rwlock.h>

#include <atomic>

namespace NYT::NThreading {

////////////////////////////////////////////////////////////////////////////////

//! Single-writer multiple-readers spin lock.
/*!
 *  Reader-side calls are pretty cheap.
 *  The lock is unfair.
 */
class TReaderWriterSpinLock
    : public TSpinLockBase
{
public:
    using TSpinLockBase::TSpinLockBase;

    //! Acquires the reader lock.
    /*!
     *  Optimized for the case of read-intensive workloads.
     *  Cheap (just one atomic increment and no spinning if no writers are present).
     *  Don't use this call if forks are possible: forking at some
     *  intermediate point inside #AcquireReader may corrupt the lock state and
     *  leave lock forever stuck for the child process.
     */
    void AcquireReader() noexcept;
    //! Acquires the reader lock.
    /*!
     *  A more expensive version of #AcquireReader (includes at least
     *  one atomic load and CAS; also may spin even if just readers are present).
     *  In contrast to #AcquireReader, this method can be used in the presence of forks.
     *  Note that fork-friendliness alone does not provide fork-safety: additional
     *  actions must be performed to release the lock after a fork.
     */
    void AcquireReaderForkFriendly() noexcept;
    //! Tries acquiring the reader lock; see #AcquireReader.
    //! Returns |true| on success.
    bool TryAcquireReader() noexcept;
    //! Tries acquiring the reader lock (and does this in a fork-friendly manner); see #AcquireReaderForkFriendly.
    //! returns |true| on success.
    bool TryAcquireReaderForkFriendly() noexcept;
    //! Releases the reader lock.
    /*!
     *  Cheap (just one atomic decrement).
     */
    void ReleaseReader() noexcept;

    //! Acquires the writer lock.
    /*!
     *  Rather cheap (just one CAS).
     */
    void AcquireWriter() noexcept;
    //! Tries acquiring the writer lock; see #AcquireWriter.
    //! Returns |true| on success.
    bool TryAcquireWriter() noexcept;
    //! Releases the writer lock.
    /*!
     *  Cheap (just one atomic store).
     */
    void ReleaseWriter() noexcept;

    //! Returns true if the lock is taken (either by a reader or writer).
    /*!
     *  This is inherently racy.
     *  Only use for debugging and diagnostic purposes.
     */
    bool IsLocked() const noexcept;

    //! Returns true if the lock is taken by reader.
    /*!
     *  This is inherently racy.
     *  Only use for debugging and diagnostic purposes.
     */
    bool IsLockedByReader() const noexcept;

    //! Returns true if the lock is taken by writer.
    /*!
     *  This is inherently racy.
     *  Only use for debugging and diagnostic purposes.
     */
    bool IsLockedByWriter() const noexcept;

private:
    using TValue = ui32;
    static constexpr TValue UnlockedValue = 0;
    static constexpr TValue WriterMask = 1;
    static constexpr TValue ReaderDelta = 2;

    std::atomic<TValue> Value_ = UnlockedValue;


    bool TryAndTryAcquireReader() noexcept;
    bool TryAndTryAcquireWriter() noexcept;

    void AcquireReaderSlow() noexcept;
    void AcquireReaderForkFriendlySlow() noexcept;
    void AcquireWriterSlow() noexcept;
};

////////////////////////////////////////////////////////////////////////////////

//! A variant of TReaderWriterSpinLock occupyig the whole cache line.
class TPaddedReaderWriterSpinLock
    : public TReaderWriterSpinLock
{
private:
    [[maybe_unused]]
    char Padding_[CacheLineSize - sizeof(TReaderWriterSpinLock)];
};

////////////////////////////////////////////////////////////////////////////////

template <class T>
struct TReaderSpinlockTraits
{
    static void Acquire(T* spinlock);
    static void Release(T* spinlock);
};

template <class T>
struct TForkFriendlyReaderSpinlockTraits
{
    static void Acquire(T* spinlock);
    static void Release(T* spinlock);
};


template <class T>
struct TWriterSpinlockTraits
{
    static void Acquire(T* spinlock);
    static void Release(T* spinlock);
};

template <class T>
using TReaderGuard = TGuard<T, TReaderSpinlockTraits<T>>;
template <class T>
using TWriterGuard = TGuard<T, TWriterSpinlockTraits<T>>;

template <class T>
auto ReaderGuard(const T& lock);
template <class T>
auto ReaderGuard(const T* lock);
template <class T>
auto ForkFriendlyReaderGuard(const T& lock);
template <class T>
auto ForkFriendlyReaderGuard(const T* lock);
template <class T>
auto WriterGuard(const T& lock);
template <class T>
auto WriterGuard(const T* lock);

////////////////////////////////////////////////////////////////////////////////

} // namespace NYT::NThreading

#define RW_SPIN_LOCK_INL_H_
#include "rw_spin_lock-inl.h"
#undef RW_SPIN_LOCK_INL_H_