use previous Executor after delayed
authorAlex Yarmula <ayarmula@fb.com>
Thu, 6 Oct 2016 14:21:22 +0000 (07:21 -0700)
committerFacebook Github Bot <facebook-github-bot-bot@fb.com>
Thu, 6 Oct 2016 14:23:30 +0000 (07:23 -0700)
Summary:
When `delayed` is called on the Future, the underlying `futures::sleep` call runs on a timer thread, and the resulting callback is called on the same thread. Therefore, in the following sequence:

  f.via(&someExecutor).within(one_ms).then([&]() { /* [1] */ })

The code in [1] is not running in someExecutor. This can cause confusion by users of the library who expect the initial `via` to be sticky.

This change returns to the prior `Executor` after `delayed` is finished.

Reviewed By: yfeldblum

Differential Revision: D3979179

fbshipit-source-id: 936ff5626e8ac377ffb15babf573349466984e3a

folly/futures/Future-inl.h
folly/futures/test/TimekeeperTest.cpp

index 1cd4035e676623f641e14e01e7d5b256ca020d29..14ae5fc43957ca44e541f6484f5dca300e8e1298 100644 (file)
@@ -972,10 +972,11 @@ Future<T> Future<T>::within(Duration dur, E e, Timekeeper* tk) {
 template <class T>
 Future<T> Future<T>::delayed(Duration dur, Timekeeper* tk) {
   return collectAll(*this, futures::sleep(dur, tk))
-    .then([](std::tuple<Try<T>, Try<Unit>> tup) {
-      Try<T>& t = std::get<0>(tup);
-      return makeFuture<T>(std::move(t));
-    });
+      .then([](std::tuple<Try<T>, Try<Unit>> tup) {
+        Try<T>& t = std::get<0>(tup);
+        return makeFuture<T>(std::move(t));
+      })
+      .via(getExecutor());
 }
 
 namespace detail {
index 664dd73708e7ab941dbef89fb7fff3b736bc0cd0..e4aeba4d1b74948c23ed78b59d1f64d286853fc0 100644 (file)
@@ -168,20 +168,36 @@ TEST(Timekeeper, chainedInterruptTest) {
   EXPECT_FALSE(test);
 }
 
-TEST(Timekeeper, executor) {
-  class ExecutorTester : public Executor {
  public:
-    void add(Func f) override {
-      count++;
-      f();
-    }
-    std::atomic<int> count{0};
+namespace {
+class ExecutorTester : public Executor {
+ public:
+  void add(Func f) override {
+    count++;
+    f();
+  }
+  std::atomic<int> count{0};
   };
-
-  auto f = makeFuture();
-  ExecutorTester tester;
-  f.via(&tester).within(one_ms).then([&](){}).wait();
-  EXPECT_EQ(2, tester.count);
+  }
+
+  TEST(Timekeeper, executor) {
+    auto f = makeFuture();
+    ExecutorTester tester;
+    f.via(&tester).within(one_ms).then([&]() {}).wait();
+    EXPECT_EQ(2, tester.count);
+  }
+
+  TEST(Timekeeper, executorSameAfterDelayed) {
+    ExecutorTester tester;
+    // make sure we're using the same Executor (tester) after delayed returns
+    // by comparing the executor count between invocations
+    makeFuture()
+        .via(&tester)
+        .delayed(one_ms)
+        .then([&]() { return tester.count.load(); })
+        .then([&](int countBefore) {
+          EXPECT_EQ(1, tester.count.load() - countBefore);
+        })
+        .wait();
 }
 
 // TODO(5921764)