1 /*
2 * Copyright (c) Facebook, Inc. and its affiliates.
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/ScopeGuard.h>
18
19 #include <condition_variable>
20 #include <functional>
21 #include <stdexcept>
22 #include <thread>
23
24 #include <glog/logging.h>
25
26 #include <folly/portability/GTest.h>
27
28 using folly::makeDismissedGuard;
29 using folly::makeGuard;
30 using std::vector;
31
returnsDouble()32 double returnsDouble() {
33 return 0.0;
34 }
35
36 class MyFunctor {
37 public:
MyFunctor(int * ptr)38 explicit MyFunctor(int* ptr) : ptr_(ptr) {}
39
operator ()()40 void operator()() { ++*ptr_; }
41
42 private:
43 int* ptr_;
44 };
45
TEST(ScopeGuard,DifferentWaysToBind)46 TEST(ScopeGuard, DifferentWaysToBind) {
47 {
48 // There is implicit conversion from func pointer
49 // double (*)() to function<void()>.
50 auto g = makeGuard(returnsDouble);
51 (void)g;
52 }
53
54 vector<int> v;
55 void (vector<int>::*push_back)(int const&) = &vector<int>::push_back;
56
57 v.push_back(1);
58 {
59 // binding to member function.
60 auto g = makeGuard(std::bind(&vector<int>::pop_back, &v));
61 (void)g;
62 }
63 EXPECT_EQ(0, v.size());
64
65 {
66 // bind member function with args. v is passed-by-value!
67 auto g = makeGuard(std::bind(push_back, v, 2));
68 (void)g;
69 }
70 EXPECT_EQ(0, v.size()); // push_back happened on a copy of v... fail!
71
72 // pass in an argument by pointer so to avoid copy.
73 {
74 auto g = makeGuard(std::bind(push_back, &v, 4));
75 (void)g;
76 }
77 EXPECT_EQ(1, v.size());
78
79 {
80 // pass in an argument by reference so to avoid copy.
81 auto g = makeGuard(std::bind(push_back, std::ref(v), 4));
82 (void)g;
83 }
84 EXPECT_EQ(2, v.size());
85
86 // lambda with a reference to v
87 {
88 auto g = makeGuard([&] { v.push_back(5); });
89 (void)g;
90 }
91 EXPECT_EQ(3, v.size());
92
93 // lambda with a copy of v
94 {
95 auto g = makeGuard([v]() mutable { v.push_back(6); });
96 (void)g;
97 }
98 EXPECT_EQ(3, v.size());
99
100 // functor object
101 int n = 0;
102 {
103 MyFunctor f(&n);
104 auto g = makeGuard(f);
105 (void)g;
106 }
107 EXPECT_EQ(1, n);
108
109 // temporary functor object
110 n = 0;
111 {
112 auto g = makeGuard(MyFunctor(&n));
113 (void)g;
114 }
115 EXPECT_EQ(1, n);
116
117 // Use auto instead of ScopeGuard
118 n = 2;
119 {
120 auto g = makeGuard(MyFunctor(&n));
121 (void)g;
122 }
123 EXPECT_EQ(3, n);
124
125 // Use const auto& instead of ScopeGuard
126 n = 10;
127 {
128 const auto& g = makeGuard(MyFunctor(&n));
129 (void)g;
130 }
131 EXPECT_EQ(11, n);
132 }
133
TEST(ScopeGuard,GuardException)134 TEST(ScopeGuard, GuardException) {
135 EXPECT_DEATH(
136 (void)makeGuard(
137 [] { throw std::runtime_error("dtors should never throw!"); }),
138 "dtors should never throw!");
139 }
140
141 /**
142 * Add an integer to a vector iff it was inserted into the
143 * db successfuly. Here is a schematic of how you would accomplish
144 * this with scope guard.
145 */
testUndoAction(bool failure)146 void testUndoAction(bool failure) {
147 vector<int64_t> v;
148 { // defines a "mini" scope
149
150 // be optimistic and insert this into memory
151 v.push_back(1);
152
153 // The guard is triggered to undo the insertion unless dismiss() is called.
154 auto guard = makeGuard([&] { v.pop_back(); });
155
156 // Do some action; Use the failure argument to pretend
157 // if it failed or succeeded.
158
159 // if there was no failure, dismiss the undo guard action.
160 if (!failure) {
161 guard.dismiss();
162 }
163 } // all stack allocated in the mini-scope will be destroyed here.
164
165 if (failure) {
166 EXPECT_EQ(0, v.size()); // the action failed => undo insertion
167 } else {
168 EXPECT_EQ(1, v.size()); // the action succeeded => keep insertion
169 }
170 }
171
TEST(ScopeGuard,UndoAction)172 TEST(ScopeGuard, UndoAction) {
173 testUndoAction(true);
174 testUndoAction(false);
175 }
176
TEST(ScopeGuard,MakeDismissedGuard)177 TEST(ScopeGuard, MakeDismissedGuard) {
178 auto test = [](bool shouldFire) {
179 bool fired = false;
180 {
181 auto guard = makeDismissedGuard([&] { fired = true; });
182 if (shouldFire) {
183 guard.rehire();
184 }
185 }
186 EXPECT_EQ(shouldFire, fired);
187 };
188
189 test(true);
190 test(false);
191 }
192
193 /**
194 * Sometimes in a try catch block we want to execute a piece of code
195 * regardless if an exception happened or not. For example, you want
196 * to close a db connection regardless if an exception was thrown during
197 * insertion. In Java and other languages there is a finally clause that
198 * helps accomplish this:
199 *
200 * try {
201 * dbConn.doInsert(sql);
202 * } catch (const DbException& dbe) {
203 * dbConn.recordFailure(dbe);
204 * } catch (const CriticalException& e) {
205 * throw e; // re-throw the exception
206 * } finally {
207 * dbConn.closeConnection(); // executes no matter what!
208 * }
209 *
210 * We can approximate this behavior in C++ with ScopeGuard.
211 */
212 enum class ErrorBehavior {
213 SUCCESS,
214 HANDLED_ERROR,
215 UNHANDLED_ERROR,
216 };
217
testFinally(ErrorBehavior error)218 void testFinally(ErrorBehavior error) {
219 bool cleanupOccurred = false;
220
221 try {
222 auto guard = makeGuard([&] { cleanupOccurred = true; });
223 (void)guard;
224
225 try {
226 if (error == ErrorBehavior::HANDLED_ERROR) {
227 throw std::runtime_error("throwing an expected error");
228 } else if (error == ErrorBehavior::UNHANDLED_ERROR) {
229 throw "never throw raw strings";
230 }
231 } catch (const std::runtime_error&) {
232 }
233 } catch (...) {
234 // Outer catch to swallow the error for the UNHANDLED_ERROR behavior
235 }
236
237 EXPECT_TRUE(cleanupOccurred);
238 }
239
TEST(ScopeGuard,TryCatchFinally)240 TEST(ScopeGuard, TryCatchFinally) {
241 testFinally(ErrorBehavior::SUCCESS);
242 testFinally(ErrorBehavior::HANDLED_ERROR);
243 testFinally(ErrorBehavior::UNHANDLED_ERROR);
244 }
245
TEST(ScopeGuard,TEST_SCOPE_EXIT)246 TEST(ScopeGuard, TEST_SCOPE_EXIT) {
247 int x = 0;
248 {
249 SCOPE_EXIT { ++x; };
250 EXPECT_EQ(0, x);
251 }
252 EXPECT_EQ(1, x);
253 }
254
255 class Foo {
256 public:
Foo()257 Foo() {}
~Foo()258 ~Foo() {
259 try {
260 auto e = std::current_exception();
261 int test = 0;
262 {
263 SCOPE_EXIT { ++test; };
264 EXPECT_EQ(0, test);
265 }
266 EXPECT_EQ(1, test);
267 } catch (const std::exception& ex) {
268 LOG(FATAL) << "Unexpected exception: " << ex.what();
269 }
270 }
271 };
272
TEST(ScopeGuard,TEST_SCOPE_FAILURE2)273 TEST(ScopeGuard, TEST_SCOPE_FAILURE2) {
274 try {
275 Foo f;
276 throw std::runtime_error("test");
277 } catch (...) {
278 }
279 }
280
testScopeFailAndScopeSuccess(ErrorBehavior error,bool expectFail)281 void testScopeFailAndScopeSuccess(ErrorBehavior error, bool expectFail) {
282 bool scopeFailExecuted = false;
283 bool scopeSuccessExecuted = false;
284
285 try {
286 SCOPE_FAIL { scopeFailExecuted = true; };
287 SCOPE_SUCCESS { scopeSuccessExecuted = true; };
288
289 try {
290 if (error == ErrorBehavior::HANDLED_ERROR) {
291 throw std::runtime_error("throwing an expected error");
292 } else if (error == ErrorBehavior::UNHANDLED_ERROR) {
293 throw "never throw raw strings";
294 }
295 } catch (const std::runtime_error&) {
296 }
297 } catch (...) {
298 // Outer catch to swallow the error for the UNHANDLED_ERROR behavior
299 }
300
301 EXPECT_EQ(expectFail, scopeFailExecuted);
302 EXPECT_EQ(!expectFail, scopeSuccessExecuted);
303 }
304
TEST(ScopeGuard,TEST_SCOPE_FAIL_EXCEPTION_PTR)305 TEST(ScopeGuard, TEST_SCOPE_FAIL_EXCEPTION_PTR) {
306 bool catchExecuted = false;
307 bool failExecuted = false;
308
309 try {
310 SCOPE_FAIL { failExecuted = true; };
311
312 std::exception_ptr ep;
313 try {
314 throw std::runtime_error("test");
315 } catch (...) {
316 ep = std::current_exception();
317 }
318 std::rethrow_exception(ep);
319 } catch (const std::exception&) {
320 catchExecuted = true;
321 }
322
323 EXPECT_TRUE(catchExecuted);
324 EXPECT_TRUE(failExecuted);
325 }
326
TEST(ScopeGuard,TEST_SCOPE_FAIL_AND_SCOPE_SUCCESS)327 TEST(ScopeGuard, TEST_SCOPE_FAIL_AND_SCOPE_SUCCESS) {
328 testScopeFailAndScopeSuccess(ErrorBehavior::SUCCESS, false);
329 testScopeFailAndScopeSuccess(ErrorBehavior::HANDLED_ERROR, false);
330 testScopeFailAndScopeSuccess(ErrorBehavior::UNHANDLED_ERROR, true);
331 }
332
TEST(ScopeGuard,TEST_SCOPE_SUCCESS_THROW)333 TEST(ScopeGuard, TEST_SCOPE_SUCCESS_THROW) {
334 auto lambda = []() {
335 SCOPE_SUCCESS { throw std::runtime_error("ehm"); };
336 };
337 EXPECT_THROW(lambda(), std::runtime_error);
338 }
339
TEST(ScopeGuard,TEST_THROWING_CLEANUP_ACTION)340 TEST(ScopeGuard, TEST_THROWING_CLEANUP_ACTION) {
341 struct ThrowingCleanupAction {
342 // clang-format off
343 explicit ThrowingCleanupAction(int& scopeExitExecuted)
344 : scopeExitExecuted_(scopeExitExecuted) {}
345 [[noreturn]] ThrowingCleanupAction(const ThrowingCleanupAction& other)
346 : scopeExitExecuted_(other.scopeExitExecuted_) {
347 throw std::runtime_error("whoa");
348 }
349 // clang-format on
350 void operator()() { ++scopeExitExecuted_; }
351
352 private:
353 int& scopeExitExecuted_;
354 };
355 int scopeExitExecuted = 0;
356 ThrowingCleanupAction onExit(scopeExitExecuted);
357 EXPECT_THROW((void)makeGuard(onExit), std::runtime_error);
358 EXPECT_EQ(scopeExitExecuted, 1);
359 }
360