Convenience functions to wrap a lambda as an AsyncTimeout.
authorMarcelo Juchem <marcelo@fb.com>
Thu, 30 Apr 2015 21:03:16 +0000 (14:03 -0700)
committerPraveen Kumar Ramakrishnan <praveenr@fb.com>
Tue, 12 May 2015 00:01:39 +0000 (17:01 -0700)
Summary: see title

Test Plan: unit tests added

Reviewed By: davejwatson@fb.com

Subscribers: folly-diffs@, yfeldblum, chalfant, andrewcox

FB internal diff: D2000579

Signature: t1:2000579:1430345677:1d7a78f94bcd8b0912423ca4987a4048c103241c

folly/io/async/AsyncTimeout.cpp
folly/io/async/AsyncTimeout.h
folly/io/async/EventBase.cpp
folly/io/async/EventBase.h
folly/io/async/TimeoutManager.h
folly/io/async/test/AsyncTimeoutTest.cpp [new file with mode: 0644]

index 4d120152144425c0d46e13cf5a47935e82e79417..0b371d776f21fdfc9452d13328afaf41c1fdc4d7 100644 (file)
@@ -81,14 +81,14 @@ AsyncTimeout::~AsyncTimeout() {
   cancelTimeout();
 }
 
-bool AsyncTimeout::scheduleTimeout(std::chrono::milliseconds timeout) {
+bool AsyncTimeout::scheduleTimeout(TimeoutManager::timeout_type timeout) {
   assert(timeoutManager_ != nullptr);
   context_ = RequestContext::saveContext();
   return timeoutManager_->scheduleTimeout(this, timeout);
 }
 
 bool AsyncTimeout::scheduleTimeout(uint32_t milliseconds) {
-  return scheduleTimeout(std::chrono::milliseconds(milliseconds));
+  return scheduleTimeout(TimeoutManager::timeout_type(milliseconds));
 }
 
 void AsyncTimeout::cancelTimeout() {
index 6107ce52fa88cf576037a7f9c43b1df9b751e7c7..189fcb199f9373d4d1f112edb441d369459861c6 100644 (file)
@@ -25,6 +25,7 @@
 #include <boost/noncopyable.hpp>
 #include <event.h>
 #include <memory>
+#include <utility>
 
 namespace folly {
 
@@ -98,7 +99,7 @@ class AsyncTimeout : private boost::noncopyable {
    *         rescheduling an existing timeout.
    */
   bool scheduleTimeout(uint32_t milliseconds);
-  bool scheduleTimeout(std::chrono::milliseconds timeout);
+  bool scheduleTimeout(TimeoutManager::timeout_type timeout);
 
   /**
    * Cancel the timeout, if it is running.
@@ -151,6 +152,72 @@ class AsyncTimeout : private boost::noncopyable {
     return &event_;
   }
 
+  /**
+   * Convenience function that wraps a function object as
+   * an AsyncTimeout instance and returns the wrapper.
+   *
+   * Specially useful when using lambdas as AsyncTimeout
+   * observers.
+   *
+   * Example:
+   *
+   *  void foo(TimeoutManager &manager) {
+   *    std::atomic_bool done = false;
+   *
+   *    auto observer = AsyncTimeout::make(manager, [&] {
+   *      std::cout << "hello, world!" << std::endl;
+   *      done = true;
+   *    });
+   *
+   *    observer->scheduleTimeout(std::chrono::seconds(5));
+   *
+   *    while (!done); // busy wait
+   *  }
+   *
+   * @author: Marcelo Juchem <marcelo@fb.com>
+   */
+  template <typename TCallback>
+  static std::unique_ptr<AsyncTimeout> make(
+    TimeoutManager &manager,
+    TCallback &&callback
+  );
+
+  /**
+   * Convenience function that wraps a function object as
+   * an AsyncTimeout instance and returns the wrapper
+   * after scheduling it using the given TimeoutManager.
+   *
+   * This is equivalent to calling `make_async_timeout`
+   * followed by a `scheduleTimeout` on the resulting
+   * wrapper.
+   *
+   * Specially useful when using lambdas as AsyncTimeout
+   * observers.
+   *
+   * Example:
+   *
+   *  void foo(TimeoutManager &manager) {
+   *    std::atomic_bool done = false;
+   *
+   *    auto observer = AsyncTimeout::schedule(
+   *      std::chrono::seconds(5), manager, [&] {
+   *        std::cout << "hello, world!" << std::endl;
+   *        done = true;
+   *      }
+   *    );
+   *
+   *    while (!done); // busy wait
+   *  }
+   *
+   * @author: Marcelo Juchem <marcelo@fb.com>
+   */
+  template <typename TCallback>
+  static std::unique_ptr<AsyncTimeout> schedule(
+    TimeoutManager::timeout_type timeout,
+    TimeoutManager &manager,
+    TCallback &&callback
+  );
+
  private:
   static void libeventCallback(int fd, short events, void* arg);
 
@@ -167,4 +234,59 @@ class AsyncTimeout : private boost::noncopyable {
   std::shared_ptr<RequestContext> context_;
 };
 
+namespace detail {
+
+/**
+ * Wraps a function object as an AsyncTimeout instance.
+ *
+ * @author: Marcelo Juchem <marcelo@fb.com>
+ */
+template <typename TCallback>
+struct async_timeout_wrapper:
+  public AsyncTimeout
+{
+  template <typename UCallback>
+  async_timeout_wrapper(TimeoutManager *manager, UCallback &&callback):
+    AsyncTimeout(manager),
+    callback_(std::forward<UCallback>(callback))
+  {}
+
+  void timeoutExpired() noexcept {
+    static_assert(
+      noexcept(std::declval<TCallback>()()),
+      "callback must be declared noexcept, e.g.: `[]() noexcept {}`"
+    );
+    callback_();
+  }
+
+private:
+  TCallback callback_;
+};
+
+} // namespace detail {
+
+template <typename TCallback>
+std::unique_ptr<AsyncTimeout> AsyncTimeout::make(
+  TimeoutManager &manager,
+  TCallback &&callback
+) {
+  return std::unique_ptr<AsyncTimeout>(
+    new detail::async_timeout_wrapper<typename std::decay<TCallback>::type>(
+      std::addressof(manager),
+      std::forward<TCallback>(callback)
+    )
+  );
+}
+
+template <typename TCallback>
+std::unique_ptr<AsyncTimeout> AsyncTimeout::schedule(
+  TimeoutManager::timeout_type timeout,
+  TimeoutManager &manager,
+  TCallback &&callback
+) {
+  auto wrapper = AsyncTimeout::make(manager, std::forward<TCallback>(callback));
+  wrapper->scheduleTimeout(timeout);
+  return wrapper;
+}
+
 } // folly
index b6cd8ca120a7a4e5fd872a71e9a7eab19d7577af..1c3ad397ec73c922ebb7c7d425cbe060dbb400e9 100644 (file)
@@ -781,7 +781,7 @@ void EventBase::detachTimeoutManager(AsyncTimeout* obj) {
 }
 
 bool EventBase::scheduleTimeout(AsyncTimeout* obj,
-                                 std::chrono::milliseconds timeout) {
+                                 TimeoutManager::timeout_type timeout) {
   assert(isInEventBaseThread());
   // Set up the timeval and add the event
   struct timeval tv;
index 0bdc428ad39956015e046c29ed61affd1afb4764..3e28d29403c9f8673b97bbd2908724b50ebda7e9 100644 (file)
@@ -575,7 +575,7 @@ bool runImmediatelyOrRunInEventBaseThreadAndWait(const Cob& fn);
 
   void detachTimeoutManager(AsyncTimeout* obj) override;
 
-  bool scheduleTimeout(AsyncTimeout* obj, std::chrono::milliseconds timeout)
+  bool scheduleTimeout(AsyncTimeout* obj, TimeoutManager::timeout_type timeout)
     override;
 
   void cancelTimeout(AsyncTimeout* obj) override;
index 16042807666fb1adf757298d01191cc05cf688cd..2b35574ccfbfb8d6ed8f29c4a8491690b9e83cef 100644 (file)
@@ -34,6 +34,8 @@ class AsyncTimeout;
  */
 class TimeoutManager {
  public:
+  typedef std::chrono::milliseconds timeout_type;
+
   enum class InternalEnum {
     INTERNAL,
     NORMAL
@@ -52,7 +54,7 @@ class TimeoutManager {
    * Schedules AsyncTimeout to fire after `timeout` milliseconds
    */
   virtual bool scheduleTimeout(AsyncTimeout* obj,
-                               std::chrono::milliseconds timeout) = 0;
+                               timeout_type timeout) = 0;
 
   /**
    * Cancels the AsyncTimeout, if scheduled
diff --git a/folly/io/async/test/AsyncTimeoutTest.cpp b/folly/io/async/test/AsyncTimeoutTest.cpp
new file mode 100644 (file)
index 0000000..ff5cd1c
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2015 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.
+ */
+
+#include <folly/io/async/AsyncTimeout.h>
+#include <folly/io/async/EventBase.h>
+
+#include <gtest/gtest.h>
+
+namespace folly {
+
+TEST(AsyncTimeout, make) {
+  int value = 0;
+  int const expected = 10;
+  EventBase manager;
+
+  auto observer = AsyncTimeout::make(
+    manager,
+    [&]() noexcept { value = expected; }
+  );
+
+  observer->scheduleTimeout(std::chrono::milliseconds(100));
+
+  manager.loop();
+
+  EXPECT_EQ(expected, value);
+}
+
+TEST(AsyncTimeout, schedule) {
+  int value = 0;
+  int const expected = 10;
+  EventBase manager;
+
+  auto observer = AsyncTimeout::schedule(
+    std::chrono::milliseconds(100),
+    manager,
+    [&]() noexcept { value = expected; }
+  );
+
+  manager.loop();
+
+  EXPECT_EQ(expected, value);
+}
+
+TEST(AsyncTimeout, cancel_make) {
+  int value = 0;
+  int const expected = 10;
+  EventBase manager;
+
+  auto observer = AsyncTimeout::make(
+    manager,
+    [&]() noexcept { value = expected; }
+  );
+
+  observer->scheduleTimeout(std::chrono::milliseconds(100));
+  observer->cancelTimeout();
+
+  manager.loop();
+
+  EXPECT_NE(expected, value);
+}
+
+TEST(AsyncTimeout, cancel_schedule) {
+  int value = 0;
+  int const expected = 10;
+  EventBase manager;
+
+  auto observer = AsyncTimeout::schedule(
+    std::chrono::milliseconds(100),
+    manager,
+    [&]() noexcept { value = expected; }
+  );
+
+  observer->cancelTimeout();
+
+  manager.loop();
+
+  EXPECT_NE(expected, value);
+}
+
+} // namespace folly {