| /* |
| * Copyright 2015 Nest Labs, 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 <gtest/gtest.h> |
| |
| #include <string> |
| |
| #include <folly/futures/Future.h> |
| #include <folly/futures/Promise.h> |
| #include <folly/futures/InlineExecutor.h> |
| #include <folly/futures/ManualExecutor.h> |
| |
| using namespace folly; |
| |
| /* If a Cancellation is invoked, the continuation shall not be invoked */ |
| TEST(CancellationFutures, basicOperation) { |
| unsigned k0 = 0, k1 = 0; |
| |
| { |
| /* putting in a block so all destructors are called */ |
| Promise<unsigned> p0, p1; |
| InlineExecutor x; |
| Cancellation cx0, cx1; |
| |
| auto f0 = p0.getFuture() |
| .via(&x, cx0) |
| .then([&k0] (unsigned v) { k0 = v; }); |
| auto f1 = p1.getFuture() |
| .via(&x, cx1) |
| .then([&k1] (unsigned v) { k1 = v; }); |
| |
| cx0.cancel(); |
| |
| p0.setValue(7); |
| p1.setValue(9); |
| } |
| |
| EXPECT_EQ(0, k0); /* cancelled */ |
| EXPECT_EQ(9, k1); /* not cancelled */ |
| } |
| |
| /* If a Cancellation is invoked, the continuation shall not be invoked |
| * The Cancellation is provided via .then call. Using .then makes the code simpler and clearer: |
| * instead of: |
| * f.via(x, cx).then(a); |
| * use: |
| * f.then(x, cx, a); |
| */ |
| TEST(CancellationFutures, Then_basicOperation) { |
| unsigned k0 = 0, k1 = 0; |
| |
| { |
| /* putting in a block so all destructors are called */ |
| Promise<unsigned> p0, p1; |
| InlineExecutor x; |
| Cancellation cx0, cx1; |
| |
| auto f0 = p0.getFuture() |
| .then(&x, cx0, [&k0] (unsigned v) { k0 = v; }); |
| auto f1 = p1.getFuture() |
| .then(&x, cx1, [&k1] (unsigned v) { k1 = v; }); |
| |
| cx0.cancel(); |
| |
| p0.setValue(7); |
| p1.setValue(9); |
| } |
| |
| EXPECT_EQ(0, k0); /* cancelled */ |
| EXPECT_EQ(9, k1); /* not cancelled */ |
| } |
| |
| /* If a Cancellation is invoked, the continuation shall not be invoked |
| * The Cancellation is provided via .thenMultiWithExecutor call. |
| * Using .thenMultiWithExecutor makes the code simpler and clearer if callbacks chain is used: |
| * instead of: |
| * f.via(x, cx).then(a).then(b).then(c) |
| * use: |
| * f.thenMultiWithExecutor(x, cx, a, b, c); |
| */ |
| TEST(CancellationFutures, thenMulti_basicOperation) { |
| unsigned k0 = 0, k1 = 0; |
| |
| { |
| /* putting in a block so all destructors are called */ |
| Promise<unsigned> p0, p1; |
| InlineExecutor x; |
| Cancellation cx0, cx1; |
| |
| auto f0 = p0.getFuture() |
| .thenMultiWithExecutor(&x, cx0, [&k0] (unsigned v) { k0 = v; }); |
| |
| auto f1 = p1.getFuture() |
| .thenMultiWithExecutor(&x, cx1, [] (unsigned v) { return v; }, [&k1] (unsigned v) { k1 = v; }); |
| cx0.cancel(); |
| |
| p0.setValue(7); |
| p1.setValue(9); |
| } |
| |
| EXPECT_EQ(0, k0); /* cancelled */ |
| EXPECT_EQ(9, k1); /* not cancelled */ |
| } |
| |
| /* Cancellations may be shared among multiple futures */ |
| TEST(CancellationFutures, sharedCancellations) { |
| InlineExecutor exe; |
| Cancellation cx0, cx1; |
| Promise<unsigned> p00, p01, p02, p03, p04; |
| Promise<unsigned> p10, p11, p12, p13, p14; |
| auto lam_control = [&] { EXPECT_TRUE(true); }; |
| auto lam_variable = [&] { EXPECT_TRUE(false); }; |
| |
| p00.getFuture().via(&exe, cx0).then(lam_control); |
| p01.getFuture().via(&exe, cx0).then(lam_control); |
| p02.getFuture().via(&exe, cx0).then(lam_control); |
| p03.getFuture().via(&exe, cx0).then(lam_control); |
| p04.getFuture().via(&exe, cx0).then(lam_control); |
| |
| p10.getFuture().via(&exe, cx1).then(lam_variable); |
| p11.getFuture().via(&exe, cx1).then(lam_variable); |
| p12.getFuture().via(&exe, cx1).then(lam_variable); |
| p13.getFuture().via(&exe, cx1).then(lam_variable); |
| p14.getFuture().via(&exe, cx1).then(lam_variable); |
| |
| cx1.cancel(); |
| |
| p00.setValue(0); |
| p01.setValue(1); |
| p02.setValue(2); |
| p03.setValue(3); |
| p04.setValue(4); |
| p10.setValue(5); |
| p11.setValue(6); |
| p12.setValue(7); |
| p13.setValue(8); |
| p14.setValue(9); |
| } |
| |
| /* Cancellations via .then may be shared among multiple futures */ |
| TEST(CancellationFutures, Then_sharedCancellations) { |
| InlineExecutor exe; |
| Cancellation cx0, cx1; |
| Promise<unsigned> p00, p01; |
| Promise<unsigned> p10, p11; |
| unsigned k00 = 0, k01 = 0, k10 = 0, k11 = 0; |
| |
| p00.getFuture().then(&exe, cx0, [&k00](unsigned v) { k00 = v; }); |
| p01.getFuture().then(&exe, cx0, [&k01](unsigned v) { k01 = v; }); |
| p10.getFuture().then(&exe, cx1, [&k10](unsigned v) { k10 = v; }); |
| p11.getFuture().then(&exe, cx1, [&k11](unsigned v) { k11 = v; }); |
| |
| cx1.cancel(); |
| |
| p00.setValue(1); |
| p01.setValue(2); |
| p10.setValue(8); |
| p11.setValue(9); |
| |
| EXPECT_EQ(1, k00); /* not cancelled */ |
| EXPECT_EQ(2, k01); /* not cancelled */ |
| EXPECT_EQ(0, k10); /* cancelled */ |
| EXPECT_EQ(0, k11); /* cancelled */ |
| } |
| |
| /* Cancellations via shall protect a dangling Executor* reference */ |
| TEST(CancellationFutures, danglingExecutorPointer) { |
| using std::unique_ptr; |
| using std::string; |
| |
| unique_ptr<ManualExecutor> x(new ManualExecutor); |
| Promise<string> p; |
| Cancellation cx; |
| string v; |
| |
| p.getFuture() |
| .via(x.get(), cx) |
| .then([&] (string s) { v = s; }); |
| |
| cx.cancel(); |
| x.reset(); |
| |
| p.setValue("foo"); // should not segfault |
| |
| EXPECT_EQ("", v); |
| } |
| |
| /* Cancellations via .then shall protect a dangling Executor* reference */ |
| TEST(CancellationFutures, Then_danglingExecutorPointer) { |
| using std::unique_ptr; |
| using std::string; |
| |
| unique_ptr<ManualExecutor> x(new ManualExecutor); |
| Promise<string> p; |
| Cancellation cx; |
| string v; |
| |
| p.getFuture() |
| .then(x.get(), cx, [&] (string s) { v = s; }); |
| |
| cx.cancel(); |
| x.reset(); |
| |
| p.setValue("foo"); // should not segfault |
| |
| EXPECT_EQ("", v); |
| } |
| |
| /* Cancellations shall prevent continuation from executing, even if scheduled on Executor |
| * |
| * This tests a specific race condition where a Cancellation is: |
| * |
| * 1. cancelled /after/ being scheduled on the Executor, |
| * 2. protecting some other reference (e.g. Object&), |
| * 3. it is cancelled before the Object is deleted, and |
| * 4. The executor fires the callback /after/ the Object is deleted. |
| * |
| * When this happens, the future's continuation should /not/ fire. |
| */ |
| TEST(CancellationFutures, cancelAfterExecutorAdd) |
| { |
| ManualExecutor x; |
| Promise<int> p; |
| Cancellation cx; |
| int flag0 = 0; |
| int flag1 = 0; |
| int val = 0; |
| |
| p.getFuture() |
| .via(&x, cx) |
| .then([&] (int i) { |
| sleep(1); |
| val = i; |
| }); |
| |
| x.add([&] { flag0 = 1; }); |
| p.setValue(42); |
| x.add([&] { flag1 = 1; }); |
| |
| cx.cancel(); |
| |
| size_t cbs = x.run(); |
| EXPECT_EQ(3, cbs); |
| EXPECT_EQ(1, flag0); |
| EXPECT_EQ(0, val); |
| EXPECT_EQ(1, flag1); |
| } |
| |
| /* Cancellations via .then shall prevent continuation from executing, even if scheduled on Executor |
| * |
| * This tests a specific race condition where a Cancellation is: |
| * |
| * 1. cancelled /after/ being scheduled on the Executor, |
| * 2. protecting some other reference (e.g. Object&), |
| * 3. it is cancelled before the Object is deleted, and |
| * 4. The executor fires the callback /after/ the Object is deleted. |
| * |
| * When this happens, the future's continuation should /not/ fire. |
| */ |
| TEST(CancellationFutures, Then_cancelAfterExecutorAdd) |
| { |
| ManualExecutor x; |
| Promise<int> p; |
| Cancellation cx; |
| int flag0 = 0; |
| int flag1 = 0; |
| int val = 0; |
| |
| p.getFuture() |
| .then(&x, cx, [&] (int i) { |
| val = i; |
| }); |
| |
| x.add([&] { flag0 = 1; }); |
| p.setValue(42); |
| x.add([&] { flag1 = 1; }); |
| |
| cx.cancel(); |
| |
| size_t cbs = x.run(); |
| EXPECT_EQ(3, cbs); |
| EXPECT_EQ(1, flag0); |
| EXPECT_EQ(0, val); |
| EXPECT_EQ(1, flag1); |
| } |
| |
| /* The cancellation shall not affect futures without a continuation */ |
| TEST(CancellationFutures, withoutContinuation) { |
| InlineExecutor exe; |
| Cancellation cx; |
| Promise<unsigned> p; |
| |
| auto f = p.getFuture(); |
| f.via(&exe, cx); |
| |
| cx.cancel(); |
| p.setValue(15); |
| unsigned v = f.get(); |
| |
| EXPECT_EQ(15, v); |
| } |
| |
| /* If a Cancellation is activated, if you still have the future (not |
| * destructed), you should still be able to retreive the value even |
| * though the continuation will not be invoked. |
| * |
| * NB: If you're using a continuation, it's bad practice to keep/use a |
| * reference to the original future. |
| */ |
| TEST(CancellationFutures, withContinuation) { |
| InlineExecutor exe; |
| Cancellation cx; |
| Promise<unsigned> p; |
| unsigned k = 0; |
| |
| auto f0 = p.getFuture(); |
| auto f1 = f0.via(&exe, cx).then([&] { ++k; }); |
| |
| cx.cancel(); |
| p.setValue(29); |
| |
| EXPECT_TRUE(f0.isReady()); |
| ASSERT_TRUE(f0.hasValue()); |
| unsigned v = f0.get(); |
| |
| EXPECT_FALSE(f1.isReady()); |
| |
| EXPECT_EQ(29, v); |
| EXPECT_EQ(0, k); |
| } |
| |
| /* If a Cancellation via is activated, if you still have the future (not |
| * destructed), you should still be able to retreive the value even |
| * though the continuation will not be invoked. |
| * The cancellation is provided via .then call. |
| * |
| * NB: If you're using a continuation, it's bad practice to keep/use a |
| * reference to the original future. |
| */ |
| TEST(CancellationFutures, Then_withContinuation) { |
| InlineExecutor exe; |
| Cancellation cx; |
| Promise<unsigned> p; |
| unsigned k = 0; |
| |
| auto f0 = p.getFuture(); |
| auto f1 = f0.then(&exe, cx, [&] { ++k; }); |
| |
| cx.cancel(); |
| p.setValue(29); |
| |
| EXPECT_TRUE(f0.isReady()); |
| ASSERT_TRUE(f0.hasValue()); |
| unsigned v = f0.get(); |
| |
| EXPECT_FALSE(f1.isReady()); |
| |
| EXPECT_EQ(29, v); |
| EXPECT_EQ(0, k); |
| } |
| |
| /* If a Cancellation is activated, values shall not leak |
| */ |
| TEST(CancellationFutures, valueLeakCheck) { |
| using std::shared_ptr; |
| |
| struct my_value { |
| unsigned& ctor_count_; |
| unsigned& dtor_count_; |
| |
| my_value() = delete; |
| |
| my_value(unsigned& ctor, unsigned& dtor) : |
| ctor_count_(ctor), |
| dtor_count_(dtor) |
| { |
| ++ctor_count_; |
| } |
| |
| my_value(const my_value& o) : |
| ctor_count_(o.ctor_count_), |
| dtor_count_(o.dtor_count_) |
| { |
| ++ctor_count_; |
| } |
| |
| my_value(my_value&& o) : |
| ctor_count_(o.ctor_count_), |
| dtor_count_(o.dtor_count_) |
| { |
| ++ctor_count_; |
| } |
| |
| ~my_value() |
| { |
| ++dtor_count_; |
| } |
| }; |
| |
| InlineExecutor exe; |
| Cancellation cx; |
| unsigned ctors = 0, dtors = 0; |
| |
| { |
| my_value v(ctors, dtors); |
| Promise<my_value> p; |
| |
| p.getFuture() |
| .via(&exe, cx) |
| .then([&] { EXPECT_TRUE(false); }); |
| |
| cx.cancel(); |
| |
| p.setValue(v); |
| } |
| |
| EXPECT_LE(1, ctors); |
| EXPECT_LE(1, dtors); |
| EXPECT_EQ(ctors, dtors); |
| } |
| |
| /* If a Cancellation is activated, values shall not leak |
| * The Cancellation is provided via .then call. |
| */ |
| TEST(CancellationFutures, Then_valueLeakCheck) { |
| using std::shared_ptr; |
| |
| struct my_value { |
| unsigned& ctor_count_; |
| unsigned& dtor_count_; |
| |
| my_value() = delete; |
| |
| my_value(unsigned& ctor, unsigned& dtor) : |
| ctor_count_(ctor), |
| dtor_count_(dtor) |
| { |
| ++ctor_count_; |
| } |
| |
| my_value(const my_value& o) : |
| ctor_count_(o.ctor_count_), |
| dtor_count_(o.dtor_count_) |
| { |
| ++ctor_count_; |
| } |
| |
| my_value(my_value&& o) : |
| ctor_count_(o.ctor_count_), |
| dtor_count_(o.dtor_count_) |
| { |
| ++ctor_count_; |
| } |
| |
| ~my_value() |
| { |
| ++dtor_count_; |
| } |
| }; |
| |
| InlineExecutor exe; |
| Cancellation cx; |
| unsigned ctors = 0, dtors = 0; |
| |
| { |
| my_value v(ctors, dtors); |
| Promise<my_value> p; |
| |
| p.getFuture() |
| .then(&exe, cx, [&] { EXPECT_TRUE(false); }); |
| |
| cx.cancel(); |
| |
| p.setValue(v); |
| } |
| |
| EXPECT_LE(1, ctors); |
| EXPECT_LE(1, dtors); |
| EXPECT_EQ(ctors, dtors); |
| } |
| |
| /* If a Cancellation is activated, exceptions shall not leak |
| */ |
| TEST(CancellationFutures, exceptionLeakCheck) { |
| struct my_exception : public std::exception { |
| unsigned& ctor_count_; |
| unsigned& dtor_count_; |
| |
| my_exception(unsigned& ctor, unsigned& dtor) : |
| std::exception(), |
| ctor_count_(ctor), |
| dtor_count_(dtor) |
| { |
| ++ctor_count_; |
| } |
| |
| my_exception(const my_exception& o) : |
| std::exception(o), |
| ctor_count_(o.ctor_count_), |
| dtor_count_(o.dtor_count_) |
| { |
| ++ctor_count_; |
| } |
| |
| my_exception(my_exception&& o) : |
| std::exception(o), |
| ctor_count_(o.ctor_count_), |
| dtor_count_(o.dtor_count_) |
| { |
| ++ctor_count_; |
| } |
| |
| ~my_exception() |
| { |
| ++dtor_count_; |
| } |
| }; |
| |
| InlineExecutor exe; |
| Cancellation cx; |
| unsigned ctors = 0, dtors = 0; |
| |
| { |
| Promise<unsigned> p; |
| |
| p.getFuture() |
| .via(&exe, cx) |
| .then([&] { EXPECT_TRUE(false); }); |
| |
| cx.cancel(); |
| |
| p.setException(my_exception(ctors, dtors)); |
| } |
| |
| EXPECT_LE(1, ctors); |
| EXPECT_LE(1, dtors); |
| EXPECT_EQ(ctors, dtors); |
| } |
| |
| /* If a Cancellation is activated, exceptions shall not leak |
| * The Cancellation is provided via .then call |
| */ |
| TEST(CancellationFutures, Then_exceptionLeakCheck) { |
| struct my_exception : public std::exception { |
| unsigned& ctor_count_; |
| unsigned& dtor_count_; |
| |
| my_exception(unsigned& ctor, unsigned& dtor) : |
| std::exception(), |
| ctor_count_(ctor), |
| dtor_count_(dtor) |
| { |
| ++ctor_count_; |
| } |
| |
| my_exception(const my_exception& o) : |
| std::exception(o), |
| ctor_count_(o.ctor_count_), |
| dtor_count_(o.dtor_count_) |
| { |
| ++ctor_count_; |
| } |
| |
| my_exception(my_exception&& o) : |
| std::exception(o), |
| ctor_count_(o.ctor_count_), |
| dtor_count_(o.dtor_count_) |
| { |
| ++ctor_count_; |
| } |
| |
| ~my_exception() |
| { |
| ++dtor_count_; |
| } |
| }; |
| |
| InlineExecutor exe; |
| Cancellation cx; |
| unsigned ctors = 0, dtors = 0; |
| |
| { |
| Promise<unsigned> p; |
| |
| p.getFuture() |
| .then(&exe, cx, [&] { EXPECT_TRUE(false); }); |
| |
| cx.cancel(); |
| |
| p.setException(my_exception(ctors, dtors)); |
| } |
| |
| EXPECT_LE(1, ctors); |
| EXPECT_LE(1, dtors); |
| EXPECT_EQ(ctors, dtors); |
| } |
| |
| /* A simple inline executor that supports cancellations */ |
| class SimpleExecutorWithCancellation : public ExecutorWithCancellation |
| { |
| public: |
| virtual void add(folly::Func f) { |
| f(); |
| } |
| |
| void setCancellation(Cancellation cx) override { |
| cx_ = std::move(cx); |
| } |
| |
| Cancellation& getCancellation() override { |
| return cx_; |
| } |
| |
| private: |
| Cancellation cx_; |
| }; |
| |
| /* This is the primary expected use-case, using Executor's implicit cancellation */ |
| TEST(CancellationFutures, executorWithCancellationViaOverrideDefaultCx) |
| { |
| /* code copied from "basicOperation" test */ |
| unsigned k0 = 0, k1 = 0; |
| |
| { |
| /* putting in a block so all destructors are called */ |
| Promise<unsigned> p0, p1; |
| SimpleExecutorWithCancellation x; |
| |
| auto f0 = p0.getFuture() |
| .via(&x) |
| .then([&k0] (unsigned v) { k0 = v; }); |
| auto f1 = p1.getFuture() |
| .via(&x) |
| .then([&k1] (unsigned v) { k1 = v; }); |
| |
| x.getCancellation().cancel(); |
| |
| p0.setValue(7); |
| p1.setValue(9); |
| } |
| |
| EXPECT_EQ(0, k0); /* cancelled */ |
| EXPECT_EQ(0, k1); /* cancelled */ |
| } |
| |
| /* Using Executor's implicit cancellation |
| * The Cancellation is provided via .then call. |
| */ |
| TEST(CancellationFutures, executorWithCancellationThenOverrideDefaultCx) |
| { |
| /* code copied from "basicOperation" test */ |
| unsigned k0 = 0, k1 = 0; |
| |
| { |
| /* putting in a block so all destructors are called */ |
| Promise<unsigned> p0, p1; |
| SimpleExecutorWithCancellation x; |
| |
| auto f0 = p0.getFuture() |
| .then(&x, [&k0] (unsigned v) { k0 = v; }); |
| auto f1 = p1.getFuture() |
| .then(&x, [&k1] (unsigned v) { k1 = v; }); |
| |
| x.getCancellation().cancel(); |
| |
| p0.setValue(7); |
| p1.setValue(9); |
| } |
| |
| EXPECT_EQ(0, k0); /* cancelled */ |
| EXPECT_EQ(0, k1); /* cancelled */ |
| } |
| |
| /* Using Executor's implicit cancellation |
| * The Cancellation is provided via .thenMultiWithExecutor call. |
| */ |
| TEST(CancellationFutures, executorWithCancellationThenMultiOverrideDefaultCx) |
| { |
| /* code copied from "basicOperation" test */ |
| unsigned k0 = 0, k1 = 0; |
| |
| { |
| /* putting in a block so all destructors are called */ |
| Promise<unsigned> p0, p1; |
| SimpleExecutorWithCancellation x; |
| |
| auto f0 = p0.getFuture() |
| .thenMultiWithExecutor(&x, [&k0] (unsigned v) { k0 = v; }); |
| auto f1 = p1.getFuture() |
| .thenMultiWithExecutor(&x, [] (unsigned v) { return v; }, [&k1](unsigned v) {k1 = v;}); |
| |
| x.getCancellation().cancel(); |
| |
| p0.setValue(7); |
| p1.setValue(9); |
| } |
| |
| EXPECT_EQ(0, k0); /* cancelled */ |
| EXPECT_EQ(0, k1); /* cancelled */ |
| } |
| |
| TEST(CancellationFutures, executorWithCancellationViaOverrideExplicitCx) |
| { |
| /* code copied from "basicOperation" test */ |
| unsigned k0 = 0, k1 = 0; |
| |
| { |
| /* putting in a block so all destructors are called */ |
| Promise<unsigned> p0, p1; |
| SimpleExecutorWithCancellation x; |
| Cancellation cx; |
| |
| x.setCancellation(cx); |
| |
| auto f0 = p0.getFuture() |
| .via(&x) |
| .then([&k0] (unsigned v) { k0 = v; }); |
| auto f1 = p1.getFuture() |
| .via(&x, cx) |
| .then([&k1] (unsigned v) { k1 = v; }); |
| |
| x.getCancellation().cancel(); |
| EXPECT_TRUE(cx.is_cancelled()); |
| EXPECT_TRUE(x.getCancellation().is_cancelled()); |
| |
| p0.setValue(7); |
| p1.setValue(9); |
| } |
| |
| EXPECT_EQ(0, k0); /* cancelled */ |
| EXPECT_EQ(0, k1); /* cancelled */ |
| } |
| |
| TEST(CancellationFutures, executorWithCancellationThenOverrideExplicitCx) |
| { |
| /* code copied from "basicOperation" test */ |
| unsigned k0 = 0, k1 = 0; |
| |
| { |
| /* putting in a block so all destructors are called */ |
| Promise<unsigned> p0, p1; |
| SimpleExecutorWithCancellation x; |
| Cancellation cx; |
| |
| x.setCancellation(cx); |
| |
| auto f0 = p0.getFuture() |
| .then(&x, [&k0] (unsigned v) { k0 = v; }); |
| auto f1 = p1.getFuture() |
| .then(&x, cx, [&k1] (unsigned v) { k1 = v; }); |
| |
| x.getCancellation().cancel(); |
| EXPECT_TRUE(cx.is_cancelled()); |
| EXPECT_TRUE(x.getCancellation().is_cancelled()); |
| |
| p0.setValue(7); |
| p1.setValue(9); |
| } |
| |
| EXPECT_EQ(0, k0); /* cancelled */ |
| EXPECT_EQ(0, k1); /* cancelled */ |
| } |
| |
| TEST(CancellationFutures, executorWithCancellationThenMultiOverrideExplicitCx) |
| { |
| /* code copied from "basicOperation" test */ |
| unsigned k0 = 0, k1 = 0, k2 = 0; |
| |
| { |
| /* putting in a block so all destructors are called */ |
| Promise<unsigned> p0, p1, p2; |
| SimpleExecutorWithCancellation x; |
| Cancellation cx, cx1; |
| |
| x.setCancellation(cx); |
| |
| auto f0 = p0.getFuture() |
| .thenMultiWithExecutor(&x, [&k0] (unsigned v) { k0 = v; }); |
| auto f1 = p1.getFuture() |
| .thenMultiWithExecutor(&x, cx, [&k1] (unsigned v) { k1 = v; }); |
| auto f2 = p2.getFuture() |
| .thenMultiWithExecutor(&x, cx1, [] (unsigned v) { return v; }, [&k2] (unsigned v) {k2=v;}); |
| |
| x.getCancellation().cancel(); |
| EXPECT_TRUE(cx.is_cancelled()); |
| EXPECT_TRUE(x.getCancellation().is_cancelled()); |
| |
| p0.setValue(7); |
| p1.setValue(9); |
| p2.setValue(11); |
| } |
| |
| EXPECT_EQ(0, k0); /* cancelled */ |
| EXPECT_EQ(0, k1); /* cancelled */ |
| EXPECT_EQ(11, k2); /* cancelled */ |
| } |
| |
| /* If e is an ExecutorWithCancellation*, .via(e) should be an alias |
| * for .via(e, e.getCancellation()). If we use .via(e, cx) to use a |
| * /different/ cancellation, we should prefer that cancellation. |
| */ |
| TEST(CancellationFutures, executorWithCancellationDoesntBreakExplicitCx) |
| { |
| /* code copied from "basicOperation" test */ |
| unsigned k0 = 0, k1 = 0; |
| |
| { |
| /* putting in a block so all destructors are called */ |
| Promise<unsigned> p0, p1; |
| SimpleExecutorWithCancellation x; |
| Cancellation cx0, cx1; |
| |
| x.setCancellation(cx0); |
| |
| auto f0 = p0.getFuture() |
| .via(&x, cx0) |
| .then([&k0] (unsigned v) { k0 = v; }); |
| auto f1 = p1.getFuture() |
| .via(&x, cx1) |
| .then([&k1] (unsigned v) { k1 = v; }); |
| |
| cx0.cancel(); |
| |
| p0.setValue(7); |
| p1.setValue(9); |
| } |
| |
| EXPECT_EQ(0, k0); /* cancelled */ |
| EXPECT_EQ(9, k1); /* not cancelled */ |
| } |
| |
| /* If e is an ExecutorWithCancellation*, .then(e) should be an alias |
| * for .then(e, e.getCancellation()). If we use .then(e, cx) to use a |
| * /different/ cancellation, we should prefer that cancellation. |
| */ |
| TEST(CancellationFutures, Then_executorWithCancellationDoesntBreakExplicitCx) |
| { |
| /* code copied from "basicOperation" test */ |
| unsigned k0 = 0, k1 = 0; |
| |
| { |
| /* putting in a block so all destructors are called */ |
| Promise<unsigned> p0, p1; |
| SimpleExecutorWithCancellation x; |
| Cancellation cx0, cx1; |
| |
| x.setCancellation(cx0); |
| |
| auto f0 = p0.getFuture() |
| .then(&x, cx0, [&k0] (unsigned v) { k0 = v; }); |
| auto f1 = p1.getFuture() |
| .then(&x, cx1, [&k1] (unsigned v) { k1 = v; }); |
| |
| cx0.cancel(); |
| |
| p0.setValue(7); |
| p1.setValue(9); |
| } |
| |
| EXPECT_EQ(0, k0); /* cancelled */ |
| EXPECT_EQ(9, k1); /* not cancelled */ |
| } |
| |
| /* If e is an ExecutorWithCancellation*, .thenMultiWithExecutor(e) |
| * should be an alias for .thenMultiWithExecutor(e, e.getCancellation()). |
| * If we use .thenMultiWithExecutor(e, cx) to use a /different/ |
| * cancellation, we should prefer that cancellation. |
| */ |
| TEST(CancellationFutures, ThenMulti_executorWithCancellationDoesntBreakExplicitCx) |
| { |
| /* code copied from "basicOperation" test */ |
| unsigned k0 = 0, k1 = 0; |
| |
| { |
| /* putting in a block so all destructors are called */ |
| Promise<unsigned> p0, p1; |
| SimpleExecutorWithCancellation x; |
| Cancellation cx0, cx1; |
| |
| x.setCancellation(cx0); |
| |
| auto f0 = p0.getFuture() |
| .thenMultiWithExecutor(&x, cx0, [&k0] (unsigned v) { k0 = v; }); |
| auto f1 = p1.getFuture() |
| .thenMultiWithExecutor(&x, cx1, [](unsigned v){return v;}, [&k1] (unsigned v) { k1 = v; }); |
| |
| cx0.cancel(); |
| |
| p0.setValue(7); |
| p1.setValue(9); |
| } |
| |
| EXPECT_EQ(0, k0); /* cancelled */ |
| EXPECT_EQ(9, k1); /* not cancelled */ |
| } |