mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 16:22:00 +00:00
Bug 1804499 - [3/4] Expose stall-and-retry code beyond mozjemalloc.cpp r=glandium
Extract the stall-and-retry logic from mozjemalloc into a header. Write a unit test for it. Differential Revision: https://phabricator.services.mozilla.com/D164107
This commit is contained in:
parent
2696c25e5c
commit
0336c0891c
@ -8,6 +8,7 @@ EXPORTS += [
|
||||
"malloc_decls.h",
|
||||
"mozjemalloc_types.h",
|
||||
"mozmemory.h",
|
||||
"mozmemory_utils.h",
|
||||
"mozmemory_wrap.h",
|
||||
]
|
||||
|
||||
@ -60,3 +61,5 @@ if CONFIG["CC_TYPE"] == "clang-cl":
|
||||
AllowCompilerWarnings() # workaround for bug 1090497
|
||||
|
||||
REQUIRES_UNIFIED_BUILD = True
|
||||
|
||||
TEST_DIRS += ["test"]
|
||||
|
@ -164,8 +164,8 @@
|
||||
#include "Mutex.h"
|
||||
#include "Utils.h"
|
||||
|
||||
// For GetGeckoProcessType(), when it's used.
|
||||
#if defined(XP_WIN) && !defined(JS_STANDALONE)
|
||||
# include "mozmemory_utils.h"
|
||||
# include "mozilla/ProcessType.h"
|
||||
#endif
|
||||
|
||||
@ -1474,60 +1474,6 @@ static inline void ApplyZeroOrJunk(void* aPtr, size_t aSize) {
|
||||
// On Windows, delay crashing on OOM.
|
||||
#ifdef XP_WIN
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
// Implementation of VirtualAlloc wrapper (bug 1716727).
|
||||
namespace MozAllocRetries {
|
||||
|
||||
|
67
memory/build/mozmemory_utils.h
Normal file
67
memory/build/mozmemory_utils.h
Normal file
@ -0,0 +1,67 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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_utils_h
|
||||
#define mozjemalloc_utils_h
|
||||
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozjemalloc_utils_h
|
152
memory/build/test/TestMozJemallocUtils.cpp
Normal file
152
memory/build/test/TestMozJemallocUtils.cpp
Normal file
@ -0,0 +1,152 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
// This is a cppunittest, rather than a gtest, in order to assert that no
|
||||
// additional DLL needs to be linked in to use the function(s) tested herein.
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
|
||||
#include "mozmemory_utils.h"
|
||||
#include "mozilla/Likely.h"
|
||||
|
||||
static bool TESTS_FAILED = false;
|
||||
|
||||
// Introduce iostream output operators for std::optional, for convenience's
|
||||
// sake.
|
||||
//
|
||||
// (This is technically undefined behavior per [namespace.std], but it's
|
||||
// unlikely to have any surprising effects when confined to this compilation
|
||||
// unit.)
|
||||
namespace std {
|
||||
template <typename T>
|
||||
std::ostream& operator<<(std::ostream& o, std::optional<T> const& s) {
|
||||
if (s) {
|
||||
return o << "std::optional{" << s.value() << "}";
|
||||
}
|
||||
return o << "std::nullopt";
|
||||
}
|
||||
std::ostream& operator<<(std::ostream& o, std::nullopt_t const& s) {
|
||||
return o << "std::nullopt";
|
||||
}
|
||||
} // namespace std
|
||||
|
||||
// EXPECT_EQ
|
||||
//
|
||||
// Assert that two expressions are equal. Print them, and their values, on
|
||||
// failure. (Based on the GTest macro of the same name.)
|
||||
template <typename X, typename Y, size_t Xn, size_t Yn>
|
||||
void AssertEqualImpl_(X&& x, Y&& y, const char* file, size_t line,
|
||||
const char (&xStr)[Xn], const char (&yStr)[Yn],
|
||||
const char* explanation = nullptr) {
|
||||
if (MOZ_LIKELY(x == y)) return;
|
||||
|
||||
TESTS_FAILED = true;
|
||||
|
||||
std::stringstream sstr;
|
||||
sstr << file << ':' << line << ": ";
|
||||
if (explanation) sstr << explanation << "\n\t";
|
||||
sstr << "expected " << xStr << " (" << x << ") == " << yStr << " (" << y
|
||||
<< ")\n";
|
||||
std::cerr << sstr.str() << std::flush;
|
||||
}
|
||||
|
||||
#define EXPECT_EQ(x, y) \
|
||||
do { \
|
||||
AssertEqualImpl_(x, y, __FILE__, __LINE__, #x, #y); \
|
||||
} while (0)
|
||||
|
||||
// STATIC_ASSERT_VALUE_IS_OF_TYPE
|
||||
//
|
||||
// Assert that a value `v` is of type `t` (ignoring cv-qualification).
|
||||
#define STATIC_ASSERT_VALUE_IS_OF_TYPE(v, t) \
|
||||
static_assert(std::is_same_v<std::remove_cv_t<decltype(v)>, t>)
|
||||
|
||||
// MockSleep
|
||||
//
|
||||
// Mock replacement for ::Sleep that merely logs its calls.
|
||||
struct MockSleep {
|
||||
size_t calls = 0;
|
||||
size_t sum = 0;
|
||||
|
||||
void operator()(size_t val) {
|
||||
++calls;
|
||||
sum += val;
|
||||
}
|
||||
|
||||
bool operator==(MockSleep const& that) const {
|
||||
return calls == that.calls && sum == that.sum;
|
||||
}
|
||||
};
|
||||
std::ostream& operator<<(std::ostream& o, MockSleep const& s) {
|
||||
return o << "MockSleep { count: " << s.calls << ", sum: " << s.sum << " }";
|
||||
}
|
||||
|
||||
// MockAlloc
|
||||
//
|
||||
// Mock memory allocation mechanism. Eventually returns a value.
|
||||
template <typename T>
|
||||
struct MockAlloc {
|
||||
size_t count;
|
||||
T value;
|
||||
|
||||
std::optional<T> operator()() {
|
||||
if (!count--) return value;
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
int main() {
|
||||
using mozilla::StallSpecs;
|
||||
|
||||
const StallSpecs stall = {.maxAttempts = 10, .delayMs = 50};
|
||||
|
||||
// semantic test: stalls as requested but still yields a value,
|
||||
// up until it doesn't
|
||||
for (size_t i = 0; i < 20; ++i) {
|
||||
MockSleep sleep;
|
||||
auto const ret =
|
||||
stall.StallAndRetry(sleep, MockAlloc<int>{.count = i, .value = 5});
|
||||
STATIC_ASSERT_VALUE_IS_OF_TYPE(ret, std::optional<int>);
|
||||
|
||||
if (i < 10) {
|
||||
EXPECT_EQ(ret, std::optional<int>(5));
|
||||
} else {
|
||||
EXPECT_EQ(ret, std::nullopt);
|
||||
}
|
||||
size_t const expectedCalls = std::min<size_t>(i + 1, 10);
|
||||
EXPECT_EQ(sleep,
|
||||
(MockSleep{.calls = expectedCalls, .sum = 50 * expectedCalls}));
|
||||
}
|
||||
|
||||
// syntactic test: inline capturing lambda is accepted for aOperation
|
||||
{
|
||||
MockSleep sleep;
|
||||
std::optional<int> value{42};
|
||||
auto const ret = stall.StallAndRetry(sleep, [&]() { return value; });
|
||||
|
||||
STATIC_ASSERT_VALUE_IS_OF_TYPE(ret, std::optional<int>);
|
||||
EXPECT_EQ(ret, std::optional(42));
|
||||
EXPECT_EQ(sleep, (MockSleep{.calls = 1, .sum = 50}));
|
||||
}
|
||||
|
||||
// syntactic test: inline capturing lambda is accepted for aDelayFunc
|
||||
{
|
||||
MockSleep sleep;
|
||||
auto const ret =
|
||||
stall.StallAndRetry([&](size_t time) { sleep(time); },
|
||||
MockAlloc<int>{.count = 0, .value = 105});
|
||||
|
||||
STATIC_ASSERT_VALUE_IS_OF_TYPE(ret, std::optional<int>);
|
||||
EXPECT_EQ(ret, std::optional(105));
|
||||
EXPECT_EQ(sleep, (MockSleep{.calls = 1, .sum = 50}));
|
||||
}
|
||||
|
||||
return TESTS_FAILED ? 1 : 0;
|
||||
}
|
17
memory/build/test/moz.build
Normal file
17
memory/build/test/moz.build
Normal file
@ -0,0 +1,17 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
# We don't link these tests against mozglue, but we do use the STL. Avoid the
|
||||
# implicit linking of `__imp_moz_xalloc` in our STL wrappers.
|
||||
DisableStlWrapping()
|
||||
|
||||
# Important: for these tests to be run, they also need to be added
|
||||
# to testing/cppunittest.ini.
|
||||
CppUnitTests(
|
||||
[
|
||||
"TestMozJemallocUtils",
|
||||
]
|
||||
)
|
@ -48,6 +48,7 @@ skip-if = os != 'win'
|
||||
skip-if = os != 'linux'
|
||||
[TestMMPolicy]
|
||||
skip-if = os != 'win'
|
||||
[TestMozJemallocUtils]
|
||||
[TestNativeNt]
|
||||
skip-if = os != 'win'
|
||||
[TestUriValidation]
|
||||
|
Loading…
Reference in New Issue
Block a user