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