MySQL 9.1.0
Source Code Documentation
pfs_lock.h
Go to the documentation of this file.
1/* Copyright (c) 2009, 2024, Oracle and/or its affiliates.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is designed to work with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have either included with
13 the program or referenced in the documentation.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License, version 2.0, for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
23
24#ifndef PFS_LOCK_H
25#define PFS_LOCK_H
26
27/**
28 @file storage/perfschema/pfs_lock.h
29 Performance schema internal locks (declarations).
30*/
31
32#include <assert.h>
33#include <atomic>
34
35#include "my_inttypes.h"
36
37/* to cause bugs, testing */
38// #define MEM(X) std::memory_order_relaxed
39/* correct code */
40#define MEM(X) X
41
42/**
43 @addtogroup performance_schema_buffers
44 @{
45*/
46
47/**
48 State of a free record.
49 Values of a free record should not be read by a reader.
50 Writers can concurrently attempt to allocate a free record.
51*/
52#define PFS_LOCK_FREE 0x00
53/**
54 State of a dirty record.
55 Values of a dirty record should not be read by a reader,
56 as the record is currently being modified.
57 Only one writer, the writer which owns the record, should
58 modify the record content.
59*/
60#define PFS_LOCK_DIRTY 0x01
61/**
62 State of an allocated record.
63 Values of an allocated record are safe to read by a reader.
64 A writer may modify some but not all properties of the record:
65 only modifying values that can never cause the reader to crash is allowed.
66*/
67#define PFS_LOCK_ALLOCATED 0x02
68
69#define VERSION_MASK 0xFFFFFFFC
70#define STATE_MASK 0x00000003
71#define VERSION_INC 4
72
75};
76
79};
80
81/**
82 A 'lock' protecting performance schema internal buffers.
83 This lock is used to mark the state of a record.
84 Access to the record is not enforced here,
85 it's up to the readers and writers to look at the record state
86 before making an actual read or write operation.
87
88 The following state diagram shows the general states maintained by the lock.
89
90 @startuml
91
92 state PFS_LOCK_FREE
93
94 state PFS_LOCK_DIRTY
95
96 state PFS_LOCK_ALLOCATED
97
98 [*] -down-> PFS_LOCK_FREE
99
100 PFS_LOCK_FREE -down-> [*]
101
102 PFS_LOCK_FREE -right-> PFS_LOCK_DIRTY : free_to_dirty()
103 PFS_LOCK_DIRTY -right-> PFS_LOCK_ALLOCATED : dirty_to_allocated()
104
105 PFS_LOCK_ALLOCATED --> PFS_LOCK_FREE : allocated_to_free()
106 PFS_LOCK_ALLOCATED --> PFS_LOCK_DIRTY : allocated_to_dirty()
107
108 @enduml
109
110 An internal version counter is also incremented, to detect each modification
111 done to a record.
112
113 The following diagram represent a fragment of all the states reached,
114 for an object creation, modification and destruction.
115
116 @startuml
117
118 state "..." as BEFORE
119
120 state "PFS_LOCK_FREE" as FREE_N
121 FREE_N : m_version = N
122
123 state "PFS_LOCK_DIRTY" as DIRTY_N
124 DIRTY_N : m_version = N
125
126 state "PFS_LOCK_ALLOCATED" as ALLOCATED_N1
127 ALLOCATED_N1 : m_version = N + 1
128
129 state "PFS_LOCK_DIRTY" as DIRTY_N1
130 DIRTY_N1 : m_version = N + 1
131
132 state "PFS_LOCK_ALLOCATED" as ALLOCATED_N2
133 ALLOCATED_N2: m_version = N + 2
134
135 state "PFS_LOCK_FREE" as FREE_N2
136 FREE_N2 : m_version = N + 2
137
138 state "..." as AFTER
139
140 BEFORE -down-> FREE_N
141 FREE_N -down-> DIRTY_N : free_to_dirty()
142 DIRTY_N -down-> ALLOCATED_N1 : dirty_to_allocated()
143 ALLOCATED_N1 -down-> DIRTY_N1 : allocated_to_dirty()
144 DIRTY_N1 -down-> ALLOCATED_N2 : dirty_to_allocated()
145 ALLOCATED_N2 -down-> FREE_N2 : allocated_to_free()
146 FREE_N2 -down-> AFTER
147
148 Note left of ALLOCATED_N1 : State after object creation
149 Note left of ALLOCATED_N2 : State after object modification
150 Note left of FREE_N2 : State after object destruction
151
152 @enduml
153*/
154struct pfs_lock {
155 /**
156 The record internal version and state
157 @sa PFS_LOCK_FREE
158 @sa PFS_LOCK_DIRTY
159 @sa PFS_LOCK_ALLOCATED
160 The version number is to transform the 'ABA' problem
161 (see http://en.wikipedia.org/wiki/ABA_problem)
162 into an 'A(n)BA(n + 1)' problem, where 'n' is the m_version number.
163 When the performance schema instrumentation deletes a record,
164 then create a different record reusing the same memory allocation,
165 the version number is incremented, so that a reader can detect that
166 the record was changed. Note that the version number is never
167 reset to zero when a new record is created.
168 The version number is stored in the high 30 bits.
169 The state is stored in the low 2 bits.
170 */
171 std::atomic<uint32> m_version_state;
172
174 uint32 copy;
175
176 copy = m_version_state; /* dirty read */
177
178 return copy;
179 }
180
181 /** Returns true if the record is free. */
182 bool is_free() {
183 const uint32 copy = m_version_state.load();
184
185 return ((copy & STATE_MASK) == PFS_LOCK_FREE);
186 }
187
188 /** Returns true if the record contains values that can be read. */
190 const uint32 copy = m_version_state.load();
191
192 return ((copy & STATE_MASK) == PFS_LOCK_ALLOCATED);
193 }
194
195 /**
196 Execute a free to dirty transition.
197 This transition is safe to execute concurrently by multiple writers.
198 Only one writer will succeed to acquire the record.
199 @return true if the operation succeed
200 */
202 uint32 old_val = m_version_state.load();
203
204 if ((old_val & STATE_MASK) != PFS_LOCK_FREE) {
205 return false;
206 }
207
208 const uint32 new_val = (old_val & VERSION_MASK) + PFS_LOCK_DIRTY;
209
210 const bool pass =
211 atomic_compare_exchange_strong(&m_version_state, &old_val, new_val);
212
213 if (pass) {
214 copy_ptr->m_version_state = new_val;
215 }
216
217 return pass;
218 }
219
220 /**
221 Execute an allocated to dirty transition.
222 This transition should be executed by the writer that owns the record,
223 before the record is modified.
224 */
227 /* Make sure the record was ALLOCATED. */
228 assert((copy & STATE_MASK) == PFS_LOCK_ALLOCATED);
229 /* Keep the same version, set the DIRTY state */
230 const uint32 new_val = (copy & VERSION_MASK) + PFS_LOCK_DIRTY;
231 /* We own the record, no need to use compare and swap. */
232
233 m_version_state.store(new_val);
234
235 copy_ptr->m_version_state = new_val;
236 }
237
238 /**
239 Execute a dirty to allocated transition.
240 This transition should be executed by the writer that owns the record,
241 after the record is in a state ready to be read.
242 */
244 /* Make sure the record was DIRTY. */
245 assert((copy->m_version_state & STATE_MASK) == PFS_LOCK_DIRTY);
246 /* Increment the version, set the ALLOCATED state */
247 const uint32 new_val = (copy->m_version_state & VERSION_MASK) +
249
250 m_version_state.store(new_val);
251 }
252
253 /**
254 Initialize a lock to allocated.
255 This transition should be executed by the writer that owns the record and
256 the lock,
257 after the record is in a state ready to be read.
258 */
260 /* Do not set the version to 0, read the previous value. */
262 /* Increment the version, set the ALLOCATED state */
263 const uint32 new_val =
265
266 m_version_state.store(new_val);
267 }
268
269 /**
270 Initialize a lock to dirty.
271 */
272 void set_dirty(pfs_dirty_state *copy_ptr) {
273 /* Do not set the version to 0, read the previous value. */
274 const uint32 copy = m_version_state.load();
275 /* Increment the version, set the DIRTY state */
276 const uint32 new_val = (copy & VERSION_MASK) + VERSION_INC + PFS_LOCK_DIRTY;
277 m_version_state.store(new_val);
278
279 copy_ptr->m_version_state = new_val;
280 }
281
282 /**
283 Execute a dirty to free transition.
284 This transition should be executed by the writer that owns the record.
285 */
287 /* Make sure the record was DIRTY. */
288 assert((copy->m_version_state & STATE_MASK) == PFS_LOCK_DIRTY);
289 /* Keep the same version, set the FREE state */
290 const uint32 new_val =
291 (copy->m_version_state & VERSION_MASK) + PFS_LOCK_FREE;
292
293 m_version_state.store(new_val);
294 }
295
296 /**
297 Execute an allocated to free transition.
298 This transition should be executed by the writer that owns the record.
299 */
301 /*
302 If this record is not in the ALLOCATED state and the caller is trying
303 to free it, this is a bug: the caller is confused,
304 and potentially damaging data owned by another thread or object.
305 */
307 /* Make sure the record was ALLOCATED. */
308 assert(((copy & STATE_MASK) == PFS_LOCK_ALLOCATED));
309 /* Keep the same version, set the FREE state */
310 const uint32 new_val = (copy & VERSION_MASK) + PFS_LOCK_FREE;
311
312 m_version_state.store(new_val);
313 }
314
315 /**
316 Start an optimistic read operation.
317 @param [out] copy Saved lock state
318 @sa end_optimist_lock.
319 */
321 copy->m_version_state = m_version_state.load();
322 }
323
324 /**
325 End an optimistic read operation.
326 @sa begin_optimist_lock.
327 @param copy Saved lock state
328 @return true if the data read is safe to use.
329 */
331 /* Check there was valid data to look at. */
332 if ((copy->m_version_state & STATE_MASK) != PFS_LOCK_ALLOCATED) {
333 return false;
334 }
335
336 const uint32 version_state = m_version_state.load();
337
338 /* Check the version + state has not changed. */
339 return (copy->m_version_state == version_state);
340 }
341
343 const uint32 version_state = m_version_state.load();
344
345 return (version_state & VERSION_MASK);
346 }
347};
348
349/** @} */
350#endif
#define VERSION_MASK
Definition: pfs_lock.h:69
#define PFS_LOCK_DIRTY
State of a dirty record.
Definition: pfs_lock.h:60
#define STATE_MASK
Definition: pfs_lock.h:70
#define PFS_LOCK_FREE
State of a free record.
Definition: pfs_lock.h:52
#define VERSION_INC
Definition: pfs_lock.h:71
#define PFS_LOCK_ALLOCATED
State of an allocated record.
Definition: pfs_lock.h:67
Some integer typedefs for easier portability.
uint32_t uint32
Definition: my_inttypes.h:67
void copy(Shards< COUNT > &dst, const Shards< COUNT > &src) noexcept
Copy the counters, overwrite destination.
Definition: ut0counter.h:354
Definition: pfs_lock.h:77
uint32 m_version_state
Definition: pfs_lock.h:78
A 'lock' protecting performance schema internal buffers.
Definition: pfs_lock.h:154
void allocated_to_free()
Execute an allocated to free transition.
Definition: pfs_lock.h:300
bool is_populated()
Returns true if the record contains values that can be read.
Definition: pfs_lock.h:189
void dirty_to_allocated(const pfs_dirty_state *copy)
Execute a dirty to allocated transition.
Definition: pfs_lock.h:243
uint32 copy_version_state()
Definition: pfs_lock.h:173
bool end_optimistic_lock(const pfs_optimistic_state *copy)
End an optimistic read operation.
Definition: pfs_lock.h:330
void allocated_to_dirty(pfs_dirty_state *copy_ptr)
Execute an allocated to dirty transition.
Definition: pfs_lock.h:225
bool is_free()
Returns true if the record is free.
Definition: pfs_lock.h:182
void begin_optimistic_lock(pfs_optimistic_state *copy)
Start an optimistic read operation.
Definition: pfs_lock.h:320
void dirty_to_free(const pfs_dirty_state *copy)
Execute a dirty to free transition.
Definition: pfs_lock.h:286
uint32 get_version()
Definition: pfs_lock.h:342
std::atomic< uint32 > m_version_state
The record internal version and state.
Definition: pfs_lock.h:171
void set_dirty(pfs_dirty_state *copy_ptr)
Initialize a lock to dirty.
Definition: pfs_lock.h:272
void set_allocated()
Initialize a lock to allocated.
Definition: pfs_lock.h:259
bool free_to_dirty(pfs_dirty_state *copy_ptr)
Execute a free to dirty transition.
Definition: pfs_lock.h:201
Definition: pfs_lock.h:73
uint32 m_version_state
Definition: pfs_lock.h:74