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