From: James Sedgwick Date: Tue, 16 Dec 2014 18:26:24 +0000 (-0800) Subject: codemod: folly/wangle/ -> folly/wangle/futures X-Git-Tag: v0.22.0~100 X-Git-Url: http://plrg.eecs.uci.edu/git/?a=commitdiff_plain;h=f2ac9a5cbd5086032a376bd93fcf23ad1af62ee3;p=folly.git codemod: folly/wangle/ -> folly/wangle/futures Summary: Last thing before moving experimental/wangle here. Once everything is in the same directory I'm probably going to consolidate experimental/wangle/concurrent with the executors in this directory into wangle/executors/. And probably rename some of these targets. For now, a dumb move is enough. Test Plan: waiting for contbuild Reviewed By: davejwatson@fb.com Subscribers: trunkagent, fbcode-common-diffs@, chaoyc, search-fbcode-diffs@, lars, ruibalp, hero-diffs@, zeus-diffs@, vikas, danielg, mcduff, cold-storage-diffs@, unicorn-diffs@, ldbrandy, targeting-diff-backend@, netego-diffs@, fugalh, adamsyta, atlas2-eng@, alandau, bmatheny, adityab, everstore-dev@, zhuohuang, sweeney, mwa, jgehring, smarlow, akr, bnitka, jcoens, luk, zhguo, jying, apodsiadlo, alikhtarov, fuegen, dzhulgakov, mshneer, folly-diffs@, wch, tingy, hannesr FB internal diff: D1740327 Tasks: 5802833 Signature: t1:1740327:1418752541:82d7486293b0a12938730ae66d480c120aded4dc --- diff --git a/folly/Makefile.am b/folly/Makefile.am index 2da84595..74bbb43f 100644 --- a/folly/Makefile.am +++ b/folly/Makefile.am @@ -237,21 +237,21 @@ nobase_follyinclude_HEADERS = \ Uri-inl.h \ Varint.h \ VersionCheck.h \ - wangle/Deprecated.h \ - wangle/Future-inl.h \ - wangle/Future.h \ - wangle/InlineExecutor.h \ - wangle/ManualExecutor.h \ - wangle/OpaqueCallbackShunt.h \ - wangle/Promise-inl.h \ - wangle/Promise.h \ - wangle/QueuedImmediateExecutor.h \ - wangle/ScheduledExecutor.h \ - wangle/Try-inl.h \ - wangle/Try.h \ - wangle/WangleException.h \ - wangle/detail/Core.h \ - wangle/detail/FSM.h + wangle/futures/Deprecated.h \ + wangle/futures/Future-inl.h \ + wangle/futures/Future.h \ + wangle/futures/InlineExecutor.h \ + wangle/futures/ManualExecutor.h \ + wangle/futures/OpaqueCallbackShunt.h \ + wangle/futures/Promise-inl.h \ + wangle/futures/Promise.h \ + wangle/futures/QueuedImmediateExecutor.h \ + wangle/futures/ScheduledExecutor.h \ + wangle/futures/Try-inl.h \ + wangle/futures/Try.h \ + wangle/futures/WangleException.h \ + wangle/futures/detail/Core.h \ + wangle/futures/detail/FSM.h FormatTables.cpp: build/generate_format_tables.py build/generate_format_tables.py @@ -323,8 +323,8 @@ libfolly_la_SOURCES = \ TimeoutQueue.cpp \ Uri.cpp \ Version.cpp \ - wangle/InlineExecutor.cpp \ - wangle/ManualExecutor.cpp \ + wangle/futures/InlineExecutor.cpp \ + wangle/futures/ManualExecutor.cpp \ experimental/io/FsUtil.cpp \ experimental/Singleton.cpp \ experimental/TestUtil.cpp \ diff --git a/folly/experimental/wangle/channel/ChannelHandler.h b/folly/experimental/wangle/channel/ChannelHandler.h index 27a32447..e7fd3135 100644 --- a/folly/experimental/wangle/channel/ChannelHandler.h +++ b/folly/experimental/wangle/channel/ChannelHandler.h @@ -16,7 +16,7 @@ #pragma once -#include +#include #include #include #include diff --git a/folly/experimental/wangle/channel/ChannelHandlerContext.h b/folly/experimental/wangle/channel/ChannelHandlerContext.h index baf413ff..59ea3ae4 100644 --- a/folly/experimental/wangle/channel/ChannelHandlerContext.h +++ b/folly/experimental/wangle/channel/ChannelHandlerContext.h @@ -17,7 +17,7 @@ #pragma once #include -#include +#include #include namespace folly { namespace wangle { diff --git a/folly/experimental/wangle/channel/ChannelPipeline.h b/folly/experimental/wangle/channel/ChannelPipeline.h index ea7959bc..386caec3 100644 --- a/folly/experimental/wangle/channel/ChannelPipeline.h +++ b/folly/experimental/wangle/channel/ChannelPipeline.h @@ -17,7 +17,7 @@ #pragma once #include -#include +#include #include #include #include diff --git a/folly/experimental/wangle/concurrent/FutureExecutor.h b/folly/experimental/wangle/concurrent/FutureExecutor.h index ecf392b7..8aeedff8 100644 --- a/folly/experimental/wangle/concurrent/FutureExecutor.h +++ b/folly/experimental/wangle/concurrent/FutureExecutor.h @@ -15,7 +15,7 @@ */ #pragma once -#include +#include namespace folly { namespace wangle { diff --git a/folly/wangle/Deprecated.h b/folly/wangle/Deprecated.h deleted file mode 100644 index 75937b15..00000000 --- a/folly/wangle/Deprecated.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2014 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 -#define DEPRECATED __attribute__((__deprecated__)) diff --git a/folly/wangle/Future-inl.h b/folly/wangle/Future-inl.h deleted file mode 100644 index 2df6fef2..00000000 --- a/folly/wangle/Future-inl.h +++ /dev/null @@ -1,631 +0,0 @@ -/* - * Copyright 2014 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 - -#include -#include - -#include -#include - -namespace folly { namespace wangle { - -template -struct isFuture { - static const bool value = false; -}; - -template -struct isFuture > { - static const bool value = true; -}; - -template -Future::Future(Future&& other) noexcept : core_(nullptr) { - *this = std::move(other); -} - -template -Future& Future::operator=(Future&& other) { - std::swap(core_, other.core_); - return *this; -} - -template -Future::~Future() { - detach(); -} - -template -void Future::detach() { - if (core_) { - core_->detachFuture(); - core_ = nullptr; - } -} - -template -void Future::throwIfInvalid() const { - if (!core_) - throw NoState(); -} - -template -template -void Future::setCallback_(F&& func) { - throwIfInvalid(); - core_->setCallback(std::move(func)); -} - -// Variant: f.then([](Try&& t){ return t.value(); }); -template -template -typename std::enable_if< - !isFuture&&)>::type>::value, - Future&&)>::type> >::type -Future::then(F&& func) { - typedef typename std::result_of&&)>::type B; - - throwIfInvalid(); - - // wrap these so we can move them into the lambda - folly::MoveWrapper> p; - folly::MoveWrapper funcm(std::forward(func)); - - // grab the Future now before we lose our handle on the Promise - auto f = p->getFuture(); - - /* This is a bit tricky. - - We can't just close over *this in case this Future gets moved. So we - make a new dummy Future. We could figure out something more - sophisticated that avoids making a new Future object when it can, as an - optimization. But this is correct. - - core_ can't be moved, it is explicitly disallowed (as is copying). But - if there's ever a reason to allow it, this is one place that makes that - assumption and would need to be fixed. We use a standard shared pointer - for core_ (by copying it in), which means in essence obj holds a shared - pointer to itself. But this shouldn't leak because Promise will not - outlive the continuation, because Promise will setException() with a - broken Promise if it is destructed before completed. We could use a - weak pointer but it would have to be converted to a shared pointer when - func is executed (because the Future returned by func may possibly - persist beyond the callback, if it gets moved), and so it is an - optimization to just make it shared from the get-go. - - We have to move in the Promise and func using the MoveWrapper - hack. (func could be copied but it's a big drag on perf). - - Two subtle but important points about this design. detail::Core has no - back pointers to Future or Promise, so if Future or Promise get moved - (and they will be moved in performant code) we don't have to do - anything fancy. And because we store the continuation in the - detail::Core, not in the Future, we can execute the continuation even - after the Future has gone out of scope. This is an intentional design - decision. It is likely we will want to be able to cancel a continuation - in some circumstances, but I think it should be explicit not implicit - in the destruction of the Future used to create it. - */ - setCallback_( - [p, funcm](Try&& t) mutable { - p->fulfil([&]() { - return (*funcm)(std::move(t)); - }); - }); - - return std::move(f); -} - -// Variant: f.then([](T&& t){ return t; }); -template -template -typename std::enable_if< - !std::is_same::value && - !isFuture::type&&)>::type>::value, - Future::type&&)>::type> >::type -Future::then(F&& func) { - typedef typename std::result_of::type B; - - throwIfInvalid(); - - folly::MoveWrapper> p; - folly::MoveWrapper funcm(std::forward(func)); - auto f = p->getFuture(); - - setCallback_( - [p, funcm](Try&& t) mutable { - if (t.hasException()) { - p->setException(t.getException()); - } else { - p->fulfil([&]() { - return (*funcm)(std::move(t.value())); - }); - } - }); - - return std::move(f); -} - -// Variant: f.then([](){ return; }); -template -template -typename std::enable_if< - std::is_same::value && - !isFuture::type>::value, - Future::type> >::type -Future::then(F&& func) { - typedef typename std::result_of::type B; - - throwIfInvalid(); - - folly::MoveWrapper> p; - folly::MoveWrapper funcm(std::forward(func)); - auto f = p->getFuture(); - - setCallback_( - [p, funcm](Try&& t) mutable { - if (t.hasException()) { - p->setException(t.getException()); - } else { - p->fulfil([&]() { - return (*funcm)(); - }); - } - }); - - return std::move(f); -} - -// Variant: f.then([](Try&& t){ return makeFuture(t.value()); }); -template -template -typename std::enable_if< - isFuture&&)>::type>::value, - Future&&)>::type::value_type> >::type -Future::then(F&& func) { - typedef typename std::result_of&&)>::type::value_type B; - - throwIfInvalid(); - - // wrap these so we can move them into the lambda - folly::MoveWrapper> p; - folly::MoveWrapper funcm(std::forward(func)); - - // grab the Future now before we lose our handle on the Promise - auto f = p->getFuture(); - - setCallback_( - [p, funcm](Try&& t) mutable { - try { - auto f2 = (*funcm)(std::move(t)); - // that didn't throw, now we can steal p - f2.setCallback_([p](Try&& b) mutable { - p->fulfilTry(std::move(b)); - }); - } catch (...) { - p->setException(std::current_exception()); - } - }); - - return std::move(f); -} - -// Variant: f.then([](T&& t){ return makeFuture(t); }); -template -template -typename std::enable_if< - !std::is_same::value && - isFuture::type&&)>::type>::value, - Future::type&&)>::type::value_type> >::type -Future::then(F&& func) { - typedef typename std::result_of::type::value_type B; - - throwIfInvalid(); - - folly::MoveWrapper> p; - folly::MoveWrapper funcm(std::forward(func)); - auto f = p->getFuture(); - - setCallback_( - [p, funcm](Try&& t) mutable { - if (t.hasException()) { - p->setException(t.getException()); - } else { - try { - auto f2 = (*funcm)(std::move(t.value())); - f2.setCallback_([p](Try&& b) mutable { - p->fulfilTry(std::move(b)); - }); - } catch (...) { - p->setException(std::current_exception()); - } - } - }); - - return std::move(f); -} - -// Variant: f.then([](){ return makeFuture(); }); -template -template -typename std::enable_if< - std::is_same::value && - isFuture::type>::value, - Future::type::value_type> >::type -Future::then(F&& func) { - typedef typename std::result_of::type::value_type B; - - throwIfInvalid(); - - folly::MoveWrapper> p; - folly::MoveWrapper funcm(std::forward(func)); - - auto f = p->getFuture(); - - setCallback_( - [p, funcm](Try&& t) mutable { - if (t.hasException()) { - p->setException(t.getException()); - } else { - try { - auto f2 = (*funcm)(); - f2.setCallback_([p](Try&& b) mutable { - p->fulfilTry(std::move(b)); - }); - } catch (...) { - p->setException(std::current_exception()); - } - } - }); - - return std::move(f); -} - -template -Future Future::then() { - return then([] (Try&& t) {}); -} - -template -typename std::add_lvalue_reference::type Future::value() { - throwIfInvalid(); - - return core_->getTry().value(); -} - -template -typename std::add_lvalue_reference::type Future::value() const { - throwIfInvalid(); - - return core_->getTry().value(); -} - -template -Try& Future::getTry() { - throwIfInvalid(); - - return core_->getTry(); -} - -template -template -inline Future Future::via(Executor* executor) && { - throwIfInvalid(); - - this->deactivate(); - core_->setExecutor(executor); - - return std::move(*this); -} - -template -template -inline Future Future::via(Executor* executor) & { - throwIfInvalid(); - - MoveWrapper> p; - auto f = p->getFuture(); - then([p](Try&& t) mutable { p->fulfilTry(std::move(t)); }); - return std::move(f).via(executor); -} - -template -bool Future::isReady() const { - throwIfInvalid(); - return core_->ready(); -} - -template -void Future::raise(std::exception_ptr exception) { - core_->raise(exception); -} - -// makeFuture - -template -Future::type> makeFuture(T&& t) { - Promise::type> p; - auto f = p.getFuture(); - p.setValue(std::forward(t)); - return std::move(f); -} - -inline // for multiple translation units -Future makeFuture() { - Promise p; - auto f = p.getFuture(); - p.setValue(); - return std::move(f); -} - -template -auto makeFutureTry( - F&& func, - typename std::enable_if::value, bool>::type sdf) - -> Future { - Promise p; - auto f = p.getFuture(); - p.fulfil( - [&func]() { - return (func)(); - }); - return std::move(f); -} - -template -auto makeFutureTry(F const& func) -> Future { - F copy = func; - return makeFutureTry(std::move(copy)); -} - -template -Future makeFuture(std::exception_ptr const& e) { - Promise p; - auto f = p.getFuture(); - p.setException(e); - return std::move(f); -} - -template -typename std::enable_if::value, - Future>::type -makeFuture(E const& e) { - Promise p; - auto f = p.getFuture(); - p.fulfil([&]() -> T { throw e; }); - return std::move(f); -} - -template -Future makeFuture(Try&& t) { - try { - return makeFuture(std::move(t.value())); - } catch (...) { - return makeFuture(std::current_exception()); - } -} - -template <> -inline Future makeFuture(Try&& t) { - try { - t.throwIfFailed(); - return makeFuture(); - } catch (...) { - return makeFuture(std::current_exception()); - } -} - -// via -template -Future via(Executor* executor) { - return makeFuture().via(executor); -} - -// when (variadic) - -template -typename detail::VariadicContext< - typename std::decay::type::value_type...>::type -whenAll(Fs&&... fs) -{ - auto ctx = - new detail::VariadicContext::type::value_type...>(); - ctx->total = sizeof...(fs); - auto f_saved = ctx->p.getFuture(); - detail::whenAllVariadicHelper(ctx, - std::forward::type>(fs)...); - return std::move(f_saved); -} - -// when (iterator) - -template -Future< - std::vector< - Try::value_type::value_type>>> -whenAll(InputIterator first, InputIterator last) -{ - typedef - typename std::iterator_traits::value_type::value_type T; - - auto n = std::distance(first, last); - if (n == 0) { - return makeFuture(std::vector>()); - } - - auto ctx = new detail::WhenAllContext(); - - ctx->results.resize(n); - - auto f_saved = ctx->p.getFuture(); - - for (size_t i = 0; first != last; ++first, ++i) { - assert(i < n); - auto& f = *first; - f.setCallback_([ctx, i, n](Try&& t) { - ctx->results[i] = std::move(t); - if (++ctx->count == n) { - ctx->p.setValue(std::move(ctx->results)); - delete ctx; - } - }); - } - - return std::move(f_saved); -} - -template -Future< - std::pair::value_type::value_type> > > -whenAny(InputIterator first, InputIterator last) { - typedef - typename std::iterator_traits::value_type::value_type T; - - auto ctx = new detail::WhenAnyContext(std::distance(first, last)); - auto f_saved = ctx->p.getFuture(); - - for (size_t i = 0; first != last; first++, i++) { - auto& f = *first; - f.setCallback_([i, ctx](Try&& t) { - if (!ctx->done.exchange(true)) { - ctx->p.setValue(std::make_pair(i, std::move(t))); - } - ctx->decref(); - }); - } - - return std::move(f_saved); -} - -template -Future::value_type::value_type>>>> -whenN(InputIterator first, InputIterator last, size_t n) { - typedef typename - std::iterator_traits::value_type::value_type T; - typedef std::vector>> V; - - struct ctx_t { - V v; - size_t completed; - Promise p; - }; - auto ctx = std::make_shared(); - ctx->completed = 0; - - // for each completed Future, increase count and add to vector, until we - // have n completed futures at which point we fulfil our Promise with the - // vector - auto it = first; - size_t i = 0; - while (it != last) { - it->then([ctx, n, i](Try&& t) { - auto& v = ctx->v; - auto c = ++ctx->completed; - if (c <= n) { - assert(ctx->v.size() < n); - v.push_back(std::make_pair(i, std::move(t))); - if (c == n) { - ctx->p.fulfilTry(Try(std::move(v))); - } - } - }); - - it++; - i++; - } - - if (i < n) { - ctx->p.setException(std::runtime_error("Not enough futures")); - } - - return ctx->p.getFuture(); -} - -template -Future -waitWithSemaphore(Future&& f) { - Baton<> baton; - auto done = f.then([&](Try &&t) { - baton.post(); - return std::move(t.value()); - }); - baton.wait(); - while (!done.isReady()) { - // There's a race here between the return here and the actual finishing of - // the future. f is completed, but the setup may not have finished on done - // after the baton has posted. - std::this_thread::yield(); - } - return done; -} - -template<> -inline Future waitWithSemaphore(Future&& f) { - Baton<> baton; - auto done = f.then([&](Try &&t) { - baton.post(); - t.value(); - }); - baton.wait(); - while (!done.isReady()) { - // There's a race here between the return here and the actual finishing of - // the future. f is completed, but the setup may not have finished on done - // after the baton has posted. - std::this_thread::yield(); - } - return done; -} - -template -Future -waitWithSemaphore(Future&& f, Duration timeout) { - auto baton = std::make_shared>(); - auto done = f.then([baton](Try &&t) { - baton->post(); - return std::move(t.value()); - }); - baton->timed_wait(std::chrono::system_clock::now() + timeout); - return done; -} - -template -Future -waitWithSemaphore(Future&& f, Duration timeout) { - auto baton = std::make_shared>(); - auto done = f.then([baton](Try &&t) { - baton->post(); - t.value(); - }); - baton->timed_wait(std::chrono::system_clock::now() + timeout); - return done; -} - -}} - -// I haven't included a Future specialization because I don't forsee us -// using it, however it is not difficult to add when needed. Refer to -// Future for guidance. std::future and boost::future code would also be -// instructive. diff --git a/folly/wangle/Future.h b/folly/wangle/Future.h deleted file mode 100644 index f124d7b5..00000000 --- a/folly/wangle/Future.h +++ /dev/null @@ -1,504 +0,0 @@ -/* - * Copyright 2014 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 - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace folly { namespace wangle { - -namespace detail { - template struct Core; - template struct VariadicContext; - - template - struct AliasIfVoid { - typedef typename std::conditional< - std::is_same::value, - int, - T>::type type; - }; -} - -template struct Promise; - -template struct isFuture; - -template -class Future { - public: - typedef T value_type; - - // not copyable - Future(Future const&) = delete; - Future& operator=(Future const&) = delete; - - // movable - Future(Future&&) noexcept; - Future& operator=(Future&&); - - ~Future(); - - /** Return the reference to result. Should not be called if !isReady(). - Will rethrow the exception if an exception has been - captured. - - This function is not thread safe - the returned Future can only - be executed from the thread that the executor runs it in. - See below for a thread safe version - */ - typename std::add_lvalue_reference::type - value(); - typename std::add_lvalue_reference::type - value() const; - - /// Returns an inactive Future which will call back on the other side of - /// executor (when it is activated). - /// - /// NB remember that Futures activate when they destruct. This is good, - /// it means that this will work: - /// - /// f.via(e).then(a).then(b); - /// - /// a and b will execute in the same context (the far side of e), because - /// the Future (temporary variable) created by via(e) does not call back - /// until it destructs, which is after then(a) and then(b) have been wired - /// up. - /// - /// But this is still racy: - /// - /// f = f.via(e).then(a); - /// f.then(b); - // The ref-qualifier allows for `this` to be moved out so we - // don't get access-after-free situations in chaining. - // https://akrzemi1.wordpress.com/2014/06/02/ref-qualifiers/ - template - Future via(Executor* executor) &&; - - /// This variant creates a new future, where the ref-qualifier && version - /// moves `this` out. This one is less efficient but avoids confusing users - /// when "return f.via(x);" fails. - template - Future via(Executor* executor) &; - - /** True when the result (or exception) is ready. */ - bool isReady() const; - - /** A reference to the Try of the value */ - Try& getTry(); - - /** When this Future has completed, execute func which is a function that - takes a Try&&. A Future for the return type of func is - returned. e.g. - - Future f2 = f1.then([](Try&&) { return string("foo"); }); - - The Future given to the functor is ready, and the functor may call - value(), which may rethrow if this has captured an exception. If func - throws, the exception will be captured in the Future that is returned. - */ - /* TODO n3428 and other async frameworks have something like then(scheduler, - Future), we might want to support a similar API which could be - implemented a little more efficiently than - f.via(executor).then(callback) */ - template - typename std::enable_if< - !isFuture&&)>::type>::value, - Future&&)>::type> >::type - then(F&& func); - - /// Variant where func takes a T directly, bypassing a try. Any exceptions - /// will be implicitly passed on to the resultant Future. - /// - /// Future f = makeFuture(42).then([](int i) { return i+1; }); - template - typename std::enable_if< - !std::is_same::value && - !isFuture::type&&)>::type>::value, - Future::type&&)>::type> >::type - then(F&& func); - - /// Like the above variant, but for void futures. That is, func takes no - /// argument. - /// - /// Future f = makeFuture().then([] { return 42; }); - template - typename std::enable_if< - std::is_same::value && - !isFuture::type>::value, - Future::type> >::type - then(F&& func); - - /// Variant where func returns a Future instead of a T. e.g. - /// - /// Future f2 = f1.then( - /// [](Try&&) { return makeFuture("foo"); }); - template - typename std::enable_if< - isFuture&&)>::type>::value, - Future&&)>::type::value_type> >::type - then(F&& func); - - /// Variant where func returns a Future and takes a T directly, bypassing - /// a Try. Any exceptions will be implicitly passed on to the resultant - /// Future. For example, - /// - /// Future f = makeFuture(42).then( - /// [](int i) { return makeFuture(i+1); }); - template - typename std::enable_if< - !std::is_same::value && - isFuture::type&&)>::type>::value, - Future::type&&)>::type::value_type> >::type - then(F&& func); - - /// Like the above variant, but for void futures. That is, func takes no - /// argument and returns a future. - /// - /// Future f = makeFuture().then( - /// [] { return makeFuture(42); }); - template - typename std::enable_if< - std::is_same::value && - isFuture::type>::value, - Future::type::value_type> >::type - then(F&& func); - - /// Variant where func is an ordinary function (static method, method) - /// - /// R doWork(Try&&); - /// - /// Future f2 = f1.then(doWork); - /// - /// or - /// - /// struct Worker { - /// static R doWork(Try&&); } - /// - /// Future f2 = f1.then(&Worker::doWork); - template - typename std::enable_if::value, Future>::type - inline then(R(*func)(Try&&)) { - return then([func](Try&& t) { - return (*func)(std::move(t)); - }); - } - - /// Variant where func returns a Future instead of a R. e.g. - /// - /// struct Worker { - /// Future doWork(Try&&); } - /// - /// Future f2 = f1.then(&Worker::doWork); - template - typename std::enable_if::value, R>::type - inline then(R(*func)(Try&&)) { - return then([func](Try&& t) { - return (*func)(std::move(t)); - }); - } - - /// Variant where func is an member function - /// - /// struct Worker { - /// R doWork(Try&&); } - /// - /// Worker *w; - /// Future f2 = f1.then(w, &Worker::doWork); - template - typename std::enable_if::value, Future>::type - inline then(Caller *instance, R(Caller::*func)(Try&&)) { - return then([instance, func](Try&& t) { - return (instance->*func)(std::move(t)); - }); - } - - // Same as above, but func takes void instead of Try&& - template - typename std::enable_if< - std::is_same::value && !isFuture::value, Future>::type - inline then(Caller *instance, R(Caller::*func)()) { - return then([instance, func]() { - return (instance->*func)(); - }); - } - - // Same as above, but func takes T&& instead of Try&& - template - typename std::enable_if< - !std::is_same::value && !isFuture::value, Future>::type - inline then( - Caller *instance, - R(Caller::*func)(typename detail::AliasIfVoid::type&&)) { - return then([instance, func](T&& t) { - return (instance->*func)(std::move(t)); - }); - } - - /// Variant where func returns a Future instead of a R. e.g. - /// - /// struct Worker { - /// Future doWork(Try&&); } - /// - /// Worker *w; - /// Future f2 = f1.then(w, &Worker::doWork); - template - typename std::enable_if::value, R>::type - inline then(Caller *instance, R(Caller::*func)(Try&&)) { - return then([instance, func](Try&& t) { - return (instance->*func)(std::move(t)); - }); - } - - // Same as above, but func takes void instead of Try&& - template - typename std::enable_if< - std::is_same::value && isFuture::value, R>::type - inline then(Caller *instance, R(Caller::*func)()) { - return then([instance, func]() { - return (instance->*func)(); - }); - } - - // Same as above, but func takes T&& instead of Try&& - template - typename std::enable_if< - !std::is_same::value && isFuture::value, R>::type - inline then( - Caller *instance, - R(Caller::*func)(typename detail::AliasIfVoid::type&&)) { - return then([instance, func](T&& t) { - return (instance->*func)(std::move(t)); - }); - } - - /// Convenience method for ignoring the value and creating a Future. - /// Exceptions still propagate. - Future then(); - - /// This is not the method you're looking for. - /// - /// This needs to be public because it's used by make* and when*, and it's - /// not worth listing all those and their fancy template signatures as - /// friends. But it's not for public consumption. - template - void setCallback_(F&& func); - - /// A Future's callback is executed when all three of these conditions have - /// become true: it has a value (set by the Promise), it has a callback (set - /// by then), and it is active (active by default). - /// - /// Inactive Futures will activate upon destruction. - Future& activate() & { - core_->activate(); - return *this; - } - Future& deactivate() & { - core_->deactivate(); - return *this; - } - Future activate() && { - core_->activate(); - return std::move(*this); - } - Future deactivate() && { - core_->deactivate(); - return std::move(*this); - } - - bool isActive() { - return core_->isActive(); - } - - template - void raise(E&& exception) { - raise(std::make_exception_ptr(std::forward(exception))); - } - - /// Raise an interrupt. If the promise holder has an interrupt - /// handler it will be called and potentially stop asynchronous work from - /// being done. This is advisory only - a promise holder may not set an - /// interrupt handler, or may do anything including ignore. But, if you know - /// your future supports this the most likely result is stopping or - /// preventing the asynchronous operation (if in time), and the promise - /// holder setting an exception on the future. (That may happen - /// asynchronously, of course.) - void raise(std::exception_ptr interrupt); - - void cancel() { - raise(FutureCancellation()); - } - - private: - typedef detail::Core* corePtr; - - // shared core state object - corePtr core_; - - explicit - Future(corePtr obj) : core_(obj) {} - - void detach(); - - void throwIfInvalid() const; - - friend class Promise; -}; - -/** - Make a completed Future by moving in a value. e.g. - - string foo = "foo"; - auto f = makeFuture(std::move(foo)); - - or - - auto f = makeFuture("foo"); -*/ -template -Future::type> makeFuture(T&& t); - -/** Make a completed void Future. */ -Future makeFuture(); - -/** Make a completed Future by executing a function. If the function throws - we capture the exception, otherwise we capture the result. */ -template -auto makeFutureTry( - F&& func, - typename std::enable_if< - !std::is_reference::value, bool>::type sdf = false) - -> Future; - -template -auto makeFutureTry( - F const& func) - -> Future; - -/// Make a failed Future from an exception_ptr. -/// Because the Future's type cannot be inferred you have to specify it, e.g. -/// -/// auto f = makeFuture(std::current_exception()); -template -Future makeFuture(std::exception_ptr const& e); - -/** Make a Future from an exception type E that can be passed to - std::make_exception_ptr(). */ -template -typename std::enable_if::value, - Future>::type -makeFuture(E const& e); - -/** Make a Future out of a Try */ -template -Future makeFuture(Try&& t); - -/* - * Return a new Future that will call back on the given Executor. - * This is just syntactic sugar for makeFuture().via(executor) - * - * @param executor the Executor to call back on - * - * @returns a void Future that will call back on the given executor - */ -template -Future via(Executor* executor); - -/** When all the input Futures complete, the returned Future will complete. - Errors do not cause early termination; this Future will always succeed - after all its Futures have finished (whether successfully or with an - error). - - The Futures are moved in, so your copies are invalid. If you need to - chain further from these Futures, use the variant with an output iterator. - - XXX is this still true? - This function is thread-safe for Futures running on different threads. - - The return type for Future input is a Future>> - */ -template -Future::value_type::value_type>>> -whenAll(InputIterator first, InputIterator last); - -/// This version takes a varying number of Futures instead of an iterator. -/// The return type for (Future, Future, ...) input -/// is a Future, Try, ...>>. -/// The Futures are moved in, so your copies are invalid. -template -typename detail::VariadicContext< - typename std::decay::type::value_type...>::type -whenAll(Fs&&... fs); - -/** The result is a pair of the index of the first Future to complete and - the Try. If multiple Futures complete at the same time (or are already - complete when passed in), the "winner" is chosen non-deterministically. - - This function is thread-safe for Futures running on different threads. - */ -template -Future::value_type::value_type>>> -whenAny(InputIterator first, InputIterator last); - -/** when n Futures have completed, the Future completes with a vector of - the index and Try of those n Futures (the indices refer to the original - order, but the result vector will be in an arbitrary order) - - Not thread safe. - */ -template -Future::value_type::value_type>>>> -whenN(InputIterator first, InputIterator last, size_t n); - -/** Wait for the given future to complete on a semaphore. Returns a completed - * future containing the result. - * - * NB if the promise for the future would be fulfilled in the same thread that - * you call this, it will deadlock. - */ -template -Future waitWithSemaphore(Future&& f); - -/** Wait for up to `timeout` for the given future to complete. Returns a future - * which may or may not be completed depending whether the given future - * completed in time - * - * Note: each call to this starts a (short-lived) thread and allocates memory. - */ -template -Future waitWithSemaphore(Future&& f, Duration timeout); - -}} // folly::wangle - -#include diff --git a/folly/wangle/InlineExecutor.cpp b/folly/wangle/InlineExecutor.cpp deleted file mode 100644 index 1ef2f068..00000000 --- a/folly/wangle/InlineExecutor.cpp +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright 2014 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. - */ diff --git a/folly/wangle/InlineExecutor.h b/folly/wangle/InlineExecutor.h deleted file mode 100644 index e6924085..00000000 --- a/folly/wangle/InlineExecutor.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2014 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 -#include - -namespace folly { namespace wangle { - - /// When work is "queued", execute it immediately inline. - /// Usually when you think you want this, you actually want a - /// QueuedImmediateExecutor. - class InlineExecutor : public Executor { - public: - void add(Func f) override { - f(); - } - }; - -}} diff --git a/folly/wangle/ManualExecutor.cpp b/folly/wangle/ManualExecutor.cpp deleted file mode 100644 index 4a2ee3a3..00000000 --- a/folly/wangle/ManualExecutor.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2014 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 - -#include -#include -#include - -#include - -namespace folly { namespace wangle { - -ManualExecutor::ManualExecutor() { - if (sem_init(&sem_, 0, 0) == -1) { - throw std::runtime_error(std::string("sem_init: ") + strerror(errno)); - } -} - -void ManualExecutor::add(Func callback) { - std::lock_guard lock(lock_); - funcs_.push(std::move(callback)); - sem_post(&sem_); -} - -size_t ManualExecutor::run() { - size_t count; - size_t n; - Func func; - - { - std::lock_guard lock(lock_); - - while (!scheduledFuncs_.empty()) { - auto& sf = scheduledFuncs_.top(); - if (sf.time > now_) - break; - funcs_.push(sf.func); - scheduledFuncs_.pop(); - } - - n = funcs_.size(); - } - - for (count = 0; count < n; count++) { - { - std::lock_guard lock(lock_); - if (funcs_.empty()) { - break; - } - - // Balance the semaphore so it doesn't grow without bound - // if nobody is calling wait(). - // This may fail (with EAGAIN), that's fine. - sem_trywait(&sem_); - - func = std::move(funcs_.front()); - funcs_.pop(); - } - func(); - } - - return count; -} - -void ManualExecutor::wait() { - while (true) { - { - std::lock_guard lock(lock_); - if (!funcs_.empty()) - break; - } - - auto ret = sem_wait(&sem_); - if (ret == 0) { - break; - } - if (errno != EINVAL) { - throw std::runtime_error(std::string("sem_wait: ") + strerror(errno)); - } - } -} - -void ManualExecutor::advanceTo(TimePoint const& t) { - if (t > now_) { - now_ = t; - } - run(); -} - -}} // namespace diff --git a/folly/wangle/ManualExecutor.h b/folly/wangle/ManualExecutor.h deleted file mode 100644 index 329f9fc6..00000000 --- a/folly/wangle/ManualExecutor.h +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2014 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 -#include -#include -#include -#include -#include -#include - -namespace folly { namespace wangle { - /// A ManualExecutor only does work when you turn the crank, by calling - /// run() or indirectly with makeProgress() or waitFor(). - /// - /// The clock for a manual executor starts at 0 and advances only when you - /// ask it to. i.e. time is also under manual control. - /// - /// NB No attempt has been made to make anything other than add and schedule - /// threadsafe. - class ManualExecutor : public ScheduledExecutor { - public: - ManualExecutor(); - - void add(Func) override; - - /// Do work. Returns the number of functions that were executed (maybe 0). - /// Non-blocking, in the sense that we don't wait for work (we can't - /// control whether one of the functions blocks). - /// This is stable, it will not chase an ever-increasing tail of work. - /// This also means, there may be more work available to perform at the - /// moment that this returns. - size_t run(); - - /// Wait for work to do. - void wait(); - - /// Wait for work to do, and do it. - void makeProgress() { - wait(); - run(); - } - - /// makeProgress until this Future is ready. - template void waitFor(F const& f) { - // TODO(5427828) -#if 0 - while (!f.isReady()) - makeProgress(); -#else - while (!f.isReady()) { - run(); - } -#endif - - } - - virtual void scheduleAt(Func&& f, TimePoint const& t) override { - std::lock_guard lock(lock_); - scheduledFuncs_.emplace(t, std::move(f)); - sem_post(&sem_); - } - - /// Advance the clock. The clock never advances on its own. - /// Advancing the clock causes some work to be done, if work is available - /// to do (perhaps newly available because of the advanced clock). - /// If dur is <= 0 this is a noop. - void advance(Duration const& dur) { - advanceTo(now_ + dur); - } - - /// Advance the clock to this absolute time. If t is <= now(), - /// this is a noop. - void advanceTo(TimePoint const& t); - - TimePoint now() override { return now_; } - - private: - std::mutex lock_; - std::queue funcs_; - sem_t sem_; - - // helper class to enable ordering of scheduled events in the priority - // queue - struct ScheduledFunc { - TimePoint time; - size_t ordinal; - Func func; - - ScheduledFunc(TimePoint const& t, Func&& f) - : time(t), func(std::move(f)) - { - static size_t seq = 0; - ordinal = seq++; - } - - bool operator<(ScheduledFunc const& b) const { - if (time == b.time) - return ordinal < b.ordinal; - return time < b.time; - } - }; - std::priority_queue scheduledFuncs_; - TimePoint now_ = now_.min(); - }; - -}} diff --git a/folly/wangle/OpaqueCallbackShunt.h b/folly/wangle/OpaqueCallbackShunt.h deleted file mode 100644 index 6cd325ec..00000000 --- a/folly/wangle/OpaqueCallbackShunt.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2014 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 - -#include - -namespace folly { namespace wangle { - -/// These classes help you wrap an existing C style callback function -/// into a Future. -/// -/// void legacy_send_async(..., void (*cb)(void*), void*); -/// -/// Future wrappedSendAsync(T&& obj) { -/// auto handle = new OpaqueCallbackShunt(obj); -/// auto future = handle->promise_.getFuture(); -/// legacy_send_async(..., OpaqueCallbackShunt::callback, handle) -/// return future; -/// } -/// -/// If the legacy function doesn't conform to void (*cb)(void*), use a lambda: -/// -/// auto cb = [](t1*, t2*, void* arg) { -/// OpaqueCallbackShunt::callback(arg); -/// }; -/// legacy_send_async(..., cb, handle); - -template -class OpaqueCallbackShunt { -public: - explicit OpaqueCallbackShunt(T&& obj) - : obj_(std::move(obj)) { } - static void callback(void* arg) { - std::unique_ptr> handle( - static_cast*>(arg)); - handle->promise_.setValue(std::move(handle->obj_)); - } - folly::wangle::Promise promise_; -private: - T obj_; -}; - -}} // folly::wangle diff --git a/folly/wangle/Promise-inl.h b/folly/wangle/Promise-inl.h deleted file mode 100644 index 68d69641..00000000 --- a/folly/wangle/Promise-inl.h +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2014 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 - -#include -#include - -#include -#include - -namespace folly { namespace wangle { - -template -Promise::Promise() : retrieved_(false), core_(new detail::Core()) -{} - -template -Promise::Promise(Promise&& other) : core_(nullptr) { - *this = std::move(other); -} - -template -Promise& Promise::operator=(Promise&& other) { - std::swap(core_, other.core_); - std::swap(retrieved_, other.retrieved_); - return *this; -} - -template -void Promise::throwIfFulfilled() { - if (!core_) - throw NoState(); - if (core_->ready()) - throw PromiseAlreadySatisfied(); -} - -template -void Promise::throwIfRetrieved() { - if (retrieved_) - throw FutureAlreadyRetrieved(); -} - -template -Promise::~Promise() { - detach(); -} - -template -void Promise::detach() { - if (core_) { - if (!retrieved_) - core_->detachFuture(); - core_->detachPromise(); - core_ = nullptr; - } -} - -template -Future Promise::getFuture() { - throwIfRetrieved(); - retrieved_ = true; - return Future(core_); -} - -template -template -void Promise::setException(E const& e) { - setException(std::make_exception_ptr(e)); -} - -template -void Promise::setException(std::exception_ptr const& e) { - throwIfFulfilled(); - core_->setResult(Try(e)); -} - -template -void Promise::setInterruptHandler( - std::function fn) { - core_->setInterruptHandler(std::move(fn)); -} - -template -void Promise::fulfilTry(Try&& t) { - throwIfFulfilled(); - core_->setResult(std::move(t)); -} - -template -template -void Promise::setValue(M&& v) { - static_assert(!std::is_same::value, - "Use setValue() instead"); - - fulfilTry(Try(std::forward(v))); -} - -template -void Promise::setValue() { - static_assert(std::is_same::value, - "Use setValue(value) instead"); - - fulfilTry(Try()); -} - -template -template -void Promise::fulfil(F&& func) { - throwIfFulfilled(); - fulfilTry(makeTryFunction(std::forward(func))); -} - -}} diff --git a/folly/wangle/Promise.h b/folly/wangle/Promise.h deleted file mode 100644 index 7442e451..00000000 --- a/folly/wangle/Promise.h +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2014 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 - -#include - -namespace folly { namespace wangle { - -// forward declaration -template class Future; - -template -class Promise { -public: - Promise(); - ~Promise(); - - // not copyable - Promise(Promise const&) = delete; - Promise& operator=(Promise const&) = delete; - - // movable - Promise(Promise&&); - Promise& operator=(Promise&&); - - /** Return a Future tied to the shared core state. This can be called only - once, thereafter Future already retrieved exception will be raised. */ - Future getFuture(); - - /** Fulfil the Promise with an exception_ptr, e.g. - try { - ... - } catch (...) { - p.setException(std::current_exception()); - } - */ - void setException(std::exception_ptr const&); - - /** Fulfil the Promise with an exception type E, which can be passed to - std::make_exception_ptr(). Useful for originating exceptions. If you - caught an exception the exception_ptr form is more appropriate. - */ - template void setException(E const&); - - /// Set an interrupt handler to handle interrupts. See the documentation for - /// Future::raise(). Your handler can do whatever it wants, but if you - /// bother to set one then you probably will want to fulfil the promise with - /// an exception (or special value) indicating how the interrupt was - /// handled. - void setInterruptHandler(std::function); - - /** Fulfil this Promise (only for Promise) */ - void setValue(); - - /** Set the value (use perfect forwarding for both move and copy) */ - template - void setValue(M&& value); - - void fulfilTry(Try&& t); - - /** Fulfil this Promise with the result of a function that takes no - arguments and returns something implicitly convertible to T. - Captures exceptions. e.g. - - p.fulfil([] { do something that may throw; return a T; }); - */ - template - void fulfil(F&& func); - -private: - typedef typename Future::corePtr corePtr; - - // Whether the Future has been retrieved (a one-time operation). - bool retrieved_; - - // shared core state object - corePtr core_; - - void throwIfFulfilled(); - void throwIfRetrieved(); - void detach(); -}; - -}} - -#include -#include diff --git a/folly/wangle/QueuedImmediateExecutor.cpp b/folly/wangle/QueuedImmediateExecutor.cpp deleted file mode 100644 index 739ba5ef..00000000 --- a/folly/wangle/QueuedImmediateExecutor.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2014 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 -#include -#include - -namespace folly { namespace wangle { - -void QueuedImmediateExecutor::add(Func callback) { - thread_local std::queue q; - - if (q.empty()) { - q.push(std::move(callback)); - while (!q.empty()) { - q.front()(); - q.pop(); - } - } else { - q.push(callback); - } -} - -}} // namespace diff --git a/folly/wangle/QueuedImmediateExecutor.h b/folly/wangle/QueuedImmediateExecutor.h deleted file mode 100644 index a82c32db..00000000 --- a/folly/wangle/QueuedImmediateExecutor.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2014 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 - -#include - -namespace folly { namespace wangle { - -/** - * Runs inline like InlineExecutor, but with a queue so that any tasks added - * to this executor by one of its own callbacks will be queued instead of - * executed inline (nested). This is usually better behavior than Inline. - */ -class QueuedImmediateExecutor : public Executor { - public: - void add(Func) override; -}; - -}} // namespace diff --git a/folly/wangle/README.md b/folly/wangle/README.md deleted file mode 100644 index cb88fe36..00000000 --- a/folly/wangle/README.md +++ /dev/null @@ -1,278 +0,0 @@ -# Wangle -Wangle is a framework for expressing asynchronous code in C++ using the Future pattern. - -**wan•gle** |ˈwaNGgəl| informal -*verb* -Obtain (something that is desired) by persuading others to comply or by manipulating events. - -*noun* -A framework for expressing asynchronous control flow in C++, that is composable and easily translated to/from synchronous code. - -*synonyms* -[Finagle](http://twitter.github.io/finagle/) - -Wangle is a futures-based async framework inspired by [Twitter's Finagle](http://twitter.github.io/finagle/) (which is in scala), and (loosely) building upon the existing (but anemic) Futures code found in the C++11 standard ([`std::future`](http://en.cppreference.com/w/cpp/thread/future)) and [`boost::future`](http://www.boost.org/doc/libs/1_53_0/boost/thread/future.hpp) (especially >= 1.53.0). Although inspired by the std::future interface, it is not syntactically drop-in compatible because some ideas didn't translate well enough and we decided to break from the API. But semantically, it should be straightforward to translate from existing std::future code to Wangle. - -The primary semantic differences are that Wangle Futures and Promises are not threadsafe; and as does `boost::future`, Wangle supports continuing callbacks (`then()`) and there are helper methods `whenAll()` and `whenAny()` which are important compositional building blocks. - -## Brief Synopsis - -```C++ -#include -using namespace folly::wangle; -using namespace std; - -void foo(int x) { - // do something with x - cout << "foo(" << x << ")" << endl; -} - -// ... - - cout << "making Promise" << endl; - Promise p; - Future f = p.getFuture(); - f.then( - [](Try&& t) { - foo(t.value()); - }); - cout << "Future chain made" << endl; - -// ... now perhaps in another event callback - - cout << "fulfilling Promise" << endl; - p.setValue(42); - cout << "Promise fulfilled" << endl; -``` - -This would print: - -``` -making Promise -Future chain made -fulfilling Promise -foo(42) -Promise fulfilled -``` - -## User Guide - -Let's begin with an example. Consider a simplified Memcache client class with this interface: - -```C++ -class MemcacheClient { - public: - struct GetReply { - enum class Result { - FOUND, - NOT_FOUND, - SERVER_ERROR, - }; - - Result result; - // The value when result is FOUND, - // The error message when result is SERVER_ERROR or CLIENT_ERROR - // undefined otherwise - std::string value; - }; - - GetReply get(std::string key); -}; -``` - -This API is synchronous, i.e. when you call `get()` you have to wait for the result. This is very simple, but unfortunately it is also very easy to write very slow code using synchronous APIs. - -Now, consider this traditional asynchronous signature for `get()`: - -```C++ -int get(std::string key, std::function callback); -``` - -When you call `get()`, your asynchronous operation begins and when it finishes your callback will be called with the result. (Unless something goes drastically wrong and you get an error code from `get()`.) Very performant code can be written with an API like this, but for nontrivial applications the code descends into a special kind of spaghetti code affectionately referred to as "callback hell". - -The Future-based API looks like this: - -```C++ -Future get(std::string key); -``` - -A `Future` is a placeholder for the `GetReply` that we will eventually get. A Future usually starts life out "unfulfilled", or incomplete, i.e.: - -```C++ -fut.isReady() == false -fut.value() // will throw an exception because the Future is not ready -``` - -At some point in the future, the Future will have been fulfilled, and we can access its value. - -```C++ -fut.isReady() == true -GetReply& reply = fut.value(); -``` - -Futures support exceptions. If something exceptional happened, your Future may represent an exception instead of a value. In that case: - -```C++ -fut.isReady() == true -fut.value() // will rethrow the exception -``` - -Just what is exceptional depends on the API. In our example we have chosen not to raise exceptions for `SERVER_ERROR`, but represent this explicitly in the `GetReply` object. On the other hand, an astute Memcache veteran would notice that we left `CLIENT_ERROR` out of `GetReply::Result`, and perhaps a `CLIENT_ERROR` would have been raised as an exception, because `CLIENT_ERROR` means there's a bug in the library and this would be truly exceptional. These decisions are judgement calls by the API designer. The important thing is that exceptional conditions (including and especially spurious exceptions that nobody expects) get captured and can be handled higher up the "stack". - -So far we have described a way to initiate an asynchronous operation via an API that returns a Future, and then sometime later after it is fulfilled, we get its value. This is slightly more useful than a synchronous API, but it's not yet ideal. There are two more very important pieces to the puzzle. - -First, we can aggregate Futures, to define a new Future that completes after some or all of the aggregated Futures complete. Consider two examples: fetching a batch of requests and waiting for all of them, and fetching a group of requests and waiting for only one of them. - -```C++ -vector> futs; -for (auto& key : keys) { - futs.push_back(mc.get(key)); -} -auto all = whenAll(futs.begin(), futs.end()); - -vector> futs; -for (auto& key : keys) { - futs.push_back(mc.get(key)); -} -auto any = whenAny(futs.begin(), futs.end()); -``` - -`all` and `any` are Futures (for the exact type and usage see the header files). They will be complete when all/one of `futs` are complete, respectively. (There is also `whenN()` for when you need *some*.) - -Second, we can attach callbacks to a Future, and chain them together monadically. An example will clarify: - -```C++ -Future fut1 = mc.get("foo"); - -Future fut2 = fut1.then( - [](Try&& t) { - if (t.value().result == MemcacheClient::GetReply::Result::FOUND) - return t.value().value; - throw SomeException("No value"); - }); - -Future fut3 = fut2.then( - [](Try&& t) { - try { - cout << t.value() << endl; - } catch (std::exception const& e) { - cerr << e.what() << endl; - } - }); -``` - -That example is a little contrived but the idea is that you can transform a result from one type to another, potentially in a chain, and unhandled errors propagate. Of course, the intermediate variables are optional. `Try` is the object wrapper that supports both value and exception. - -Using `then` to add callbacks is idiomatic. It brings all the code into one place, which avoids callback hell. - -Up to this point we have skirted around the matter of waiting for Futures. You may never need to wait for a Future, because your code is event-driven and all follow-up action happens in a then-block. But if want to have a batch workflow, where you initiate a batch of asynchronous operations and then wait for them all to finish at a synchronization point, then you will want to wait for a Future. - -Other future frameworks like Finagle and std::future/boost::future, give you the ability to wait directly on a Future, by calling `fut.wait()` (naturally enough). Wangle has diverged from this pattern because we don't want to be in the business of dictating how your thread waits. We may work out something that we feel is sufficiently general, in the meantime adapt this spin loop to however your thread should wait: - - while (!f.isReady()) {} - -(Hint: you might want to use an event loop or a semaphore or something. You probably don't want to just spin like this.) - -Wangle is partially threadsafe. A Promise or Future can migrate between threads as long as there's a full memory barrier of some sort. `Future::then` and `Promise::setValue` (and all variants that boil down to those two calls) can be called from different threads. BUT, be warned that you might be surprised about which thread your callback executes on. Let's consider an example. - -```C++ -// Thread A -Promise p; -auto f = p.getFuture(); - -// Thread B -f.then(x).then(y).then(z); - -// Thread A -p.setValue(); -``` - -This is legal and technically threadsafe. However, it is important to realize that you do not know in which thread `x`, `y`, and/or `z` will execute. Maybe they will execute in Thread A when `p.setValue()` is called. Or, maybe they will execute in Thread B when `f.then` is called. Or, maybe `x` will execute in Thread B, but `y` and/or `z` will execute in Thread A. There's a race between `setValue` and `then`—whichever runs last will execute the callback. The only guarantee is that one of them will run the callback. - -Naturally, you will want some control over which thread executes callbacks. We have a few mechanisms to help. - -The first and most useful is `via`, which passes execution through an `Executor`, which usually has the effect of running the callback in a new thread. -```C++ -aFuture - .then(x) - .via(e1).then(y1).then(y2) - .via(e2).then(z); -``` -`x` will execute in the current thread. `y1` and `y2` will execute in the thread on the other side of `e1`, and `z` will execute in the thread on the other side of `e2`. `y1` and `y2` will execute on the same thread, whichever thread that is. If `e1` and `e2` execute in different threads than the current thread, then the final callback does not happen in the current thread. If you want to get back to the current thread, you need to get there via an executor. - -This works because `via` returns a deactivated ("cold") Future, which blocks the propagation of callbacks until it is activated. Activation happens either explicitly (`activate`) or implicitly when the Future returned by `via` is destructed. In this example, there is no ambiguity about in which context any of the callbacks happen (including `y2`), because propagation is blocked at the `via` callsites until after everything is wired up (temporaries are destructed after the calls to `then` have completed). - -You can still have a race after `via` if you break it into multiple statements, e.g. in this counterexample: -```C++ -f = f.via(e1).then(y1).then(y2); // nothing racy here -f2.then(y3); // racy -``` - -## You make me Promises, Promises - -If you are wrapping an asynchronous operation, or providing an asynchronous API to users, then you will want to make Promises. Every Future has a corresponding Promise (except Futures that spring into existence already completed, with `makeFuture()`). Promises are simple, you make one, you extract the Future, and you fulfil it with a value or an exception. Example: - -```C++ -Promise p; -Future f = p.getFuture(); - -f.isReady() == false - -p.setValue(42); - -f.isReady() == true -f.value() == 42 -``` - -and an exception example: - -```C++ -Promise p; -Future f = p.getFuture(); - -f.isReady() == false - -p.setException(std::runtime_error("Fail")); - -f.isReady() == true -f.value() // throws the exception -``` - -It's good practice to use fulfil which takes a function and automatically captures exceptions, e.g. - -```C++ -Promise p; -p.fulfil([]{ - try { - // do stuff that may throw - return 42; - } catch (MySpecialException const& e) { - // handle it - return 7; - } - // Any exceptions that we didn't catch, will be caught for us -}); -``` - -## FAQ - -### Why not use std::future? -No callback support. -See also http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3428.pdf - -### Why not use boost::future? -- 1.53 is brand new, and not in fbcode -- It's still a bit buggy/bleeding-edge -- They haven't fleshed out the threading model very well yet, e.g. every single `then` currently spawns a new thread unless you explicitly ask it to work on this thread only, and there is no support for executors yet. - -### Why use heap-allocated shared state? Why is Promise not a subclass of Future? -C++. It boils down to wanting to return a Future by value for performance (move semantics and compiler optimizations), and programmer sanity, and needing a reference to the shared state by both the user (which holds the Future) and the asynchronous operation (which holds the Promise), and allowing either to go out of scope. - -### What about proper continuations? Futures suck. -People mean two things here, they either mean using continuations (as in CSP) or they mean using generators which require continuations. It's important to know those are two distinct questions, but in our context the answer is the same because continuations are a prerequisite for generators. - -C++ doesn't directly support continuations very well. But there are some ways to do them in C/C++ that rely on some rather low-level facilities like `setjmp` and `longjmp` (among others). So yes, they are possible (cf. [Mordor](https://github.com/ccutrer/mordor)). - -The tradeoff is memory. Each continuation has a stack, and that stack is usually fixed-size and has to be big enough to support whatever ordinary computation you might want to do on it. So each living continuation requires a relatively large amount of memory. If you know the number of continuations will be small, this might be a good fit. In particular, it might be faster and the code might read cleaner. - -Wangle takes the middle road between callback hell and continuations, one which has been trodden and proved useful in other languages. It doesn't claim to be the best model for all situations. Use your tools wisely. diff --git a/folly/wangle/ScheduledExecutor.h b/folly/wangle/ScheduledExecutor.h deleted file mode 100644 index 94850c28..00000000 --- a/folly/wangle/ScheduledExecutor.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2014 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 - -#include -#include -#include -#include - -namespace folly { namespace wangle { - // An executor that supports timed scheduling. Like RxScheduler. - class ScheduledExecutor : public Executor { - public: - // Reality is that better than millisecond resolution is very hard to - // achieve. However, we reserve the right to be incredible. - typedef std::chrono::microseconds Duration; - typedef std::chrono::steady_clock::time_point TimePoint; - - virtual ~ScheduledExecutor() = default; - - virtual void add(Func) override = 0; - - /// Alias for add() (for Rx consistency) - void schedule(Func&& a) { add(std::move(a)); } - - /// Schedule a Func to be executed after dur time has elapsed - /// Expect millisecond resolution at best. - void schedule(Func&& a, Duration const& dur) { - scheduleAt(std::move(a), now() + dur); - } - - /// Schedule a Func to be executed at time t, or as soon afterward as - /// possible. Expect millisecond resolution at best. Must be threadsafe. - virtual void scheduleAt(Func&& a, TimePoint const& t) { - throw std::logic_error("unimplemented"); - } - - /// Get this executor's notion of time. Must be threadsafe. - virtual TimePoint now() { - return std::chrono::steady_clock::now(); - } - }; -}} diff --git a/folly/wangle/Try-inl.h b/folly/wangle/Try-inl.h deleted file mode 100644 index 935f54bc..00000000 --- a/folly/wangle/Try-inl.h +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2014 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 - -#include - -#include - -namespace folly { namespace wangle { - -template -Try::Try(Try&& t) : contains_(t.contains_) { - if (contains_ == Contains::VALUE) { - new (&value_)T(std::move(t.value_)); - } else if (contains_ == Contains::EXCEPTION) { - new (&e_)std::exception_ptr(t.e_); - } -} - -template -Try& Try::operator=(Try&& t) { - this->~Try(); - contains_ = t.contains_; - if (contains_ == Contains::VALUE) { - new (&value_)T(std::move(t.value_)); - } else if (contains_ == Contains::EXCEPTION) { - new (&e_)std::exception_ptr(t.e_); - } - return *this; -} - -template -Try::~Try() { - if (contains_ == Contains::VALUE) { - value_.~T(); - } else if (contains_ == Contains::EXCEPTION) { - e_.~exception_ptr(); - } -} - -template -T& Try::value() { - throwIfFailed(); - return value_; -} - -template -const T& Try::value() const { - throwIfFailed(); - return value_; -} - -template -void Try::throwIfFailed() const { - if (contains_ != Contains::VALUE) { - if (contains_ == Contains::EXCEPTION) { - std::rethrow_exception(e_); - } else { - throw UsingUninitializedTry(); - } - } -} - -void Try::throwIfFailed() const { - if (!hasValue_) { - std::rethrow_exception(e_); - } -} - -template -inline T moveFromTry(wangle::Try&& t) { - return std::move(t.value()); -} - -inline void moveFromTry(wangle::Try&& t) { - return t.value(); -} - -template -typename std::enable_if< - !std::is_same::type, void>::value, - Try::type>>::type -makeTryFunction(F&& f) { - typedef typename std::result_of::type ResultType; - try { - auto value = f(); - return Try(std::move(value)); - } catch (...) { - return Try(std::current_exception()); - } -} - -template -typename std::enable_if< - std::is_same::type, void>::value, - Try>::type -makeTryFunction(F&& f) { - try { - f(); - return Try(); - } catch (...) { - return Try(std::current_exception()); - } -} - -}} diff --git a/folly/wangle/Try.h b/folly/wangle/Try.h deleted file mode 100644 index bc7800d8..00000000 --- a/folly/wangle/Try.h +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2014 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 - -#include -#include -#include -#include -#include - -namespace folly { namespace wangle { - -template -class Try { - static_assert(!std::is_reference::value, - "Try may not be used with reference types"); - - enum class Contains { - VALUE, - EXCEPTION, - NOTHING, - }; - - public: - typedef T element_type; - - Try() : contains_(Contains::NOTHING) {} - explicit Try(const T& v) : contains_(Contains::VALUE), value_(v) {} - explicit Try(T&& v) : contains_(Contains::VALUE), value_(std::move(v)) {} - explicit Try(std::exception_ptr e) : contains_(Contains::EXCEPTION), e_(e) {} - - // move - Try(Try&& t); - Try& operator=(Try&& t); - - // no copy - Try(const Try& t) = delete; - Try& operator=(const Try& t) = delete; - - ~Try(); - - T& value(); - const T& value() const; - - void throwIfFailed() const; - - const T& operator*() const { return value(); } - T& operator*() { return value(); } - - const T* operator->() const { return &value(); } - T* operator->() { return &value(); } - - bool hasValue() const { return contains_ == Contains::VALUE; } - bool hasException() const { return contains_ == Contains::EXCEPTION; } - - std::exception_ptr getException() const { - if (UNLIKELY(!hasException())) { - throw WangleException( - "getException(): Try does not contain an exception"); - } - return e_; - } - - private: - Contains contains_; - union { - T value_; - std::exception_ptr e_; - }; -}; - -template <> -class Try { - public: - Try() : hasValue_(true) {} - explicit Try(std::exception_ptr e) : hasValue_(false), e_(e) {} - - void value() const { throwIfFailed(); } - void operator*() const { return value(); } - - inline void throwIfFailed() const; - - bool hasValue() const { return hasValue_; } - bool hasException() const { return !hasValue_; } - - std::exception_ptr getException() const { - if (UNLIKELY(!hasException())) { - throw WangleException( - "getException(): Try does not contain an exception"); - } - return e_; - } - - private: - bool hasValue_; - std::exception_ptr e_; -}; - -/** - * Extracts value from try and returns it. Throws if try contained an exception. - */ -template -T moveFromTry(wangle::Try&& t); - -/** - * Throws if try contained an exception. - */ -void moveFromTry(wangle::Try&& t); - -/** - * Constructs Try based on the result of execution of function f (e.g. result - * or exception). - */ -template -typename std::enable_if< - !std::is_same::type, void>::value, - Try::type>>::type -makeTryFunction(F&& f); - -/** - * makeTryFunction specialization for void functions. - */ -template -typename std::enable_if< - std::is_same::type, void>::value, - Try>::type -makeTryFunction(F&& f); - - -}} - -#include diff --git a/folly/wangle/WangleException.h b/folly/wangle/WangleException.h deleted file mode 100644 index bddf86c7..00000000 --- a/folly/wangle/WangleException.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2014 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 - -#include -#include - -namespace folly { namespace wangle { - -class WangleException : public std::exception { - -public: - - explicit WangleException(std::string message_arg) - : message(message_arg) {} - - ~WangleException() throw(){} - - virtual const char *what() const throw() { - return message.c_str(); - } - - bool operator==(const WangleException &other) const{ - return other.message == this->message; - } - - bool operator!=(const WangleException &other) const{ - return !(*this == other); - } - - protected: - std::string message; -}; - -class BrokenPromise : public WangleException { - public: - explicit BrokenPromise() : - WangleException("Broken promise") { } -}; - -class NoState : public WangleException { - public: - explicit NoState() : WangleException("No state") { } -}; - -class PromiseAlreadySatisfied : public WangleException { - public: - explicit PromiseAlreadySatisfied() : - WangleException("Promise already satisfied") { } -}; - -class FutureNotReady : public WangleException { - public: - explicit FutureNotReady() : - WangleException("Future not ready") { } -}; - -class FutureAlreadyRetrieved : public WangleException { - public: - explicit FutureAlreadyRetrieved () : - WangleException("Future already retrieved") { } -}; - -class UsingUninitializedTry : public WangleException { - public: - explicit UsingUninitializedTry() : - WangleException("Using unitialized try") { } -}; - -class FutureCancellation : public WangleException { - public: - FutureCancellation() : WangleException("Future was cancelled") {} -}; - -}} diff --git a/folly/wangle/detail/Core.h b/folly/wangle/detail/Core.h deleted file mode 100644 index 933cd7ad..00000000 --- a/folly/wangle/detail/Core.h +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright 2014 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 - -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include - -#include - -namespace folly { namespace wangle { namespace detail { - -// As of GCC 4.8.1, the std::function in libstdc++ optimizes only for pointers -// to functions, using a helper avoids a call to malloc. -template -void empty_callback(Try&&) { } - -enum class State { - Waiting, - Interruptible, - Interrupted, - Done, -}; - -/** The shared state object for Future and Promise. */ -template -class Core : protected FSM { - public: - // This must be heap-constructed. There's probably a way to enforce that in - // code but since this is just internal detail code and I don't know how - // off-hand, I'm punting. - Core() : FSM(State::Waiting) {} - ~Core() { - assert(calledBack_); - assert(detached_ == 2); - } - - // not copyable - Core(Core const&) = delete; - Core& operator=(Core const&) = delete; - - // not movable (see comment in the implementation of Future::then) - Core(Core&&) noexcept = delete; - Core& operator=(Core&&) = delete; - - Try& getTry() { - if (ready()) { - return *result_; - } else { - throw FutureNotReady(); - } - } - - template - void setCallback(F func) { - auto setCallback_ = [&]{ - if (callback_) { - throw std::logic_error("setCallback called twice"); - } - - context_ = RequestContext::saveContext(); - callback_ = std::move(func); - }; - - FSM_START - case State::Waiting: - case State::Interruptible: - case State::Interrupted: - FSM_UPDATE(state, setCallback_); - break; - - case State::Done: - FSM_UPDATE2(State::Done, - setCallback_, - [&]{ maybeCallback(); }); - break; - FSM_END - } - - void setResult(Try&& t) { - FSM_START - case State::Waiting: - case State::Interruptible: - case State::Interrupted: - FSM_UPDATE2(State::Done, - [&]{ result_ = std::move(t); }, - [&]{ maybeCallback(); }); - break; - - case State::Done: - throw std::logic_error("setResult called twice"); - FSM_END - } - - bool ready() const { - return getState() == State::Done; - } - - // Called by a destructing Future - void detachFuture() { - if (!callback_) { - setCallback(empty_callback); - } - activate(); - detachOne(); - } - - // Called by a destructing Promise - void detachPromise() { - if (!ready()) { - setResult(Try(std::make_exception_ptr(BrokenPromise()))); - } - detachOne(); - } - - void deactivate() { - active_ = false; - } - - void activate() { - active_ = true; - if (ready()) { - maybeCallback(); - } - } - - bool isActive() { return active_; } - - void setExecutor(Executor* x) { - executor_ = x; - } - - void raise(std::exception_ptr const& e) { - FSM_START - case State::Interruptible: - FSM_UPDATE2(State::Interrupted, - [&]{ interrupt_ = e; }, - [&]{ interruptHandler_(interrupt_); }); - break; - - case State::Waiting: - case State::Interrupted: - FSM_UPDATE(State::Interrupted, - [&]{ interrupt_ = e; }); - break; - - case State::Done: - FSM_BREAK - FSM_END - } - - void setInterruptHandler(std::function fn) { - FSM_START - case State::Waiting: - case State::Interruptible: - FSM_UPDATE(State::Interruptible, - [&]{ interruptHandler_ = std::move(fn); }); - break; - - case State::Interrupted: - fn(interrupt_); - FSM_BREAK - - case State::Done: - FSM_BREAK - FSM_END - } - - private: - void maybeCallback() { - assert(ready()); - if (isActive() && callback_) { - if (!calledBack_.exchange(true)) { - // TODO(5306911) we should probably try/catch - Executor* x = executor_; - - RequestContext::setContext(context_); - if (x) { - MoveWrapper&&)>> cb(std::move(callback_)); - MoveWrapper>> val(std::move(result_)); - x->add([cb, val]() mutable { (*cb)(std::move(**val)); }); - } else { - callback_(std::move(*result_)); - } - } - } - } - - void detachOne() { - auto d = ++detached_; - assert(d >= 1); - assert(d <= 2); - if (d == 2) { - // we should have already executed the callback with the value - assert(calledBack_); - delete this; - } - } - - folly::Optional> result_; - std::function&&)> callback_; - std::shared_ptr context_{nullptr}; - std::atomic calledBack_ {false}; - std::atomic detached_ {0}; - std::atomic active_ {true}; - std::atomic executor_ {nullptr}; - std::exception_ptr interrupt_; - std::function interruptHandler_; -}; - -template -struct VariadicContext { - VariadicContext() : total(0), count(0) {} - Promise... > > p; - std::tuple... > results; - size_t total; - std::atomic count; - typedef Future...>> type; -}; - -template -typename std::enable_if::type -whenAllVariadicHelper(VariadicContext *ctx, THead&& head, Fs&&... tail) { - head.setCallback_([ctx](Try&& t) { - std::get(ctx->results) = std::move(t); - if (++ctx->count == ctx->total) { - ctx->p.setValue(std::move(ctx->results)); - delete ctx; - } - }); -} - -template -typename std::enable_if::type -whenAllVariadicHelper(VariadicContext *ctx, THead&& head, Fs&&... tail) { - head.setCallback_([ctx](Try&& t) { - std::get(ctx->results) = std::move(t); - if (++ctx->count == ctx->total) { - ctx->p.setValue(std::move(ctx->results)); - delete ctx; - } - }); - // template tail-recursion - whenAllVariadicHelper(ctx, std::forward(tail)...); -} - -template -struct WhenAllContext { - WhenAllContext() : count(0) {} - Promise > > p; - std::vector > results; - std::atomic count; -}; - -template -struct WhenAnyContext { - explicit WhenAnyContext(size_t n) : done(false), ref_count(n) {}; - Promise>> p; - std::atomic done; - std::atomic ref_count; - void decref() { - if (--ref_count == 0) { - delete this; - } - } -}; - -}}} // namespace diff --git a/folly/wangle/detail/Dummy.cpp b/folly/wangle/detail/Dummy.cpp deleted file mode 100644 index 02a58d4f..00000000 --- a/folly/wangle/detail/Dummy.cpp +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2014 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. - */ - -// fbbuild is too dumb to know that .h files in the directory affect -// our project, unless we have a .cpp file in the target, in the same -// directory. diff --git a/folly/wangle/detail/FSM.h b/folly/wangle/detail/FSM.h deleted file mode 100644 index be4eb8ae..00000000 --- a/folly/wangle/detail/FSM.h +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2014 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 - -#include -#include -#include - -namespace folly { namespace wangle { namespace detail { - -/// Finite State Machine helper base class. -/// Inherit from this. -/// For best results, use an "enum class" for Enum. -template -class FSM { -private: - // I am not templatizing this because folly::MicroSpinLock needs to be - // zero-initialized (or call init) which isn't generic enough for something - // that behaves like std::mutex. :( - using Mutex = folly::MicroSpinLock; - Mutex mutex_ {0}; - - // This might not be necessary for all Enum types, e.g. anything - // that is atomically updated in practice on this CPU and there's no risk - // of returning a bogus state because of tearing. - // An optimization would be to use a static conditional on the Enum type. - std::atomic state_; - -public: - explicit FSM(Enum startState) : state_(startState) {} - - Enum getState() const { - return state_.load(std::memory_order_relaxed); - } - - /// Atomically do a state transition with accompanying action. - /// The action will see the old state. - /// @returns true on success, false and action unexecuted otherwise - template - bool updateState(Enum A, Enum B, F const& action) { - std::lock_guard lock(mutex_); - if (state_ != A) return false; - action(); - state_ = B; - return true; - } - - /// Atomically do a state transition with accompanying action. Then do the - /// unprotected action without holding the lock. If the atomic transition - /// fails, returns false and neither action was executed. - /// - /// This facilitates code like this: - /// bool done = false; - /// while (!done) { - /// switch (getState()) { - /// case State::Foo: - /// done = updateState(State::Foo, State::Bar, - /// [&]{ /* do protected stuff */ }, - /// [&]{ /* do unprotected stuff */}); - /// break; - /// - /// Which reads nicer than code like this: - /// while (true) { - /// switch (getState()) { - /// case State::Foo: - /// if (!updateState(State::Foo, State::Bar, - /// [&]{ /* do protected stuff */ })) { - /// continue; - /// } - /// /* do unprotected stuff */ - /// return; // or otherwise break out of the loop - /// - /// The protected action will see the old state, and the unprotected action - /// will see the new state. - template - bool updateState(Enum A, Enum B, - F1 const& protectedAction, F2 const& unprotectedAction) { - bool result = updateState(A, B, protectedAction); - if (result) { - unprotectedAction(); - } - return result; - } -}; - -#define FSM_START \ - {bool done = false; while (!done) { auto state = getState(); switch (state) { - -#define FSM_UPDATE2(b, protectedAction, unprotectedAction) \ - done = updateState(state, (b), (protectedAction), (unprotectedAction)); - -#define FSM_UPDATE(b, action) FSM_UPDATE2((b), (action), []{}) - -#define FSM_CASE(a, b, action) \ - case (a): \ - FSM_UPDATE((b), (action)); \ - break; - -#define FSM_CASE2(a, b, protectedAction, unprotectedAction) \ - case (a): \ - FSM_UPDATE2((b), (protectedAction), (unprotectedAction)); \ - break; - -#define FSM_BREAK done = true; break; -#define FSM_END }}} - - -}}} diff --git a/folly/wangle/futures/Deprecated.h b/folly/wangle/futures/Deprecated.h new file mode 100644 index 00000000..75937b15 --- /dev/null +++ b/folly/wangle/futures/Deprecated.h @@ -0,0 +1,18 @@ +/* + * Copyright 2014 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 +#define DEPRECATED __attribute__((__deprecated__)) diff --git a/folly/wangle/futures/Future-inl.h b/folly/wangle/futures/Future-inl.h new file mode 100644 index 00000000..074571db --- /dev/null +++ b/folly/wangle/futures/Future-inl.h @@ -0,0 +1,631 @@ +/* + * Copyright 2014 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 + +#include +#include + +#include +#include + +namespace folly { namespace wangle { + +template +struct isFuture { + static const bool value = false; +}; + +template +struct isFuture > { + static const bool value = true; +}; + +template +Future::Future(Future&& other) noexcept : core_(nullptr) { + *this = std::move(other); +} + +template +Future& Future::operator=(Future&& other) { + std::swap(core_, other.core_); + return *this; +} + +template +Future::~Future() { + detach(); +} + +template +void Future::detach() { + if (core_) { + core_->detachFuture(); + core_ = nullptr; + } +} + +template +void Future::throwIfInvalid() const { + if (!core_) + throw NoState(); +} + +template +template +void Future::setCallback_(F&& func) { + throwIfInvalid(); + core_->setCallback(std::move(func)); +} + +// Variant: f.then([](Try&& t){ return t.value(); }); +template +template +typename std::enable_if< + !isFuture&&)>::type>::value, + Future&&)>::type> >::type +Future::then(F&& func) { + typedef typename std::result_of&&)>::type B; + + throwIfInvalid(); + + // wrap these so we can move them into the lambda + folly::MoveWrapper> p; + folly::MoveWrapper funcm(std::forward(func)); + + // grab the Future now before we lose our handle on the Promise + auto f = p->getFuture(); + + /* This is a bit tricky. + + We can't just close over *this in case this Future gets moved. So we + make a new dummy Future. We could figure out something more + sophisticated that avoids making a new Future object when it can, as an + optimization. But this is correct. + + core_ can't be moved, it is explicitly disallowed (as is copying). But + if there's ever a reason to allow it, this is one place that makes that + assumption and would need to be fixed. We use a standard shared pointer + for core_ (by copying it in), which means in essence obj holds a shared + pointer to itself. But this shouldn't leak because Promise will not + outlive the continuation, because Promise will setException() with a + broken Promise if it is destructed before completed. We could use a + weak pointer but it would have to be converted to a shared pointer when + func is executed (because the Future returned by func may possibly + persist beyond the callback, if it gets moved), and so it is an + optimization to just make it shared from the get-go. + + We have to move in the Promise and func using the MoveWrapper + hack. (func could be copied but it's a big drag on perf). + + Two subtle but important points about this design. detail::Core has no + back pointers to Future or Promise, so if Future or Promise get moved + (and they will be moved in performant code) we don't have to do + anything fancy. And because we store the continuation in the + detail::Core, not in the Future, we can execute the continuation even + after the Future has gone out of scope. This is an intentional design + decision. It is likely we will want to be able to cancel a continuation + in some circumstances, but I think it should be explicit not implicit + in the destruction of the Future used to create it. + */ + setCallback_( + [p, funcm](Try&& t) mutable { + p->fulfil([&]() { + return (*funcm)(std::move(t)); + }); + }); + + return std::move(f); +} + +// Variant: f.then([](T&& t){ return t; }); +template +template +typename std::enable_if< + !std::is_same::value && + !isFuture::type&&)>::type>::value, + Future::type&&)>::type> >::type +Future::then(F&& func) { + typedef typename std::result_of::type B; + + throwIfInvalid(); + + folly::MoveWrapper> p; + folly::MoveWrapper funcm(std::forward(func)); + auto f = p->getFuture(); + + setCallback_( + [p, funcm](Try&& t) mutable { + if (t.hasException()) { + p->setException(t.getException()); + } else { + p->fulfil([&]() { + return (*funcm)(std::move(t.value())); + }); + } + }); + + return std::move(f); +} + +// Variant: f.then([](){ return; }); +template +template +typename std::enable_if< + std::is_same::value && + !isFuture::type>::value, + Future::type> >::type +Future::then(F&& func) { + typedef typename std::result_of::type B; + + throwIfInvalid(); + + folly::MoveWrapper> p; + folly::MoveWrapper funcm(std::forward(func)); + auto f = p->getFuture(); + + setCallback_( + [p, funcm](Try&& t) mutable { + if (t.hasException()) { + p->setException(t.getException()); + } else { + p->fulfil([&]() { + return (*funcm)(); + }); + } + }); + + return std::move(f); +} + +// Variant: f.then([](Try&& t){ return makeFuture(t.value()); }); +template +template +typename std::enable_if< + isFuture&&)>::type>::value, + Future&&)>::type::value_type> >::type +Future::then(F&& func) { + typedef typename std::result_of&&)>::type::value_type B; + + throwIfInvalid(); + + // wrap these so we can move them into the lambda + folly::MoveWrapper> p; + folly::MoveWrapper funcm(std::forward(func)); + + // grab the Future now before we lose our handle on the Promise + auto f = p->getFuture(); + + setCallback_( + [p, funcm](Try&& t) mutable { + try { + auto f2 = (*funcm)(std::move(t)); + // that didn't throw, now we can steal p + f2.setCallback_([p](Try&& b) mutable { + p->fulfilTry(std::move(b)); + }); + } catch (...) { + p->setException(std::current_exception()); + } + }); + + return std::move(f); +} + +// Variant: f.then([](T&& t){ return makeFuture(t); }); +template +template +typename std::enable_if< + !std::is_same::value && + isFuture::type&&)>::type>::value, + Future::type&&)>::type::value_type> >::type +Future::then(F&& func) { + typedef typename std::result_of::type::value_type B; + + throwIfInvalid(); + + folly::MoveWrapper> p; + folly::MoveWrapper funcm(std::forward(func)); + auto f = p->getFuture(); + + setCallback_( + [p, funcm](Try&& t) mutable { + if (t.hasException()) { + p->setException(t.getException()); + } else { + try { + auto f2 = (*funcm)(std::move(t.value())); + f2.setCallback_([p](Try&& b) mutable { + p->fulfilTry(std::move(b)); + }); + } catch (...) { + p->setException(std::current_exception()); + } + } + }); + + return std::move(f); +} + +// Variant: f.then([](){ return makeFuture(); }); +template +template +typename std::enable_if< + std::is_same::value && + isFuture::type>::value, + Future::type::value_type> >::type +Future::then(F&& func) { + typedef typename std::result_of::type::value_type B; + + throwIfInvalid(); + + folly::MoveWrapper> p; + folly::MoveWrapper funcm(std::forward(func)); + + auto f = p->getFuture(); + + setCallback_( + [p, funcm](Try&& t) mutable { + if (t.hasException()) { + p->setException(t.getException()); + } else { + try { + auto f2 = (*funcm)(); + f2.setCallback_([p](Try&& b) mutable { + p->fulfilTry(std::move(b)); + }); + } catch (...) { + p->setException(std::current_exception()); + } + } + }); + + return std::move(f); +} + +template +Future Future::then() { + return then([] (Try&& t) {}); +} + +template +typename std::add_lvalue_reference::type Future::value() { + throwIfInvalid(); + + return core_->getTry().value(); +} + +template +typename std::add_lvalue_reference::type Future::value() const { + throwIfInvalid(); + + return core_->getTry().value(); +} + +template +Try& Future::getTry() { + throwIfInvalid(); + + return core_->getTry(); +} + +template +template +inline Future Future::via(Executor* executor) && { + throwIfInvalid(); + + this->deactivate(); + core_->setExecutor(executor); + + return std::move(*this); +} + +template +template +inline Future Future::via(Executor* executor) & { + throwIfInvalid(); + + MoveWrapper> p; + auto f = p->getFuture(); + then([p](Try&& t) mutable { p->fulfilTry(std::move(t)); }); + return std::move(f).via(executor); +} + +template +bool Future::isReady() const { + throwIfInvalid(); + return core_->ready(); +} + +template +void Future::raise(std::exception_ptr exception) { + core_->raise(exception); +} + +// makeFuture + +template +Future::type> makeFuture(T&& t) { + Promise::type> p; + auto f = p.getFuture(); + p.setValue(std::forward(t)); + return std::move(f); +} + +inline // for multiple translation units +Future makeFuture() { + Promise p; + auto f = p.getFuture(); + p.setValue(); + return std::move(f); +} + +template +auto makeFutureTry( + F&& func, + typename std::enable_if::value, bool>::type sdf) + -> Future { + Promise p; + auto f = p.getFuture(); + p.fulfil( + [&func]() { + return (func)(); + }); + return std::move(f); +} + +template +auto makeFutureTry(F const& func) -> Future { + F copy = func; + return makeFutureTry(std::move(copy)); +} + +template +Future makeFuture(std::exception_ptr const& e) { + Promise p; + auto f = p.getFuture(); + p.setException(e); + return std::move(f); +} + +template +typename std::enable_if::value, + Future>::type +makeFuture(E const& e) { + Promise p; + auto f = p.getFuture(); + p.fulfil([&]() -> T { throw e; }); + return std::move(f); +} + +template +Future makeFuture(Try&& t) { + try { + return makeFuture(std::move(t.value())); + } catch (...) { + return makeFuture(std::current_exception()); + } +} + +template <> +inline Future makeFuture(Try&& t) { + try { + t.throwIfFailed(); + return makeFuture(); + } catch (...) { + return makeFuture(std::current_exception()); + } +} + +// via +template +Future via(Executor* executor) { + return makeFuture().via(executor); +} + +// when (variadic) + +template +typename detail::VariadicContext< + typename std::decay::type::value_type...>::type +whenAll(Fs&&... fs) +{ + auto ctx = + new detail::VariadicContext::type::value_type...>(); + ctx->total = sizeof...(fs); + auto f_saved = ctx->p.getFuture(); + detail::whenAllVariadicHelper(ctx, + std::forward::type>(fs)...); + return std::move(f_saved); +} + +// when (iterator) + +template +Future< + std::vector< + Try::value_type::value_type>>> +whenAll(InputIterator first, InputIterator last) +{ + typedef + typename std::iterator_traits::value_type::value_type T; + + auto n = std::distance(first, last); + if (n == 0) { + return makeFuture(std::vector>()); + } + + auto ctx = new detail::WhenAllContext(); + + ctx->results.resize(n); + + auto f_saved = ctx->p.getFuture(); + + for (size_t i = 0; first != last; ++first, ++i) { + assert(i < n); + auto& f = *first; + f.setCallback_([ctx, i, n](Try&& t) { + ctx->results[i] = std::move(t); + if (++ctx->count == n) { + ctx->p.setValue(std::move(ctx->results)); + delete ctx; + } + }); + } + + return std::move(f_saved); +} + +template +Future< + std::pair::value_type::value_type> > > +whenAny(InputIterator first, InputIterator last) { + typedef + typename std::iterator_traits::value_type::value_type T; + + auto ctx = new detail::WhenAnyContext(std::distance(first, last)); + auto f_saved = ctx->p.getFuture(); + + for (size_t i = 0; first != last; first++, i++) { + auto& f = *first; + f.setCallback_([i, ctx](Try&& t) { + if (!ctx->done.exchange(true)) { + ctx->p.setValue(std::make_pair(i, std::move(t))); + } + ctx->decref(); + }); + } + + return std::move(f_saved); +} + +template +Future::value_type::value_type>>>> +whenN(InputIterator first, InputIterator last, size_t n) { + typedef typename + std::iterator_traits::value_type::value_type T; + typedef std::vector>> V; + + struct ctx_t { + V v; + size_t completed; + Promise p; + }; + auto ctx = std::make_shared(); + ctx->completed = 0; + + // for each completed Future, increase count and add to vector, until we + // have n completed futures at which point we fulfil our Promise with the + // vector + auto it = first; + size_t i = 0; + while (it != last) { + it->then([ctx, n, i](Try&& t) { + auto& v = ctx->v; + auto c = ++ctx->completed; + if (c <= n) { + assert(ctx->v.size() < n); + v.push_back(std::make_pair(i, std::move(t))); + if (c == n) { + ctx->p.fulfilTry(Try(std::move(v))); + } + } + }); + + it++; + i++; + } + + if (i < n) { + ctx->p.setException(std::runtime_error("Not enough futures")); + } + + return ctx->p.getFuture(); +} + +template +Future +waitWithSemaphore(Future&& f) { + Baton<> baton; + auto done = f.then([&](Try &&t) { + baton.post(); + return std::move(t.value()); + }); + baton.wait(); + while (!done.isReady()) { + // There's a race here between the return here and the actual finishing of + // the future. f is completed, but the setup may not have finished on done + // after the baton has posted. + std::this_thread::yield(); + } + return done; +} + +template<> +inline Future waitWithSemaphore(Future&& f) { + Baton<> baton; + auto done = f.then([&](Try &&t) { + baton.post(); + t.value(); + }); + baton.wait(); + while (!done.isReady()) { + // There's a race here between the return here and the actual finishing of + // the future. f is completed, but the setup may not have finished on done + // after the baton has posted. + std::this_thread::yield(); + } + return done; +} + +template +Future +waitWithSemaphore(Future&& f, Duration timeout) { + auto baton = std::make_shared>(); + auto done = f.then([baton](Try &&t) { + baton->post(); + return std::move(t.value()); + }); + baton->timed_wait(std::chrono::system_clock::now() + timeout); + return done; +} + +template +Future +waitWithSemaphore(Future&& f, Duration timeout) { + auto baton = std::make_shared>(); + auto done = f.then([baton](Try &&t) { + baton->post(); + t.value(); + }); + baton->timed_wait(std::chrono::system_clock::now() + timeout); + return done; +} + +}} + +// I haven't included a Future specialization because I don't forsee us +// using it, however it is not difficult to add when needed. Refer to +// Future for guidance. std::future and boost::future code would also be +// instructive. diff --git a/folly/wangle/futures/Future.h b/folly/wangle/futures/Future.h new file mode 100644 index 00000000..cdea93df --- /dev/null +++ b/folly/wangle/futures/Future.h @@ -0,0 +1,504 @@ +/* + * Copyright 2014 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 + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace folly { namespace wangle { + +namespace detail { + template struct Core; + template struct VariadicContext; + + template + struct AliasIfVoid { + typedef typename std::conditional< + std::is_same::value, + int, + T>::type type; + }; +} + +template struct Promise; + +template struct isFuture; + +template +class Future { + public: + typedef T value_type; + + // not copyable + Future(Future const&) = delete; + Future& operator=(Future const&) = delete; + + // movable + Future(Future&&) noexcept; + Future& operator=(Future&&); + + ~Future(); + + /** Return the reference to result. Should not be called if !isReady(). + Will rethrow the exception if an exception has been + captured. + + This function is not thread safe - the returned Future can only + be executed from the thread that the executor runs it in. + See below for a thread safe version + */ + typename std::add_lvalue_reference::type + value(); + typename std::add_lvalue_reference::type + value() const; + + /// Returns an inactive Future which will call back on the other side of + /// executor (when it is activated). + /// + /// NB remember that Futures activate when they destruct. This is good, + /// it means that this will work: + /// + /// f.via(e).then(a).then(b); + /// + /// a and b will execute in the same context (the far side of e), because + /// the Future (temporary variable) created by via(e) does not call back + /// until it destructs, which is after then(a) and then(b) have been wired + /// up. + /// + /// But this is still racy: + /// + /// f = f.via(e).then(a); + /// f.then(b); + // The ref-qualifier allows for `this` to be moved out so we + // don't get access-after-free situations in chaining. + // https://akrzemi1.wordpress.com/2014/06/02/ref-qualifiers/ + template + Future via(Executor* executor) &&; + + /// This variant creates a new future, where the ref-qualifier && version + /// moves `this` out. This one is less efficient but avoids confusing users + /// when "return f.via(x);" fails. + template + Future via(Executor* executor) &; + + /** True when the result (or exception) is ready. */ + bool isReady() const; + + /** A reference to the Try of the value */ + Try& getTry(); + + /** When this Future has completed, execute func which is a function that + takes a Try&&. A Future for the return type of func is + returned. e.g. + + Future f2 = f1.then([](Try&&) { return string("foo"); }); + + The Future given to the functor is ready, and the functor may call + value(), which may rethrow if this has captured an exception. If func + throws, the exception will be captured in the Future that is returned. + */ + /* TODO n3428 and other async frameworks have something like then(scheduler, + Future), we might want to support a similar API which could be + implemented a little more efficiently than + f.via(executor).then(callback) */ + template + typename std::enable_if< + !isFuture&&)>::type>::value, + Future&&)>::type> >::type + then(F&& func); + + /// Variant where func takes a T directly, bypassing a try. Any exceptions + /// will be implicitly passed on to the resultant Future. + /// + /// Future f = makeFuture(42).then([](int i) { return i+1; }); + template + typename std::enable_if< + !std::is_same::value && + !isFuture::type&&)>::type>::value, + Future::type&&)>::type> >::type + then(F&& func); + + /// Like the above variant, but for void futures. That is, func takes no + /// argument. + /// + /// Future f = makeFuture().then([] { return 42; }); + template + typename std::enable_if< + std::is_same::value && + !isFuture::type>::value, + Future::type> >::type + then(F&& func); + + /// Variant where func returns a Future instead of a T. e.g. + /// + /// Future f2 = f1.then( + /// [](Try&&) { return makeFuture("foo"); }); + template + typename std::enable_if< + isFuture&&)>::type>::value, + Future&&)>::type::value_type> >::type + then(F&& func); + + /// Variant where func returns a Future and takes a T directly, bypassing + /// a Try. Any exceptions will be implicitly passed on to the resultant + /// Future. For example, + /// + /// Future f = makeFuture(42).then( + /// [](int i) { return makeFuture(i+1); }); + template + typename std::enable_if< + !std::is_same::value && + isFuture::type&&)>::type>::value, + Future::type&&)>::type::value_type> >::type + then(F&& func); + + /// Like the above variant, but for void futures. That is, func takes no + /// argument and returns a future. + /// + /// Future f = makeFuture().then( + /// [] { return makeFuture(42); }); + template + typename std::enable_if< + std::is_same::value && + isFuture::type>::value, + Future::type::value_type> >::type + then(F&& func); + + /// Variant where func is an ordinary function (static method, method) + /// + /// R doWork(Try&&); + /// + /// Future f2 = f1.then(doWork); + /// + /// or + /// + /// struct Worker { + /// static R doWork(Try&&); } + /// + /// Future f2 = f1.then(&Worker::doWork); + template + typename std::enable_if::value, Future>::type + inline then(R(*func)(Try&&)) { + return then([func](Try&& t) { + return (*func)(std::move(t)); + }); + } + + /// Variant where func returns a Future instead of a R. e.g. + /// + /// struct Worker { + /// Future doWork(Try&&); } + /// + /// Future f2 = f1.then(&Worker::doWork); + template + typename std::enable_if::value, R>::type + inline then(R(*func)(Try&&)) { + return then([func](Try&& t) { + return (*func)(std::move(t)); + }); + } + + /// Variant where func is an member function + /// + /// struct Worker { + /// R doWork(Try&&); } + /// + /// Worker *w; + /// Future f2 = f1.then(w, &Worker::doWork); + template + typename std::enable_if::value, Future>::type + inline then(Caller *instance, R(Caller::*func)(Try&&)) { + return then([instance, func](Try&& t) { + return (instance->*func)(std::move(t)); + }); + } + + // Same as above, but func takes void instead of Try&& + template + typename std::enable_if< + std::is_same::value && !isFuture::value, Future>::type + inline then(Caller *instance, R(Caller::*func)()) { + return then([instance, func]() { + return (instance->*func)(); + }); + } + + // Same as above, but func takes T&& instead of Try&& + template + typename std::enable_if< + !std::is_same::value && !isFuture::value, Future>::type + inline then( + Caller *instance, + R(Caller::*func)(typename detail::AliasIfVoid::type&&)) { + return then([instance, func](T&& t) { + return (instance->*func)(std::move(t)); + }); + } + + /// Variant where func returns a Future instead of a R. e.g. + /// + /// struct Worker { + /// Future doWork(Try&&); } + /// + /// Worker *w; + /// Future f2 = f1.then(w, &Worker::doWork); + template + typename std::enable_if::value, R>::type + inline then(Caller *instance, R(Caller::*func)(Try&&)) { + return then([instance, func](Try&& t) { + return (instance->*func)(std::move(t)); + }); + } + + // Same as above, but func takes void instead of Try&& + template + typename std::enable_if< + std::is_same::value && isFuture::value, R>::type + inline then(Caller *instance, R(Caller::*func)()) { + return then([instance, func]() { + return (instance->*func)(); + }); + } + + // Same as above, but func takes T&& instead of Try&& + template + typename std::enable_if< + !std::is_same::value && isFuture::value, R>::type + inline then( + Caller *instance, + R(Caller::*func)(typename detail::AliasIfVoid::type&&)) { + return then([instance, func](T&& t) { + return (instance->*func)(std::move(t)); + }); + } + + /// Convenience method for ignoring the value and creating a Future. + /// Exceptions still propagate. + Future then(); + + /// This is not the method you're looking for. + /// + /// This needs to be public because it's used by make* and when*, and it's + /// not worth listing all those and their fancy template signatures as + /// friends. But it's not for public consumption. + template + void setCallback_(F&& func); + + /// A Future's callback is executed when all three of these conditions have + /// become true: it has a value (set by the Promise), it has a callback (set + /// by then), and it is active (active by default). + /// + /// Inactive Futures will activate upon destruction. + Future& activate() & { + core_->activate(); + return *this; + } + Future& deactivate() & { + core_->deactivate(); + return *this; + } + Future activate() && { + core_->activate(); + return std::move(*this); + } + Future deactivate() && { + core_->deactivate(); + return std::move(*this); + } + + bool isActive() { + return core_->isActive(); + } + + template + void raise(E&& exception) { + raise(std::make_exception_ptr(std::forward(exception))); + } + + /// Raise an interrupt. If the promise holder has an interrupt + /// handler it will be called and potentially stop asynchronous work from + /// being done. This is advisory only - a promise holder may not set an + /// interrupt handler, or may do anything including ignore. But, if you know + /// your future supports this the most likely result is stopping or + /// preventing the asynchronous operation (if in time), and the promise + /// holder setting an exception on the future. (That may happen + /// asynchronously, of course.) + void raise(std::exception_ptr interrupt); + + void cancel() { + raise(FutureCancellation()); + } + + private: + typedef detail::Core* corePtr; + + // shared core state object + corePtr core_; + + explicit + Future(corePtr obj) : core_(obj) {} + + void detach(); + + void throwIfInvalid() const; + + friend class Promise; +}; + +/** + Make a completed Future by moving in a value. e.g. + + string foo = "foo"; + auto f = makeFuture(std::move(foo)); + + or + + auto f = makeFuture("foo"); +*/ +template +Future::type> makeFuture(T&& t); + +/** Make a completed void Future. */ +Future makeFuture(); + +/** Make a completed Future by executing a function. If the function throws + we capture the exception, otherwise we capture the result. */ +template +auto makeFutureTry( + F&& func, + typename std::enable_if< + !std::is_reference::value, bool>::type sdf = false) + -> Future; + +template +auto makeFutureTry( + F const& func) + -> Future; + +/// Make a failed Future from an exception_ptr. +/// Because the Future's type cannot be inferred you have to specify it, e.g. +/// +/// auto f = makeFuture(std::current_exception()); +template +Future makeFuture(std::exception_ptr const& e); + +/** Make a Future from an exception type E that can be passed to + std::make_exception_ptr(). */ +template +typename std::enable_if::value, + Future>::type +makeFuture(E const& e); + +/** Make a Future out of a Try */ +template +Future makeFuture(Try&& t); + +/* + * Return a new Future that will call back on the given Executor. + * This is just syntactic sugar for makeFuture().via(executor) + * + * @param executor the Executor to call back on + * + * @returns a void Future that will call back on the given executor + */ +template +Future via(Executor* executor); + +/** When all the input Futures complete, the returned Future will complete. + Errors do not cause early termination; this Future will always succeed + after all its Futures have finished (whether successfully or with an + error). + + The Futures are moved in, so your copies are invalid. If you need to + chain further from these Futures, use the variant with an output iterator. + + XXX is this still true? + This function is thread-safe for Futures running on different threads. + + The return type for Future input is a Future>> + */ +template +Future::value_type::value_type>>> +whenAll(InputIterator first, InputIterator last); + +/// This version takes a varying number of Futures instead of an iterator. +/// The return type for (Future, Future, ...) input +/// is a Future, Try, ...>>. +/// The Futures are moved in, so your copies are invalid. +template +typename detail::VariadicContext< + typename std::decay::type::value_type...>::type +whenAll(Fs&&... fs); + +/** The result is a pair of the index of the first Future to complete and + the Try. If multiple Futures complete at the same time (or are already + complete when passed in), the "winner" is chosen non-deterministically. + + This function is thread-safe for Futures running on different threads. + */ +template +Future::value_type::value_type>>> +whenAny(InputIterator first, InputIterator last); + +/** when n Futures have completed, the Future completes with a vector of + the index and Try of those n Futures (the indices refer to the original + order, but the result vector will be in an arbitrary order) + + Not thread safe. + */ +template +Future::value_type::value_type>>>> +whenN(InputIterator first, InputIterator last, size_t n); + +/** Wait for the given future to complete on a semaphore. Returns a completed + * future containing the result. + * + * NB if the promise for the future would be fulfilled in the same thread that + * you call this, it will deadlock. + */ +template +Future waitWithSemaphore(Future&& f); + +/** Wait for up to `timeout` for the given future to complete. Returns a future + * which may or may not be completed depending whether the given future + * completed in time + * + * Note: each call to this starts a (short-lived) thread and allocates memory. + */ +template +Future waitWithSemaphore(Future&& f, Duration timeout); + +}} // folly::wangle + +#include diff --git a/folly/wangle/futures/InlineExecutor.cpp b/folly/wangle/futures/InlineExecutor.cpp new file mode 100644 index 00000000..1ef2f068 --- /dev/null +++ b/folly/wangle/futures/InlineExecutor.cpp @@ -0,0 +1,15 @@ +/* + * Copyright 2014 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. + */ diff --git a/folly/wangle/futures/InlineExecutor.h b/folly/wangle/futures/InlineExecutor.h new file mode 100644 index 00000000..e6924085 --- /dev/null +++ b/folly/wangle/futures/InlineExecutor.h @@ -0,0 +1,32 @@ +/* + * Copyright 2014 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 +#include + +namespace folly { namespace wangle { + + /// When work is "queued", execute it immediately inline. + /// Usually when you think you want this, you actually want a + /// QueuedImmediateExecutor. + class InlineExecutor : public Executor { + public: + void add(Func f) override { + f(); + } + }; + +}} diff --git a/folly/wangle/futures/ManualExecutor.cpp b/folly/wangle/futures/ManualExecutor.cpp new file mode 100644 index 00000000..9b68b1a1 --- /dev/null +++ b/folly/wangle/futures/ManualExecutor.cpp @@ -0,0 +1,104 @@ +/* + * Copyright 2014 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 + +#include +#include +#include + +#include + +namespace folly { namespace wangle { + +ManualExecutor::ManualExecutor() { + if (sem_init(&sem_, 0, 0) == -1) { + throw std::runtime_error(std::string("sem_init: ") + strerror(errno)); + } +} + +void ManualExecutor::add(Func callback) { + std::lock_guard lock(lock_); + funcs_.push(std::move(callback)); + sem_post(&sem_); +} + +size_t ManualExecutor::run() { + size_t count; + size_t n; + Func func; + + { + std::lock_guard lock(lock_); + + while (!scheduledFuncs_.empty()) { + auto& sf = scheduledFuncs_.top(); + if (sf.time > now_) + break; + funcs_.push(sf.func); + scheduledFuncs_.pop(); + } + + n = funcs_.size(); + } + + for (count = 0; count < n; count++) { + { + std::lock_guard lock(lock_); + if (funcs_.empty()) { + break; + } + + // Balance the semaphore so it doesn't grow without bound + // if nobody is calling wait(). + // This may fail (with EAGAIN), that's fine. + sem_trywait(&sem_); + + func = std::move(funcs_.front()); + funcs_.pop(); + } + func(); + } + + return count; +} + +void ManualExecutor::wait() { + while (true) { + { + std::lock_guard lock(lock_); + if (!funcs_.empty()) + break; + } + + auto ret = sem_wait(&sem_); + if (ret == 0) { + break; + } + if (errno != EINVAL) { + throw std::runtime_error(std::string("sem_wait: ") + strerror(errno)); + } + } +} + +void ManualExecutor::advanceTo(TimePoint const& t) { + if (t > now_) { + now_ = t; + } + run(); +} + +}} // namespace diff --git a/folly/wangle/futures/ManualExecutor.h b/folly/wangle/futures/ManualExecutor.h new file mode 100644 index 00000000..72a1c1d2 --- /dev/null +++ b/folly/wangle/futures/ManualExecutor.h @@ -0,0 +1,120 @@ +/* + * Copyright 2014 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 +#include +#include +#include +#include +#include +#include + +namespace folly { namespace wangle { + /// A ManualExecutor only does work when you turn the crank, by calling + /// run() or indirectly with makeProgress() or waitFor(). + /// + /// The clock for a manual executor starts at 0 and advances only when you + /// ask it to. i.e. time is also under manual control. + /// + /// NB No attempt has been made to make anything other than add and schedule + /// threadsafe. + class ManualExecutor : public ScheduledExecutor { + public: + ManualExecutor(); + + void add(Func) override; + + /// Do work. Returns the number of functions that were executed (maybe 0). + /// Non-blocking, in the sense that we don't wait for work (we can't + /// control whether one of the functions blocks). + /// This is stable, it will not chase an ever-increasing tail of work. + /// This also means, there may be more work available to perform at the + /// moment that this returns. + size_t run(); + + /// Wait for work to do. + void wait(); + + /// Wait for work to do, and do it. + void makeProgress() { + wait(); + run(); + } + + /// makeProgress until this Future is ready. + template void waitFor(F const& f) { + // TODO(5427828) +#if 0 + while (!f.isReady()) + makeProgress(); +#else + while (!f.isReady()) { + run(); + } +#endif + + } + + virtual void scheduleAt(Func&& f, TimePoint const& t) override { + std::lock_guard lock(lock_); + scheduledFuncs_.emplace(t, std::move(f)); + sem_post(&sem_); + } + + /// Advance the clock. The clock never advances on its own. + /// Advancing the clock causes some work to be done, if work is available + /// to do (perhaps newly available because of the advanced clock). + /// If dur is <= 0 this is a noop. + void advance(Duration const& dur) { + advanceTo(now_ + dur); + } + + /// Advance the clock to this absolute time. If t is <= now(), + /// this is a noop. + void advanceTo(TimePoint const& t); + + TimePoint now() override { return now_; } + + private: + std::mutex lock_; + std::queue funcs_; + sem_t sem_; + + // helper class to enable ordering of scheduled events in the priority + // queue + struct ScheduledFunc { + TimePoint time; + size_t ordinal; + Func func; + + ScheduledFunc(TimePoint const& t, Func&& f) + : time(t), func(std::move(f)) + { + static size_t seq = 0; + ordinal = seq++; + } + + bool operator<(ScheduledFunc const& b) const { + if (time == b.time) + return ordinal < b.ordinal; + return time < b.time; + } + }; + std::priority_queue scheduledFuncs_; + TimePoint now_ = now_.min(); + }; + +}} diff --git a/folly/wangle/futures/OpaqueCallbackShunt.h b/folly/wangle/futures/OpaqueCallbackShunt.h new file mode 100644 index 00000000..2e7dbd16 --- /dev/null +++ b/folly/wangle/futures/OpaqueCallbackShunt.h @@ -0,0 +1,57 @@ +/* + * Copyright 2014 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 + +#include + +namespace folly { namespace wangle { + +/// These classes help you wrap an existing C style callback function +/// into a Future. +/// +/// void legacy_send_async(..., void (*cb)(void*), void*); +/// +/// Future wrappedSendAsync(T&& obj) { +/// auto handle = new OpaqueCallbackShunt(obj); +/// auto future = handle->promise_.getFuture(); +/// legacy_send_async(..., OpaqueCallbackShunt::callback, handle) +/// return future; +/// } +/// +/// If the legacy function doesn't conform to void (*cb)(void*), use a lambda: +/// +/// auto cb = [](t1*, t2*, void* arg) { +/// OpaqueCallbackShunt::callback(arg); +/// }; +/// legacy_send_async(..., cb, handle); + +template +class OpaqueCallbackShunt { +public: + explicit OpaqueCallbackShunt(T&& obj) + : obj_(std::move(obj)) { } + static void callback(void* arg) { + std::unique_ptr> handle( + static_cast*>(arg)); + handle->promise_.setValue(std::move(handle->obj_)); + } + folly::wangle::Promise promise_; +private: + T obj_; +}; + +}} // folly::wangle diff --git a/folly/wangle/futures/Promise-inl.h b/folly/wangle/futures/Promise-inl.h new file mode 100644 index 00000000..ec1afe17 --- /dev/null +++ b/folly/wangle/futures/Promise-inl.h @@ -0,0 +1,127 @@ +/* + * Copyright 2014 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 + +#include +#include + +#include +#include + +namespace folly { namespace wangle { + +template +Promise::Promise() : retrieved_(false), core_(new detail::Core()) +{} + +template +Promise::Promise(Promise&& other) : core_(nullptr) { + *this = std::move(other); +} + +template +Promise& Promise::operator=(Promise&& other) { + std::swap(core_, other.core_); + std::swap(retrieved_, other.retrieved_); + return *this; +} + +template +void Promise::throwIfFulfilled() { + if (!core_) + throw NoState(); + if (core_->ready()) + throw PromiseAlreadySatisfied(); +} + +template +void Promise::throwIfRetrieved() { + if (retrieved_) + throw FutureAlreadyRetrieved(); +} + +template +Promise::~Promise() { + detach(); +} + +template +void Promise::detach() { + if (core_) { + if (!retrieved_) + core_->detachFuture(); + core_->detachPromise(); + core_ = nullptr; + } +} + +template +Future Promise::getFuture() { + throwIfRetrieved(); + retrieved_ = true; + return Future(core_); +} + +template +template +void Promise::setException(E const& e) { + setException(std::make_exception_ptr(e)); +} + +template +void Promise::setException(std::exception_ptr const& e) { + throwIfFulfilled(); + core_->setResult(Try(e)); +} + +template +void Promise::setInterruptHandler( + std::function fn) { + core_->setInterruptHandler(std::move(fn)); +} + +template +void Promise::fulfilTry(Try&& t) { + throwIfFulfilled(); + core_->setResult(std::move(t)); +} + +template +template +void Promise::setValue(M&& v) { + static_assert(!std::is_same::value, + "Use setValue() instead"); + + fulfilTry(Try(std::forward(v))); +} + +template +void Promise::setValue() { + static_assert(std::is_same::value, + "Use setValue(value) instead"); + + fulfilTry(Try()); +} + +template +template +void Promise::fulfil(F&& func) { + throwIfFulfilled(); + fulfilTry(makeTryFunction(std::forward(func))); +} + +}} diff --git a/folly/wangle/futures/Promise.h b/folly/wangle/futures/Promise.h new file mode 100644 index 00000000..31b76cfe --- /dev/null +++ b/folly/wangle/futures/Promise.h @@ -0,0 +1,101 @@ +/* + * Copyright 2014 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 + +#include + +namespace folly { namespace wangle { + +// forward declaration +template class Future; + +template +class Promise { +public: + Promise(); + ~Promise(); + + // not copyable + Promise(Promise const&) = delete; + Promise& operator=(Promise const&) = delete; + + // movable + Promise(Promise&&); + Promise& operator=(Promise&&); + + /** Return a Future tied to the shared core state. This can be called only + once, thereafter Future already retrieved exception will be raised. */ + Future getFuture(); + + /** Fulfil the Promise with an exception_ptr, e.g. + try { + ... + } catch (...) { + p.setException(std::current_exception()); + } + */ + void setException(std::exception_ptr const&); + + /** Fulfil the Promise with an exception type E, which can be passed to + std::make_exception_ptr(). Useful for originating exceptions. If you + caught an exception the exception_ptr form is more appropriate. + */ + template void setException(E const&); + + /// Set an interrupt handler to handle interrupts. See the documentation for + /// Future::raise(). Your handler can do whatever it wants, but if you + /// bother to set one then you probably will want to fulfil the promise with + /// an exception (or special value) indicating how the interrupt was + /// handled. + void setInterruptHandler(std::function); + + /** Fulfil this Promise (only for Promise) */ + void setValue(); + + /** Set the value (use perfect forwarding for both move and copy) */ + template + void setValue(M&& value); + + void fulfilTry(Try&& t); + + /** Fulfil this Promise with the result of a function that takes no + arguments and returns something implicitly convertible to T. + Captures exceptions. e.g. + + p.fulfil([] { do something that may throw; return a T; }); + */ + template + void fulfil(F&& func); + +private: + typedef typename Future::corePtr corePtr; + + // Whether the Future has been retrieved (a one-time operation). + bool retrieved_; + + // shared core state object + corePtr core_; + + void throwIfFulfilled(); + void throwIfRetrieved(); + void detach(); +}; + +}} + +#include +#include diff --git a/folly/wangle/futures/QueuedImmediateExecutor.cpp b/folly/wangle/futures/QueuedImmediateExecutor.cpp new file mode 100644 index 00000000..b82a486e --- /dev/null +++ b/folly/wangle/futures/QueuedImmediateExecutor.cpp @@ -0,0 +1,37 @@ +/* + * Copyright 2014 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 +#include +#include + +namespace folly { namespace wangle { + +void QueuedImmediateExecutor::add(Func callback) { + thread_local std::queue q; + + if (q.empty()) { + q.push(std::move(callback)); + while (!q.empty()) { + q.front()(); + q.pop(); + } + } else { + q.push(callback); + } +} + +}} // namespace diff --git a/folly/wangle/futures/QueuedImmediateExecutor.h b/folly/wangle/futures/QueuedImmediateExecutor.h new file mode 100644 index 00000000..a82c32db --- /dev/null +++ b/folly/wangle/futures/QueuedImmediateExecutor.h @@ -0,0 +1,33 @@ +/* + * Copyright 2014 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 + +#include + +namespace folly { namespace wangle { + +/** + * Runs inline like InlineExecutor, but with a queue so that any tasks added + * to this executor by one of its own callbacks will be queued instead of + * executed inline (nested). This is usually better behavior than Inline. + */ +class QueuedImmediateExecutor : public Executor { + public: + void add(Func) override; +}; + +}} // namespace diff --git a/folly/wangle/futures/README.md b/folly/wangle/futures/README.md new file mode 100644 index 00000000..a23d7c6e --- /dev/null +++ b/folly/wangle/futures/README.md @@ -0,0 +1,278 @@ +# Wangle +Wangle is a framework for expressing asynchronous code in C++ using the Future pattern. + +**wan•gle** |ˈwaNGgəl| informal +*verb* +Obtain (something that is desired) by persuading others to comply or by manipulating events. + +*noun* +A framework for expressing asynchronous control flow in C++, that is composable and easily translated to/from synchronous code. + +*synonyms* +[Finagle](http://twitter.github.io/finagle/) + +Wangle is a futures-based async framework inspired by [Twitter's Finagle](http://twitter.github.io/finagle/) (which is in scala), and (loosely) building upon the existing (but anemic) Futures code found in the C++11 standard ([`std::future`](http://en.cppreference.com/w/cpp/thread/future)) and [`boost::future`](http://www.boost.org/doc/libs/1_53_0/boost/thread/future.hpp) (especially >= 1.53.0). Although inspired by the std::future interface, it is not syntactically drop-in compatible because some ideas didn't translate well enough and we decided to break from the API. But semantically, it should be straightforward to translate from existing std::future code to Wangle. + +The primary semantic differences are that Wangle Futures and Promises are not threadsafe; and as does `boost::future`, Wangle supports continuing callbacks (`then()`) and there are helper methods `whenAll()` and `whenAny()` which are important compositional building blocks. + +## Brief Synopsis + +```C++ +#include +using namespace folly::wangle; +using namespace std; + +void foo(int x) { + // do something with x + cout << "foo(" << x << ")" << endl; +} + +// ... + + cout << "making Promise" << endl; + Promise p; + Future f = p.getFuture(); + f.then( + [](Try&& t) { + foo(t.value()); + }); + cout << "Future chain made" << endl; + +// ... now perhaps in another event callback + + cout << "fulfilling Promise" << endl; + p.setValue(42); + cout << "Promise fulfilled" << endl; +``` + +This would print: + +``` +making Promise +Future chain made +fulfilling Promise +foo(42) +Promise fulfilled +``` + +## User Guide + +Let's begin with an example. Consider a simplified Memcache client class with this interface: + +```C++ +class MemcacheClient { + public: + struct GetReply { + enum class Result { + FOUND, + NOT_FOUND, + SERVER_ERROR, + }; + + Result result; + // The value when result is FOUND, + // The error message when result is SERVER_ERROR or CLIENT_ERROR + // undefined otherwise + std::string value; + }; + + GetReply get(std::string key); +}; +``` + +This API is synchronous, i.e. when you call `get()` you have to wait for the result. This is very simple, but unfortunately it is also very easy to write very slow code using synchronous APIs. + +Now, consider this traditional asynchronous signature for `get()`: + +```C++ +int get(std::string key, std::function callback); +``` + +When you call `get()`, your asynchronous operation begins and when it finishes your callback will be called with the result. (Unless something goes drastically wrong and you get an error code from `get()`.) Very performant code can be written with an API like this, but for nontrivial applications the code descends into a special kind of spaghetti code affectionately referred to as "callback hell". + +The Future-based API looks like this: + +```C++ +Future get(std::string key); +``` + +A `Future` is a placeholder for the `GetReply` that we will eventually get. A Future usually starts life out "unfulfilled", or incomplete, i.e.: + +```C++ +fut.isReady() == false +fut.value() // will throw an exception because the Future is not ready +``` + +At some point in the future, the Future will have been fulfilled, and we can access its value. + +```C++ +fut.isReady() == true +GetReply& reply = fut.value(); +``` + +Futures support exceptions. If something exceptional happened, your Future may represent an exception instead of a value. In that case: + +```C++ +fut.isReady() == true +fut.value() // will rethrow the exception +``` + +Just what is exceptional depends on the API. In our example we have chosen not to raise exceptions for `SERVER_ERROR`, but represent this explicitly in the `GetReply` object. On the other hand, an astute Memcache veteran would notice that we left `CLIENT_ERROR` out of `GetReply::Result`, and perhaps a `CLIENT_ERROR` would have been raised as an exception, because `CLIENT_ERROR` means there's a bug in the library and this would be truly exceptional. These decisions are judgement calls by the API designer. The important thing is that exceptional conditions (including and especially spurious exceptions that nobody expects) get captured and can be handled higher up the "stack". + +So far we have described a way to initiate an asynchronous operation via an API that returns a Future, and then sometime later after it is fulfilled, we get its value. This is slightly more useful than a synchronous API, but it's not yet ideal. There are two more very important pieces to the puzzle. + +First, we can aggregate Futures, to define a new Future that completes after some or all of the aggregated Futures complete. Consider two examples: fetching a batch of requests and waiting for all of them, and fetching a group of requests and waiting for only one of them. + +```C++ +vector> futs; +for (auto& key : keys) { + futs.push_back(mc.get(key)); +} +auto all = whenAll(futs.begin(), futs.end()); + +vector> futs; +for (auto& key : keys) { + futs.push_back(mc.get(key)); +} +auto any = whenAny(futs.begin(), futs.end()); +``` + +`all` and `any` are Futures (for the exact type and usage see the header files). They will be complete when all/one of `futs` are complete, respectively. (There is also `whenN()` for when you need *some*.) + +Second, we can attach callbacks to a Future, and chain them together monadically. An example will clarify: + +```C++ +Future fut1 = mc.get("foo"); + +Future fut2 = fut1.then( + [](Try&& t) { + if (t.value().result == MemcacheClient::GetReply::Result::FOUND) + return t.value().value; + throw SomeException("No value"); + }); + +Future fut3 = fut2.then( + [](Try&& t) { + try { + cout << t.value() << endl; + } catch (std::exception const& e) { + cerr << e.what() << endl; + } + }); +``` + +That example is a little contrived but the idea is that you can transform a result from one type to another, potentially in a chain, and unhandled errors propagate. Of course, the intermediate variables are optional. `Try` is the object wrapper that supports both value and exception. + +Using `then` to add callbacks is idiomatic. It brings all the code into one place, which avoids callback hell. + +Up to this point we have skirted around the matter of waiting for Futures. You may never need to wait for a Future, because your code is event-driven and all follow-up action happens in a then-block. But if want to have a batch workflow, where you initiate a batch of asynchronous operations and then wait for them all to finish at a synchronization point, then you will want to wait for a Future. + +Other future frameworks like Finagle and std::future/boost::future, give you the ability to wait directly on a Future, by calling `fut.wait()` (naturally enough). Wangle has diverged from this pattern because we don't want to be in the business of dictating how your thread waits. We may work out something that we feel is sufficiently general, in the meantime adapt this spin loop to however your thread should wait: + + while (!f.isReady()) {} + +(Hint: you might want to use an event loop or a semaphore or something. You probably don't want to just spin like this.) + +Wangle is partially threadsafe. A Promise or Future can migrate between threads as long as there's a full memory barrier of some sort. `Future::then` and `Promise::setValue` (and all variants that boil down to those two calls) can be called from different threads. BUT, be warned that you might be surprised about which thread your callback executes on. Let's consider an example. + +```C++ +// Thread A +Promise p; +auto f = p.getFuture(); + +// Thread B +f.then(x).then(y).then(z); + +// Thread A +p.setValue(); +``` + +This is legal and technically threadsafe. However, it is important to realize that you do not know in which thread `x`, `y`, and/or `z` will execute. Maybe they will execute in Thread A when `p.setValue()` is called. Or, maybe they will execute in Thread B when `f.then` is called. Or, maybe `x` will execute in Thread B, but `y` and/or `z` will execute in Thread A. There's a race between `setValue` and `then`—whichever runs last will execute the callback. The only guarantee is that one of them will run the callback. + +Naturally, you will want some control over which thread executes callbacks. We have a few mechanisms to help. + +The first and most useful is `via`, which passes execution through an `Executor`, which usually has the effect of running the callback in a new thread. +```C++ +aFuture + .then(x) + .via(e1).then(y1).then(y2) + .via(e2).then(z); +``` +`x` will execute in the current thread. `y1` and `y2` will execute in the thread on the other side of `e1`, and `z` will execute in the thread on the other side of `e2`. `y1` and `y2` will execute on the same thread, whichever thread that is. If `e1` and `e2` execute in different threads than the current thread, then the final callback does not happen in the current thread. If you want to get back to the current thread, you need to get there via an executor. + +This works because `via` returns a deactivated ("cold") Future, which blocks the propagation of callbacks until it is activated. Activation happens either explicitly (`activate`) or implicitly when the Future returned by `via` is destructed. In this example, there is no ambiguity about in which context any of the callbacks happen (including `y2`), because propagation is blocked at the `via` callsites until after everything is wired up (temporaries are destructed after the calls to `then` have completed). + +You can still have a race after `via` if you break it into multiple statements, e.g. in this counterexample: +```C++ +f = f.via(e1).then(y1).then(y2); // nothing racy here +f2.then(y3); // racy +``` + +## You make me Promises, Promises + +If you are wrapping an asynchronous operation, or providing an asynchronous API to users, then you will want to make Promises. Every Future has a corresponding Promise (except Futures that spring into existence already completed, with `makeFuture()`). Promises are simple, you make one, you extract the Future, and you fulfil it with a value or an exception. Example: + +```C++ +Promise p; +Future f = p.getFuture(); + +f.isReady() == false + +p.setValue(42); + +f.isReady() == true +f.value() == 42 +``` + +and an exception example: + +```C++ +Promise p; +Future f = p.getFuture(); + +f.isReady() == false + +p.setException(std::runtime_error("Fail")); + +f.isReady() == true +f.value() // throws the exception +``` + +It's good practice to use fulfil which takes a function and automatically captures exceptions, e.g. + +```C++ +Promise p; +p.fulfil([]{ + try { + // do stuff that may throw + return 42; + } catch (MySpecialException const& e) { + // handle it + return 7; + } + // Any exceptions that we didn't catch, will be caught for us +}); +``` + +## FAQ + +### Why not use std::future? +No callback support. +See also http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3428.pdf + +### Why not use boost::future? +- 1.53 is brand new, and not in fbcode +- It's still a bit buggy/bleeding-edge +- They haven't fleshed out the threading model very well yet, e.g. every single `then` currently spawns a new thread unless you explicitly ask it to work on this thread only, and there is no support for executors yet. + +### Why use heap-allocated shared state? Why is Promise not a subclass of Future? +C++. It boils down to wanting to return a Future by value for performance (move semantics and compiler optimizations), and programmer sanity, and needing a reference to the shared state by both the user (which holds the Future) and the asynchronous operation (which holds the Promise), and allowing either to go out of scope. + +### What about proper continuations? Futures suck. +People mean two things here, they either mean using continuations (as in CSP) or they mean using generators which require continuations. It's important to know those are two distinct questions, but in our context the answer is the same because continuations are a prerequisite for generators. + +C++ doesn't directly support continuations very well. But there are some ways to do them in C/C++ that rely on some rather low-level facilities like `setjmp` and `longjmp` (among others). So yes, they are possible (cf. [Mordor](https://github.com/ccutrer/mordor)). + +The tradeoff is memory. Each continuation has a stack, and that stack is usually fixed-size and has to be big enough to support whatever ordinary computation you might want to do on it. So each living continuation requires a relatively large amount of memory. If you know the number of continuations will be small, this might be a good fit. In particular, it might be faster and the code might read cleaner. + +Wangle takes the middle road between callback hell and continuations, one which has been trodden and proved useful in other languages. It doesn't claim to be the best model for all situations. Use your tools wisely. diff --git a/folly/wangle/futures/ScheduledExecutor.h b/folly/wangle/futures/ScheduledExecutor.h new file mode 100644 index 00000000..94850c28 --- /dev/null +++ b/folly/wangle/futures/ScheduledExecutor.h @@ -0,0 +1,57 @@ +/* + * Copyright 2014 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 + +#include +#include +#include +#include + +namespace folly { namespace wangle { + // An executor that supports timed scheduling. Like RxScheduler. + class ScheduledExecutor : public Executor { + public: + // Reality is that better than millisecond resolution is very hard to + // achieve. However, we reserve the right to be incredible. + typedef std::chrono::microseconds Duration; + typedef std::chrono::steady_clock::time_point TimePoint; + + virtual ~ScheduledExecutor() = default; + + virtual void add(Func) override = 0; + + /// Alias for add() (for Rx consistency) + void schedule(Func&& a) { add(std::move(a)); } + + /// Schedule a Func to be executed after dur time has elapsed + /// Expect millisecond resolution at best. + void schedule(Func&& a, Duration const& dur) { + scheduleAt(std::move(a), now() + dur); + } + + /// Schedule a Func to be executed at time t, or as soon afterward as + /// possible. Expect millisecond resolution at best. Must be threadsafe. + virtual void scheduleAt(Func&& a, TimePoint const& t) { + throw std::logic_error("unimplemented"); + } + + /// Get this executor's notion of time. Must be threadsafe. + virtual TimePoint now() { + return std::chrono::steady_clock::now(); + } + }; +}} diff --git a/folly/wangle/futures/Try-inl.h b/folly/wangle/futures/Try-inl.h new file mode 100644 index 00000000..44f2cea4 --- /dev/null +++ b/folly/wangle/futures/Try-inl.h @@ -0,0 +1,120 @@ +/* + * Copyright 2014 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 + +#include + +#include + +namespace folly { namespace wangle { + +template +Try::Try(Try&& t) : contains_(t.contains_) { + if (contains_ == Contains::VALUE) { + new (&value_)T(std::move(t.value_)); + } else if (contains_ == Contains::EXCEPTION) { + new (&e_)std::exception_ptr(t.e_); + } +} + +template +Try& Try::operator=(Try&& t) { + this->~Try(); + contains_ = t.contains_; + if (contains_ == Contains::VALUE) { + new (&value_)T(std::move(t.value_)); + } else if (contains_ == Contains::EXCEPTION) { + new (&e_)std::exception_ptr(t.e_); + } + return *this; +} + +template +Try::~Try() { + if (contains_ == Contains::VALUE) { + value_.~T(); + } else if (contains_ == Contains::EXCEPTION) { + e_.~exception_ptr(); + } +} + +template +T& Try::value() { + throwIfFailed(); + return value_; +} + +template +const T& Try::value() const { + throwIfFailed(); + return value_; +} + +template +void Try::throwIfFailed() const { + if (contains_ != Contains::VALUE) { + if (contains_ == Contains::EXCEPTION) { + std::rethrow_exception(e_); + } else { + throw UsingUninitializedTry(); + } + } +} + +void Try::throwIfFailed() const { + if (!hasValue_) { + std::rethrow_exception(e_); + } +} + +template +inline T moveFromTry(wangle::Try&& t) { + return std::move(t.value()); +} + +inline void moveFromTry(wangle::Try&& t) { + return t.value(); +} + +template +typename std::enable_if< + !std::is_same::type, void>::value, + Try::type>>::type +makeTryFunction(F&& f) { + typedef typename std::result_of::type ResultType; + try { + auto value = f(); + return Try(std::move(value)); + } catch (...) { + return Try(std::current_exception()); + } +} + +template +typename std::enable_if< + std::is_same::type, void>::value, + Try>::type +makeTryFunction(F&& f) { + try { + f(); + return Try(); + } catch (...) { + return Try(std::current_exception()); + } +} + +}} diff --git a/folly/wangle/futures/Try.h b/folly/wangle/futures/Try.h new file mode 100644 index 00000000..a90aa4d5 --- /dev/null +++ b/folly/wangle/futures/Try.h @@ -0,0 +1,146 @@ +/* + * Copyright 2014 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 + +#include +#include +#include +#include +#include + +namespace folly { namespace wangle { + +template +class Try { + static_assert(!std::is_reference::value, + "Try may not be used with reference types"); + + enum class Contains { + VALUE, + EXCEPTION, + NOTHING, + }; + + public: + typedef T element_type; + + Try() : contains_(Contains::NOTHING) {} + explicit Try(const T& v) : contains_(Contains::VALUE), value_(v) {} + explicit Try(T&& v) : contains_(Contains::VALUE), value_(std::move(v)) {} + explicit Try(std::exception_ptr e) : contains_(Contains::EXCEPTION), e_(e) {} + + // move + Try(Try&& t); + Try& operator=(Try&& t); + + // no copy + Try(const Try& t) = delete; + Try& operator=(const Try& t) = delete; + + ~Try(); + + T& value(); + const T& value() const; + + void throwIfFailed() const; + + const T& operator*() const { return value(); } + T& operator*() { return value(); } + + const T* operator->() const { return &value(); } + T* operator->() { return &value(); } + + bool hasValue() const { return contains_ == Contains::VALUE; } + bool hasException() const { return contains_ == Contains::EXCEPTION; } + + std::exception_ptr getException() const { + if (UNLIKELY(!hasException())) { + throw WangleException( + "getException(): Try does not contain an exception"); + } + return e_; + } + + private: + Contains contains_; + union { + T value_; + std::exception_ptr e_; + }; +}; + +template <> +class Try { + public: + Try() : hasValue_(true) {} + explicit Try(std::exception_ptr e) : hasValue_(false), e_(e) {} + + void value() const { throwIfFailed(); } + void operator*() const { return value(); } + + inline void throwIfFailed() const; + + bool hasValue() const { return hasValue_; } + bool hasException() const { return !hasValue_; } + + std::exception_ptr getException() const { + if (UNLIKELY(!hasException())) { + throw WangleException( + "getException(): Try does not contain an exception"); + } + return e_; + } + + private: + bool hasValue_; + std::exception_ptr e_; +}; + +/** + * Extracts value from try and returns it. Throws if try contained an exception. + */ +template +T moveFromTry(wangle::Try&& t); + +/** + * Throws if try contained an exception. + */ +void moveFromTry(wangle::Try&& t); + +/** + * Constructs Try based on the result of execution of function f (e.g. result + * or exception). + */ +template +typename std::enable_if< + !std::is_same::type, void>::value, + Try::type>>::type +makeTryFunction(F&& f); + +/** + * makeTryFunction specialization for void functions. + */ +template +typename std::enable_if< + std::is_same::type, void>::value, + Try>::type +makeTryFunction(F&& f); + + +}} + +#include diff --git a/folly/wangle/futures/WangleException.h b/folly/wangle/futures/WangleException.h new file mode 100644 index 00000000..bddf86c7 --- /dev/null +++ b/folly/wangle/futures/WangleException.h @@ -0,0 +1,89 @@ +/* + * Copyright 2014 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 + +#include +#include + +namespace folly { namespace wangle { + +class WangleException : public std::exception { + +public: + + explicit WangleException(std::string message_arg) + : message(message_arg) {} + + ~WangleException() throw(){} + + virtual const char *what() const throw() { + return message.c_str(); + } + + bool operator==(const WangleException &other) const{ + return other.message == this->message; + } + + bool operator!=(const WangleException &other) const{ + return !(*this == other); + } + + protected: + std::string message; +}; + +class BrokenPromise : public WangleException { + public: + explicit BrokenPromise() : + WangleException("Broken promise") { } +}; + +class NoState : public WangleException { + public: + explicit NoState() : WangleException("No state") { } +}; + +class PromiseAlreadySatisfied : public WangleException { + public: + explicit PromiseAlreadySatisfied() : + WangleException("Promise already satisfied") { } +}; + +class FutureNotReady : public WangleException { + public: + explicit FutureNotReady() : + WangleException("Future not ready") { } +}; + +class FutureAlreadyRetrieved : public WangleException { + public: + explicit FutureAlreadyRetrieved () : + WangleException("Future already retrieved") { } +}; + +class UsingUninitializedTry : public WangleException { + public: + explicit UsingUninitializedTry() : + WangleException("Using unitialized try") { } +}; + +class FutureCancellation : public WangleException { + public: + FutureCancellation() : WangleException("Future was cancelled") {} +}; + +}} diff --git a/folly/wangle/futures/detail/Core.h b/folly/wangle/futures/detail/Core.h new file mode 100644 index 00000000..cd9e5af5 --- /dev/null +++ b/folly/wangle/futures/detail/Core.h @@ -0,0 +1,292 @@ +/* + * Copyright 2014 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 + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace folly { namespace wangle { namespace detail { + +// As of GCC 4.8.1, the std::function in libstdc++ optimizes only for pointers +// to functions, using a helper avoids a call to malloc. +template +void empty_callback(Try&&) { } + +enum class State { + Waiting, + Interruptible, + Interrupted, + Done, +}; + +/** The shared state object for Future and Promise. */ +template +class Core : protected FSM { + public: + // This must be heap-constructed. There's probably a way to enforce that in + // code but since this is just internal detail code and I don't know how + // off-hand, I'm punting. + Core() : FSM(State::Waiting) {} + ~Core() { + assert(calledBack_); + assert(detached_ == 2); + } + + // not copyable + Core(Core const&) = delete; + Core& operator=(Core const&) = delete; + + // not movable (see comment in the implementation of Future::then) + Core(Core&&) noexcept = delete; + Core& operator=(Core&&) = delete; + + Try& getTry() { + if (ready()) { + return *result_; + } else { + throw FutureNotReady(); + } + } + + template + void setCallback(F func) { + auto setCallback_ = [&]{ + if (callback_) { + throw std::logic_error("setCallback called twice"); + } + + context_ = RequestContext::saveContext(); + callback_ = std::move(func); + }; + + FSM_START + case State::Waiting: + case State::Interruptible: + case State::Interrupted: + FSM_UPDATE(state, setCallback_); + break; + + case State::Done: + FSM_UPDATE2(State::Done, + setCallback_, + [&]{ maybeCallback(); }); + break; + FSM_END + } + + void setResult(Try&& t) { + FSM_START + case State::Waiting: + case State::Interruptible: + case State::Interrupted: + FSM_UPDATE2(State::Done, + [&]{ result_ = std::move(t); }, + [&]{ maybeCallback(); }); + break; + + case State::Done: + throw std::logic_error("setResult called twice"); + FSM_END + } + + bool ready() const { + return getState() == State::Done; + } + + // Called by a destructing Future + void detachFuture() { + if (!callback_) { + setCallback(empty_callback); + } + activate(); + detachOne(); + } + + // Called by a destructing Promise + void detachPromise() { + if (!ready()) { + setResult(Try(std::make_exception_ptr(BrokenPromise()))); + } + detachOne(); + } + + void deactivate() { + active_ = false; + } + + void activate() { + active_ = true; + if (ready()) { + maybeCallback(); + } + } + + bool isActive() { return active_; } + + void setExecutor(Executor* x) { + executor_ = x; + } + + void raise(std::exception_ptr const& e) { + FSM_START + case State::Interruptible: + FSM_UPDATE2(State::Interrupted, + [&]{ interrupt_ = e; }, + [&]{ interruptHandler_(interrupt_); }); + break; + + case State::Waiting: + case State::Interrupted: + FSM_UPDATE(State::Interrupted, + [&]{ interrupt_ = e; }); + break; + + case State::Done: + FSM_BREAK + FSM_END + } + + void setInterruptHandler(std::function fn) { + FSM_START + case State::Waiting: + case State::Interruptible: + FSM_UPDATE(State::Interruptible, + [&]{ interruptHandler_ = std::move(fn); }); + break; + + case State::Interrupted: + fn(interrupt_); + FSM_BREAK + + case State::Done: + FSM_BREAK + FSM_END + } + + private: + void maybeCallback() { + assert(ready()); + if (isActive() && callback_) { + if (!calledBack_.exchange(true)) { + // TODO(5306911) we should probably try/catch + Executor* x = executor_; + + RequestContext::setContext(context_); + if (x) { + MoveWrapper&&)>> cb(std::move(callback_)); + MoveWrapper>> val(std::move(result_)); + x->add([cb, val]() mutable { (*cb)(std::move(**val)); }); + } else { + callback_(std::move(*result_)); + } + } + } + } + + void detachOne() { + auto d = ++detached_; + assert(d >= 1); + assert(d <= 2); + if (d == 2) { + // we should have already executed the callback with the value + assert(calledBack_); + delete this; + } + } + + folly::Optional> result_; + std::function&&)> callback_; + std::shared_ptr context_{nullptr}; + std::atomic calledBack_ {false}; + std::atomic detached_ {0}; + std::atomic active_ {true}; + std::atomic executor_ {nullptr}; + std::exception_ptr interrupt_; + std::function interruptHandler_; +}; + +template +struct VariadicContext { + VariadicContext() : total(0), count(0) {} + Promise... > > p; + std::tuple... > results; + size_t total; + std::atomic count; + typedef Future...>> type; +}; + +template +typename std::enable_if::type +whenAllVariadicHelper(VariadicContext *ctx, THead&& head, Fs&&... tail) { + head.setCallback_([ctx](Try&& t) { + std::get(ctx->results) = std::move(t); + if (++ctx->count == ctx->total) { + ctx->p.setValue(std::move(ctx->results)); + delete ctx; + } + }); +} + +template +typename std::enable_if::type +whenAllVariadicHelper(VariadicContext *ctx, THead&& head, Fs&&... tail) { + head.setCallback_([ctx](Try&& t) { + std::get(ctx->results) = std::move(t); + if (++ctx->count == ctx->total) { + ctx->p.setValue(std::move(ctx->results)); + delete ctx; + } + }); + // template tail-recursion + whenAllVariadicHelper(ctx, std::forward(tail)...); +} + +template +struct WhenAllContext { + WhenAllContext() : count(0) {} + Promise > > p; + std::vector > results; + std::atomic count; +}; + +template +struct WhenAnyContext { + explicit WhenAnyContext(size_t n) : done(false), ref_count(n) {}; + Promise>> p; + std::atomic done; + std::atomic ref_count; + void decref() { + if (--ref_count == 0) { + delete this; + } + } +}; + +}}} // namespace diff --git a/folly/wangle/futures/detail/Dummy.cpp b/folly/wangle/futures/detail/Dummy.cpp new file mode 100644 index 00000000..02a58d4f --- /dev/null +++ b/folly/wangle/futures/detail/Dummy.cpp @@ -0,0 +1,19 @@ +/* + * Copyright 2014 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. + */ + +// fbbuild is too dumb to know that .h files in the directory affect +// our project, unless we have a .cpp file in the target, in the same +// directory. diff --git a/folly/wangle/futures/detail/FSM.h b/folly/wangle/futures/detail/FSM.h new file mode 100644 index 00000000..be4eb8ae --- /dev/null +++ b/folly/wangle/futures/detail/FSM.h @@ -0,0 +1,122 @@ +/* + * Copyright 2014 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 + +#include +#include +#include + +namespace folly { namespace wangle { namespace detail { + +/// Finite State Machine helper base class. +/// Inherit from this. +/// For best results, use an "enum class" for Enum. +template +class FSM { +private: + // I am not templatizing this because folly::MicroSpinLock needs to be + // zero-initialized (or call init) which isn't generic enough for something + // that behaves like std::mutex. :( + using Mutex = folly::MicroSpinLock; + Mutex mutex_ {0}; + + // This might not be necessary for all Enum types, e.g. anything + // that is atomically updated in practice on this CPU and there's no risk + // of returning a bogus state because of tearing. + // An optimization would be to use a static conditional on the Enum type. + std::atomic state_; + +public: + explicit FSM(Enum startState) : state_(startState) {} + + Enum getState() const { + return state_.load(std::memory_order_relaxed); + } + + /// Atomically do a state transition with accompanying action. + /// The action will see the old state. + /// @returns true on success, false and action unexecuted otherwise + template + bool updateState(Enum A, Enum B, F const& action) { + std::lock_guard lock(mutex_); + if (state_ != A) return false; + action(); + state_ = B; + return true; + } + + /// Atomically do a state transition with accompanying action. Then do the + /// unprotected action without holding the lock. If the atomic transition + /// fails, returns false and neither action was executed. + /// + /// This facilitates code like this: + /// bool done = false; + /// while (!done) { + /// switch (getState()) { + /// case State::Foo: + /// done = updateState(State::Foo, State::Bar, + /// [&]{ /* do protected stuff */ }, + /// [&]{ /* do unprotected stuff */}); + /// break; + /// + /// Which reads nicer than code like this: + /// while (true) { + /// switch (getState()) { + /// case State::Foo: + /// if (!updateState(State::Foo, State::Bar, + /// [&]{ /* do protected stuff */ })) { + /// continue; + /// } + /// /* do unprotected stuff */ + /// return; // or otherwise break out of the loop + /// + /// The protected action will see the old state, and the unprotected action + /// will see the new state. + template + bool updateState(Enum A, Enum B, + F1 const& protectedAction, F2 const& unprotectedAction) { + bool result = updateState(A, B, protectedAction); + if (result) { + unprotectedAction(); + } + return result; + } +}; + +#define FSM_START \ + {bool done = false; while (!done) { auto state = getState(); switch (state) { + +#define FSM_UPDATE2(b, protectedAction, unprotectedAction) \ + done = updateState(state, (b), (protectedAction), (unprotectedAction)); + +#define FSM_UPDATE(b, action) FSM_UPDATE2((b), (action), []{}) + +#define FSM_CASE(a, b, action) \ + case (a): \ + FSM_UPDATE((b), (action)); \ + break; + +#define FSM_CASE2(a, b, protectedAction, unprotectedAction) \ + case (a): \ + FSM_UPDATE2((b), (protectedAction), (unprotectedAction)); \ + break; + +#define FSM_BREAK done = true; break; +#define FSM_END }}} + + +}}} diff --git a/folly/wangle/futures/test/Benchmark.cpp b/folly/wangle/futures/test/Benchmark.cpp new file mode 100644 index 00000000..863fa95e --- /dev/null +++ b/folly/wangle/futures/test/Benchmark.cpp @@ -0,0 +1,172 @@ +/* + * Copyright 2014 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 +#include +#include +#include +#include +#include +#include + +using namespace folly::wangle; +using namespace std; + +namespace { + +template +T incr(Try&& t) { + return t.value() + 1; +} + +void someThens(size_t n) { + auto f = makeFuture(42); + for (size_t i = 0; i < n; i++) { + f = f.then(incr); + } +} + +} // anonymous namespace + +BENCHMARK(constantFuture) { + makeFuture(42); +} + +// This shouldn't get too far below 100% +BENCHMARK_RELATIVE(promiseAndFuture) { + Promise p; + Future f = p.getFuture(); + p.setValue(42); + f.value(); +} + +// The higher the better. At the time of writing, it's only about 40% :( +BENCHMARK_RELATIVE(withThen) { + Promise p; + Future f = p.getFuture().then(incr); + p.setValue(42); + f.value(); +} + +// thens +BENCHMARK_DRAW_LINE() + +BENCHMARK(oneThen) { + someThens(1); +} + +// look for >= 50% relative +BENCHMARK_RELATIVE(twoThens) { + someThens(2); +} + +// look for >= 25% relative +BENCHMARK_RELATIVE(fourThens) { + someThens(4); +} + +// look for >= 1% relative +BENCHMARK_RELATIVE(hundredThens) { + someThens(100); +} + +// Lock contention. Although in practice fulfil()s tend to be temporally +// separate from then()s, still sometimes they will be concurrent. So the +// higher this number is, the better. +BENCHMARK_DRAW_LINE() + +BENCHMARK(no_contention) { + vector> promises(10000); + vector> futures; + std::thread producer, consumer; + + BENCHMARK_SUSPEND { + folly::Baton<> b1, b2; + for (auto& p : promises) + futures.push_back(p.getFuture()); + + consumer = std::thread([&]{ + b1.post(); + for (auto& f : futures) f.then(incr); + }); + consumer.join(); + + producer = std::thread([&]{ + b2.post(); + for (auto& p : promises) p.setValue(42); + }); + + b1.wait(); + b2.wait(); + } + + // The only thing we are measuring is how long fulfil + callbacks take + producer.join(); +} + +BENCHMARK_RELATIVE(contention) { + vector> promises(10000); + vector> futures; + std::thread producer, consumer; + sem_t sem; + sem_init(&sem, 0, 0); + + BENCHMARK_SUSPEND { + folly::Baton<> b1, b2; + for (auto& p : promises) + futures.push_back(p.getFuture()); + + consumer = std::thread([&]{ + b1.post(); + for (auto& f : futures) { + sem_wait(&sem); + f.then(incr); + } + }); + + producer = std::thread([&]{ + b2.post(); + for (auto& p : promises) { + sem_post(&sem); + p.setValue(42); + } + }); + + b1.wait(); + b2.wait(); + } + + // The astute reader will notice that we're not *precisely* comparing apples + // to apples here. Well, maybe it's like comparing Granny Smith to + // Braeburn or something. In the serial version, we waited for the futures + // to be all set up, but here we are probably still doing that work + // (although in parallel). But even though there is more work (on the order + // of 2x), it is being done by two threads. Hopefully most of the difference + // we see is due to lock contention and not false parallelism. + // + // Be warned that if the box is under heavy load, this will greatly skew + // these results (scheduling overhead will begin to dwarf lock contention). + // I'm not sure but I'd guess in Windtunnel this will mean large variance, + // because I expect they load the boxes as much as they can? + consumer.join(); + producer.join(); +} + +int main(int argc, char** argv) { + gflags::ParseCommandLineFlags(&argc, &argv, true); + folly::runBenchmarks(); + return 0; +} diff --git a/folly/wangle/futures/test/ClientCompile.cpp b/folly/wangle/futures/test/ClientCompile.cpp new file mode 100644 index 00000000..ab18d12e --- /dev/null +++ b/folly/wangle/futures/test/ClientCompile.cpp @@ -0,0 +1,22 @@ +/* + * Copyright 2014 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. + */ + +// amazing what things can go wrong if you include things in an unexpected +// order. +#include +#include +#include +int main() { return 0; } diff --git a/folly/wangle/futures/test/ExecutorTest.cpp b/folly/wangle/futures/test/ExecutorTest.cpp new file mode 100644 index 00000000..67f43ec5 --- /dev/null +++ b/folly/wangle/futures/test/ExecutorTest.cpp @@ -0,0 +1,158 @@ +/* + * Copyright 2014 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 +#include +#include +#include +#include +#include + +using namespace folly::wangle; +using namespace std::chrono; +using namespace testing; + +TEST(ManualExecutor, runIsStable) { + ManualExecutor x; + size_t count = 0; + auto f1 = [&]() { count++; }; + auto f2 = [&]() { x.add(f1); x.add(f1); }; + x.add(f2); + x.run(); +} + +TEST(ManualExecutor, scheduleDur) { + ManualExecutor x; + size_t count = 0; + milliseconds dur {10}; + x.schedule([&]{ count++; }, dur); + EXPECT_EQ(count, 0); + x.run(); + EXPECT_EQ(count, 0); + x.advance(dur/2); + EXPECT_EQ(count, 0); + x.advance(dur/2); + EXPECT_EQ(count, 1); +} + +TEST(ManualExecutor, clockStartsAt0) { + ManualExecutor x; + EXPECT_EQ(x.now(), x.now().min()); +} + +TEST(ManualExecutor, scheduleAbs) { + ManualExecutor x; + size_t count = 0; + x.scheduleAt([&]{ count++; }, x.now() + milliseconds(10)); + EXPECT_EQ(count, 0); + x.advance(milliseconds(10)); + EXPECT_EQ(count, 1); +} + +TEST(ManualExecutor, advanceTo) { + ManualExecutor x; + size_t count = 0; + x.scheduleAt([&]{ count++; }, steady_clock::now()); + EXPECT_EQ(count, 0); + x.advanceTo(steady_clock::now()); + EXPECT_EQ(count, 1); +} + +TEST(ManualExecutor, advanceBack) { + ManualExecutor x; + size_t count = 0; + x.advance(microseconds(5)); + x.schedule([&]{ count++; }, microseconds(6)); + EXPECT_EQ(count, 0); + x.advanceTo(x.now() - microseconds(1)); + EXPECT_EQ(count, 0); +} + +TEST(ManualExecutor, advanceNeg) { + ManualExecutor x; + size_t count = 0; + x.advance(microseconds(5)); + x.schedule([&]{ count++; }, microseconds(6)); + EXPECT_EQ(count, 0); + x.advance(microseconds(-1)); + EXPECT_EQ(count, 0); +} + +TEST(ManualExecutor, waitForDoesNotDeadlock) { + ManualExecutor east, west; + folly::Baton<> baton; + auto f = makeFuture() + .via(&east) + .then([](Try){ return makeFuture(); }) + .via(&west); + std::thread t([&]{ + baton.post(); + west.waitFor(f); + }); + baton.wait(); + east.run(); + t.join(); +} + +TEST(Executor, InlineExecutor) { + InlineExecutor x; + size_t counter = 0; + x.add([&]{ + x.add([&]{ + EXPECT_EQ(counter++, 0); + }); + EXPECT_EQ(counter++, 1); + }); + EXPECT_EQ(counter, 2); +} + +TEST(Executor, QueuedImmediateExecutor) { + QueuedImmediateExecutor x; + size_t counter = 0; + x.add([&]{ + x.add([&]{ + EXPECT_EQ(1, counter++); + }); + EXPECT_EQ(0, counter++); + }); + EXPECT_EQ(2, counter); +} + +TEST(Executor, Runnable) { + InlineExecutor x; + size_t counter = 0; + struct Runnable { + std::function fn; + void operator()() { fn(); } + }; + Runnable f; + f.fn = [&]{ counter++; }; + x.add(f); + EXPECT_EQ(counter, 1); +} + +TEST(Executor, RunnablePtr) { + InlineExecutor x; + struct Runnable { + std::function fn; + void operator()() { fn(); } + }; + size_t counter = 0; + auto fnp = std::make_shared(); + fnp->fn = [&]{ counter++; }; + x.addPtr(fnp); + EXPECT_EQ(counter, 1); +} diff --git a/folly/wangle/futures/test/FSM.cpp b/folly/wangle/futures/test/FSM.cpp new file mode 100644 index 00000000..ff75f868 --- /dev/null +++ b/folly/wangle/futures/test/FSM.cpp @@ -0,0 +1,115 @@ +/* + * Copyright 2014 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 +#include + +using namespace folly::wangle::detail; + +enum class State { A, B }; + +TEST(FSM, example) { + FSM fsm(State::A); + int count = 0; + int unprotectedCount = 0; + + // somebody set up us the switch + auto tryTransition = [&]{ + switch (fsm.getState()) { + case State::A: + return fsm.updateState(State::A, State::B, [&]{ count++; }); + case State::B: + return fsm.updateState(State::B, State::A, + [&]{ count--; }, [&]{ unprotectedCount--; }); + } + return false; // unreachable + }; + + // keep retrying until success (like a cas) + while (!tryTransition()) ; + EXPECT_EQ(State::B, fsm.getState()); + EXPECT_EQ(1, count); + EXPECT_EQ(0, unprotectedCount); + + while (!tryTransition()) ; + EXPECT_EQ(State::A, fsm.getState()); + EXPECT_EQ(0, count); + EXPECT_EQ(-1, unprotectedCount); +} + +TEST(FSM, magicMacrosExample) { + struct MyFSM : public FSM { + int count = 0; + int unprotectedCount = 0; + MyFSM() : FSM(State::A) {} + void twiddle() { + FSM_START + FSM_CASE(State::A, State::B, [&]{ count++; }); + FSM_CASE2(State::B, State::A, + [&]{ count--; }, [&]{ unprotectedCount--; }); + FSM_END + } + }; + + MyFSM fsm; + + fsm.twiddle(); + EXPECT_EQ(State::B, fsm.getState()); + EXPECT_EQ(1, fsm.count); + EXPECT_EQ(0, fsm.unprotectedCount); + + fsm.twiddle(); + EXPECT_EQ(State::A, fsm.getState()); + EXPECT_EQ(0, fsm.count); + EXPECT_EQ(-1, fsm.unprotectedCount); +} + + +TEST(FSM, ctor) { + FSM fsm(State::A); + EXPECT_EQ(State::A, fsm.getState()); +} + +TEST(FSM, update) { + FSM fsm(State::A); + EXPECT_TRUE(fsm.updateState(State::A, State::B, []{})); + EXPECT_EQ(State::B, fsm.getState()); +} + +TEST(FSM, badUpdate) { + FSM fsm(State::A); + EXPECT_FALSE(fsm.updateState(State::B, State::A, []{})); +} + +TEST(FSM, actionOnUpdate) { + FSM fsm(State::A); + int count = 0; + fsm.updateState(State::A, State::B, [&]{ count++; }); + EXPECT_EQ(1, count); +} + +TEST(FSM, noActionOnBadUpdate) { + FSM fsm(State::A); + int count = 0; + fsm.updateState(State::B, State::A, [&]{ count++; }); + EXPECT_EQ(0, count); +} + +TEST(FSM, stateTransitionAfterAction) { + FSM fsm(State::A); + fsm.updateState(State::A, State::B, + [&]{ EXPECT_EQ(State::A, fsm.getState()); }); +} diff --git a/folly/wangle/futures/test/FutureTest.cpp b/folly/wangle/futures/test/FutureTest.cpp new file mode 100644 index 00000000..fedd7a35 --- /dev/null +++ b/folly/wangle/futures/test/FutureTest.cpp @@ -0,0 +1,1014 @@ +/* + * Copyright 2014 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace folly; +using namespace folly::wangle; +using std::pair; +using std::string; +using std::unique_ptr; +using std::vector; + +#define EXPECT_TYPE(x, T) \ + EXPECT_TRUE((std::is_same::value)) + +/// Simple executor that does work in another thread +class ThreadExecutor : public Executor { + folly::MPMCQueue funcs; + std::atomic done {false}; + std::thread worker; + folly::Baton<> baton; + + void work() { + baton.post(); + Func fn; + while (!done) { + while (!funcs.isEmpty()) { + funcs.blockingRead(fn); + fn(); + } + } + } + + public: + ThreadExecutor(size_t n = 1024) + : funcs(n), worker(std::bind(&ThreadExecutor::work, this)) {} + + ~ThreadExecutor() { + done = true; + funcs.write([]{}); + worker.join(); + } + + void add(Func fn) override { + funcs.blockingWrite(std::move(fn)); + } + + void waitForStartup() { + baton.wait(); + } +}; + +typedef WangleException eggs_t; +static eggs_t eggs("eggs"); + +// Future + +TEST(Future, try) { + class A { + public: + A(int x) : x_(x) {} + + int x() const { + return x_; + } + private: + int x_; + }; + + A a(5); + Try t_a(std::move(a)); + + Try t_void; + + EXPECT_EQ(5, t_a.value().x()); +} + +TEST(Future, special) { + EXPECT_FALSE(std::is_copy_constructible>::value); + EXPECT_FALSE(std::is_copy_assignable>::value); + EXPECT_TRUE(std::is_move_constructible>::value); + EXPECT_TRUE(std::is_move_assignable>::value); +} + +TEST(Future, thenTry) { + bool flag = false; + + makeFuture(42).then([&](Try&& t) { + flag = true; + EXPECT_EQ(42, t.value()); + }); + EXPECT_TRUE(flag); flag = false; + + makeFuture(42) + .then([](Try&& t) { return t.value(); }) + .then([&](Try&& t) { flag = true; EXPECT_EQ(42, t.value()); }); + EXPECT_TRUE(flag); flag = false; + + makeFuture().then([&](Try&& t) { flag = true; t.value(); }); + EXPECT_TRUE(flag); flag = false; + + Promise p; + auto f = p.getFuture().then([&](Try&& t) { flag = true; }); + EXPECT_FALSE(flag); + EXPECT_FALSE(f.isReady()); + p.setValue(); + EXPECT_TRUE(flag); + EXPECT_TRUE(f.isReady()); +} + +TEST(Future, thenValue) { + bool flag = false; + makeFuture(42).then([&](int i){ + EXPECT_EQ(42, i); + flag = true; + }); + EXPECT_TRUE(flag); flag = false; + + makeFuture(42) + .then([](int i){ return i; }) + .then([&](int i) { flag = true; EXPECT_EQ(42, i); }); + EXPECT_TRUE(flag); flag = false; + + makeFuture().then([&]{ + flag = true; + }); + EXPECT_TRUE(flag); flag = false; + + auto f = makeFuture(eggs).then([&](int i){}); + EXPECT_THROW(f.value(), eggs_t); + + f = makeFuture(eggs).then([&]{}); + EXPECT_THROW(f.value(), eggs_t); +} + +TEST(Future, thenValueFuture) { + bool flag = false; + makeFuture(42) + .then([](int i){ return makeFuture(std::move(i)); }) + .then([&](Try&& t) { flag = true; EXPECT_EQ(42, t.value()); }); + EXPECT_TRUE(flag); flag = false; + + makeFuture() + .then([]{ return makeFuture(); }) + .then([&](Try&& t) { flag = true; }); + EXPECT_TRUE(flag); flag = false; +} + +static string doWorkStatic(Try&& t) { + return t.value() + ";static"; +} + +TEST(Future, thenFunction) { + struct Worker { + string doWork(Try&& t) { + return t.value() + ";class"; + } + static string doWorkStatic(Try&& t) { + return t.value() + ";class-static"; + } + } w; + + auto f = makeFuture("start") + .then(doWorkStatic) + .then(Worker::doWorkStatic) + .then(&w, &Worker::doWork); + + EXPECT_EQ(f.value(), "start;static;class-static;class"); +} + +static Future doWorkStaticFuture(Try&& t) { + return makeFuture(t.value() + ";static"); +} + +TEST(Future, thenFunctionFuture) { + struct Worker { + Future doWorkFuture(Try&& t) { + return makeFuture(t.value() + ";class"); + } + static Future doWorkStaticFuture(Try&& t) { + return makeFuture(t.value() + ";class-static"); + } + } w; + + auto f = makeFuture("start") + .then(doWorkStaticFuture) + .then(Worker::doWorkStaticFuture) + .then(&w, &Worker::doWorkFuture); + + EXPECT_EQ(f.value(), "start;static;class-static;class"); +} + +TEST(Future, value) { + auto f = makeFuture(unique_ptr(new int(42))); + auto up = std::move(f.value()); + EXPECT_EQ(42, *up); + + EXPECT_THROW(makeFuture(eggs).value(), eggs_t); +} + +TEST(Future, isReady) { + Promise p; + auto f = p.getFuture(); + EXPECT_FALSE(f.isReady()); + p.setValue(42); + EXPECT_TRUE(f.isReady()); + } + +TEST(Future, futureNotReady) { + Promise p; + Future f = p.getFuture(); + EXPECT_THROW(f.value(), eggs_t); +} + +TEST(Future, hasException) { + EXPECT_TRUE(makeFuture(eggs).getTry().hasException()); + EXPECT_FALSE(makeFuture(42).getTry().hasException()); +} + +TEST(Future, hasValue) { + EXPECT_TRUE(makeFuture(42).getTry().hasValue()); + EXPECT_FALSE(makeFuture(eggs).getTry().hasValue()); +} + +TEST(Future, makeFuture) { + EXPECT_TYPE(makeFuture(42), Future); + EXPECT_EQ(42, makeFuture(42).value()); + + EXPECT_TYPE(makeFuture(42), Future); + EXPECT_EQ(42, makeFuture(42).value()); + + auto fun = [] { return 42; }; + EXPECT_TYPE(makeFutureTry(fun), Future); + EXPECT_EQ(42, makeFutureTry(fun).value()); + + auto failfun = []() -> int { throw eggs; }; + EXPECT_TYPE(makeFutureTry(failfun), Future); + EXPECT_THROW(makeFutureTry(failfun).value(), eggs_t); + + EXPECT_TYPE(makeFuture(), Future); +} + +// Promise + +TEST(Promise, special) { + EXPECT_FALSE(std::is_copy_constructible>::value); + EXPECT_FALSE(std::is_copy_assignable>::value); + EXPECT_TRUE(std::is_move_constructible>::value); + EXPECT_TRUE(std::is_move_assignable>::value); +} + +TEST(Promise, getFuture) { + Promise p; + Future f = p.getFuture(); + EXPECT_FALSE(f.isReady()); +} + +TEST(Promise, setValue) { + Promise fund; + auto ffund = fund.getFuture(); + fund.setValue(42); + EXPECT_EQ(42, ffund.value()); + + struct Foo { + string name; + int value; + }; + + Promise pod; + auto fpod = pod.getFuture(); + Foo f = {"the answer", 42}; + pod.setValue(f); + Foo f2 = fpod.value(); + EXPECT_EQ(f.name, f2.name); + EXPECT_EQ(f.value, f2.value); + + pod = Promise(); + fpod = pod.getFuture(); + pod.setValue(std::move(f2)); + Foo f3 = fpod.value(); + EXPECT_EQ(f.name, f3.name); + EXPECT_EQ(f.value, f3.value); + + Promise> mov; + auto fmov = mov.getFuture(); + mov.setValue(unique_ptr(new int(42))); + unique_ptr ptr = std::move(fmov.value()); + EXPECT_EQ(42, *ptr); + + Promise v; + auto fv = v.getFuture(); + v.setValue(); + EXPECT_TRUE(fv.isReady()); +} + +TEST(Promise, setException) { + { + Promise p; + auto f = p.getFuture(); + p.setException(eggs); + EXPECT_THROW(f.value(), eggs_t); + } + { + Promise p; + auto f = p.getFuture(); + try { + throw eggs; + } catch (...) { + p.setException(std::current_exception()); + } + EXPECT_THROW(f.value(), eggs_t); + } +} + +TEST(Promise, fulfil) { + { + Promise p; + auto f = p.getFuture(); + p.fulfil([] { return 42; }); + EXPECT_EQ(42, f.value()); + } + { + Promise p; + auto f = p.getFuture(); + p.fulfil([]() -> int { throw eggs; }); + EXPECT_THROW(f.value(), eggs_t); + } +} + +TEST(Future, finish) { + auto x = std::make_shared(0); + { + Promise p; + auto f = p.getFuture().then([x](Try&& t) { *x = t.value(); }); + + // The callback hasn't executed + EXPECT_EQ(0, *x); + + // The callback has a reference to x + EXPECT_EQ(2, x.use_count()); + + p.setValue(42); + + // the callback has executed + EXPECT_EQ(42, *x); + } + // the callback has been destructed + // and has released its reference to x + EXPECT_EQ(1, x.use_count()); +} + +TEST(Future, unwrap) { + Promise a; + Promise b; + + auto fa = a.getFuture(); + auto fb = b.getFuture(); + + bool flag1 = false; + bool flag2 = false; + + // do a, then do b, and get the result of a + b. + Future f = fa.then([&](Try&& ta) { + auto va = ta.value(); + flag1 = true; + return fb.then([va, &flag2](Try&& tb) { + flag2 = true; + return va + tb.value(); + }); + }); + + EXPECT_FALSE(flag1); + EXPECT_FALSE(flag2); + EXPECT_FALSE(f.isReady()); + + a.setValue(3); + EXPECT_TRUE(flag1); + EXPECT_FALSE(flag2); + EXPECT_FALSE(f.isReady()); + + b.setValue(4); + EXPECT_TRUE(flag1); + EXPECT_TRUE(flag2); + EXPECT_EQ(7, f.value()); +} + +TEST(Future, whenAll) { + // returns a vector variant + { + vector> promises(10); + vector> futures; + + for (auto& p : promises) + futures.push_back(p.getFuture()); + + auto allf = whenAll(futures.begin(), futures.end()); + + random_shuffle(promises.begin(), promises.end()); + for (auto& p : promises) { + EXPECT_FALSE(allf.isReady()); + p.setValue(42); + } + + EXPECT_TRUE(allf.isReady()); + auto& results = allf.value(); + for (auto& t : results) { + EXPECT_EQ(42, t.value()); + } + } + + // check error semantics + { + vector> promises(4); + vector> futures; + + for (auto& p : promises) + futures.push_back(p.getFuture()); + + auto allf = whenAll(futures.begin(), futures.end()); + + + promises[0].setValue(42); + promises[1].setException(eggs); + + EXPECT_FALSE(allf.isReady()); + + promises[2].setValue(42); + + EXPECT_FALSE(allf.isReady()); + + promises[3].setException(eggs); + + EXPECT_TRUE(allf.isReady()); + EXPECT_FALSE(allf.getTry().hasException()); + + auto& results = allf.value(); + EXPECT_EQ(42, results[0].value()); + EXPECT_TRUE(results[1].hasException()); + EXPECT_EQ(42, results[2].value()); + EXPECT_TRUE(results[3].hasException()); + } + + // check that futures are ready in then() + { + vector> promises(10); + vector> futures; + + for (auto& p : promises) + futures.push_back(p.getFuture()); + + auto allf = whenAll(futures.begin(), futures.end()) + .then([](Try>>&& ts) { + for (auto& f : ts.value()) + f.value(); + }); + + random_shuffle(promises.begin(), promises.end()); + for (auto& p : promises) + p.setValue(); + EXPECT_TRUE(allf.isReady()); + } +} + + +TEST(Future, whenAny) { + { + vector> promises(10); + vector> futures; + + for (auto& p : promises) + futures.push_back(p.getFuture()); + + for (auto& f : futures) { + EXPECT_FALSE(f.isReady()); + } + + auto anyf = whenAny(futures.begin(), futures.end()); + + /* futures were moved in, so these are invalid now */ + EXPECT_FALSE(anyf.isReady()); + + promises[7].setValue(42); + EXPECT_TRUE(anyf.isReady()); + auto& idx_fut = anyf.value(); + + auto i = idx_fut.first; + EXPECT_EQ(7, i); + + auto& f = idx_fut.second; + EXPECT_EQ(42, f.value()); + } + + // error + { + vector> promises(10); + vector> futures; + + for (auto& p : promises) + futures.push_back(p.getFuture()); + + for (auto& f : futures) { + EXPECT_FALSE(f.isReady()); + } + + auto anyf = whenAny(futures.begin(), futures.end()); + + EXPECT_FALSE(anyf.isReady()); + + promises[3].setException(eggs); + EXPECT_TRUE(anyf.isReady()); + EXPECT_TRUE(anyf.value().second.hasException()); + } + + // then() + { + vector> promises(10); + vector> futures; + + for (auto& p : promises) + futures.push_back(p.getFuture()); + + auto anyf = whenAny(futures.begin(), futures.end()) + .then([](Try>>&& f) { + EXPECT_EQ(42, f.value().second.value()); + }); + + promises[3].setValue(42); + EXPECT_TRUE(anyf.isReady()); + } +} + + +TEST(when, already_completed) { + { + vector> fs; + for (int i = 0; i < 10; i++) + fs.push_back(makeFuture()); + + whenAll(fs.begin(), fs.end()) + .then([&](Try>>&& t) { + EXPECT_EQ(fs.size(), t.value().size()); + }); + } + { + vector> fs; + for (int i = 0; i < 10; i++) + fs.push_back(makeFuture(i)); + + whenAny(fs.begin(), fs.end()) + .then([&](Try>>&& t) { + auto& p = t.value(); + EXPECT_EQ(p.first, p.second.value()); + }); + } +} + +TEST(when, whenN) { + vector> promises(10); + vector> futures; + + for (auto& p : promises) + futures.push_back(p.getFuture()); + + bool flag = false; + size_t n = 3; + whenN(futures.begin(), futures.end(), n) + .then([&](Try>>>&& t) { + flag = true; + auto v = t.value(); + EXPECT_EQ(n, v.size()); + for (auto& tt : v) + EXPECT_TRUE(tt.second.hasValue()); + }); + + promises[0].setValue(); + EXPECT_FALSE(flag); + promises[1].setValue(); + EXPECT_FALSE(flag); + promises[2].setValue(); + EXPECT_TRUE(flag); +} + +/* Ensure that we can compile when_{all,any} with folly::small_vector */ +TEST(when, small_vector) { + + static_assert(!FOLLY_IS_TRIVIALLY_COPYABLE(Future), + "Futures should not be trivially copyable"); + static_assert(!FOLLY_IS_TRIVIALLY_COPYABLE(Future), + "Futures should not be trivially copyable"); + + using folly::small_vector; + { + small_vector> futures; + + for (int i = 0; i < 10; i++) + futures.push_back(makeFuture()); + + auto anyf = whenAny(futures.begin(), futures.end()); + } + + { + small_vector> futures; + + for (int i = 0; i < 10; i++) + futures.push_back(makeFuture()); + + auto allf = whenAll(futures.begin(), futures.end()); + } +} + +TEST(Future, whenAllVariadic) { + Promise pb; + Promise pi; + Future fb = pb.getFuture(); + Future fi = pi.getFuture(); + bool flag = false; + whenAll(std::move(fb), std::move(fi)) + .then([&](Try, Try>>&& t) { + flag = true; + EXPECT_TRUE(t.hasValue()); + EXPECT_TRUE(std::get<0>(t.value()).hasValue()); + EXPECT_EQ(std::get<0>(t.value()).value(), true); + EXPECT_TRUE(std::get<1>(t.value()).hasValue()); + EXPECT_EQ(std::get<1>(t.value()).value(), 42); + }); + pb.setValue(true); + EXPECT_FALSE(flag); + pi.setValue(42); + EXPECT_TRUE(flag); +} + +TEST(Future, whenAllVariadicReferences) { + Promise pb; + Promise pi; + Future fb = pb.getFuture(); + Future fi = pi.getFuture(); + bool flag = false; + whenAll(fb, fi) + .then([&](Try, Try>>&& t) { + flag = true; + EXPECT_TRUE(t.hasValue()); + EXPECT_TRUE(std::get<0>(t.value()).hasValue()); + EXPECT_EQ(std::get<0>(t.value()).value(), true); + EXPECT_TRUE(std::get<1>(t.value()).hasValue()); + EXPECT_EQ(std::get<1>(t.value()).value(), 42); + }); + pb.setValue(true); + EXPECT_FALSE(flag); + pi.setValue(42); + EXPECT_TRUE(flag); +} + +TEST(Future, whenAll_none) { + vector> fs; + auto f = whenAll(fs.begin(), fs.end()); + EXPECT_TRUE(f.isReady()); +} + +TEST(Future, throwCaughtInImmediateThen) { + // Neither of these should throw "Promise already satisfied" + makeFuture().then( + [=](Try&&) -> int { throw std::exception(); }); + makeFuture().then( + [=](Try&&) -> Future { throw std::exception(); }); +} + +TEST(Future, throwIfFailed) { + makeFuture(eggs) + .then([=](Try&& t) { + EXPECT_THROW(t.throwIfFailed(), eggs_t); + }); + makeFuture() + .then([=](Try&& t) { + EXPECT_NO_THROW(t.throwIfFailed()); + }); + + makeFuture(eggs) + .then([=](Try&& t) { + EXPECT_THROW(t.throwIfFailed(), eggs_t); + }); + makeFuture(42) + .then([=](Try&& t) { + EXPECT_NO_THROW(t.throwIfFailed()); + }); +} + +TEST(Future, waitWithSemaphoreImmediate) { + waitWithSemaphore(makeFuture()); + auto done = waitWithSemaphore(makeFuture(42)).value(); + EXPECT_EQ(42, done); + + vector v{1,2,3}; + auto done_v = waitWithSemaphore(makeFuture(v)).value(); + EXPECT_EQ(v.size(), done_v.size()); + EXPECT_EQ(v, done_v); + + vector> v_f; + v_f.push_back(makeFuture()); + v_f.push_back(makeFuture()); + auto done_v_f = waitWithSemaphore(whenAll(v_f.begin(), v_f.end())).value(); + EXPECT_EQ(2, done_v_f.size()); + + vector> v_fb; + v_fb.push_back(makeFuture(true)); + v_fb.push_back(makeFuture(false)); + auto fut = whenAll(v_fb.begin(), v_fb.end()); + auto done_v_fb = std::move(waitWithSemaphore(std::move(fut)).value()); + EXPECT_EQ(2, done_v_fb.size()); +} + +TEST(Future, waitWithSemaphore) { + Promise p; + Future f = p.getFuture(); + std::atomic flag{false}; + std::atomic result{1}; + std::atomic id; + + std::thread t([&](Future&& tf){ + auto n = tf.then([&](Try && t) { + id = std::this_thread::get_id(); + return t.value(); + }); + flag = true; + result.store(waitWithSemaphore(std::move(n)).value()); + }, + std::move(f) + ); + while(!flag){} + EXPECT_EQ(result.load(), 1); + p.setValue(42); + t.join(); + // validate that the callback ended up executing in this thread, which + // is more to ensure that this test actually tests what it should + EXPECT_EQ(id, std::this_thread::get_id()); + EXPECT_EQ(result.load(), 42); +} + +TEST(Future, waitWithSemaphoreForTime) { + { + Promise p; + Future f = p.getFuture(); + auto t = waitWithSemaphore(std::move(f), + std::chrono::microseconds(1)); + EXPECT_FALSE(t.isReady()); + p.setValue(1); + EXPECT_TRUE(t.isReady()); + } + { + Promise p; + Future f = p.getFuture(); + p.setValue(1); + auto t = waitWithSemaphore(std::move(f), + std::chrono::milliseconds(1)); + EXPECT_TRUE(t.isReady()); + } + { + vector> v_fb; + v_fb.push_back(makeFuture(true)); + v_fb.push_back(makeFuture(false)); + auto f = whenAll(v_fb.begin(), v_fb.end()); + auto t = waitWithSemaphore(std::move(f), + std::chrono::milliseconds(1)); + EXPECT_TRUE(t.isReady()); + EXPECT_EQ(2, t.value().size()); + } + { + vector> v_fb; + Promise p1; + Promise p2; + v_fb.push_back(p1.getFuture()); + v_fb.push_back(p2.getFuture()); + auto f = whenAll(v_fb.begin(), v_fb.end()); + auto t = waitWithSemaphore(std::move(f), + std::chrono::milliseconds(1)); + EXPECT_FALSE(t.isReady()); + p1.setValue(true); + EXPECT_FALSE(t.isReady()); + p2.setValue(true); + EXPECT_TRUE(t.isReady()); + } + { + auto t = waitWithSemaphore(makeFuture(), + std::chrono::milliseconds(1)); + EXPECT_TRUE(t.isReady()); + } +} + +TEST(Future, callbackAfterActivate) { + Promise p; + auto f = p.getFuture(); + f.deactivate(); + + size_t count = 0; + f.then([&](Try&&) { count++; }); + + p.setValue(); + EXPECT_EQ(0, count); + + f.activate(); + EXPECT_EQ(1, count); +} + +TEST(Future, activateOnDestruct) { + auto f = std::make_shared>(makeFuture()); + f->deactivate(); + + size_t count = 0; + f->then([&](Try&&) { count++; }); + EXPECT_EQ(0, count); + + f.reset(); + EXPECT_EQ(1, count); +} + +TEST(Future, viaActsCold) { + ManualExecutor x; + size_t count = 0; + + auto fv = via(&x); + fv.then([&](Try&&) { count++; }); + + EXPECT_EQ(0, count); + + fv.activate(); + + EXPECT_EQ(1, x.run()); + EXPECT_EQ(1, count); +} + +TEST(Future, viaIsCold) { + ManualExecutor x; + EXPECT_FALSE(via(&x).isActive()); +} + +TEST(Future, viaRaces) { + ManualExecutor x; + Promise p; + auto tid = std::this_thread::get_id(); + bool done = false; + + std::thread t1([&] { + p.getFuture() + .via(&x) + .then([&](Try&&) { EXPECT_EQ(tid, std::this_thread::get_id()); }) + .then([&](Try&&) { EXPECT_EQ(tid, std::this_thread::get_id()); }) + .then([&](Try&&) { done = true; }); + }); + + std::thread t2([&] { + p.setValue(); + }); + + while (!done) x.run(); + t1.join(); + t2.join(); +} + +// TODO(#4920689) +TEST(Future, viaRaces_2stage) { + ManualExecutor x; + Promise p; + auto tid = std::this_thread::get_id(); + bool done = false; + + std::thread t1([&] { + auto f2 = p.getFuture().via(&x); + f2.then([&](Try&&) { EXPECT_EQ(tid, std::this_thread::get_id()); }) + .then([&](Try&&) { EXPECT_EQ(tid, std::this_thread::get_id()); }) + .then([&](Try&&) { done = true; }); + + // the bug was in the promise being fulfilled before f2 is reactivated. we + // could sleep, but yielding should cause this to fail with reasonable + // probability + std::this_thread::yield(); + f2.activate(); + }); + + std::thread t2([&] { + p.setValue(); + }); + + while (!done) x.run(); + t1.join(); + t2.join(); +} + +TEST(Future, getFuture_after_setValue) { + Promise p; + p.setValue(42); + EXPECT_EQ(42, p.getFuture().value()); +} + +TEST(Future, getFuture_after_setException) { + Promise p; + p.fulfil([]() -> void { throw std::logic_error("foo"); }); + EXPECT_THROW(p.getFuture().value(), std::logic_error); +} + +TEST(Future, detachRace) { + // Task #5438209 + // This test is designed to detect a race that was in Core::detachOne() + // where detached_ was incremented and then tested, and that + // allowed a race where both Promise and Future would think they were the + // second and both try to delete. This showed up at scale but was very + // difficult to reliably repro in a test. As it is, this only fails about + // once in every 1,000 executions. Doing this 1,000 times is going to make a + // slow test so I won't do that but if it ever fails, take it seriously, and + // run the test binary with "--gtest_repeat=10000 --gtest_filter=*detachRace" + // (Don't forget to enable ASAN) + auto p = folly::make_unique>(); + auto f = folly::make_unique>(p->getFuture()); + folly::Baton<> baton; + std::thread t1([&]{ + baton.post(); + p.reset(); + }); + baton.wait(); + f.reset(); + t1.join(); +} + +class TestData : public RequestData { + public: + explicit TestData(int data) : data_(data) {} + virtual ~TestData() {} + int data_; +}; + +TEST(Future, context) { + + // Start a new context + RequestContext::create(); + + EXPECT_EQ(nullptr, RequestContext::get()->getContextData("test")); + + // Set some test data + RequestContext::get()->setContextData( + "test", + std::unique_ptr(new TestData(10))); + + // Start a future + Promise p; + auto future = p.getFuture().then([&]{ + // Check that the context followed the future + EXPECT_TRUE(RequestContext::get() != nullptr); + auto a = dynamic_cast( + RequestContext::get()->getContextData("test")); + auto data = a->data_; + EXPECT_EQ(10, data); + }); + + // Clear the context + RequestContext::setContext(nullptr); + + EXPECT_EQ(nullptr, RequestContext::get()->getContextData("test")); + + // Fulfil the promise + p.setValue(); +} + + +// This only fails about 1 in 1k times when the bug is present :( +TEST(Future, t5506504) { + ThreadExecutor x; + + auto fn = [&x]{ + auto promises = std::make_shared>>(4); + vector> futures; + + for (auto& p : *promises) { + futures.emplace_back( + p.getFuture() + .via(&x) + .then([](Try&&){})); + } + + x.waitForStartup(); + x.add([promises]{ + for (auto& p : *promises) p.setValue(); + }); + + return whenAll(futures.begin(), futures.end()); + }; + + waitWithSemaphore(fn()); +} diff --git a/folly/wangle/futures/test/Interrupts.cpp b/folly/wangle/futures/test/Interrupts.cpp new file mode 100644 index 00000000..3ecf050d --- /dev/null +++ b/folly/wangle/futures/test/Interrupts.cpp @@ -0,0 +1,74 @@ +/* + * Copyright 2014 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 + +#include +#include + +using namespace folly::wangle; + +TEST(Interrupts, raise) { + std::runtime_error eggs("eggs"); + Promise p; + p.setInterruptHandler([&](std::exception_ptr e) { + EXPECT_THROW(std::rethrow_exception(e), decltype(eggs)); + }); + p.getFuture().raise(eggs); +} + +TEST(Interrupts, cancel) { + Promise p; + p.setInterruptHandler([&](std::exception_ptr e) { + EXPECT_THROW(std::rethrow_exception(e), FutureCancellation); + }); + p.getFuture().cancel(); +} + +TEST(Interrupts, handleThenInterrupt) { + Promise p; + bool flag = false; + p.setInterruptHandler([&](std::exception_ptr e) { flag = true; }); + p.getFuture().cancel(); + EXPECT_TRUE(flag); +} + +TEST(Interrupts, interruptThenHandle) { + Promise p; + bool flag = false; + p.getFuture().cancel(); + p.setInterruptHandler([&](std::exception_ptr e) { flag = true; }); + EXPECT_TRUE(flag); +} + +TEST(Interrupts, interruptAfterFulfilNoop) { + Promise p; + bool flag = false; + p.setInterruptHandler([&](std::exception_ptr e) { flag = true; }); + p.setValue(); + p.getFuture().cancel(); + EXPECT_FALSE(flag); +} + +TEST(Interrupts, secondInterruptNoop) { + Promise p; + int count = 0; + p.setInterruptHandler([&](std::exception_ptr e) { count++; }); + auto f = p.getFuture(); + f.cancel(); + f.cancel(); + EXPECT_EQ(1, count); +} diff --git a/folly/wangle/futures/test/Thens.cpp b/folly/wangle/futures/test/Thens.cpp new file mode 100644 index 00000000..2ebbf486 --- /dev/null +++ b/folly/wangle/futures/test/Thens.cpp @@ -0,0 +1,34 @@ +// This file is @generated by thens.rb. Do not edit directly. + +// TODO: fails to compile with clang:dev. See task #4412111 +#ifndef __clang__ + +#include + +TEST(Future, thenVariants) { + SomeClass anObject; + folly::Executor* anExecutor; + + {Future f = someFuture().then(&aFunction, Try&&>);} + {Future f = someFuture().then(&SomeClass::aStaticMethod, Try&&>);} + {Future f = someFuture().then(&anObject, &SomeClass::aMethod, Try&&>);} + {Future f = someFuture().then(aStdFunction, Try&&>());} + {Future f = someFuture().then([&](Try&&){return someFuture();});} + {Future f = someFuture().then(&aFunction, A&&>);} + {Future f = someFuture().then(&SomeClass::aStaticMethod, A&&>);} + {Future f = someFuture().then(&anObject, &SomeClass::aMethod, A&&>);} + {Future f = someFuture().then(aStdFunction, A&&>());} + {Future f = someFuture().then([&](A&&){return someFuture();});} + {Future f = someFuture().then(&aFunction&&>);} + {Future f = someFuture().then(&SomeClass::aStaticMethod&&>);} + {Future f = someFuture().then(&anObject, &SomeClass::aMethod&&>);} + {Future f = someFuture().then(aStdFunction&&>());} + {Future f = someFuture().then([&](Try&&){return B();});} + {Future f = someFuture().then(&aFunction);} + {Future f = someFuture().then(&SomeClass::aStaticMethod);} + {Future f = someFuture().then(&anObject, &SomeClass::aMethod);} + {Future f = someFuture().then(aStdFunction());} + {Future f = someFuture().then([&](A&&){return B();});} +} + +#endif diff --git a/folly/wangle/futures/test/Thens.h b/folly/wangle/futures/test/Thens.h new file mode 100644 index 00000000..d0398487 --- /dev/null +++ b/folly/wangle/futures/test/Thens.h @@ -0,0 +1,107 @@ +/* + * Copyright 2014 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 +#include +#include +#include +#include + +using namespace folly::wangle; +using namespace std; +using namespace testing; + +typedef unique_ptr A; +struct B {}; + +template +using EnableIfFuture = typename std::enable_if::value>::type; + +template +using EnableUnlessFuture = typename std::enable_if::value>::type; + +template +Future someFuture() { + return makeFuture(T()); +} + +template +Ret aFunction(Params...); + +template +typename std::enable_if::value, Ret>::type +aFunction(Params...) { + typedef typename Ret::value_type T; + return makeFuture(T()); +} + +template +typename std::enable_if::value, Ret>::type +aFunction(Params...) { + return Ret(); +} + +template +std::function +aStdFunction( + typename std::enable_if::value, bool>::type = false) { + return [](Params...) -> Ret { return Ret(); }; +} + +template +std::function +aStdFunction(typename std::enable_if::value, bool>::type = true) { + typedef typename Ret::value_type T; + return [](Params...) -> Future { return makeFuture(T()); }; +} + +class SomeClass { + B b; +public: + template + static Ret aStaticMethod(Params...); + + template + static + typename std::enable_if::value, Ret>::type + aStaticMethod(Params...) { + return Ret(); + } + + template + static + typename std::enable_if::value, Ret>::type + aStaticMethod(Params...) { + typedef typename Ret::value_type T; + return makeFuture(T()); + } + + template + Ret aMethod(Params...); + + template + typename std::enable_if::value, Ret>::type + aMethod(Params...) { + return Ret(); + } + + template + typename std::enable_if::value, Ret>::type + aMethod(Params...) { + typedef typename Ret::value_type T; + return makeFuture(T()); + } +}; diff --git a/folly/wangle/futures/test/Try.cpp b/folly/wangle/futures/test/Try.cpp new file mode 100644 index 00000000..2c8a6068 --- /dev/null +++ b/folly/wangle/futures/test/Try.cpp @@ -0,0 +1,61 @@ +/* + * Copyright 2014 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 + +#include +#include + +using namespace folly::wangle; + +TEST(Try, makeTryFunction) { + auto func = []() { + return folly::make_unique(1); + }; + + auto result = makeTryFunction(func); + EXPECT_TRUE(result.hasValue()); + EXPECT_EQ(*result.value(), 1); +} + +TEST(Try, makeTryFunctionThrow) { + auto func = []() { + throw std::runtime_error("Runtime"); + return folly::make_unique(1); + }; + + auto result = makeTryFunction(func); + EXPECT_TRUE(result.hasException()); +} + +TEST(Try, makeTryFunctionVoid) { + auto func = []() { + return; + }; + + auto result = makeTryFunction(func); + EXPECT_TRUE(result.hasValue()); +} + +TEST(Try, makeTryFunctionVoidThrow) { + auto func = []() { + throw std::runtime_error("Runtime"); + return; + }; + + auto result = makeTryFunction(func); + EXPECT_TRUE(result.hasException()); +} diff --git a/folly/wangle/futures/test/ViaTest.cpp b/folly/wangle/futures/test/ViaTest.cpp new file mode 100644 index 00000000..80765ff0 --- /dev/null +++ b/folly/wangle/futures/test/ViaTest.cpp @@ -0,0 +1,186 @@ +/* + * Copyright 2014 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 +#include + +#include +#include +#include + +using namespace folly::wangle; + +struct ManualWaiter { + explicit ManualWaiter(std::shared_ptr ex) : ex(ex) {} + + void makeProgress() { + ex->wait(); + ex->run(); + } + + std::shared_ptr ex; +}; + +struct ViaFixture : public testing::Test { + ViaFixture() : + westExecutor(new ManualExecutor), + eastExecutor(new ManualExecutor), + waiter(new ManualWaiter(westExecutor)), + done(false) + { + t = std::thread([=] { + ManualWaiter eastWaiter(eastExecutor); + while (!done) + eastWaiter.makeProgress(); + }); + } + + ~ViaFixture() { + done = true; + eastExecutor->add([=]() { }); + t.join(); + } + + void addAsync(int a, int b, std::function&& cob) { + eastExecutor->add([=]() { + cob(a + b); + }); + } + + std::shared_ptr westExecutor; + std::shared_ptr eastExecutor; + std::shared_ptr waiter; + InlineExecutor inlineExecutor; + bool done; + std::thread t; +}; + +TEST(Via, exception_on_launch) { + auto future = makeFuture(std::runtime_error("E")); + EXPECT_THROW(future.value(), std::runtime_error); +} + +TEST(Via, then_value) { + auto future = makeFuture(std::move(1)) + .then([](Try&& t) { + return t.value() == 1; + }) + ; + + EXPECT_TRUE(future.value()); +} + +TEST(Via, then_future) { + auto future = makeFuture(1) + .then([](Try&& t) { + return makeFuture(t.value() == 1); + }) + ; + EXPECT_TRUE(future.value()); +} + +static Future doWorkStatic(Try&& t) { + return makeFuture(t.value() + ";static"); +} + +TEST(Via, then_function) { + struct Worker { + Future doWork(Try&& t) { + return makeFuture(t.value() + ";class"); + } + static Future doWorkStatic(Try&& t) { + return makeFuture(t.value() + ";class-static"); + } + } w; + + auto f = makeFuture(std::string("start")) + .then(doWorkStatic) + .then(Worker::doWorkStatic) + .then(&w, &Worker::doWork) + ; + + EXPECT_EQ(f.value(), "start;static;class-static;class"); +} + +TEST_F(ViaFixture, deactivateChain) { + bool flag = false; + auto f = makeFuture().deactivate(); + EXPECT_FALSE(f.isActive()); + auto f2 = f.then([&](Try){ flag = true; }); + EXPECT_FALSE(flag); +} + +TEST_F(ViaFixture, deactivateActivateChain) { + bool flag = false; + // you can do this all day long with temporaries. + auto f1 = makeFuture().deactivate().activate().deactivate(); + // Chaining on activate/deactivate requires an rvalue, so you have to move + // one of these two ways (if you're not using a temporary). + auto f2 = std::move(f1).activate(); + f2.deactivate(); + auto f3 = std::move(f2.activate()); + f3.then([&](Try){ flag = true; }); + EXPECT_TRUE(flag); +} + +TEST_F(ViaFixture, thread_hops) { + auto westThreadId = std::this_thread::get_id(); + auto f = via(eastExecutor.get()).then([=](Try&& t) { + EXPECT_NE(std::this_thread::get_id(), westThreadId); + return makeFuture(1); + }).via(westExecutor.get() + ).then([=](Try&& t) { + EXPECT_EQ(std::this_thread::get_id(), westThreadId); + return t.value(); + }); + while (!f.isReady()) { + waiter->makeProgress(); + } + EXPECT_EQ(f.value(), 1); +} + +TEST_F(ViaFixture, chain_vias) { + auto westThreadId = std::this_thread::get_id(); + auto f = via(eastExecutor.get()).then([=](Try&& t) { + EXPECT_NE(std::this_thread::get_id(), westThreadId); + return makeFuture(1); + }).then([=](Try&& t) { + int val = t.value(); + return makeFuture(std::move(val)).via(westExecutor.get()) + .then([=](Try&& t) mutable { + EXPECT_EQ(std::this_thread::get_id(), westThreadId); + return t.value(); + }); + }).then([=](Try&& t) { + EXPECT_EQ(std::this_thread::get_id(), westThreadId); + return t.value(); + }); + + while (!f.isReady()) { + waiter->makeProgress(); + } + EXPECT_EQ(f.value(), 1); +} + +TEST_F(ViaFixture, bareViaAssignment) { + auto f = via(eastExecutor.get()); +} +TEST_F(ViaFixture, viaAssignment) { + // via()&& + auto f = makeFuture().via(eastExecutor.get()); + // via()& + auto f2 = f.via(eastExecutor.get()); +} diff --git a/folly/wangle/futures/test/main.cpp b/folly/wangle/futures/test/main.cpp new file mode 100644 index 00000000..7dbf27d4 --- /dev/null +++ b/folly/wangle/futures/test/main.cpp @@ -0,0 +1,22 @@ +/* + * Copyright 2014 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 + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/folly/wangle/futures/test/thens.rb b/folly/wangle/futures/test/thens.rb new file mode 100755 index 00000000..4aaa8463 --- /dev/null +++ b/folly/wangle/futures/test/thens.rb @@ -0,0 +1,79 @@ +#!/usr/bin/env ruby + +# ruby thens.rb > Thens.cpp + +# An exercise in combinatorics. +# (ordinary/static function, member function, std::function, lambda) +# X +# returns (Future, R) +# X +# accepts (Try&&, Try const&, Try, T&&, T const&, T, nothing) + +def test(*args) + args = args.join(", ") + [ + "{Future f = someFuture().then(#{args});}", + #"{Future f = makeFuture(A()).then(#{args}, anExecutor);}", + ] +end + +def retval(ret) + { + "Future" => "someFuture()", + "Try" => "Try(B())", + "B" => "B()" + }[ret] +end + +return_types = [ + "Future", + "B", + #"Try", +] +param_types = [ + "Try&&", + #"Try const&", + #"Try", + #"Try&", + "A&&", + #"A const&", + #"A", + #"A&", + #"", + ] + +tests = ( + return_types.map { |ret| + param_types.map { |param| + both = "#{ret}, #{param}" + [ + ["&aFunction<#{both}>"], + ["&SomeClass::aStaticMethod<#{both}>"], + # TODO switch these around (std::bind-style) + ["&anObject", "&SomeClass::aMethod<#{both}>"], + ["aStdFunction<#{both}>()"], + ["[&](#{param}){return #{retval(ret)};}"], + ] + } + }.flatten(2) + [ + #[""], + ] +).map {|a| test(a)}.flatten + +print < + +TEST(Future, thenVariants) { + SomeClass anObject; + folly::Executor* anExecutor; + + #{tests.join("\n ")} +} + +#endif +EOF diff --git a/folly/wangle/test/Benchmark.cpp b/folly/wangle/test/Benchmark.cpp deleted file mode 100644 index 95fcef9f..00000000 --- a/folly/wangle/test/Benchmark.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2014 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 -#include -#include -#include -#include -#include -#include - -using namespace folly::wangle; -using namespace std; - -namespace { - -template -T incr(Try&& t) { - return t.value() + 1; -} - -void someThens(size_t n) { - auto f = makeFuture(42); - for (size_t i = 0; i < n; i++) { - f = f.then(incr); - } -} - -} // anonymous namespace - -BENCHMARK(constantFuture) { - makeFuture(42); -} - -// This shouldn't get too far below 100% -BENCHMARK_RELATIVE(promiseAndFuture) { - Promise p; - Future f = p.getFuture(); - p.setValue(42); - f.value(); -} - -// The higher the better. At the time of writing, it's only about 40% :( -BENCHMARK_RELATIVE(withThen) { - Promise p; - Future f = p.getFuture().then(incr); - p.setValue(42); - f.value(); -} - -// thens -BENCHMARK_DRAW_LINE() - -BENCHMARK(oneThen) { - someThens(1); -} - -// look for >= 50% relative -BENCHMARK_RELATIVE(twoThens) { - someThens(2); -} - -// look for >= 25% relative -BENCHMARK_RELATIVE(fourThens) { - someThens(4); -} - -// look for >= 1% relative -BENCHMARK_RELATIVE(hundredThens) { - someThens(100); -} - -// Lock contention. Although in practice fulfil()s tend to be temporally -// separate from then()s, still sometimes they will be concurrent. So the -// higher this number is, the better. -BENCHMARK_DRAW_LINE() - -BENCHMARK(no_contention) { - vector> promises(10000); - vector> futures; - std::thread producer, consumer; - - BENCHMARK_SUSPEND { - folly::Baton<> b1, b2; - for (auto& p : promises) - futures.push_back(p.getFuture()); - - consumer = std::thread([&]{ - b1.post(); - for (auto& f : futures) f.then(incr); - }); - consumer.join(); - - producer = std::thread([&]{ - b2.post(); - for (auto& p : promises) p.setValue(42); - }); - - b1.wait(); - b2.wait(); - } - - // The only thing we are measuring is how long fulfil + callbacks take - producer.join(); -} - -BENCHMARK_RELATIVE(contention) { - vector> promises(10000); - vector> futures; - std::thread producer, consumer; - sem_t sem; - sem_init(&sem, 0, 0); - - BENCHMARK_SUSPEND { - folly::Baton<> b1, b2; - for (auto& p : promises) - futures.push_back(p.getFuture()); - - consumer = std::thread([&]{ - b1.post(); - for (auto& f : futures) { - sem_wait(&sem); - f.then(incr); - } - }); - - producer = std::thread([&]{ - b2.post(); - for (auto& p : promises) { - sem_post(&sem); - p.setValue(42); - } - }); - - b1.wait(); - b2.wait(); - } - - // The astute reader will notice that we're not *precisely* comparing apples - // to apples here. Well, maybe it's like comparing Granny Smith to - // Braeburn or something. In the serial version, we waited for the futures - // to be all set up, but here we are probably still doing that work - // (although in parallel). But even though there is more work (on the order - // of 2x), it is being done by two threads. Hopefully most of the difference - // we see is due to lock contention and not false parallelism. - // - // Be warned that if the box is under heavy load, this will greatly skew - // these results (scheduling overhead will begin to dwarf lock contention). - // I'm not sure but I'd guess in Windtunnel this will mean large variance, - // because I expect they load the boxes as much as they can? - consumer.join(); - producer.join(); -} - -int main(int argc, char** argv) { - gflags::ParseCommandLineFlags(&argc, &argv, true); - folly::runBenchmarks(); - return 0; -} diff --git a/folly/wangle/test/ClientCompile.cpp b/folly/wangle/test/ClientCompile.cpp deleted file mode 100644 index 45ad8cd4..00000000 --- a/folly/wangle/test/ClientCompile.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2014 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. - */ - -// amazing what things can go wrong if you include things in an unexpected -// order. -#include -#include -#include -int main() { return 0; } diff --git a/folly/wangle/test/ExecutorTest.cpp b/folly/wangle/test/ExecutorTest.cpp deleted file mode 100644 index b4ec7684..00000000 --- a/folly/wangle/test/ExecutorTest.cpp +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2014 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 -#include -#include -#include -#include -#include - -using namespace folly::wangle; -using namespace std::chrono; -using namespace testing; - -TEST(ManualExecutor, runIsStable) { - ManualExecutor x; - size_t count = 0; - auto f1 = [&]() { count++; }; - auto f2 = [&]() { x.add(f1); x.add(f1); }; - x.add(f2); - x.run(); -} - -TEST(ManualExecutor, scheduleDur) { - ManualExecutor x; - size_t count = 0; - milliseconds dur {10}; - x.schedule([&]{ count++; }, dur); - EXPECT_EQ(count, 0); - x.run(); - EXPECT_EQ(count, 0); - x.advance(dur/2); - EXPECT_EQ(count, 0); - x.advance(dur/2); - EXPECT_EQ(count, 1); -} - -TEST(ManualExecutor, clockStartsAt0) { - ManualExecutor x; - EXPECT_EQ(x.now(), x.now().min()); -} - -TEST(ManualExecutor, scheduleAbs) { - ManualExecutor x; - size_t count = 0; - x.scheduleAt([&]{ count++; }, x.now() + milliseconds(10)); - EXPECT_EQ(count, 0); - x.advance(milliseconds(10)); - EXPECT_EQ(count, 1); -} - -TEST(ManualExecutor, advanceTo) { - ManualExecutor x; - size_t count = 0; - x.scheduleAt([&]{ count++; }, steady_clock::now()); - EXPECT_EQ(count, 0); - x.advanceTo(steady_clock::now()); - EXPECT_EQ(count, 1); -} - -TEST(ManualExecutor, advanceBack) { - ManualExecutor x; - size_t count = 0; - x.advance(microseconds(5)); - x.schedule([&]{ count++; }, microseconds(6)); - EXPECT_EQ(count, 0); - x.advanceTo(x.now() - microseconds(1)); - EXPECT_EQ(count, 0); -} - -TEST(ManualExecutor, advanceNeg) { - ManualExecutor x; - size_t count = 0; - x.advance(microseconds(5)); - x.schedule([&]{ count++; }, microseconds(6)); - EXPECT_EQ(count, 0); - x.advance(microseconds(-1)); - EXPECT_EQ(count, 0); -} - -TEST(ManualExecutor, waitForDoesNotDeadlock) { - ManualExecutor east, west; - folly::Baton<> baton; - auto f = makeFuture() - .via(&east) - .then([](Try){ return makeFuture(); }) - .via(&west); - std::thread t([&]{ - baton.post(); - west.waitFor(f); - }); - baton.wait(); - east.run(); - t.join(); -} - -TEST(Executor, InlineExecutor) { - InlineExecutor x; - size_t counter = 0; - x.add([&]{ - x.add([&]{ - EXPECT_EQ(counter++, 0); - }); - EXPECT_EQ(counter++, 1); - }); - EXPECT_EQ(counter, 2); -} - -TEST(Executor, QueuedImmediateExecutor) { - QueuedImmediateExecutor x; - size_t counter = 0; - x.add([&]{ - x.add([&]{ - EXPECT_EQ(1, counter++); - }); - EXPECT_EQ(0, counter++); - }); - EXPECT_EQ(2, counter); -} - -TEST(Executor, Runnable) { - InlineExecutor x; - size_t counter = 0; - struct Runnable { - std::function fn; - void operator()() { fn(); } - }; - Runnable f; - f.fn = [&]{ counter++; }; - x.add(f); - EXPECT_EQ(counter, 1); -} - -TEST(Executor, RunnablePtr) { - InlineExecutor x; - struct Runnable { - std::function fn; - void operator()() { fn(); } - }; - size_t counter = 0; - auto fnp = std::make_shared(); - fnp->fn = [&]{ counter++; }; - x.addPtr(fnp); - EXPECT_EQ(counter, 1); -} diff --git a/folly/wangle/test/FSM.cpp b/folly/wangle/test/FSM.cpp deleted file mode 100644 index 80ee5364..00000000 --- a/folly/wangle/test/FSM.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2014 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 -#include - -using namespace folly::wangle::detail; - -enum class State { A, B }; - -TEST(FSM, example) { - FSM fsm(State::A); - int count = 0; - int unprotectedCount = 0; - - // somebody set up us the switch - auto tryTransition = [&]{ - switch (fsm.getState()) { - case State::A: - return fsm.updateState(State::A, State::B, [&]{ count++; }); - case State::B: - return fsm.updateState(State::B, State::A, - [&]{ count--; }, [&]{ unprotectedCount--; }); - } - return false; // unreachable - }; - - // keep retrying until success (like a cas) - while (!tryTransition()) ; - EXPECT_EQ(State::B, fsm.getState()); - EXPECT_EQ(1, count); - EXPECT_EQ(0, unprotectedCount); - - while (!tryTransition()) ; - EXPECT_EQ(State::A, fsm.getState()); - EXPECT_EQ(0, count); - EXPECT_EQ(-1, unprotectedCount); -} - -TEST(FSM, magicMacrosExample) { - struct MyFSM : public FSM { - int count = 0; - int unprotectedCount = 0; - MyFSM() : FSM(State::A) {} - void twiddle() { - FSM_START - FSM_CASE(State::A, State::B, [&]{ count++; }); - FSM_CASE2(State::B, State::A, - [&]{ count--; }, [&]{ unprotectedCount--; }); - FSM_END - } - }; - - MyFSM fsm; - - fsm.twiddle(); - EXPECT_EQ(State::B, fsm.getState()); - EXPECT_EQ(1, fsm.count); - EXPECT_EQ(0, fsm.unprotectedCount); - - fsm.twiddle(); - EXPECT_EQ(State::A, fsm.getState()); - EXPECT_EQ(0, fsm.count); - EXPECT_EQ(-1, fsm.unprotectedCount); -} - - -TEST(FSM, ctor) { - FSM fsm(State::A); - EXPECT_EQ(State::A, fsm.getState()); -} - -TEST(FSM, update) { - FSM fsm(State::A); - EXPECT_TRUE(fsm.updateState(State::A, State::B, []{})); - EXPECT_EQ(State::B, fsm.getState()); -} - -TEST(FSM, badUpdate) { - FSM fsm(State::A); - EXPECT_FALSE(fsm.updateState(State::B, State::A, []{})); -} - -TEST(FSM, actionOnUpdate) { - FSM fsm(State::A); - int count = 0; - fsm.updateState(State::A, State::B, [&]{ count++; }); - EXPECT_EQ(1, count); -} - -TEST(FSM, noActionOnBadUpdate) { - FSM fsm(State::A); - int count = 0; - fsm.updateState(State::B, State::A, [&]{ count++; }); - EXPECT_EQ(0, count); -} - -TEST(FSM, stateTransitionAfterAction) { - FSM fsm(State::A); - fsm.updateState(State::A, State::B, - [&]{ EXPECT_EQ(State::A, fsm.getState()); }); -} diff --git a/folly/wangle/test/FutureTest.cpp b/folly/wangle/test/FutureTest.cpp deleted file mode 100644 index d6cfbfbc..00000000 --- a/folly/wangle/test/FutureTest.cpp +++ /dev/null @@ -1,1014 +0,0 @@ -/* - * Copyright 2014 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace folly; -using namespace folly::wangle; -using std::pair; -using std::string; -using std::unique_ptr; -using std::vector; - -#define EXPECT_TYPE(x, T) \ - EXPECT_TRUE((std::is_same::value)) - -/// Simple executor that does work in another thread -class ThreadExecutor : public Executor { - folly::MPMCQueue funcs; - std::atomic done {false}; - std::thread worker; - folly::Baton<> baton; - - void work() { - baton.post(); - Func fn; - while (!done) { - while (!funcs.isEmpty()) { - funcs.blockingRead(fn); - fn(); - } - } - } - - public: - ThreadExecutor(size_t n = 1024) - : funcs(n), worker(std::bind(&ThreadExecutor::work, this)) {} - - ~ThreadExecutor() { - done = true; - funcs.write([]{}); - worker.join(); - } - - void add(Func fn) override { - funcs.blockingWrite(std::move(fn)); - } - - void waitForStartup() { - baton.wait(); - } -}; - -typedef WangleException eggs_t; -static eggs_t eggs("eggs"); - -// Future - -TEST(Future, try) { - class A { - public: - A(int x) : x_(x) {} - - int x() const { - return x_; - } - private: - int x_; - }; - - A a(5); - Try t_a(std::move(a)); - - Try t_void; - - EXPECT_EQ(5, t_a.value().x()); -} - -TEST(Future, special) { - EXPECT_FALSE(std::is_copy_constructible>::value); - EXPECT_FALSE(std::is_copy_assignable>::value); - EXPECT_TRUE(std::is_move_constructible>::value); - EXPECT_TRUE(std::is_move_assignable>::value); -} - -TEST(Future, thenTry) { - bool flag = false; - - makeFuture(42).then([&](Try&& t) { - flag = true; - EXPECT_EQ(42, t.value()); - }); - EXPECT_TRUE(flag); flag = false; - - makeFuture(42) - .then([](Try&& t) { return t.value(); }) - .then([&](Try&& t) { flag = true; EXPECT_EQ(42, t.value()); }); - EXPECT_TRUE(flag); flag = false; - - makeFuture().then([&](Try&& t) { flag = true; t.value(); }); - EXPECT_TRUE(flag); flag = false; - - Promise p; - auto f = p.getFuture().then([&](Try&& t) { flag = true; }); - EXPECT_FALSE(flag); - EXPECT_FALSE(f.isReady()); - p.setValue(); - EXPECT_TRUE(flag); - EXPECT_TRUE(f.isReady()); -} - -TEST(Future, thenValue) { - bool flag = false; - makeFuture(42).then([&](int i){ - EXPECT_EQ(42, i); - flag = true; - }); - EXPECT_TRUE(flag); flag = false; - - makeFuture(42) - .then([](int i){ return i; }) - .then([&](int i) { flag = true; EXPECT_EQ(42, i); }); - EXPECT_TRUE(flag); flag = false; - - makeFuture().then([&]{ - flag = true; - }); - EXPECT_TRUE(flag); flag = false; - - auto f = makeFuture(eggs).then([&](int i){}); - EXPECT_THROW(f.value(), eggs_t); - - f = makeFuture(eggs).then([&]{}); - EXPECT_THROW(f.value(), eggs_t); -} - -TEST(Future, thenValueFuture) { - bool flag = false; - makeFuture(42) - .then([](int i){ return makeFuture(std::move(i)); }) - .then([&](Try&& t) { flag = true; EXPECT_EQ(42, t.value()); }); - EXPECT_TRUE(flag); flag = false; - - makeFuture() - .then([]{ return makeFuture(); }) - .then([&](Try&& t) { flag = true; }); - EXPECT_TRUE(flag); flag = false; -} - -static string doWorkStatic(Try&& t) { - return t.value() + ";static"; -} - -TEST(Future, thenFunction) { - struct Worker { - string doWork(Try&& t) { - return t.value() + ";class"; - } - static string doWorkStatic(Try&& t) { - return t.value() + ";class-static"; - } - } w; - - auto f = makeFuture("start") - .then(doWorkStatic) - .then(Worker::doWorkStatic) - .then(&w, &Worker::doWork); - - EXPECT_EQ(f.value(), "start;static;class-static;class"); -} - -static Future doWorkStaticFuture(Try&& t) { - return makeFuture(t.value() + ";static"); -} - -TEST(Future, thenFunctionFuture) { - struct Worker { - Future doWorkFuture(Try&& t) { - return makeFuture(t.value() + ";class"); - } - static Future doWorkStaticFuture(Try&& t) { - return makeFuture(t.value() + ";class-static"); - } - } w; - - auto f = makeFuture("start") - .then(doWorkStaticFuture) - .then(Worker::doWorkStaticFuture) - .then(&w, &Worker::doWorkFuture); - - EXPECT_EQ(f.value(), "start;static;class-static;class"); -} - -TEST(Future, value) { - auto f = makeFuture(unique_ptr(new int(42))); - auto up = std::move(f.value()); - EXPECT_EQ(42, *up); - - EXPECT_THROW(makeFuture(eggs).value(), eggs_t); -} - -TEST(Future, isReady) { - Promise p; - auto f = p.getFuture(); - EXPECT_FALSE(f.isReady()); - p.setValue(42); - EXPECT_TRUE(f.isReady()); - } - -TEST(Future, futureNotReady) { - Promise p; - Future f = p.getFuture(); - EXPECT_THROW(f.value(), eggs_t); -} - -TEST(Future, hasException) { - EXPECT_TRUE(makeFuture(eggs).getTry().hasException()); - EXPECT_FALSE(makeFuture(42).getTry().hasException()); -} - -TEST(Future, hasValue) { - EXPECT_TRUE(makeFuture(42).getTry().hasValue()); - EXPECT_FALSE(makeFuture(eggs).getTry().hasValue()); -} - -TEST(Future, makeFuture) { - EXPECT_TYPE(makeFuture(42), Future); - EXPECT_EQ(42, makeFuture(42).value()); - - EXPECT_TYPE(makeFuture(42), Future); - EXPECT_EQ(42, makeFuture(42).value()); - - auto fun = [] { return 42; }; - EXPECT_TYPE(makeFutureTry(fun), Future); - EXPECT_EQ(42, makeFutureTry(fun).value()); - - auto failfun = []() -> int { throw eggs; }; - EXPECT_TYPE(makeFutureTry(failfun), Future); - EXPECT_THROW(makeFutureTry(failfun).value(), eggs_t); - - EXPECT_TYPE(makeFuture(), Future); -} - -// Promise - -TEST(Promise, special) { - EXPECT_FALSE(std::is_copy_constructible>::value); - EXPECT_FALSE(std::is_copy_assignable>::value); - EXPECT_TRUE(std::is_move_constructible>::value); - EXPECT_TRUE(std::is_move_assignable>::value); -} - -TEST(Promise, getFuture) { - Promise p; - Future f = p.getFuture(); - EXPECT_FALSE(f.isReady()); -} - -TEST(Promise, setValue) { - Promise fund; - auto ffund = fund.getFuture(); - fund.setValue(42); - EXPECT_EQ(42, ffund.value()); - - struct Foo { - string name; - int value; - }; - - Promise pod; - auto fpod = pod.getFuture(); - Foo f = {"the answer", 42}; - pod.setValue(f); - Foo f2 = fpod.value(); - EXPECT_EQ(f.name, f2.name); - EXPECT_EQ(f.value, f2.value); - - pod = Promise(); - fpod = pod.getFuture(); - pod.setValue(std::move(f2)); - Foo f3 = fpod.value(); - EXPECT_EQ(f.name, f3.name); - EXPECT_EQ(f.value, f3.value); - - Promise> mov; - auto fmov = mov.getFuture(); - mov.setValue(unique_ptr(new int(42))); - unique_ptr ptr = std::move(fmov.value()); - EXPECT_EQ(42, *ptr); - - Promise v; - auto fv = v.getFuture(); - v.setValue(); - EXPECT_TRUE(fv.isReady()); -} - -TEST(Promise, setException) { - { - Promise p; - auto f = p.getFuture(); - p.setException(eggs); - EXPECT_THROW(f.value(), eggs_t); - } - { - Promise p; - auto f = p.getFuture(); - try { - throw eggs; - } catch (...) { - p.setException(std::current_exception()); - } - EXPECT_THROW(f.value(), eggs_t); - } -} - -TEST(Promise, fulfil) { - { - Promise p; - auto f = p.getFuture(); - p.fulfil([] { return 42; }); - EXPECT_EQ(42, f.value()); - } - { - Promise p; - auto f = p.getFuture(); - p.fulfil([]() -> int { throw eggs; }); - EXPECT_THROW(f.value(), eggs_t); - } -} - -TEST(Future, finish) { - auto x = std::make_shared(0); - { - Promise p; - auto f = p.getFuture().then([x](Try&& t) { *x = t.value(); }); - - // The callback hasn't executed - EXPECT_EQ(0, *x); - - // The callback has a reference to x - EXPECT_EQ(2, x.use_count()); - - p.setValue(42); - - // the callback has executed - EXPECT_EQ(42, *x); - } - // the callback has been destructed - // and has released its reference to x - EXPECT_EQ(1, x.use_count()); -} - -TEST(Future, unwrap) { - Promise a; - Promise b; - - auto fa = a.getFuture(); - auto fb = b.getFuture(); - - bool flag1 = false; - bool flag2 = false; - - // do a, then do b, and get the result of a + b. - Future f = fa.then([&](Try&& ta) { - auto va = ta.value(); - flag1 = true; - return fb.then([va, &flag2](Try&& tb) { - flag2 = true; - return va + tb.value(); - }); - }); - - EXPECT_FALSE(flag1); - EXPECT_FALSE(flag2); - EXPECT_FALSE(f.isReady()); - - a.setValue(3); - EXPECT_TRUE(flag1); - EXPECT_FALSE(flag2); - EXPECT_FALSE(f.isReady()); - - b.setValue(4); - EXPECT_TRUE(flag1); - EXPECT_TRUE(flag2); - EXPECT_EQ(7, f.value()); -} - -TEST(Future, whenAll) { - // returns a vector variant - { - vector> promises(10); - vector> futures; - - for (auto& p : promises) - futures.push_back(p.getFuture()); - - auto allf = whenAll(futures.begin(), futures.end()); - - random_shuffle(promises.begin(), promises.end()); - for (auto& p : promises) { - EXPECT_FALSE(allf.isReady()); - p.setValue(42); - } - - EXPECT_TRUE(allf.isReady()); - auto& results = allf.value(); - for (auto& t : results) { - EXPECT_EQ(42, t.value()); - } - } - - // check error semantics - { - vector> promises(4); - vector> futures; - - for (auto& p : promises) - futures.push_back(p.getFuture()); - - auto allf = whenAll(futures.begin(), futures.end()); - - - promises[0].setValue(42); - promises[1].setException(eggs); - - EXPECT_FALSE(allf.isReady()); - - promises[2].setValue(42); - - EXPECT_FALSE(allf.isReady()); - - promises[3].setException(eggs); - - EXPECT_TRUE(allf.isReady()); - EXPECT_FALSE(allf.getTry().hasException()); - - auto& results = allf.value(); - EXPECT_EQ(42, results[0].value()); - EXPECT_TRUE(results[1].hasException()); - EXPECT_EQ(42, results[2].value()); - EXPECT_TRUE(results[3].hasException()); - } - - // check that futures are ready in then() - { - vector> promises(10); - vector> futures; - - for (auto& p : promises) - futures.push_back(p.getFuture()); - - auto allf = whenAll(futures.begin(), futures.end()) - .then([](Try>>&& ts) { - for (auto& f : ts.value()) - f.value(); - }); - - random_shuffle(promises.begin(), promises.end()); - for (auto& p : promises) - p.setValue(); - EXPECT_TRUE(allf.isReady()); - } -} - - -TEST(Future, whenAny) { - { - vector> promises(10); - vector> futures; - - for (auto& p : promises) - futures.push_back(p.getFuture()); - - for (auto& f : futures) { - EXPECT_FALSE(f.isReady()); - } - - auto anyf = whenAny(futures.begin(), futures.end()); - - /* futures were moved in, so these are invalid now */ - EXPECT_FALSE(anyf.isReady()); - - promises[7].setValue(42); - EXPECT_TRUE(anyf.isReady()); - auto& idx_fut = anyf.value(); - - auto i = idx_fut.first; - EXPECT_EQ(7, i); - - auto& f = idx_fut.second; - EXPECT_EQ(42, f.value()); - } - - // error - { - vector> promises(10); - vector> futures; - - for (auto& p : promises) - futures.push_back(p.getFuture()); - - for (auto& f : futures) { - EXPECT_FALSE(f.isReady()); - } - - auto anyf = whenAny(futures.begin(), futures.end()); - - EXPECT_FALSE(anyf.isReady()); - - promises[3].setException(eggs); - EXPECT_TRUE(anyf.isReady()); - EXPECT_TRUE(anyf.value().second.hasException()); - } - - // then() - { - vector> promises(10); - vector> futures; - - for (auto& p : promises) - futures.push_back(p.getFuture()); - - auto anyf = whenAny(futures.begin(), futures.end()) - .then([](Try>>&& f) { - EXPECT_EQ(42, f.value().second.value()); - }); - - promises[3].setValue(42); - EXPECT_TRUE(anyf.isReady()); - } -} - - -TEST(when, already_completed) { - { - vector> fs; - for (int i = 0; i < 10; i++) - fs.push_back(makeFuture()); - - whenAll(fs.begin(), fs.end()) - .then([&](Try>>&& t) { - EXPECT_EQ(fs.size(), t.value().size()); - }); - } - { - vector> fs; - for (int i = 0; i < 10; i++) - fs.push_back(makeFuture(i)); - - whenAny(fs.begin(), fs.end()) - .then([&](Try>>&& t) { - auto& p = t.value(); - EXPECT_EQ(p.first, p.second.value()); - }); - } -} - -TEST(when, whenN) { - vector> promises(10); - vector> futures; - - for (auto& p : promises) - futures.push_back(p.getFuture()); - - bool flag = false; - size_t n = 3; - whenN(futures.begin(), futures.end(), n) - .then([&](Try>>>&& t) { - flag = true; - auto v = t.value(); - EXPECT_EQ(n, v.size()); - for (auto& tt : v) - EXPECT_TRUE(tt.second.hasValue()); - }); - - promises[0].setValue(); - EXPECT_FALSE(flag); - promises[1].setValue(); - EXPECT_FALSE(flag); - promises[2].setValue(); - EXPECT_TRUE(flag); -} - -/* Ensure that we can compile when_{all,any} with folly::small_vector */ -TEST(when, small_vector) { - - static_assert(!FOLLY_IS_TRIVIALLY_COPYABLE(Future), - "Futures should not be trivially copyable"); - static_assert(!FOLLY_IS_TRIVIALLY_COPYABLE(Future), - "Futures should not be trivially copyable"); - - using folly::small_vector; - { - small_vector> futures; - - for (int i = 0; i < 10; i++) - futures.push_back(makeFuture()); - - auto anyf = whenAny(futures.begin(), futures.end()); - } - - { - small_vector> futures; - - for (int i = 0; i < 10; i++) - futures.push_back(makeFuture()); - - auto allf = whenAll(futures.begin(), futures.end()); - } -} - -TEST(Future, whenAllVariadic) { - Promise pb; - Promise pi; - Future fb = pb.getFuture(); - Future fi = pi.getFuture(); - bool flag = false; - whenAll(std::move(fb), std::move(fi)) - .then([&](Try, Try>>&& t) { - flag = true; - EXPECT_TRUE(t.hasValue()); - EXPECT_TRUE(std::get<0>(t.value()).hasValue()); - EXPECT_EQ(std::get<0>(t.value()).value(), true); - EXPECT_TRUE(std::get<1>(t.value()).hasValue()); - EXPECT_EQ(std::get<1>(t.value()).value(), 42); - }); - pb.setValue(true); - EXPECT_FALSE(flag); - pi.setValue(42); - EXPECT_TRUE(flag); -} - -TEST(Future, whenAllVariadicReferences) { - Promise pb; - Promise pi; - Future fb = pb.getFuture(); - Future fi = pi.getFuture(); - bool flag = false; - whenAll(fb, fi) - .then([&](Try, Try>>&& t) { - flag = true; - EXPECT_TRUE(t.hasValue()); - EXPECT_TRUE(std::get<0>(t.value()).hasValue()); - EXPECT_EQ(std::get<0>(t.value()).value(), true); - EXPECT_TRUE(std::get<1>(t.value()).hasValue()); - EXPECT_EQ(std::get<1>(t.value()).value(), 42); - }); - pb.setValue(true); - EXPECT_FALSE(flag); - pi.setValue(42); - EXPECT_TRUE(flag); -} - -TEST(Future, whenAll_none) { - vector> fs; - auto f = whenAll(fs.begin(), fs.end()); - EXPECT_TRUE(f.isReady()); -} - -TEST(Future, throwCaughtInImmediateThen) { - // Neither of these should throw "Promise already satisfied" - makeFuture().then( - [=](Try&&) -> int { throw std::exception(); }); - makeFuture().then( - [=](Try&&) -> Future { throw std::exception(); }); -} - -TEST(Future, throwIfFailed) { - makeFuture(eggs) - .then([=](Try&& t) { - EXPECT_THROW(t.throwIfFailed(), eggs_t); - }); - makeFuture() - .then([=](Try&& t) { - EXPECT_NO_THROW(t.throwIfFailed()); - }); - - makeFuture(eggs) - .then([=](Try&& t) { - EXPECT_THROW(t.throwIfFailed(), eggs_t); - }); - makeFuture(42) - .then([=](Try&& t) { - EXPECT_NO_THROW(t.throwIfFailed()); - }); -} - -TEST(Future, waitWithSemaphoreImmediate) { - waitWithSemaphore(makeFuture()); - auto done = waitWithSemaphore(makeFuture(42)).value(); - EXPECT_EQ(42, done); - - vector v{1,2,3}; - auto done_v = waitWithSemaphore(makeFuture(v)).value(); - EXPECT_EQ(v.size(), done_v.size()); - EXPECT_EQ(v, done_v); - - vector> v_f; - v_f.push_back(makeFuture()); - v_f.push_back(makeFuture()); - auto done_v_f = waitWithSemaphore(whenAll(v_f.begin(), v_f.end())).value(); - EXPECT_EQ(2, done_v_f.size()); - - vector> v_fb; - v_fb.push_back(makeFuture(true)); - v_fb.push_back(makeFuture(false)); - auto fut = whenAll(v_fb.begin(), v_fb.end()); - auto done_v_fb = std::move(waitWithSemaphore(std::move(fut)).value()); - EXPECT_EQ(2, done_v_fb.size()); -} - -TEST(Future, waitWithSemaphore) { - Promise p; - Future f = p.getFuture(); - std::atomic flag{false}; - std::atomic result{1}; - std::atomic id; - - std::thread t([&](Future&& tf){ - auto n = tf.then([&](Try && t) { - id = std::this_thread::get_id(); - return t.value(); - }); - flag = true; - result.store(waitWithSemaphore(std::move(n)).value()); - }, - std::move(f) - ); - while(!flag){} - EXPECT_EQ(result.load(), 1); - p.setValue(42); - t.join(); - // validate that the callback ended up executing in this thread, which - // is more to ensure that this test actually tests what it should - EXPECT_EQ(id, std::this_thread::get_id()); - EXPECT_EQ(result.load(), 42); -} - -TEST(Future, waitWithSemaphoreForTime) { - { - Promise p; - Future f = p.getFuture(); - auto t = waitWithSemaphore(std::move(f), - std::chrono::microseconds(1)); - EXPECT_FALSE(t.isReady()); - p.setValue(1); - EXPECT_TRUE(t.isReady()); - } - { - Promise p; - Future f = p.getFuture(); - p.setValue(1); - auto t = waitWithSemaphore(std::move(f), - std::chrono::milliseconds(1)); - EXPECT_TRUE(t.isReady()); - } - { - vector> v_fb; - v_fb.push_back(makeFuture(true)); - v_fb.push_back(makeFuture(false)); - auto f = whenAll(v_fb.begin(), v_fb.end()); - auto t = waitWithSemaphore(std::move(f), - std::chrono::milliseconds(1)); - EXPECT_TRUE(t.isReady()); - EXPECT_EQ(2, t.value().size()); - } - { - vector> v_fb; - Promise p1; - Promise p2; - v_fb.push_back(p1.getFuture()); - v_fb.push_back(p2.getFuture()); - auto f = whenAll(v_fb.begin(), v_fb.end()); - auto t = waitWithSemaphore(std::move(f), - std::chrono::milliseconds(1)); - EXPECT_FALSE(t.isReady()); - p1.setValue(true); - EXPECT_FALSE(t.isReady()); - p2.setValue(true); - EXPECT_TRUE(t.isReady()); - } - { - auto t = waitWithSemaphore(makeFuture(), - std::chrono::milliseconds(1)); - EXPECT_TRUE(t.isReady()); - } -} - -TEST(Future, callbackAfterActivate) { - Promise p; - auto f = p.getFuture(); - f.deactivate(); - - size_t count = 0; - f.then([&](Try&&) { count++; }); - - p.setValue(); - EXPECT_EQ(0, count); - - f.activate(); - EXPECT_EQ(1, count); -} - -TEST(Future, activateOnDestruct) { - auto f = std::make_shared>(makeFuture()); - f->deactivate(); - - size_t count = 0; - f->then([&](Try&&) { count++; }); - EXPECT_EQ(0, count); - - f.reset(); - EXPECT_EQ(1, count); -} - -TEST(Future, viaActsCold) { - ManualExecutor x; - size_t count = 0; - - auto fv = via(&x); - fv.then([&](Try&&) { count++; }); - - EXPECT_EQ(0, count); - - fv.activate(); - - EXPECT_EQ(1, x.run()); - EXPECT_EQ(1, count); -} - -TEST(Future, viaIsCold) { - ManualExecutor x; - EXPECT_FALSE(via(&x).isActive()); -} - -TEST(Future, viaRaces) { - ManualExecutor x; - Promise p; - auto tid = std::this_thread::get_id(); - bool done = false; - - std::thread t1([&] { - p.getFuture() - .via(&x) - .then([&](Try&&) { EXPECT_EQ(tid, std::this_thread::get_id()); }) - .then([&](Try&&) { EXPECT_EQ(tid, std::this_thread::get_id()); }) - .then([&](Try&&) { done = true; }); - }); - - std::thread t2([&] { - p.setValue(); - }); - - while (!done) x.run(); - t1.join(); - t2.join(); -} - -// TODO(#4920689) -TEST(Future, viaRaces_2stage) { - ManualExecutor x; - Promise p; - auto tid = std::this_thread::get_id(); - bool done = false; - - std::thread t1([&] { - auto f2 = p.getFuture().via(&x); - f2.then([&](Try&&) { EXPECT_EQ(tid, std::this_thread::get_id()); }) - .then([&](Try&&) { EXPECT_EQ(tid, std::this_thread::get_id()); }) - .then([&](Try&&) { done = true; }); - - // the bug was in the promise being fulfilled before f2 is reactivated. we - // could sleep, but yielding should cause this to fail with reasonable - // probability - std::this_thread::yield(); - f2.activate(); - }); - - std::thread t2([&] { - p.setValue(); - }); - - while (!done) x.run(); - t1.join(); - t2.join(); -} - -TEST(Future, getFuture_after_setValue) { - Promise p; - p.setValue(42); - EXPECT_EQ(42, p.getFuture().value()); -} - -TEST(Future, getFuture_after_setException) { - Promise p; - p.fulfil([]() -> void { throw std::logic_error("foo"); }); - EXPECT_THROW(p.getFuture().value(), std::logic_error); -} - -TEST(Future, detachRace) { - // Task #5438209 - // This test is designed to detect a race that was in Core::detachOne() - // where detached_ was incremented and then tested, and that - // allowed a race where both Promise and Future would think they were the - // second and both try to delete. This showed up at scale but was very - // difficult to reliably repro in a test. As it is, this only fails about - // once in every 1,000 executions. Doing this 1,000 times is going to make a - // slow test so I won't do that but if it ever fails, take it seriously, and - // run the test binary with "--gtest_repeat=10000 --gtest_filter=*detachRace" - // (Don't forget to enable ASAN) - auto p = folly::make_unique>(); - auto f = folly::make_unique>(p->getFuture()); - folly::Baton<> baton; - std::thread t1([&]{ - baton.post(); - p.reset(); - }); - baton.wait(); - f.reset(); - t1.join(); -} - -class TestData : public RequestData { - public: - explicit TestData(int data) : data_(data) {} - virtual ~TestData() {} - int data_; -}; - -TEST(Future, context) { - - // Start a new context - RequestContext::create(); - - EXPECT_EQ(nullptr, RequestContext::get()->getContextData("test")); - - // Set some test data - RequestContext::get()->setContextData( - "test", - std::unique_ptr(new TestData(10))); - - // Start a future - Promise p; - auto future = p.getFuture().then([&]{ - // Check that the context followed the future - EXPECT_TRUE(RequestContext::get() != nullptr); - auto a = dynamic_cast( - RequestContext::get()->getContextData("test")); - auto data = a->data_; - EXPECT_EQ(10, data); - }); - - // Clear the context - RequestContext::setContext(nullptr); - - EXPECT_EQ(nullptr, RequestContext::get()->getContextData("test")); - - // Fulfil the promise - p.setValue(); -} - - -// This only fails about 1 in 1k times when the bug is present :( -TEST(Future, t5506504) { - ThreadExecutor x; - - auto fn = [&x]{ - auto promises = std::make_shared>>(4); - vector> futures; - - for (auto& p : *promises) { - futures.emplace_back( - p.getFuture() - .via(&x) - .then([](Try&&){})); - } - - x.waitForStartup(); - x.add([promises]{ - for (auto& p : *promises) p.setValue(); - }); - - return whenAll(futures.begin(), futures.end()); - }; - - waitWithSemaphore(fn()); -} diff --git a/folly/wangle/test/Interrupts.cpp b/folly/wangle/test/Interrupts.cpp deleted file mode 100644 index 41d5bf7d..00000000 --- a/folly/wangle/test/Interrupts.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2014 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 - -#include -#include - -using namespace folly::wangle; - -TEST(Interrupts, raise) { - std::runtime_error eggs("eggs"); - Promise p; - p.setInterruptHandler([&](std::exception_ptr e) { - EXPECT_THROW(std::rethrow_exception(e), decltype(eggs)); - }); - p.getFuture().raise(eggs); -} - -TEST(Interrupts, cancel) { - Promise p; - p.setInterruptHandler([&](std::exception_ptr e) { - EXPECT_THROW(std::rethrow_exception(e), FutureCancellation); - }); - p.getFuture().cancel(); -} - -TEST(Interrupts, handleThenInterrupt) { - Promise p; - bool flag = false; - p.setInterruptHandler([&](std::exception_ptr e) { flag = true; }); - p.getFuture().cancel(); - EXPECT_TRUE(flag); -} - -TEST(Interrupts, interruptThenHandle) { - Promise p; - bool flag = false; - p.getFuture().cancel(); - p.setInterruptHandler([&](std::exception_ptr e) { flag = true; }); - EXPECT_TRUE(flag); -} - -TEST(Interrupts, interruptAfterFulfilNoop) { - Promise p; - bool flag = false; - p.setInterruptHandler([&](std::exception_ptr e) { flag = true; }); - p.setValue(); - p.getFuture().cancel(); - EXPECT_FALSE(flag); -} - -TEST(Interrupts, secondInterruptNoop) { - Promise p; - int count = 0; - p.setInterruptHandler([&](std::exception_ptr e) { count++; }); - auto f = p.getFuture(); - f.cancel(); - f.cancel(); - EXPECT_EQ(1, count); -} diff --git a/folly/wangle/test/Thens.cpp b/folly/wangle/test/Thens.cpp deleted file mode 100644 index e8ac875d..00000000 --- a/folly/wangle/test/Thens.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// This file is @generated by thens.rb. Do not edit directly. - -// TODO: fails to compile with clang:dev. See task #4412111 -#ifndef __clang__ - -#include - -TEST(Future, thenVariants) { - SomeClass anObject; - folly::Executor* anExecutor; - - {Future f = someFuture().then(&aFunction, Try&&>);} - {Future f = someFuture().then(&SomeClass::aStaticMethod, Try&&>);} - {Future f = someFuture().then(&anObject, &SomeClass::aMethod, Try&&>);} - {Future f = someFuture().then(aStdFunction, Try&&>());} - {Future f = someFuture().then([&](Try&&){return someFuture();});} - {Future f = someFuture().then(&aFunction, A&&>);} - {Future f = someFuture().then(&SomeClass::aStaticMethod, A&&>);} - {Future f = someFuture().then(&anObject, &SomeClass::aMethod, A&&>);} - {Future f = someFuture().then(aStdFunction, A&&>());} - {Future f = someFuture().then([&](A&&){return someFuture();});} - {Future f = someFuture().then(&aFunction&&>);} - {Future f = someFuture().then(&SomeClass::aStaticMethod&&>);} - {Future f = someFuture().then(&anObject, &SomeClass::aMethod&&>);} - {Future f = someFuture().then(aStdFunction&&>());} - {Future f = someFuture().then([&](Try&&){return B();});} - {Future f = someFuture().then(&aFunction);} - {Future f = someFuture().then(&SomeClass::aStaticMethod);} - {Future f = someFuture().then(&anObject, &SomeClass::aMethod);} - {Future f = someFuture().then(aStdFunction());} - {Future f = someFuture().then([&](A&&){return B();});} -} - -#endif diff --git a/folly/wangle/test/Thens.h b/folly/wangle/test/Thens.h deleted file mode 100644 index 928f8205..00000000 --- a/folly/wangle/test/Thens.h +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2014 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 -#include -#include -#include -#include - -using namespace folly::wangle; -using namespace std; -using namespace testing; - -typedef unique_ptr A; -struct B {}; - -template -using EnableIfFuture = typename std::enable_if::value>::type; - -template -using EnableUnlessFuture = typename std::enable_if::value>::type; - -template -Future someFuture() { - return makeFuture(T()); -} - -template -Ret aFunction(Params...); - -template -typename std::enable_if::value, Ret>::type -aFunction(Params...) { - typedef typename Ret::value_type T; - return makeFuture(T()); -} - -template -typename std::enable_if::value, Ret>::type -aFunction(Params...) { - return Ret(); -} - -template -std::function -aStdFunction( - typename std::enable_if::value, bool>::type = false) { - return [](Params...) -> Ret { return Ret(); }; -} - -template -std::function -aStdFunction(typename std::enable_if::value, bool>::type = true) { - typedef typename Ret::value_type T; - return [](Params...) -> Future { return makeFuture(T()); }; -} - -class SomeClass { - B b; -public: - template - static Ret aStaticMethod(Params...); - - template - static - typename std::enable_if::value, Ret>::type - aStaticMethod(Params...) { - return Ret(); - } - - template - static - typename std::enable_if::value, Ret>::type - aStaticMethod(Params...) { - typedef typename Ret::value_type T; - return makeFuture(T()); - } - - template - Ret aMethod(Params...); - - template - typename std::enable_if::value, Ret>::type - aMethod(Params...) { - return Ret(); - } - - template - typename std::enable_if::value, Ret>::type - aMethod(Params...) { - typedef typename Ret::value_type T; - return makeFuture(T()); - } -}; diff --git a/folly/wangle/test/Try.cpp b/folly/wangle/test/Try.cpp deleted file mode 100644 index a472bed6..00000000 --- a/folly/wangle/test/Try.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2014 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 - -#include -#include - -using namespace folly::wangle; - -TEST(Try, makeTryFunction) { - auto func = []() { - return folly::make_unique(1); - }; - - auto result = makeTryFunction(func); - EXPECT_TRUE(result.hasValue()); - EXPECT_EQ(*result.value(), 1); -} - -TEST(Try, makeTryFunctionThrow) { - auto func = []() { - throw std::runtime_error("Runtime"); - return folly::make_unique(1); - }; - - auto result = makeTryFunction(func); - EXPECT_TRUE(result.hasException()); -} - -TEST(Try, makeTryFunctionVoid) { - auto func = []() { - return; - }; - - auto result = makeTryFunction(func); - EXPECT_TRUE(result.hasValue()); -} - -TEST(Try, makeTryFunctionVoidThrow) { - auto func = []() { - throw std::runtime_error("Runtime"); - return; - }; - - auto result = makeTryFunction(func); - EXPECT_TRUE(result.hasException()); -} diff --git a/folly/wangle/test/ViaTest.cpp b/folly/wangle/test/ViaTest.cpp deleted file mode 100644 index 3e9953e0..00000000 --- a/folly/wangle/test/ViaTest.cpp +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright 2014 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 -#include - -#include -#include -#include - -using namespace folly::wangle; - -struct ManualWaiter { - explicit ManualWaiter(std::shared_ptr ex) : ex(ex) {} - - void makeProgress() { - ex->wait(); - ex->run(); - } - - std::shared_ptr ex; -}; - -struct ViaFixture : public testing::Test { - ViaFixture() : - westExecutor(new ManualExecutor), - eastExecutor(new ManualExecutor), - waiter(new ManualWaiter(westExecutor)), - done(false) - { - t = std::thread([=] { - ManualWaiter eastWaiter(eastExecutor); - while (!done) - eastWaiter.makeProgress(); - }); - } - - ~ViaFixture() { - done = true; - eastExecutor->add([=]() { }); - t.join(); - } - - void addAsync(int a, int b, std::function&& cob) { - eastExecutor->add([=]() { - cob(a + b); - }); - } - - std::shared_ptr westExecutor; - std::shared_ptr eastExecutor; - std::shared_ptr waiter; - InlineExecutor inlineExecutor; - bool done; - std::thread t; -}; - -TEST(Via, exception_on_launch) { - auto future = makeFuture(std::runtime_error("E")); - EXPECT_THROW(future.value(), std::runtime_error); -} - -TEST(Via, then_value) { - auto future = makeFuture(std::move(1)) - .then([](Try&& t) { - return t.value() == 1; - }) - ; - - EXPECT_TRUE(future.value()); -} - -TEST(Via, then_future) { - auto future = makeFuture(1) - .then([](Try&& t) { - return makeFuture(t.value() == 1); - }) - ; - EXPECT_TRUE(future.value()); -} - -static Future doWorkStatic(Try&& t) { - return makeFuture(t.value() + ";static"); -} - -TEST(Via, then_function) { - struct Worker { - Future doWork(Try&& t) { - return makeFuture(t.value() + ";class"); - } - static Future doWorkStatic(Try&& t) { - return makeFuture(t.value() + ";class-static"); - } - } w; - - auto f = makeFuture(std::string("start")) - .then(doWorkStatic) - .then(Worker::doWorkStatic) - .then(&w, &Worker::doWork) - ; - - EXPECT_EQ(f.value(), "start;static;class-static;class"); -} - -TEST_F(ViaFixture, deactivateChain) { - bool flag = false; - auto f = makeFuture().deactivate(); - EXPECT_FALSE(f.isActive()); - auto f2 = f.then([&](Try){ flag = true; }); - EXPECT_FALSE(flag); -} - -TEST_F(ViaFixture, deactivateActivateChain) { - bool flag = false; - // you can do this all day long with temporaries. - auto f1 = makeFuture().deactivate().activate().deactivate(); - // Chaining on activate/deactivate requires an rvalue, so you have to move - // one of these two ways (if you're not using a temporary). - auto f2 = std::move(f1).activate(); - f2.deactivate(); - auto f3 = std::move(f2.activate()); - f3.then([&](Try){ flag = true; }); - EXPECT_TRUE(flag); -} - -TEST_F(ViaFixture, thread_hops) { - auto westThreadId = std::this_thread::get_id(); - auto f = via(eastExecutor.get()).then([=](Try&& t) { - EXPECT_NE(std::this_thread::get_id(), westThreadId); - return makeFuture(1); - }).via(westExecutor.get() - ).then([=](Try&& t) { - EXPECT_EQ(std::this_thread::get_id(), westThreadId); - return t.value(); - }); - while (!f.isReady()) { - waiter->makeProgress(); - } - EXPECT_EQ(f.value(), 1); -} - -TEST_F(ViaFixture, chain_vias) { - auto westThreadId = std::this_thread::get_id(); - auto f = via(eastExecutor.get()).then([=](Try&& t) { - EXPECT_NE(std::this_thread::get_id(), westThreadId); - return makeFuture(1); - }).then([=](Try&& t) { - int val = t.value(); - return makeFuture(std::move(val)).via(westExecutor.get()) - .then([=](Try&& t) mutable { - EXPECT_EQ(std::this_thread::get_id(), westThreadId); - return t.value(); - }); - }).then([=](Try&& t) { - EXPECT_EQ(std::this_thread::get_id(), westThreadId); - return t.value(); - }); - - while (!f.isReady()) { - waiter->makeProgress(); - } - EXPECT_EQ(f.value(), 1); -} - -TEST_F(ViaFixture, bareViaAssignment) { - auto f = via(eastExecutor.get()); -} -TEST_F(ViaFixture, viaAssignment) { - // via()&& - auto f = makeFuture().via(eastExecutor.get()); - // via()& - auto f2 = f.via(eastExecutor.get()); -} diff --git a/folly/wangle/test/main.cpp b/folly/wangle/test/main.cpp deleted file mode 100644 index 7dbf27d4..00000000 --- a/folly/wangle/test/main.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2014 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 - -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/folly/wangle/test/thens.rb b/folly/wangle/test/thens.rb deleted file mode 100755 index 61ca26de..00000000 --- a/folly/wangle/test/thens.rb +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env ruby - -# ruby thens.rb > Thens.cpp - -# An exercise in combinatorics. -# (ordinary/static function, member function, std::function, lambda) -# X -# returns (Future, R) -# X -# accepts (Try&&, Try const&, Try, T&&, T const&, T, nothing) - -def test(*args) - args = args.join(", ") - [ - "{Future f = someFuture().then(#{args});}", - #"{Future f = makeFuture(A()).then(#{args}, anExecutor);}", - ] -end - -def retval(ret) - { - "Future" => "someFuture()", - "Try" => "Try(B())", - "B" => "B()" - }[ret] -end - -return_types = [ - "Future", - "B", - #"Try", -] -param_types = [ - "Try&&", - #"Try const&", - #"Try", - #"Try&", - "A&&", - #"A const&", - #"A", - #"A&", - #"", - ] - -tests = ( - return_types.map { |ret| - param_types.map { |param| - both = "#{ret}, #{param}" - [ - ["&aFunction<#{both}>"], - ["&SomeClass::aStaticMethod<#{both}>"], - # TODO switch these around (std::bind-style) - ["&anObject", "&SomeClass::aMethod<#{both}>"], - ["aStdFunction<#{both}>()"], - ["[&](#{param}){return #{retval(ret)};}"], - ] - } - }.flatten(2) + [ - #[""], - ] -).map {|a| test(a)}.flatten - -print < - -TEST(Future, thenVariants) { - SomeClass anObject; - folly::Executor* anExecutor; - - #{tests.join("\n ")} -} - -#endif -EOF