1/*
2 * Copyright 2014-present Facebook, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <folly/detail/MemoryIdler.h>
18
19#include <folly/portability/GMock.h>
20#include <folly/portability/GTest.h>
21#include <folly/synchronization/Baton.h>
22
23#include <memory>
24#include <thread>
25
26using namespace folly;
27using namespace folly::detail;
28using namespace testing;
29
30TEST(MemoryIdler, releaseStack) {
31 MemoryIdler::unmapUnusedStack();
32}
33
34TEST(MemoryIdler, releaseStackMinExtra) {
35 MemoryIdler::unmapUnusedStack(0);
36}
37
38TEST(MemoryIdler, releaseStackLargeExtra) {
39 MemoryIdler::unmapUnusedStack(30000000);
40}
41
42TEST(MemoryIdler, releaseMallocTLS) {
43 auto p = new int[4];
44 MemoryIdler::flushLocalMallocCaches();
45 delete[] p;
46 MemoryIdler::flushLocalMallocCaches();
47 p = new int[4];
48 MemoryIdler::flushLocalMallocCaches();
49 delete[] p;
50}
51
52/// MockClock is a bit tricky because we are mocking a static function
53/// (now()), so we need to find the corresponding mock instance without
54/// extending its scope beyond that of the test. I generally avoid
55/// shared_ptr, but a weak_ptr is just the ticket here
56struct MockClock {
57 using duration = std::chrono::steady_clock::duration;
58 using time_point = std::chrono::time_point<MockClock, duration>;
59
60 MOCK_METHOD0(nowImpl, time_point());
61
62 /// Hold on to the returned shared_ptr until the end of the test
63 static std::shared_ptr<StrictMock<MockClock>> setup() {
64 auto rv = std::make_shared<StrictMock<MockClock>>();
65 s_mockClockInstance = rv;
66 return rv;
67 }
68
69 static time_point now() {
70 return s_mockClockInstance.lock()->nowImpl();
71 }
72
73 static std::weak_ptr<StrictMock<MockClock>> s_mockClockInstance;
74};
75
76std::weak_ptr<StrictMock<MockClock>> MockClock::s_mockClockInstance;
77static auto const forever = MockClock::time_point::max();
78
79/// MockedAtom gives us a way to select a mocked Futex implementation
80/// inside Baton, even though the atom itself isn't exercised by the
81/// mocked futex
82///
83/// Futex<MockAtom> is our mocked futex implementation. Note that the method
84/// signatures differ from the real Futex because we have elided unused default
85/// params and collapsed templated methods into the used type
86template <typename T>
87struct MockAtom : public std::atomic<T> {
88 explicit MockAtom(T init = 0) : std::atomic<T>(init) {}
89
90 MOCK_CONST_METHOD2(futexWait, FutexResult(uint32_t, uint32_t));
91 MOCK_CONST_METHOD3(
92 futexWaitUntil,
93 FutexResult(uint32_t, const MockClock::time_point&, uint32_t));
94};
95
96FutexResult
97futexWait(const Futex<MockAtom>* futex, uint32_t expected, uint32_t waitMask) {
98 return futex->futexWait(expected, waitMask);
99}
100template <typename Clock, typename Duration>
101FutexResult futexWaitUntil(
102 const Futex<MockAtom>* futex,
103 std::uint32_t expected,
104 std::chrono::time_point<Clock, Duration> const& deadline,
105 uint32_t waitMask) {
106 return futex->futexWaitUntil(expected, deadline, waitMask);
107}
108
109TEST(MemoryIdler, futexWaitValueChangedEarly) {
110 Futex<MockAtom> fut;
111 auto clock = MockClock::setup();
112 auto begin = MockClock::time_point(std::chrono::seconds(100));
113 auto idleTimeout = MemoryIdler::defaultIdleTimeout.load();
114
115 EXPECT_CALL(*clock, nowImpl()).WillOnce(Return(begin));
116 EXPECT_CALL(
117 fut,
118 futexWaitUntil(
119 1, AllOf(Ge(begin + idleTimeout), Lt(begin + 2 * idleTimeout)), -1))
120 .WillOnce(Return(FutexResult::VALUE_CHANGED));
121 EXPECT_EQ(
122 FutexResult::VALUE_CHANGED, MemoryIdler::futexWaitUntil(fut, 1, forever));
123}
124
125TEST(MemoryIdler, futexWaitValueChangedLate) {
126 Futex<MockAtom> fut;
127 auto clock = MockClock::setup();
128 auto begin = MockClock::time_point(std::chrono::seconds(100));
129 auto idleTimeout = MemoryIdler::defaultIdleTimeout.load();
130
131 EXPECT_CALL(*clock, nowImpl()).WillOnce(Return(begin));
132 EXPECT_CALL(
133 fut,
134 futexWaitUntil(
135 1, AllOf(Ge(begin + idleTimeout), Lt(begin + 2 * idleTimeout)), -1))
136 .WillOnce(Return(FutexResult::TIMEDOUT));
137 EXPECT_CALL(fut, futexWaitUntil(1, forever, -1))
138 .WillOnce(Return(FutexResult::VALUE_CHANGED));
139 EXPECT_EQ(
140 FutexResult::VALUE_CHANGED, MemoryIdler::futexWaitUntil(fut, 1, forever));
141}
142
143TEST(MemoryIdler, futexWaitAwokenEarly) {
144 Futex<MockAtom> fut;
145 auto clock = MockClock::setup();
146 auto begin = MockClock::time_point(std::chrono::seconds(100));
147 auto idleTimeout = MemoryIdler::defaultIdleTimeout.load();
148
149 EXPECT_CALL(*clock, nowImpl()).WillOnce(Return(begin));
150 EXPECT_CALL(fut, futexWaitUntil(1, Ge(begin + idleTimeout), -1))
151 .WillOnce(Return(FutexResult::AWOKEN));
152 EXPECT_EQ(FutexResult::AWOKEN, MemoryIdler::futexWaitUntil(fut, 1, forever));
153}
154
155TEST(MemoryIdler, futexWaitAwokenLate) {
156 Futex<MockAtom> fut;
157 auto clock = MockClock::setup();
158 auto begin = MockClock::time_point(std::chrono::seconds(100));
159 auto idleTimeout = MemoryIdler::defaultIdleTimeout.load();
160
161 EXPECT_CALL(*clock, nowImpl()).WillOnce(Return(begin));
162 EXPECT_CALL(fut, futexWaitUntil(1, begin + idleTimeout, -1))
163 .WillOnce(Return(FutexResult::TIMEDOUT));
164 EXPECT_CALL(fut, futexWaitUntil(1, forever, -1))
165 .WillOnce(Return(FutexResult::AWOKEN));
166 EXPECT_EQ(
167 FutexResult::AWOKEN,
168 MemoryIdler::futexWaitUntil(fut, 1, forever, -1, idleTimeout, 100, 0.0f));
169}
170
171TEST(MemoryIdler, futexWaitImmediateFlush) {
172 Futex<MockAtom> fut;
173 auto clock = MockClock::setup();
174
175 EXPECT_CALL(fut, futexWaitUntil(2, forever, 0xff))
176 .WillOnce(Return(FutexResult::AWOKEN));
177 EXPECT_EQ(
178 FutexResult::AWOKEN,
179 MemoryIdler::futexWaitUntil(
180 fut, 2, forever, 0xff, std::chrono::seconds(0)));
181}
182
183TEST(MemoryIdler, futexWaitNeverFlush) {
184 Futex<MockAtom> fut;
185 auto clock = MockClock::setup();
186
187 EXPECT_CALL(fut, futexWaitUntil(1, forever, -1))
188 .WillOnce(Return(FutexResult::AWOKEN));
189 EXPECT_EQ(
190 FutexResult::AWOKEN,
191 MemoryIdler::futexWaitUntil(
192 fut, 1, forever, -1, std::chrono::seconds(-7)));
193}
194