/* Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ #ifndef PFS_LOCK_H #define PFS_LOCK_H /** @file storage/perfschema/pfs_lock.h Performance schema internal locks (declarations). */ #include "pfs_atomic.h" /** @addtogroup Performance_schema_buffers @{ */ /** State of a free record. Values of a free record should not be read by a reader. Writers can concurrently attempt to allocate a free record. */ #define PFS_LOCK_FREE 0x00 /** State of a dirty record. Values of a dirty record should not be read by a reader, as the record is currently being modified. Only one writer, the writer which owns the record, should modify the record content. */ #define PFS_LOCK_DIRTY 0x01 /** State of an allocated record. Values of an allocated record are safe to read by a reader. A writer may modify some but not all properties of the record: only modifying values that can never cause the reader to crash is allowed. */ #define PFS_LOCK_ALLOCATED 0x02 #define VERSION_MASK 0xFFFFFFFC #define STATE_MASK 0x00000003 #define VERSION_INC 4 /** A 'lock' protecting performance schema internal buffers. This lock is used to mark the state of a record. Access to the record is not enforced here, it's up to the readers and writers to look at the record state before making an actual read or write operation. */ struct pfs_lock { /** The record internal version and state @sa PFS_LOCK_FREE @sa PFS_LOCK_DIRTY @sa PFS_LOCK_ALLOCATED The version number is to transform the 'ABA' problem (see http://en.wikipedia.org/wiki/ABA_problem) into an 'A(n)BA(n + 1)' problem, where 'n' is the m_version number. When the performance schema instrumentation deletes a record, then create a different record reusing the same memory allocation, the version number is incremented, so that a reader can detect that the record was changed. Note that the version number is never reset to zero when a new record is created. The version number is stored in the high 30 bits. The state is stored in the low 2 bits. */ volatile uint32 m_version_state; /** Returns true if the record is free. */ bool is_free(void) { uint32 copy= m_version_state; /* non volatile copy, and dirty read */ return ((copy & STATE_MASK) == PFS_LOCK_FREE); } /** Returns true if the record contains values that can be read. */ bool is_populated(void) { uint32 copy= m_version_state; /* non volatile copy, and dirty read */ return ((copy & STATE_MASK) == PFS_LOCK_ALLOCATED); } /** Execute a free to dirty transition. This transition is safe to execute concurrently by multiple writers. Only one writer will succeed to acquire the record. @return true if the operation succeed */ bool free_to_dirty(void) { uint32 copy= m_version_state; /* non volatile copy, and dirty read */ uint32 old_val= (copy & VERSION_MASK) + PFS_LOCK_FREE; uint32 new_val= (copy & VERSION_MASK) + PFS_LOCK_DIRTY; return (PFS_atomic::cas_u32(&m_version_state, &old_val, new_val)); } /** Execute an allocated to dirty transition. This transition should be executed by the writer that owns the record, before the record is modified. */ void allocated_to_dirty(void) { uint32 copy= PFS_atomic::load_u32(&m_version_state); /* Make sure the record was ALLOCATED. */ DBUG_ASSERT((copy & STATE_MASK) == PFS_LOCK_ALLOCATED); /* Keep the same version, set the DIRTY state */ uint32 new_val= (copy & VERSION_MASK) + PFS_LOCK_DIRTY; /* We own the record, no need to use compare and swap. */ PFS_atomic::store_u32(&m_version_state, new_val); } /** Execute a dirty to allocated transition. This transition should be executed by the writer that owns the record, after the record is in a state ready to be read. */ void dirty_to_allocated(void) { uint32 copy= PFS_atomic::load_u32(&m_version_state); /* Make sure the record was DIRTY. */ DBUG_ASSERT((copy & STATE_MASK) == PFS_LOCK_DIRTY); /* Increment the version, set the ALLOCATED state */ uint32 new_val= (copy & VERSION_MASK) + VERSION_INC + PFS_LOCK_ALLOCATED; PFS_atomic::store_u32(&m_version_state, new_val); } /** Initialize a lock to allocated. This transition should be executed by the writer that owns the record and the lock, after the record is in a state ready to be read. */ void set_allocated(void) { /* Do not set the version to 0, read the previous value. */ uint32 copy= PFS_atomic::load_u32(&m_version_state); /* Increment the version, set the ALLOCATED state */ uint32 new_val= (copy & VERSION_MASK) + VERSION_INC + PFS_LOCK_ALLOCATED; PFS_atomic::store_u32(&m_version_state, new_val); } /** Initialize a lock to dirty. */ void set_dirty(void) { /* Do not set the version to 0, read the previous value. */ uint32 copy= PFS_atomic::load_u32(&m_version_state); /* Increment the version, set the DIRTY state */ uint32 new_val= (copy & VERSION_MASK) + VERSION_INC + PFS_LOCK_DIRTY; PFS_atomic::store_u32(&m_version_state, new_val); } /** Execute a dirty to free transition. This transition should be executed by the writer that owns the record. */ void dirty_to_free(void) { uint32 copy= PFS_atomic::load_u32(&m_version_state); /* Make sure the record was DIRTY. */ DBUG_ASSERT((copy & STATE_MASK) == PFS_LOCK_DIRTY); /* Keep the same version, set the FREE state */ uint32 new_val= (copy & VERSION_MASK) + PFS_LOCK_FREE; PFS_atomic::store_u32(&m_version_state, new_val); } /** Execute an allocated to free transition. This transition should be executed by the writer that owns the record. */ void allocated_to_free(void) { /* If this record is not in the ALLOCATED state and the caller is trying to free it, this is a bug: the caller is confused, and potentially damaging data owned by another thread or object. The correct assert to use here to guarantee data integrity is simply: DBUG_ASSERT(m_state == PFS_LOCK_ALLOCATED); */ uint32 copy= PFS_atomic::load_u32(&m_version_state); /* Make sure the record was ALLOCATED. */ DBUG_ASSERT(((copy & STATE_MASK) == PFS_LOCK_ALLOCATED)); /* Keep the same version, set the FREE state */ uint32 new_val= (copy & VERSION_MASK) + PFS_LOCK_FREE; PFS_atomic::store_u32(&m_version_state, new_val); } /** Start an optimistic read operation. @sa end_optimist_lock. */ void begin_optimistic_lock(struct pfs_lock *copy) { copy->m_version_state= PFS_atomic::load_u32(&m_version_state); } /** End an optimistic read operation. @sa begin_optimist_lock. @return true if the data read is safe to use. */ bool end_optimistic_lock(struct pfs_lock *copy) { /* Check there was valid data to look at. */ if ((copy->m_version_state & STATE_MASK) != PFS_LOCK_ALLOCATED) return false; /* Check the version + state has not changed. */ if (copy->m_version_state != PFS_atomic::load_u32(&m_version_state)) return false; return true; } uint32 get_version() { return (PFS_atomic::load_u32(&m_version_state) & VERSION_MASK); } }; /** @} */ #endif