| /* |
| * 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; |
| } |