/* Copyright (c) 2009, 2010, 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, Suite 500, 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 0
/**
  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 1
/**
  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 2

/**
  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 state.
    @sa PFS_LOCK_FREE
    @sa PFS_LOCK_DIRTY
    @sa PFS_LOCK_ALLOCATED
  */
  volatile int32 m_state;
  /**
    The record internal version number.
    This 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.
  */
  volatile uint32 m_version;

  /** Returns true if the record is free. */
  bool is_free(void)
  {
    /* This is a dirty read */
    return (m_state == PFS_LOCK_FREE);
  }

  /** Returns true if the record contains values that can be read. */
  bool is_populated(void)
  {
    int32 copy= m_state; /* non volatile copy, and dirty read */
    return (copy == 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)
  {
    int32 old_state= PFS_LOCK_FREE;
    int32 new_state= PFS_LOCK_DIRTY;

    return (PFS_atomic::cas_32(&m_state, &old_state, new_state));
  }

  /**
    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)
  {
    DBUG_ASSERT(m_state == PFS_LOCK_DIRTY);
    PFS_atomic::add_u32(&m_version, 1);
    PFS_atomic::store_32(&m_state, PFS_LOCK_ALLOCATED);
  }

  /**
    Execute a dirty to free transition.
    This transition should be executed by the writer that owns the record.
  */
  void dirty_to_free(void)
  {
    DBUG_ASSERT(m_state == PFS_LOCK_DIRTY);
    PFS_atomic::store_32(&m_state, PFS_LOCK_FREE);
  }

  /**
    Execute an allocated to free transition.
    This transition should be executed by the writer that owns the record.
  */
  void allocated_to_free(void)
  {
#ifndef DBUG_OFF
    extern volatile bool ready_to_exit;
#endif

    /*
      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);
      Now, because of Bug#56666 (Race condition between the server main thread
      and the kill server thread), this assert actually fails during shutdown,
      and the failure is legitimate, on concurrent calls to mysql_*_destroy(),
      when destroying the instrumentation of an object ... twice.
      During shutdown this has no consequences for the performance schema,
      so the assert is relaxed with the "|| ready_to_exit" condition as a work
      around until Bug#56666 is fixed.
    */
    DBUG_ASSERT((m_state == PFS_LOCK_ALLOCATED) || ready_to_exit);
    PFS_atomic::store_32(&m_state, PFS_LOCK_FREE);
  }

  /**
    Start an optimistic read operation.
    @sa end_optimist_lock.
  */
  void begin_optimistic_lock(struct pfs_lock *copy)
  {
    copy->m_version= PFS_atomic::load_u32(&m_version);
    copy->m_state= PFS_atomic::load_32(&m_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)
  {
    /*
      return true if:
      - the version + state has not changed
      - and there was valid data to look at
    */
    return ((copy->m_version == PFS_atomic::load_u32(&m_version)) &&
            (copy->m_state == PFS_atomic::load_32(&m_state)) &&
            (copy->m_state == PFS_LOCK_ALLOCATED));
  }
};


/** @} */
#endif

