/* Copyright (C) 2014 InfiniDB, Inc.
   Copyright (C) 2016-2022 MariaDB Corporation

   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, Inc., 51 Franklin Street, Fifth Floor, Boston,
   MA 02110-1301, USA. */

/******************************************************************************
 * $Id$
 *
 *****************************************************************************/

/** @file
 * class RWLock interface
 */

#pragma once

#include <array>

#include <unistd.h>
#include <stdexcept>

#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <boost/interprocess/sync/interprocess_semaphore.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

#define EXPORT

namespace rwlock
{
const std::array<const std::string, 7> RWLockNames = {{
    "all",
    "VSS",
    "ExtentMap",
    "FreeList",
    "VBBM",
    "CopyLocks",
    "ExtentMapIndex",
}};

/// the layout of the shmseg
struct State
{
  int writerswaiting;
  int writing;
  int readerswaiting;
  int reading;
  boost::interprocess::interprocess_semaphore sems[3];
};

/* the lock state without the semaphores, passed out by timed_write_lock() for
class RWLockMonitor
*/
struct LockState
{
  int writerswaiting;
  int writing;
  int readerswaiting;
  int reading;
  bool mutexLocked;
};

class RWLockShmImpl
{
 public:
  ~RWLockShmImpl() = delete;

  static RWLockShmImpl* makeRWLockShmImpl(int key, bool* excl = nullptr);

  boost::interprocess::shared_memory_object fStateShm;
  boost::interprocess::mapped_region fRegion;
  State* fState;

  std::string keyString()
  {
    return fKeyString;
  }

 private:
  explicit RWLockShmImpl(int key, bool excl = false);
  RWLockShmImpl(const RWLockShmImpl& rhs);
  RWLockShmImpl& operator=(const RWLockShmImpl& rhs);
  std::string fKeyString;
};

class not_excl : public std::exception
{
 public:
  const char* what() const noexcept override
  {
    return "not_excl";
  }
};

class wouldblock : public std::exception
{
 public:
  const char* what() const noexcept override
  {
    return "wouldblock";
  }
};

/** @brief Implements RW locks for use across threads & processes
 *
 * Implements RW locks for use across threads & processes.  Every
 * instance that shares a lock must be instantiated using the same
 * key.  There is 'no limit' on the number of RW locks that can
 * exist on the system at any one time.
 *
 * Summary of operation:
 * 		- readers can work concurrently
 *		- writers get exclusive access
 * 		- writers have priority
 *		- all state persists across all invocations sharing a given key
 *
 * Note: because state has to persist, it will have to be cleaned
 * up somewhere else.  Crashes while holding a read or write lock will
 * eventually deadlock the set of processes that share the same key obviously.
 */
class RWLock
{
 public:
  // semaphore numbers
  static const int MUTEX = 0;
  static const int READERS = 1;
  static const int WRITERS = 2;

  /** @brief Keyed constructor.
   *
   * Instantiate an RWLock with the given key.  All instances that
   * share a key share the same lock.
   *
   * @param key The key
   * @param excl If true and this is the first instance with the
   * supplied key, it will return holding the write lock.  If true and
   * this is not the first instance, it will throw not_excl.  The intent
   * is similar to the IPC_EXCL flag in the sem/shm implementations.
   */
  EXPORT explicit RWLock(int key, bool* excl = nullptr);

  EXPORT ~RWLock();

  /** @brief Grab a read lock
   *
   * Grab a read lock.  This will block iff writers are waiting or
   * a writer is active. The version with priority ignores any
   * waiting threads and grabs the lock.
   *
   * @param block (For testing only) If false, will throw
   * wouldblock instead of blocking
   */
  EXPORT void read_lock(bool block = true);

  EXPORT void read_lock_priority(bool block = true);

  /** @brief Release a read lock.
   *
   * Release a read lock.
   */
  EXPORT void read_unlock();

  /** @brief Grab a write lock
   *
   * Grab a write lock.  This will block while another writer or reader is
   * active and will have exclusive access on waking.
   *
   * @param block (For testing only) If false, will throw
   * wouldblock instead of blocking
   */
  EXPORT void write_lock(bool block = true);

  /** @brief A timed write lock.
   *
   * Queues up for the write lock for a specified amount of time.  Returns
   * true if it got the lock, return false if it timed out first.
   * If the timeout happens, it will also return the lock state if passed
   * a non-NULL LockState struct.  This is a specialization for supporting
   * the RWLockMonitor class.
   */
  EXPORT bool timed_write_lock(const struct timespec& ts, struct LockState* state = nullptr);

  /** @brief Release a write lock.
   *
   * Release a write lock.
   */
  EXPORT void write_unlock();

  /* note: these haven't been proven yet */

  /** @brief Upgrade a read lock to a write lock
   *
   * Upgrade a read lock to a write lock.  It may have to block
   * if there are other readers currently reading.  No guarantees of atomicity.
   */
  EXPORT void upgrade_to_write();

  /** @brief Downgrade a write lock to a read lock
   *
   * Downgrade a write lock to a read lock.  The conversion happens
   * atomically.
   */
  EXPORT void downgrade_to_read();

  /** @brief Reset the lock's state (Use with caution!)
   *
   * If the lock gets into a bad state in testing or something,
   * this will reset the state.
   * @warning This is safe only if there are no other threads using this
   * lock.
   */
  EXPORT void reset();

  /* These are for white box testing only */
  inline void lock()
  {
    down(MUTEX, true);
  }
  inline void unlock()
  {
    up(MUTEX);
  }
  inline int getWriting() const
  {
    return fPImpl->fState->writing;
  }
  inline int getReading() const
  {
    return fPImpl->fState->reading;
  }
  inline int getWritersWaiting() const
  {
    return fPImpl->fState->writerswaiting;
  }
  inline int getReadersWaiting() const
  {
    return fPImpl->fState->readerswaiting;
  }
  LockState getLockState();

 private:
  RWLock(const RWLock& rwl);
  RWLock& operator=(const RWLock& rwl);

  inline int getSemval(int) const
  {
    return 0;
  }
  void down(int num, bool block = true);
  bool timed_down(int num, const boost::posix_time::ptime& ts);  // to support timed_write_lock()
  void up(int num);

  RWLockShmImpl* fPImpl;
};

}  // namespace rwlock

#undef EXPORT
