-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathspinLock.h
More file actions
166 lines (149 loc) · 5.11 KB
/
Copy pathspinLock.h
File metadata and controls
166 lines (149 loc) · 5.11 KB
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
/*
* Copyright 2017 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _SPINLOCK_H
#define _SPINLOCK_H
#include "arch.h"
#include <cassert>
// Cannot use regular mutexes inside signal handler.
// This lock is based on CAS busy loop. GCC atomic builtins imply full barrier.
// Aligned to cache line size (64 bytes) to prevent false sharing between SpinLock instances
class alignas(DEFAULT_CACHE_LINE_SIZE) SpinLock {
private:
// 0 - unlocked
// 1 - exclusive lock
// <0 - shared lock
volatile int _lock;
char _padding[DEFAULT_CACHE_LINE_SIZE - sizeof(_lock)];
public:
explicit constexpr SpinLock(int initial_state = 0) : _lock(initial_state), _padding() {
static_assert(sizeof(SpinLock) == DEFAULT_CACHE_LINE_SIZE);
}
void reset() { __atomic_store_n(&_lock, 0, __ATOMIC_RELAXED); }
bool tryLock() { return __sync_bool_compare_and_swap(&_lock, 0, 1); }
void lock() {
while (!tryLock()) {
spinPause();
}
}
void unlock() {
assert(__atomic_load_n(&_lock, __ATOMIC_RELAXED) == 1);
__sync_fetch_and_sub(&_lock, 1);
}
// Spin budget for bounded shared acquisition in signal handlers.
// Bounds the number of CAS-retry iterations under reader contention;
// does NOT bound wall-clock latency (CAS stall time is hardware-dependent).
static constexpr int DEFAULT_SHARED_SPIN_BUDGET = 256;
bool tryLockShared() {
// Spins while no exclusive lock is held and the CAS to acquire a shared
// lock fails (due to concurrent reader contention). Returns false ONLY when
// an exclusive lock is observed (_lock > 0); never returns false spuriously.
int value;
while ((value = __atomic_load_n(&_lock, __ATOMIC_ACQUIRE)) <= 0) {
if (__sync_bool_compare_and_swap(&_lock, value, value - 1)) {
return true;
}
spinPause();
}
return false;
}
// Bounded variant for signal-handler paths. Returns false when an exclusive
// lock is observed OR the spin budget is exhausted under reader contention.
bool tryLockShared(int max_spins) {
int value;
int spins = 0;
while ((value = __atomic_load_n(&_lock, __ATOMIC_ACQUIRE)) <= 0) {
if (__sync_bool_compare_and_swap(&_lock, value, value - 1)) {
return true;
}
if (++spins >= max_spins) {
return false;
}
spinPause();
}
return false;
}
void lockShared() {
int value;
while ((value = __atomic_load_n(&_lock, __ATOMIC_ACQUIRE)) > 0 ||
!__sync_bool_compare_and_swap(&_lock, value, value - 1)) {
spinPause();
}
}
void unlockShared() {
assert(__atomic_load_n(&_lock, __ATOMIC_RELAXED) < 0);
__sync_fetch_and_add(&_lock, 1);
}
};
// RAII guard classes for automatic lock management
class SharedLockGuard {
private:
SpinLock* _lock;
public:
explicit SharedLockGuard(SpinLock* lock) : _lock(lock) {
_lock->lockShared();
}
~SharedLockGuard() {
_lock->unlockShared();
}
// Non-copyable and non-movable
SharedLockGuard(const SharedLockGuard&) = delete;
SharedLockGuard& operator=(const SharedLockGuard&) = delete;
SharedLockGuard(SharedLockGuard&&) = delete;
SharedLockGuard& operator=(SharedLockGuard&&) = delete;
};
// Acquires a shared lock with a bounded CAS-retry budget. Returns without
// acquiring (ownsLock() == false) when an exclusive lock is observed or
// the spin budget is exhausted under reader contention. Safe to use in
// signal-handler paths.
class OptionalSharedLockGuard {
SpinLock* _lock;
public:
explicit OptionalSharedLockGuard(
SpinLock* lock, int max_spins = SpinLock::DEFAULT_SHARED_SPIN_BUDGET)
: _lock(lock) {
if (!_lock->tryLockShared(max_spins)) {
_lock = nullptr;
}
}
~OptionalSharedLockGuard() {
if (_lock != nullptr) {
_lock->unlockShared();
}
}
bool ownsLock() const { return _lock != nullptr; }
// Non-copyable and non-movable
OptionalSharedLockGuard(const OptionalSharedLockGuard&) = delete;
OptionalSharedLockGuard& operator=(const OptionalSharedLockGuard&) = delete;
OptionalSharedLockGuard(OptionalSharedLockGuard&&) = delete;
OptionalSharedLockGuard& operator=(OptionalSharedLockGuard&&) = delete;
};
class ExclusiveLockGuard {
private:
SpinLock* _lock;
public:
explicit ExclusiveLockGuard(SpinLock* lock) : _lock(lock) {
_lock->lock();
}
~ExclusiveLockGuard() {
_lock->unlock();
}
// Non-copyable and non-movable
ExclusiveLockGuard(const ExclusiveLockGuard&) = delete;
ExclusiveLockGuard& operator=(const ExclusiveLockGuard&) = delete;
ExclusiveLockGuard(ExclusiveLockGuard&&) = delete;
ExclusiveLockGuard& operator=(ExclusiveLockGuard&&) = delete;
};
#endif // _SPINLOCK_H