blob: c94bb43b99fa5881d5b1d8e0229e0936ef19cbb5 [file] [log] [blame]
/*
* Copyright 2015 Facebook, Inc.
*
* 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.
*/
#include <folly/detail/MemoryIdler.h>
#include <folly/Baton.h>
#include <memory>
#include <thread>
#include <assert.h>
#include <semaphore.h>
#include <gflags/gflags.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <folly/Benchmark.h>
using namespace folly;
using namespace folly::detail;
using namespace testing;
TEST(MemoryIdler, releaseStack) {
MemoryIdler::unmapUnusedStack();
}
TEST(MemoryIdler, releaseStackMinExtra) {
MemoryIdler::unmapUnusedStack(0);
}
TEST(MemoryIdler, releaseStackLargeExtra) {
MemoryIdler::unmapUnusedStack(30000000);
}
TEST(MemoryIdler, releaseMallocTLS) {
auto p = new int[4];
MemoryIdler::flushLocalMallocCaches();
delete[] p;
MemoryIdler::flushLocalMallocCaches();
p = new int[4];
MemoryIdler::flushLocalMallocCaches();
delete[] p;
}
/// MockedAtom gives us a way to select a mocked Futex implementation
/// inside Baton, even though the atom itself isn't exercised by the
/// mocked futex
template <typename T>
struct MockAtom : public std::atomic<T> {
explicit MockAtom(T init = 0) : std::atomic<T>(init) {}
};
/// MockClock is a bit tricky because we are mocking a static function
/// (now()), so we need to find the corresponding mock instance without
/// extending its scope beyond that of the test. I generally avoid
/// shared_ptr, but a weak_ptr is just the ticket here
struct MockClock {
typedef std::chrono::steady_clock::duration duration;
typedef std::chrono::steady_clock::time_point time_point;
MOCK_METHOD0(nowImpl, time_point(void));
/// Hold on to the returned shared_ptr until the end of the test
static std::shared_ptr<StrictMock<MockClock>> setup() {
auto rv = std::make_shared<StrictMock<MockClock>>();
s_mockClockInstance = rv;
return rv;
}
static time_point now() {
return s_mockClockInstance.lock()->nowImpl();
}
static std::weak_ptr<StrictMock<MockClock>> s_mockClockInstance;
};
std::weak_ptr<StrictMock<MockClock>> MockClock::s_mockClockInstance;
namespace folly { namespace detail {
/// Futex<MockAtom> is our mocked futex implementation. Note that the
/// method signatures differ from the real Futex because we have elided
/// unused default params and collapsed templated methods into the
/// used type
template<>
struct Futex<MockAtom> {
MOCK_METHOD2(futexWait, bool(uint32_t, uint32_t));
MOCK_METHOD3(futexWaitUntil,
FutexResult(uint32_t, const MockClock::time_point&, uint32_t));
};
}}
TEST(MemoryIdler, futexWaitValueChangedEarly) {
StrictMock<Futex<MockAtom>> fut;
auto clock = MockClock::setup();
auto begin = MockClock::time_point(std::chrono::seconds(100));
auto idleTimeout = MemoryIdler::defaultIdleTimeout.load();
EXPECT_CALL(*clock, nowImpl())
.WillOnce(Return(begin));
EXPECT_CALL(fut, futexWaitUntil(1, AllOf(Ge(begin + idleTimeout),
Lt(begin + 2 * idleTimeout)), -1))
.WillOnce(Return(FutexResult::VALUE_CHANGED));
EXPECT_FALSE((MemoryIdler::futexWait<MockAtom, MockClock>(fut, 1)));
}
TEST(MemoryIdler, futexWaitValueChangedLate) {
StrictMock<Futex<MockAtom>> fut;
auto clock = MockClock::setup();
auto begin = MockClock::time_point(std::chrono::seconds(100));
auto idleTimeout = MemoryIdler::defaultIdleTimeout.load();
EXPECT_CALL(*clock, nowImpl())
.WillOnce(Return(begin));
EXPECT_CALL(fut, futexWaitUntil(1, AllOf(Ge(begin + idleTimeout),
Lt(begin + 2 * idleTimeout)), -1))
.WillOnce(Return(FutexResult::TIMEDOUT));
EXPECT_CALL(fut, futexWait(1, -1))
.WillOnce(Return(false));
EXPECT_FALSE((MemoryIdler::futexWait<MockAtom, MockClock>(fut, 1)));
}
TEST(MemoryIdler, futexWaitAwokenEarly) {
StrictMock<Futex<MockAtom>> fut;
auto clock = MockClock::setup();
auto begin = MockClock::time_point(std::chrono::seconds(100));
auto idleTimeout = MemoryIdler::defaultIdleTimeout.load();
EXPECT_CALL(*clock, nowImpl())
.WillOnce(Return(begin));
EXPECT_CALL(fut, futexWaitUntil(1, Ge(begin + idleTimeout), -1))
.WillOnce(Return(FutexResult::AWOKEN));
EXPECT_TRUE((MemoryIdler::futexWait<MockAtom, MockClock>(fut, 1)));
}
TEST(MemoryIdler, futexWaitAwokenLate) {
StrictMock<Futex<MockAtom>> fut;
auto clock = MockClock::setup();
auto begin = MockClock::time_point(std::chrono::seconds(100));
auto idleTimeout = MemoryIdler::defaultIdleTimeout.load();
EXPECT_CALL(*clock, nowImpl())
.WillOnce(Return(begin));
EXPECT_CALL(fut, futexWaitUntil(1, begin + idleTimeout, -1))
.WillOnce(Return(FutexResult::TIMEDOUT));
EXPECT_CALL(fut, futexWait(1, -1))
.WillOnce(Return(true));
EXPECT_TRUE((MemoryIdler::futexWait<MockAtom, MockClock>(
fut, 1, -1, idleTimeout, 100, 0.0f)));
}
TEST(MemoryIdler, futexWaitImmediateFlush) {
StrictMock<Futex<MockAtom>> fut;
auto clock = MockClock::setup();
EXPECT_CALL(fut, futexWait(2, 0xff))
.WillOnce(Return(true));
EXPECT_TRUE((MemoryIdler::futexWait<MockAtom, MockClock>(
fut, 2, 0xff, std::chrono::seconds(0))));
}
TEST(MemoryIdler, futexWaitNeverFlush) {
StrictMock<Futex<MockAtom>> fut;
auto clock = MockClock::setup();
EXPECT_CALL(fut, futexWait(1, -1))
.WillOnce(Return(true));
EXPECT_TRUE((MemoryIdler::futexWait<MockAtom, MockClock>(
fut, 1, -1, MockClock::duration::max())));
}
BENCHMARK(releaseStack, iters) {
for (size_t i = 0; i < iters; ++i) {
MemoryIdler::unmapUnusedStack();
}
}
BENCHMARK(releaseMallocTLS, iters) {
for (size_t i = 0; i < iters; ++i) {
MemoryIdler::flushLocalMallocCaches();
}
}
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
gflags::ParseCommandLineFlags(&argc, &argv, true);
auto rv = RUN_ALL_TESTS();
if (!rv && FLAGS_benchmark) {
folly::runBenchmarks();
}
return rv;
}