blob: a7e712007278dbe8e41bd80750d2d2a4b2a2e7de [file] [log] [blame]
/*
* 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 */
}