/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozjemalloc_stall_h
#define mozjemalloc_stall_h

#include <optional>

#if defined(MOZ_MEMORY) && defined(XP_WIN)
#  include "mozmemory_wrap.h"
#endif

// See
// https://hacks.mozilla.org/2022/11/improving-firefox-stability-with-this-one-weird-trick/

namespace mozilla {

namespace detail {
// Helper for StallAndRetry error messages.
template <typename T>
constexpr bool is_std_optional = false;
template <typename T>
constexpr bool is_std_optional<std::optional<T>> = true;
}  // namespace detail

struct StallSpecs {
  // Maximum number of retry-attempts before giving up.
  size_t maxAttempts;
  // Delay time between successive events.
  size_t delayMs;

  // Retry a fallible operation until it succeeds or until we've run out of
  // retries.
  //
  // Note that this invokes `aDelayFunc` immediately upon being called! It's
  // intended for use in the unhappy path, after an initial attempt has failed.
  //
  // The function type here may be read:
  // ```
  // fn StallAndRetry<R>(
  //     delay_func: impl Fn(usize) -> (),
  //     operation: impl Fn() -> Option<R>,
  // ) -> Option<R>;
  // ```
  //
  template <typename DelayFunc, typename OpFunc>
  auto StallAndRetry(DelayFunc&& aDelayFunc, OpFunc&& aOperation) const
      -> decltype(aOperation()) {
    {
      // Explicit typecheck for OpFunc, to provide an explicit error message.
      using detail::is_std_optional;
      static_assert(is_std_optional<decltype(aOperation())>,
                    "aOperation() must return std::optional");

      // (clang's existing error messages suffice for aDelayFunc.)
    }

    for (size_t i = 0; i < maxAttempts; ++i) {
      aDelayFunc(delayMs);
      if (const auto opt = aOperation()) {
        return opt;
      }
    }
    return std::nullopt;
  }
};

#if defined(MOZ_MEMORY) && defined(XP_WIN)
MOZ_JEMALLOC_API StallSpecs GetAllocatorStallSpecs();
MOZ_JEMALLOC_API_NODISCARD void* MozVirtualAlloc(void* lpAddress, size_t dwSize,
                                                 uint32_t flAllocationType,
                                                 uint32_t flProtect);
#endif

}  // namespace mozilla

#endif  // mozjemalloc_stall_h
