/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <folly/CppAttributes.h>
#include <folly/Function.h>

#include <atomic>
#include <memory>
#include <thread>
#include <type_traits>

namespace folly {

class CancellationCallback;
class CancellationSource;
struct OperationCancelled : public std::exception {
  const char* what() const noexcept override {
    return "coroutine operation cancelled";
  }
};

namespace detail {
class CancellationState;
struct CancellationStateTokenDeleter {
  void operator()(CancellationState*) noexcept;
};
struct CancellationStateSourceDeleter {
  void operator()(CancellationState*) noexcept;
};
using CancellationStateTokenPtr =
    std::unique_ptr<CancellationState, CancellationStateTokenDeleter>;
using CancellationStateSourcePtr =
    std::unique_ptr<CancellationState, CancellationStateSourceDeleter>;
template <typename...>
struct WithDataTag;
} // namespace detail

/**
 * A CancellationToken is an object that can be passed into an function or
 * operation that allows the caller to later request that the operation be
 * cancelled.
 *
 * A CancellationToken object can be obtained by calling the .getToken()
 * method on a CancellationSource or by copying another CancellationToken
 * object. All CancellationToken objects obtained from the same original
 * CancellationSource object all reference the same underlying cancellation
 * state and will all be cancelled together.
 *
 * If your function needs to be cancellable but does not need to request
 * cancellation then you should take a CancellationToken as a parameter.
 * If your function needs to be able to request cancellation then you
 * should instead take a CancellationSource as a parameter.
 *
 * @refcode folly/docs/examples/folly/CancellationToken.cpp
 * @class folly::CancellationToken
 */
class CancellationToken {
 public:
  /**
   * Constructs to a token that can never be cancelled.
   *
   * Pass a default-constructed CancellationToken into an operation that
   * you never intend to cancel. These objects are very cheap to create.
   */
  CancellationToken() noexcept = default;

  /// Construct a copy of the token that shares the same underlying state.
  CancellationToken(const CancellationToken& other) noexcept;

  /// Construct a token by moving the underlying state
  CancellationToken(CancellationToken&& other) noexcept;

  CancellationToken& operator=(const CancellationToken& other) noexcept;
  CancellationToken& operator=(CancellationToken&& other) noexcept;

  /**
   * Query whether someone has called .requestCancellation() on an instance
   * of CancellationSource object associated with this CancellationToken.
   */
  bool isCancellationRequested() const noexcept;

  /**
   * Query whether this CancellationToken can ever have cancellation requested
   * on it.
   *
   * This will return false if the CancellationToken is not associated with a
   * CancellationSource object. eg. because the CancellationToken was
   * default-constructed, has been moved-from or because the last
   * CancellationSource object associated with the underlying cancellation state
   * has been destroyed and the operation has not yet been cancelled and so
   * never will be.
   *
   * Implementations of operations may be able to take more efficient code-paths
   * if they know they can never be cancelled.
   */
  bool canBeCancelled() const noexcept;

  /**
   * Obtain a CancellationToken linked to any number of other
   * CancellationTokens.
   *
   * This token will have cancellation requested when any of the passed-in
   * tokens do.
   * This token is cancellable if any of the passed-in tokens are at the time of
   * construction.
   */
  template <typename... Ts>
  static CancellationToken merge(Ts&&... tokens);

  /**
   * Swaps the underlying state of the cancellation token with the token that is
   * passed-in.
   */
  void swap(CancellationToken& other) noexcept;

  friend bool operator==(
      const CancellationToken& a, const CancellationToken& b) noexcept;

 private:
  friend class CancellationCallback;
  friend class CancellationSource;

  explicit CancellationToken(detail::CancellationStateTokenPtr state) noexcept;

  detail::CancellationStateTokenPtr state_;
};

bool operator==(
    const CancellationToken& a, const CancellationToken& b) noexcept;
bool operator!=(
    const CancellationToken& a, const CancellationToken& b) noexcept;

/**
 * A CancellationSource object provides the ability to request cancellation of
 * operations that an associated CancellationToken was passed to.
 *
 * @refcode folly/docs/examples/folly/CancellationSource.cpp
 * @class folly::CancellationSource
 */
// Example usage:
//   CancellationSource cs;
//   Future<void> f = startSomeOperation(cs.getToken());
//
//   // Later...
//   cs.requestCancellation();
class CancellationSource {
 public:
  /// Construct to a new, independent cancellation source.
  CancellationSource();

  /**
   * Construct a new reference to the same underlying cancellation state.
   *
   * Either the original or the new copy can be used to request cancellation
   * of associated work.
   */
  CancellationSource(const CancellationSource& other) noexcept;

  /**
   * This leaves 'other' in an empty state where 'requestCancellation()' is a
   * no-op and 'canBeCancelled()' returns false.
   */
  CancellationSource(CancellationSource&& other) noexcept;

  CancellationSource& operator=(const CancellationSource& other) noexcept;
  CancellationSource& operator=(CancellationSource&& other) noexcept;

  /**
   * Construct a CancellationSource that cannot be cancelled.
   *
   * This factory function can be used to obtain a CancellationSource that
   * is equivalent to a moved-from CancellationSource object without needing
   * to allocate any shared-state.
   */
  static CancellationSource invalid() noexcept;

  /**
   * Query if cancellation has already been requested on this CancellationSource
   * or any other CancellationSource object copied from the same original
   * CancellationSource object.
   */
  bool isCancellationRequested() const noexcept;

