/*
 * 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 <cassert>
#include <cstdlib>
#include <type_traits>
#include <utility>

namespace folly {

/**
 * A helper class to allow a DelayedDestruction object to be instantiated on
 * the stack.
 *
 * This class derives from an existing DelayedDestruction type and makes the
 * destructor public again.  This allows objects of this type to be declared on
 * the stack or directly inside another class.  Normally DelayedDestruction
 * objects must be dynamically allocated on the heap.
 *
 * However, the trade-off is that you lose some of the protections provided by
 * DelayedDestruction::destroy().  DelayedDestruction::destroy() will
 * automatically delay destruction of the object until it is safe to do so.
 * If you use UndelayedDestruction, you become responsible for ensuring that
 * you only destroy the object where it is safe to do so.  Attempting to
 * destroy a UndelayedDestruction object while it has a non-zero destructor
 * guard count will abort the program.
 */
template <typename TDD>
class UndelayedDestruction : public TDD {
 public:
  // We could just use constructor inheritance, but not all compilers
  // support that. So, just use a forwarding constructor.
  //
  // Ideally we would use std::enable_if<> and std::is_constructible<> to
  // provide only constructor methods that are valid for our parent class.
  // Unfortunately std::is_constructible<> doesn't work for types that aren't
  // destructible.  In gcc-4.6 it results in a compiler error.  In the latest
  // gcc code it looks like it has been fixed to return false.  (The language
  // in the standard seems to indicate that returning false is the correct
  // behavior for non-destructible types, which is unfortunate.)
  template <typename... Args>
  explicit UndelayedDestruction(Args&&... args)
      : TDD(std::forward<Args>(args)...) {}

  /**
   * Public destructor.
   *
   * The caller is responsible for ensuring that the object is only destroyed
   * where it is safe to do so.  (i.e., when the destructor guard count is 0).
   *
   * The exact conditions for meeting this may be dependent upon your class
   * semantics.  Typically you are only guaranteed that it is safe to destroy
   * the object directly from the event loop (e.g., directly from a
   * EventBase::LoopCallback), or when the event loop is stopped.
   */
  ~UndelayedDestruction() override {
    // Crash if the caller is destroying us with outstanding destructor guards.
    if (this->getDestructorGuardCount() != 0) {
      abort();
    }
    // Invoke destroy.  This is necessary since our base class may have
    // implemented custom behavior in destroy().
    this->destroy();
  }

  void onDelayedDestroy(bool delayed) override {
    if (delayed && !this->TDD::getDestroyPending()) {
      return;
    }
    // Do nothing.  This will always be invoked from the call to destroy
    // inside our destructor.
    assert(!delayed);
    // prevent unused variable warnings when asserts are compiled out.
    (void)delayed;
  }

 protected:
  /**
   * Override our parent's destroy() method to make it protected.
   * Callers should use the normal destructor instead of destroy
   */
  void destroy() override { this->TDD::destroy(); }

 private:
  // Forbidden copy constructor and assignment operator
  UndelayedDestruction(UndelayedDestruction const&) = delete;
  UndelayedDestruction& operator=(UndelayedDestruction const&) = delete;
};

} // namespace folly
