Bug 1180299 - Implement ScopeExit for running actions at the end of a scope, r=Waldo
authorSteve Fink <sfink@mozilla.com>
Fri, 03 Jul 2015 14:20:55 -0700
changeset 251982 2f9998ce7db7f16745c30d86e7cc7c8dde12e42d
parent 251932 803a6e0978273b5cfbb4e4ec48fcee189b47e366
child 251983 67f475daf729ca99a71abacd14409422d0241ac6
push id13940
push userryanvm@gmail.com
push dateThu, 09 Jul 2015 17:53:51 +0000
treeherderfx-team@db100b3f4cbf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo
bugs1180299
milestone42.0a1
Bug 1180299 - Implement ScopeExit for running actions at the end of a scope, r=Waldo
mfbt/ScopeExit.h
mfbt/moz.build
mfbt/tests/TestScopeExit.cpp
mfbt/tests/moz.build
new file mode 100644
--- /dev/null
+++ b/mfbt/ScopeExit.h
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* RAII class for executing arbitrary actions at scope end. */
+
+#ifndef mozilla_ScopeExit_h
+#define mozilla_ScopeExit_h
+
+/*
+ * See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189.pdf for a
+ * standards-track version of this.
+ *
+ * Error handling can be complex when various actions need to be performed that
+ * need to be undone if an error occurs midway. This can be handled with a
+ * collection of boolean state variables and gotos, which can get clunky and
+ * error-prone:
+ *
+ * {
+ *   if (!a.setup())
+ *       goto fail;
+ *   isASetup = true;
+ *
+ *   if (!b.setup())
+ *       goto fail;
+ *   isBSetup = true;
+ *
+ *   ...
+ *   return true;
+ *
+ *   fail:
+ *     if (isASetup)
+ *         a.teardown();
+ *     if (isBSetup)
+ *         b.teardown();
+ *     return false;
+ *  }
+ *
+ * ScopeExit is a mechanism to simplify this pattern by keeping an RAII guard
+ * class that will perform the teardown on destruction, unless released. So the
+ * above would become:
+ *
+ * {
+ *   if (!a.setup()) {
+ *       return false;
+ *   }
+ *   auto guardA = MakeScopeExit([&] {
+ *       a.teardown();
+ *   });
+ *
+ *   if (!b.setup()) {
+ *       return false;
+ *   }
+ *   auto guardB = MakeScopeExit([&] {
+ *       b.teardown();
+ *   });
+ *
+ *   ...
+ *   guardA.release();
+ *   guardB.release();
+ *   return true;
+ * }
+ *
+ * This header provides:
+ *
+ * - |ScopeExit| - a container for a cleanup call, automically called at the
+ *   end of the scope;
+ * - |MakeScopeExit| - a convenience function for constructing a |ScopeExit|
+ *   with a given cleanup routine, commonly used with a lambda function.
+ *
+ * Note that the RAII classes defined in this header do _not_ perform any form
+ * of reference-counting or garbage-collection. These classes have exactly two
+ * behaviors:
+ *
+ * - if |release()| has not been called, the cleanup is always performed at
+ *   the end of the scope;
+ * - if |release()| has been called, nothing will happen at the end of the
+ *   scope.
+ */
+
+#include "mozilla/GuardObjects.h"
+#include "mozilla/Move.h"
+
+namespace mozilla {
+
+template <typename ExitFunction>
+class ScopeExit {
+  ExitFunction mExitFunction;
+  bool mExecuteOnDestruction;
+  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+
+public:
+  explicit ScopeExit(ExitFunction&& cleanup
+                     MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+   : mExitFunction(cleanup)
+   , mExecuteOnDestruction(true)
+  {
+    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+  }
+
+  ScopeExit(ScopeExit&& rhs)
+   : mExitFunction(mozilla::Move(rhs.mExitFunction))
+   , mExecuteOnDestruction(rhs.mExecuteOnDestruction)
+  {
+    rhs.release();
+  }
+
+  ~ScopeExit() {
+    if (mExecuteOnDestruction) {
+      mExitFunction();
+    }
+  }
+
+  void release() {
+    mExecuteOnDestruction = false;
+  }
+
+private:
+  explicit ScopeExit(const ScopeExit&) = delete;
+  ScopeExit& operator=(const ScopeExit&) = delete;
+  ScopeExit& operator=(ScopeExit&&) = delete;
+};
+
+template <typename ExitFunction>
+ScopeExit<ExitFunction>
+MakeScopeExit(ExitFunction&& exitFunction)
+{
+  return ScopeExit<ExitFunction>(mozilla::Move(exitFunction));
+}
+
+} /* namespace mozilla */
+
+#endif /* mozilla_ScopeExit_h */
--- a/mfbt/moz.build
+++ b/mfbt/moz.build
@@ -66,16 +66,17 @@ EXPORTS.mozilla = [
     'RangedArray.h',
     'RangedPtr.h',
     'RefCountType.h',
     'ReentrancyGuard.h',
     'RefPtr.h',
     'ReverseIterator.h',
     'RollingMean.h',
     'Scoped.h',
+    'ScopeExit.h',
     'SegmentedVector.h',
     'SHA1.h',
     'SizePrintfMacros.h',
     'Snprintf.h',
     'SplayTree.h',
     'TaggedAnonymousMemory.h',
     'TemplateLib.h',
     'ThreadLocal.h',
new file mode 100644
--- /dev/null
+++ b/mfbt/tests/TestScopeExit.cpp
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Assertions.h"
+#include "mozilla/ScopeExit.h"
+
+using mozilla::MakeScopeExit;
+
+#define CHECK(c) \
+  do { \
+    bool cond = !!(c); \
+    MOZ_RELEASE_ASSERT(cond, "Failed assertion: " #c); \
+    if (!cond) { \
+      return false; \
+    } \
+  } while (false)
+
+static bool
+Test()
+{
+  int a = 1;
+  int b = 1;
+
+  {
+    a++;
+    auto guardA = MakeScopeExit([&] {
+      a--;
+    });
+
+    b++;
+    auto guardB = MakeScopeExit([&] {
+      b--;
+    });
+
+    guardB.release();
+  }
+
+  CHECK(a == 1);
+  CHECK(b == 2);
+
+  return true;
+}
+
+int
+main()
+{
+  if (!Test()) {
+    return 1;
+  }
+  return 0;
+}
--- a/mfbt/tests/moz.build
+++ b/mfbt/tests/moz.build
@@ -21,16 +21,17 @@ CppUnitTests([
     'TestIntegerRange',
     'TestJSONWriter',
     'TestMacroArgs',
     'TestMacroForEach',
     'TestMaybe',
     'TestPair',
     'TestRefPtr',
     'TestRollingMean',
+    'TestScopeExit',
     'TestSegmentedVector',
     'TestSHA1',
     'TestSplayTree',
     'TestTemplateLib',
     'TestTuple',
     'TestTypedEnum',
     'TestTypeTraits',
     'TestUniquePtr',