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 | |
26 | using namespace folly; |
27 | using namespace folly::detail; |
28 | using namespace testing; |
29 | |
30 | TEST(MemoryIdler, releaseStack) { |
31 | MemoryIdler::unmapUnusedStack(); |
32 | } |
33 | |
34 | TEST(MemoryIdler, releaseStackMinExtra) { |
35 | MemoryIdler::unmapUnusedStack(0); |
36 | } |
37 | |
38 | TEST(MemoryIdler, releaseStackLargeExtra) { |
39 | MemoryIdler::unmapUnusedStack(30000000); |
40 | } |
41 | |
42 | TEST(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 |
56 | struct 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 | |
76 | std::weak_ptr<StrictMock<MockClock>> MockClock::s_mockClockInstance; |
77 | static 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 |
86 | template <typename T> |
87 | struct 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 | |
96 | FutexResult |
97 | futexWait(const Futex<MockAtom>* futex, uint32_t expected, uint32_t waitMask) { |
98 | return futex->futexWait(expected, waitMask); |
99 | } |
100 | template <typename Clock, typename Duration> |
101 | FutexResult 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 | |
109 | TEST(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 | |
125 | TEST(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 | |
143 | TEST(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 | |
155 | TEST(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 | |
171 | TEST(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 | |
183 | TEST(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 | |