/* * 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. */ #include #include #include #include #include #include using namespace folly::detail; using namespace testing; TEST(MemoryIdler, isUnmapUnusedStackAvailableInMainThread) { // presume this is the main thread EXPECT_NE(folly::kIsLinux, MemoryIdler::isUnmapUnusedStackAvailable()); } TEST(MemoryIdler, isUnmapUnusedStackAvailableInAltThread) { auto fn = [&] { EXPECT_TRUE(MemoryIdler::isUnmapUnusedStackAvailable()); }; std::thread(fn).join(); // definitely not the main thread } 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; } /// 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 { using duration = std::chrono::steady_clock::duration; using time_point = std::chrono::time_point; MOCK_METHOD(time_point, nowImpl, ()); /// Hold on to the returned shared_ptr until the end of the test static std::shared_ptr> setup() { auto rv = std::make_shared>(); s_mockClockInstance = rv; return rv; } static time_point now() { return s_mockClockInstance.lock()->nowImpl(); } static std::weak_ptr> s_mockClockInstance; }; std::weak_ptr> MockClock::s_mockClockInstance; static auto const forever = MockClock::time_point::max(); /// 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 /// /// Futex 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 MockAtom : public std::atomic { explicit MockAtom(T init = 0) : std::atomic(init) {} MOCK_METHOD(FutexResult, futexWait, (uint32_t, uint32_t), (const)); MOCK_METHOD( FutexResult, futexWaitUntil, (uint32_t, const MockClock::time_point&, uint32_t), (const)); }; FutexResult futexWait( const Futex* futex, uint32_t expected, uint32_t waitMask) { return futex->futexWait(expected, waitMask); } template FutexResult futexWaitUntil( const Futex* futex, std::uint32_t expected, std::chrono::time_point const& deadline, uint32_t waitMask) { return futex->futexWaitUntil(expected, deadline, waitMask); } TEST(MemoryIdler, futexWaitValueChangedEarly) { Futex 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_EQ( FutexResult::VALUE_CHANGED, MemoryIdler::futexWaitUntil(fut, 1, forever)); } TEST(MemoryIdler, futexWaitValueChangedLate) { Futex 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, futexWaitUntil(1, forever, -1)) .WillOnce(Return(FutexResult::VALUE_CHANGED)); EXPECT_EQ( FutexResult::VALUE_CHANGED, MemoryIdler::futexWaitUntil(fut, 1, forever)); } TEST(MemoryIdler, futexWaitAwokenEarly) { Futex 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_EQ(FutexResult::AWOKEN, MemoryIdler::futexWaitUntil(fut, 1, forever)); } TEST(MemoryIdler, futexWaitAwokenLate) { Futex 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, futexWaitUntil(1, forever, -1)) .WillOnce(Return(FutexResult::AWOKEN)); EXPECT_EQ( FutexResult::AWOKEN, MemoryIdler::futexWaitUntil(fut, 1, forever, -1, idleTimeout, 100, 0.0f)); } TEST(MemoryIdler, futexWaitImmediateFlush) { Futex fut; auto clock = MockClock::setup(); EXPECT_CALL(fut, futexWaitUntil(2, forever, 0xff)) .WillOnce(Return(FutexResult::AWOKEN)); EXPECT_EQ( FutexResult::AWOKEN, MemoryIdler::futexWaitUntil( fut, 2, forever, 0xff, std::chrono::seconds(0))); } TEST(MemoryIdler, futexWaitNeverFlush) { Futex fut; auto clock = MockClock::setup(); EXPECT_CALL(fut, futexWaitUntil(1, forever, -1)) .WillOnce(Return(FutexResult::AWOKEN)); EXPECT_EQ( FutexResult::AWOKEN, MemoryIdler::futexWaitUntil( fut, 1, forever, -1, std::chrono::seconds(-7))); }