Moving DestructorCheck from proxygen library to folly.
[folly.git] / folly / io / async / DestructorCheck.h
diff --git a/folly/io/async/DestructorCheck.h b/folly/io/async/DestructorCheck.h
new file mode 100644 (file)
index 0000000..2d9930f
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2017 Facebook, Inc.
+ *
+ * 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.
+ */
+#pragma once
+
+namespace folly {
+
+/**
+ * DestructorCheck is a helper class that helps to detect if a tracked object
+ * was deleted.
+ * This is useful for objects that request callbacks from other components.
+ *
+ * Classes needing this functionality should:
+ * - derive from DestructorCheck
+ *
+ * Callback context can be extended with an instance of DestructorCheck::Safety
+ * object initialized with a reference to the object dereferenced from the
+ * callback.  Once the callback is invoked, it can use this safety object to
+ * check if the object was not deallocated yet before dereferencing it.
+ *
+ * DestructorCheck does not perform any locking.  It is intended to be used
+ * only from a single thread.
+ *
+ * Example:
+ *
+ * class AsyncFoo : public DestructorCheck {
+ *  public:
+ *   ~AsyncFoo();
+ *   // awesome async code with circuitous deletion paths
+ *   void async1();
+ *   void async2();
+ * };
+ *
+ * righteousFunc(AsyncFoo& f) {
+ *   DestructorCheck::Safety safety(f);
+ *
+ *   f.async1(); // might have deleted f, oh noes
+ *   if (!safety.destroyed()) {
+ *     // phew, still there
+ *     f.async2();
+ *   }
+ * }
+ */
+
+class DestructorCheck {
+ public:
+  virtual ~DestructorCheck() {
+    rootGuard_.setAllDestroyed();
+  }
+
+  class Safety;
+
+  class ForwardLink {
+   // These methods are mostly private because an outside caller could violate
+   // the integrity of the linked list.
+   private:
+    void setAllDestroyed() {
+      for (auto guard = next_; guard; guard = guard->next_) {
+        guard->setDestroyed();
+      }
+    }
+
+    // This is used to maintain the double-linked list. An intrusive list does
+    // not require any heap allocations, like a standard container would. This
+    // isolation of next_ in its own class means that the DestructorCheck can
+    // easily hold a next_ pointer without needing to hold a prev_ pointer.
+    // DestructorCheck never needs a prev_ pointer because it is the head node
+    // and this is a special list where the head never moves and never has a
+    // previous node.
+    Safety* next_{nullptr};
+
+    friend DestructorCheck::~DestructorCheck();
+    friend class Safety;
+  };
+
+  // See above example for usage
+  class Safety : public ForwardLink {
+   public:
+    explicit Safety(DestructorCheck& destructorCheck) {
+      // Insert this node at the head of the list.
+      prev_ = &destructorCheck.rootGuard_;
+      next_ = prev_->next_;
+      if (next_ != nullptr) {
+        next_->prev_ = this;
+      }
+      prev_->next_ = this;
+     }
+
+    ~Safety() {
+      if (!destroyed()) {
+        // Remove this node from the list.
+        prev_->next_ = next_;
+        if (next_ != nullptr) {
+          next_->prev_ = prev_;
+        }
+      }
+    }
+
+    bool destroyed() const {
+      return prev_ == nullptr;
+    }
+
+   private:
+    void setDestroyed() {
+      prev_ = nullptr;
+    }
+
+    // This field is used to maintain the double-linked list. If the root has
+    // been destroyed then the field is set to the nullptr sentinel value.
+    ForwardLink* prev_;
+
+    friend class ForwardLink;
+  };
+
+ private:
+  ForwardLink rootGuard_;
+};
+
+}