  /**
   * Query if cancellation can be requested through this CancellationSource
   * object. This will only return false if the CancellationSource object has
   * been moved-from.
   */
  bool canBeCancelled() const noexcept;

  /**
   * Obtain a CancellationToken linked to this CancellationSource.
   *
   * This token can be passed into cancellable operations to allow the caller
   * to later request cancellation of that operation.
   */
  CancellationToken getToken() const noexcept;

  /**
   * Request cancellation of work associated with this CancellationSource.
   *
   * This will ensure subsequent calls to isCancellationRequested() on any
   * CancellationSource or CancellationToken object associated with the same
   * underlying cancellation state to return true.
   *
   * If this is the first call to requestCancellation() on any
   * CancellationSource object with the same underlying state then this call
   * will also execute the callbacks associated with any CancellationCallback
   * objects that were constructed with an associated CancellationToken.
   *
   * Note that it is possible that another thread may be concurrently
   * registering a callback with CancellationCallback. This method guarantees
   * that either this thread will see the callback registration and will
   * ensure that the callback is called, or the CancellationCallback constructor
   * will see the cancellation-requested signal and will execute the callback
   * inline inside the constructor.
   *
   * Returns the previous state of 'isCancellationRequested()'. i.e.
   *  - 'true' if cancellation had previously been requested.
   *  - 'false' if this was the first call to request cancellation.
   */
  bool requestCancellation() const noexcept;

  /**
   * Swaps the underlying state of the cancellation source with the source that
   * is passed-in.
   *
   * @param other The other cancellation source to copy the underlying state
   * from.
   */
  void swap(CancellationSource& other) noexcept;

  friend bool operator==(
      const CancellationSource& a, const CancellationSource& b) noexcept;

  /**
   * Returns a pair of <CancellationSource, Data> where the underlying state is
   * created using the arguments that is passed-in.
   */
  template <typename... Data, typename... Args>
  static std::pair<CancellationSource, std::tuple<Data...>*> create(
      detail::WithDataTag<Data...>, Args&&...);

 private:
  explicit CancellationSource(
      detail::CancellationStateSourcePtr&& state) noexcept;

  detail::CancellationStateSourcePtr state_;
};

bool operator==(
    const CancellationSource& a, const CancellationSource& b) noexcept;
bool operator!=(
    const CancellationSource& a, const CancellationSource& b) noexcept;

/**
 * A CancellationCallback object registers the callback with the specified
 * CancellationToken such that the callback will be
 * executed if the corresponding CancellationSource object has the
 * requestCancellation() method called on it.
 *
 * If the CancellationToken object already had cancellation requested
 * then the callback will be executed inline on the current thread before
 * the constructor returns. Otherwise, the callback will be executed on
 * in the execution context of the first thread to call requestCancellation()
 * on a corresponding CancellationSource.
 *
 * The callback object must not throw any unhandled exceptions. Doing so
 * will result in the program terminating via std::terminate().
 *
 * A CancellationCallback object is neither copyable nor movable.
 *
 * @refcode folly/docs/examples/folly/CancellationCallback.cpp
 * @class folly::CancellationCallback
 */
class CancellationCallback {
  using VoidFunction = folly::Function<void()>;

 public:
  template <
      typename Callable,
      std::enable_if_t<
          std::is_constructible<VoidFunction, Callable>::value,
          int> = 0>
  CancellationCallback(CancellationToken&& ct, Callable&& callable);
  template <
      typename Callable,
      std::enable_if_t<
          std::is_constructible<VoidFunction, Callable>::value,
          int> = 0>
  CancellationCallback(const CancellationToken& ct, Callable&& callable);

  /**
   * Deregisters the callback from the CancellationToken.
   *
   * If cancellation has been requested concurrently on another thread and the
   * callback is currently executing then the destructor will block until after
   * the callback has returned (otherwise it might be left with a dangling
   * reference).
   *
   * You should generally try to implement your callback functions to be lock
   * free to avoid deadlocks between the callback executing and the
   * CancellationCallback destructor trying to deregister the callback.
   *
   * If the callback has not started executing yet then the callback will be
   * deregistered from the CancellationToken before the destructor completes.
   *
   * Once the destructor returns you can be guaranteed that the callback will
   * not be called by a subsequent call to 'requestCancellation()' on a
   * CancellationSource associated with the CancellationToken passed to the
   * constructor.
   */
  ~CancellationCallback();

  // Not copyable/movable
  CancellationCallback(const CancellationCallback&) = delete;
  CancellationCallback(CancellationCallback&&) = delete;
  CancellationCallback& operator=(const CancellationCallback&) = delete;
  CancellationCallback& operator=(CancellationCallback&&) = delete;

 private:
  friend class detail::CancellationState;

  void invokeCallback() noexcept;

  CancellationCallback* next_;

  // Pointer to the pointer that points to this node in the linked list.
  // This could be the 'next_' of a previous CancellationCallback or could
  // be the 'head_' pointer of the CancellationState.
  // If this node is inserted in the list then this will be non-null.
  CancellationCallback** prevNext_;

  detail::CancellationState* state_;
  VoidFunction callback_;

  // Pointer to a flag stored on the stack of the caller to invokeCallback()
  // that is used to indicate to the caller of invokeCallback() that the
  // destructor has run and it is no longer valid to access the callback
  // object.
  bool* destructorHasRunInsideCallback_;

  // Flag used to signal that the callback has completed executing on another
  // thread and it is now safe to exit the destructor.
  std::atomic<bool> callbackCompleted_;
};

} // namespace folly

#include <folly/CancellationToken-inl.h>
