Move wangle to folly
authorDave Watson <davejwatson@fb.com>
Fri, 21 Feb 2014 20:59:25 +0000 (12:59 -0800)
committerDave Watson <davejwatson@fb.com>
Fri, 28 Feb 2014 22:02:15 +0000 (14:02 -0800)
Summary:
* git mv
* codemod facebook::wangle folly::wangle
* Change 'runnable' to be a base class in wangle instead of thrift

Justification:
* std::future doesn't have then, whenall, etc.
* boost::future doesn't support executors

@override-unit-failures

Test Plan: contbuild and pray

Reviewed By: hans@fb.com

FB internal diff: D1185194

24 files changed:
folly/Makefile.am
folly/wangle/Executor.h [new file with mode: 0644]
folly/wangle/Future-inl.h [new file with mode: 0644]
folly/wangle/Future.h [new file with mode: 0644]
folly/wangle/GenericThreadGate.h [new file with mode: 0644]
folly/wangle/InlineExecutor.cpp [new file with mode: 0644]
folly/wangle/InlineExecutor.h [new file with mode: 0644]
folly/wangle/Later-inl.h [new file with mode: 0644]
folly/wangle/Later.h [new file with mode: 0644]
folly/wangle/ManualExecutor.cpp [new file with mode: 0644]
folly/wangle/ManualExecutor.h [new file with mode: 0644]
folly/wangle/Promise-inl.h [new file with mode: 0644]
folly/wangle/Promise.h [new file with mode: 0644]
folly/wangle/README [new file with mode: 0644]
folly/wangle/ThreadGate.cpp [new file with mode: 0644]
folly/wangle/ThreadGate.h [new file with mode: 0644]
folly/wangle/Try-inl.h [new file with mode: 0644]
folly/wangle/Try.h [new file with mode: 0644]
folly/wangle/WangleException.h [new file with mode: 0644]
folly/wangle/detail.h [new file with mode: 0644]
folly/wangle/test/FutureTest.cpp [new file with mode: 0644]
folly/wangle/test/LaterTest.cpp [new file with mode: 0644]
folly/wangle/test/ThreadGateTest.cpp [new file with mode: 0644]
folly/wangle/test/main.cpp [new file with mode: 0644]

index e5fe63555bf7613d74721073ce4590394b94282c..c89c16b17caf7d394876eac68844b3fa5fbdb5b8 100644 (file)
@@ -125,7 +125,22 @@ nobase_follyinclude_HEADERS = \
        Unicode.h \
        Uri.h \
        Uri-inl.h \
-       Varint.h
+       Varint.h \
+       wangle/Executor.h \
+       wangle/Future-inl.h \
+       wangle/Future.h \
+       wangle/GenericThreadGate.h \
+       wangle/InlineExecutor.h \
+       wangle/Later-inl.h \
+       wangle/Later.h \
+       wangle/ManualExecutor.h \
+       wangle/Promise-inl.h \
+       wangle/Promise.h \
+       wangle/ThreadGate.h \
+       wangle/Try-inl.h \
+       wangle/Try.h \
+       wangle/WangleException.h \
+       wangle/detail.h
 
 FormatTables.cpp: build/generate_format_tables.py
        build/generate_format_tables.py
@@ -168,7 +183,10 @@ libfolly_la_SOURCES = \
        ThreadCachedArena.cpp \
        TimeoutQueue.cpp \
        Unicode.cpp \
-       Uri.cpp
+       Uri.cpp \
+       wangle/InlineExecutor.cpp \
+       wangle/ManualExecutor.cpp \
+       wangle/ThreadGate.cpp
 
 if !HAVE_LINUX
 nobase_follyinclude_HEADERS += detail/Clock.h
@@ -196,4 +214,3 @@ libfollybenchmark_la_LIBADD = libfolly.la
 
 libfollytimeout_queue_la_SOURCES = TimeoutQueue.cpp
 libfollytimeout_queue_la_LIBADD = libfolly.la
-
diff --git a/folly/wangle/Executor.h b/folly/wangle/Executor.h
new file mode 100644 (file)
index 0000000..29037b4
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * 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 <boost/noncopyable.hpp>
+#include <functional>
+
+namespace folly { namespace wangle {
+  class Executor : boost::noncopyable {
+   public:
+     virtual ~Executor() = default;
+     virtual void add(std::function<void()>&&) = 0;
+  };
+}}
diff --git a/folly/wangle/Future-inl.h b/folly/wangle/Future-inl.h
new file mode 100644 (file)
index 0000000..ce9cb7f
--- /dev/null
@@ -0,0 +1,402 @@
+/*
+ * 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 "detail.h"
+
+namespace folly { namespace wangle {
+
+template <typename T>
+struct isFuture {
+  static const bool value = false;
+};
+
+template <typename T>
+struct isFuture<Future<T> > {
+  static const bool value = true;
+};
+
+template <class T>
+Future<T>::Future(Future<T>&& other) : obj_(other.obj_) {
+  other.obj_ = nullptr;
+}
+
+template <class T>
+Future<T>& Future<T>::operator=(Future<T>&& other) {
+  std::swap(obj_, other.obj_);
+  return *this;
+}
+
+template <class T>
+Future<T>::~Future() {
+  if (obj_) {
+    if (obj_->ready()) {
+      delete obj_;
+    } else {
+      setContinuation([](Try<T>&&) {}); // detach
+    }
+  }
+}
+
+template <class T>
+void Future<T>::throwIfInvalid() const {
+  if (!obj_)
+    throw NoState();
+}
+
+template <class T>
+template <class F>
+void Future<T>::setContinuation(F&& func) {
+  throwIfInvalid();
+  obj_->setContinuation(std::move(func));
+  obj_ = nullptr;
+}
+
+template <class T>
+template <class F>
+typename std::enable_if<
+  !isFuture<typename std::result_of<F(Try<T>&&)>::type>::value,
+  Future<typename std::result_of<F(Try<T>&&)>::type> >::type
+Future<T>::then(F&& func) {
+  typedef typename std::result_of<F(Try<T>&&)>::type B;
+
+  throwIfInvalid();
+
+  // wrap these so we can move them into the lambda
+  folly::MoveWrapper<Promise<B>> p;
+  folly::MoveWrapper<F> funcm(std::forward<F>(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.
+
+     obj_ 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 obj_ (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. FutureObject 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
+     FutureObject, 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.
+     */
+  setContinuation(
+    [p, funcm](Try<T>&& t) mutable {
+      p->fulfil([&]() {
+          return (*funcm)(std::move(t));
+        });
+    });
+
+  return std::move(f);
+}
+
+template <class T>
+template <class F>
+typename std::enable_if<
+  isFuture<typename std::result_of<F(Try<T>&&)>::type>::value,
+  Future<typename std::result_of<F(Try<T>&&)>::type::value_type> >::type
+Future<T>::then(F&& func) {
+  typedef typename std::result_of<F(Try<T>&&)>::type::value_type B;
+
+  throwIfInvalid();
+
+  // wrap these so we can move them into the lambda
+  folly::MoveWrapper<Promise<B>> p;
+  folly::MoveWrapper<F> funcm(std::forward<F>(func));
+
+  // grab the Future now before we lose our handle on the Promise
+  auto f = p->getFuture();
+
+  setContinuation(
+    [p, funcm](Try<T>&& t) mutable {
+      try {
+        auto f2 = (*funcm)(std::move(t));
+        // that didn't throw, now we can steal p
+        f2.setContinuation([p](Try<B>&& b) mutable {
+            p->fulfilTry(std::move(b));
+          });
+      } catch (...) {
+        p->setException(std::current_exception());
+      }
+    });
+
+  return std::move(f);
+}
+
+template <class T>
+Future<void> Future<T>::then() {
+  return then([] (Try<T>&& t) {});
+}
+
+template <class T>
+typename std::add_lvalue_reference<T>::type Future<T>::value() {
+  throwIfInvalid();
+
+  return obj_->value();
+}
+
+template <class T>
+typename std::add_lvalue_reference<const T>::type Future<T>::value() const {
+  throwIfInvalid();
+
+  return obj_->value();
+}
+
+template <class T>
+Try<T>& Future<T>::valueTry() {
+  throwIfInvalid();
+
+  return obj_->valueTry();
+}
+
+template <class T>
+template <typename Executor>
+inline Future<T> Future<T>::executeWithSameThread(Executor* executor) {
+  throwIfInvalid();
+
+  folly::MoveWrapper<Promise<T>> p;
+  auto f = p->getFuture();
+
+  setContinuation([executor, p](Try<T>&& t) mutable {
+      folly::MoveWrapper<Try<T>> tt(std::move(t));
+      executor->add([p, tt]() mutable {
+          p->fulfilTry(std::move(*tt));
+        });
+    });
+
+  return f;
+}
+
+template <class T>
+template <typename Executor>
+inline void Future<T>::executeWith(
+    Executor* executor, Promise<T>&& cont_promise) {
+  throwIfInvalid();
+
+  folly::MoveWrapper<Promise<T>> p(std::move(cont_promise));
+
+  setContinuation([executor, p](Try<T>&& t) mutable {
+      folly::MoveWrapper<Try<T>> tt(std::move(t));
+      executor->add([p, tt]() mutable {
+          p->fulfilTry(std::move(*tt));
+        });
+    });
+}
+
+template <class T>
+bool Future<T>::isReady() const {
+  throwIfInvalid();
+  return obj_->ready();
+}
+
+// makeFuture
+
+template <class T>
+Future<typename std::decay<T>::type> makeFuture(T&& t) {
+  Promise<typename std::decay<T>::type> p;
+  auto f = p.getFuture();
+  p.setValue(std::forward<T>(t));
+  return std::move(f);
+}
+
+inline // for multiple translation units
+Future<void> makeFuture() {
+  Promise<void> p;
+  auto f = p.getFuture();
+  p.setValue();
+  return std::move(f);
+}
+
+template <class F>
+auto makeFutureTry(
+    F&& func,
+    typename std::enable_if<!std::is_reference<F>::value, bool>::type sdf)
+    -> Future<decltype(func())> {
+  Promise<decltype(func())> p;
+  auto f = p.getFuture();
+  p.fulfil(
+    [&func]() {
+      return (func)();
+    });
+  return std::move(f);
+}
+
+template <class F>
+auto makeFutureTry(F const& func) -> Future<decltype(func())> {
+  F copy = func;
+  return makeFutureTry(std::move(copy));
+}
+
+template <class T>
+Future<T> makeFuture(std::exception_ptr const& e) {
+  Promise<T> p;
+  auto f = p.getFuture();
+  p.setException(e);
+  return std::move(f);
+}
+
+template <class T, class E>
+typename std::enable_if<std::is_base_of<std::exception, E>::value, Future<T>>::type
+makeFuture(E const& e) {
+  Promise<T> p;
+  auto f = p.getFuture();
+  p.fulfil([&]() -> T { throw e; });
+  return std::move(f);
+}
+
+// when (variadic)
+
+template <typename... Fs>
+typename detail::VariadicContext<typename Fs::value_type...>::type
+whenAll(Fs&... fs)
+{
+  auto ctx = new detail::VariadicContext<typename Fs::value_type...>();
+  ctx->total = sizeof...(fs);
+  auto f_saved = ctx->p.getFuture();
+  detail::whenAllVariadicHelper(ctx, fs...);
+  return std::move(f_saved);
+}
+
+// when (iterator)
+
+template <class InputIterator>
+Future<
+  std::vector<
+  Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>>
+whenAll(InputIterator first, InputIterator last)
+{
+  typedef
+    typename std::iterator_traits<InputIterator>::value_type::value_type T;
+
+  auto n = std::distance(first, last);
+  if (n == 0)
+    return makeFuture<std::vector<Try<T>>>({});
+
+  auto ctx = new detail::WhenAllContext<T>();
+
+  ctx->total = n;
+  ctx->results.resize(ctx->total);
+
+  auto f_saved = ctx->p.getFuture();
+
+  for (size_t i = 0; first != last; ++first, ++i) {
+     auto& f = *first;
+     f.setContinuation([ctx, i](Try<T>&& t) {
+         ctx->results[i] = std::move(t);
+         if (++ctx->count == ctx->total) {
+           ctx->p.setValue(std::move(ctx->results));
+           delete ctx;
+         }
+       });
+  }
+
+  return std::move(f_saved);
+}
+
+template <class InputIterator>
+Future<
+  std::pair<size_t,
+            Try<
+              typename
+              std::iterator_traits<InputIterator>::value_type::value_type> > >
+whenAny(InputIterator first, InputIterator last) {
+  typedef
+    typename std::iterator_traits<InputIterator>::value_type::value_type T;
+
+  auto ctx = new detail::WhenAnyContext<T>(std::distance(first, last));
+  auto f_saved = ctx->p.getFuture();
+
+  for (size_t i = 0; first != last; first++, i++) {
+    auto& f = *first;
+    f.setContinuation([i, ctx](Try<T>&& t) {
+      if (!ctx->done.exchange(true)) {
+        ctx->p.setValue(std::make_pair(i, std::move(t)));
+      }
+      ctx->decref();
+    });
+  }
+
+  return std::move(f_saved);
+}
+
+template <class InputIterator>
+Future<std::vector<std::pair<size_t, Try<typename
+  std::iterator_traits<InputIterator>::value_type::value_type>>>>
+whenN(InputIterator first, InputIterator last, size_t n) {
+  typedef typename
+    std::iterator_traits<InputIterator>::value_type::value_type T;
+  typedef std::vector<std::pair<size_t, Try<T>>> V;
+
+  struct ctx_t {
+    V v;
+    size_t completed;
+    Promise<V> p;
+  };
+  auto ctx = std::make_shared<ctx_t>();
+  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>&& 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<V>(std::move(v)));
+        }
+      }
+    });
+
+    it++;
+    i++;
+  }
+
+  if (i < n) {
+    ctx->p.setException(std::runtime_error("Not enough futures"));
+  }
+
+  return ctx->p.getFuture();
+}
+
+}}
diff --git a/folly/wangle/Future.h b/folly/wangle/Future.h
new file mode 100644 (file)
index 0000000..48e8822
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+ * 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 <algorithm>
+#include <exception>
+#include <functional>
+#include <memory>
+#include <type_traits>
+
+#include "folly/MoveWrapper.h"
+#include "Promise.h"
+#include "Try.h"
+
+namespace folly { namespace wangle {
+
+template <typename T> struct isFuture;
+
+template <class T>
+class Future {
+ public:
+  typedef T value_type;
+
+  // not copyable
+  Future(Future const&) = delete;
+  Future& operator=(Future const&) = delete;
+
+  // movable
+  Future(Future&&);
+  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<T>::type
+  value();
+  typename std::add_lvalue_reference<const T>::type
+  value() const;
+
+  template <typename Executor>
+  Future<T> executeWithSameThread(Executor* executor);
+
+  /**
+     Thread-safe version of executeWith
+
+     Since an executor would likely start executing the Future chain
+     right away, it would be a race condition to call:
+     Future.executeWith(...).then(...), as there would be race
+     condition between the then and the running Future.
+     Instead, you may pass in a Promise so that we can set up
+     the rest of the chain in advance, without any racey
+     modifications of the continuation
+   */
+  template <typename Executor>
+  void executeWith(Executor* executor, Promise<T>&& cont_promise);
+
+  /** True when the result (or exception) is ready.  value() will not block
+      when this returns true. */
+  bool isReady() const;
+
+  /** Wait until the result (or exception) is ready. Once this returns,
+    value() will not block, and isReady() will return true.
+
+    XXX This implementation is simplistic and inefficient, but it does work
+    and a fully intelligent implementation is coming down the pipe.
+  */
+  void wait() const {
+    while (!isReady()) {
+      // spin
+      std::this_thread::yield();
+    }
+  }
+
+  Try<T>& valueTry();
+
+  /** When this Future has completed, execute func which is a function that
+    takes a Try<T>&&. A Future for the return type of func is
+    returned. e.g.
+
+    Future<string> f2 = f1.then([](Try<T>&&) { return string("foo"); });
+
+    The functor given may call value() without blocking, which may rethrow if
+    this has captured an exception. If func throws, the exception will be
+    captured in the Future that is returned.
+    */
+  /* n3428 has then(scheduler&, F&&), we might want to reorganize to use
+     similar API. or maybe not */
+  template <class F>
+  typename std::enable_if<
+    !isFuture<typename std::result_of<F(Try<T>&&)>::type>::value,
+    Future<typename std::result_of<F(Try<T>&&)>::type> >::type
+  then(F&& func);
+
+  template <class F>
+  typename std::enable_if<
+    isFuture<typename std::result_of<F(Try<T>&&)>::type>::value,
+    Future<typename std::result_of<F(Try<T>&&)>::type::value_type> >::type
+  then(F&& func);
+
+  /** Use this method on the Future when we don't really care about the
+    returned value and want to convert the Future<T> to a Future<void>
+    Convenience function
+    */
+  Future<void> then();
+
+  template <class F>
+  void setContinuation(F&& func);
+
+ private:
+  /* Eventually this may not be a shared_ptr, but something similar without
+     expensive thread-safety. */
+  typedef detail::FutureObject<T>* objPtr;
+
+  // shared state object
+  objPtr obj_;
+
+  explicit
+  Future(objPtr obj) : obj_(obj) {}
+
+  void throwIfInvalid() const;
+
+  friend class Promise<T>;
+};
+
+/** Make a completed Future by moving in a value. e.g.
+  auto f = makeFuture(string("foo"));
+*/
+template <class T>
+Future<typename std::decay<T>::type> makeFuture(T&& t);
+
+/** Make a completed void Future. */
+Future<void> makeFuture();
+
+/** Make a completed Future by executing a function. If the function throws
+  we capture the exception, otherwise we capture the result. */
+template <class F>
+auto makeFutureTry(
+  F&& func,
+  typename std::enable_if<
+    !std::is_reference<F>::value, bool>::type sdf = false)
+  -> Future<decltype(func())>;
+
+template <class F>
+auto makeFutureTry(
+  F const& func)
+  -> Future<decltype(func())>;
+
+/** Make a completed (error) Future from an exception_ptr. Because the type
+can't be inferred you have to give it, e.g.
+
+auto f = makeFuture<string>(std::current_exception());
+*/
+template <class T>
+Future<T> makeFuture(std::exception_ptr const& e);
+
+/** Make a Future from an exception type E that can be passed to
+  std::make_exception_ptr(). */
+template <class T, class E>
+typename std::enable_if<std::is_base_of<std::exception, E>::value, Future<T>>::type
+makeFuture(E const& e);
+
+/** 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.
+
+  This function is thread-safe for Futures running on different threads.
+
+  The return type for Future<T> input is a Future<vector<Try<T>>>
+  */
+template <class InputIterator>
+Future<std::vector<Try<
+  typename std::iterator_traits<InputIterator>::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<T1>, Future<T2>, ...) input
+  is a Future<tuple<Try<T1>, Try<T2>, ...>>.
+  */
+template <typename... Fs>
+typename detail::VariadicContext<typename Fs::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 <class InputIterator>
+Future<std::pair<
+  size_t,
+  Try<typename std::iterator_traits<InputIterator>::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 <class InputIterator>
+Future<std::vector<std::pair<
+  size_t,
+  Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>>>
+whenN(InputIterator first, InputIterator last, size_t n);
+
+}} // folly::wangle
+
+#include "Future-inl.h"
+
+/*
+
+TODO
+
+I haven't included a Future<T&> specialization because I don't forsee us
+using it, however it is not difficult to add when needed. Refer to
+Future<void> for guidance. std::Future and boost::Future code would also be
+instructive.
+
+I think that this might be a good candidate for folly, once it has baked for
+awhile.
+
+*/
diff --git a/folly/wangle/GenericThreadGate.h b/folly/wangle/GenericThreadGate.h
new file mode 100644 (file)
index 0000000..01a41c2
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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 "ThreadGate.h"
+#include "Executor.h"
+#include <type_traits>
+
+namespace folly { namespace wangle {
+
+template <
+  class WestExecutorPtr = Executor*,
+  class EastExecutorPtr = Executor*,
+  class WaiterPtr = void*>
+class GenericThreadGate : public ThreadGate {
+public:
+  /**
+    EastExecutor and WestExecutor respond threadsafely to
+    `add(std::function<void()>&&)`
+
+    Waiter responds to `makeProgress()`. It may block, as long as progress
+    will be made on the west front.
+    */
+  GenericThreadGate(WestExecutorPtr west,
+                    EastExecutorPtr east,
+                    WaiterPtr waiter = nullptr) :
+    westExecutor(west),
+    eastExecutor(east),
+    waiter(waiter)
+  {}
+
+  void addWest(std::function<void()>&& fn) { westExecutor->add(std::move(fn)); }
+  void addEast(std::function<void()>&& fn) { eastExecutor->add(std::move(fn)); }
+
+  virtual void makeProgress() {
+    makeProgress_(std::is_same<WaiterPtr, void*>());
+  }
+
+  WestExecutorPtr westExecutor;
+  EastExecutorPtr eastExecutor;
+  WaiterPtr waiter;
+private:
+  void makeProgress_(std::true_type const&) {
+    throw std::logic_error("No waiter.");
+  }
+
+  void makeProgress_(std::false_type const&) {
+    waiter->makeProgress();
+  }
+};
+
+}} // executor
diff --git a/folly/wangle/InlineExecutor.cpp b/folly/wangle/InlineExecutor.cpp
new file mode 100644 (file)
index 0000000..632558c
--- /dev/null
@@ -0,0 +1,16 @@
+/*
+ * 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
new file mode 100644 (file)
index 0000000..8c338b1
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * 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 "folly/wangle/Executor.h"
+
+namespace folly { namespace wangle {
+
+  class InlineExecutor : public Executor {
+   public:
+    void add(std::function<void()>&& f) override {
+      f();
+    }
+  };
+
+}}
diff --git a/folly/wangle/Later-inl.h b/folly/wangle/Later-inl.h
new file mode 100644 (file)
index 0000000..e17f2d7
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * 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 "folly/wangle/Executor.h"
+#include "folly/wangle/Future.h"
+#include "folly/Optional.h"
+
+namespace folly { namespace wangle {
+
+template <typename T>
+struct isLater {
+  static const bool value = false;
+};
+
+template <typename T>
+struct isLater<Later<T> > {
+  static const bool value = true;
+};
+
+template <typename T>
+struct isLaterOrFuture {
+  static const bool value = false;
+};
+
+template <typename T>
+struct isLaterOrFuture<Later<T>> {
+  static const bool value = true;
+};
+
+template <typename T>
+struct isLaterOrFuture<Future<T>> {
+  static const bool value = true;
+};
+
+template <typename T>
+template <class U, class Unused, class Unused2>
+Later<T>::Later() {
+  future_ = starter_.getFuture();
+}
+
+template <typename T>
+Later<T>::Later(Promise<void>&& starter)
+  : starter_(std::forward<Promise<void>>(starter)) { }
+
+template <class T>
+template <class U, class Unused, class Unused2>
+Later<T>::Later(U&& input) {
+  folly::MoveWrapper<Promise<U>> promise;
+  folly::MoveWrapper<U> inputm(std::forward<U>(input));
+  future_ = promise->getFuture();
+  starter_.getFuture().then([=](Try<void>&& t) mutable {
+    promise->setValue(std::move(*inputm));
+  });
+}
+
+template <class T>
+template <class F>
+typename std::enable_if<
+  !isLaterOrFuture<typename std::result_of<F(Try<T>&&)>::type>::value,
+  Later<typename std::result_of<F(Try<T>&&)>::type> >::type
+Later<T>::then(F&& fn) {
+  typedef typename std::result_of<F(Try<T>&&)>::type B;
+
+  Later<B> later(std::move(starter_));
+  later.future_ = future_->then(std::forward<F>(fn));
+  return later;
+}
+
+template <class T>
+template <class F>
+typename std::enable_if<
+  isFuture<typename std::result_of<F(Try<T>&&)>::type>::value,
+  Later<typename std::result_of<F(Try<T>&&)>::type::value_type> >::type
+Later<T>::then(F&& fn) {
+  typedef typename std::result_of<F(Try<T>&&)>::type::value_type B;
+
+  Later<B> later(std::move(starter_));
+  later.future_ = future_->then(std::move(fn));
+  return later;
+}
+
+template <class T>
+template <class F>
+typename std::enable_if<
+  isLater<typename std::result_of<F(Try<T>&&)>::type>::value,
+  Later<typename std::result_of<F(Try<T>&&)>::type::value_type> >::type
+Later<T>::then(F&& fn) {
+  typedef typename std::result_of<F(Try<T>&&)>::type::value_type B;
+
+  folly::MoveWrapper<Promise<B>> promise;
+  folly::MoveWrapper<F> fnm(std::move(fn));
+  Later<B> later(std::move(starter_));
+  later.future_ = promise->getFuture();
+  future_->then([=](Try<T>&& t) mutable {
+    (*fnm)(std::move(t))
+    .then([=](Try<B>&& t2) mutable {
+      promise->fulfilTry(std::move(t2));
+    })
+    .launch();
+  });
+  return later;
+}
+
+template <class T>
+Later<T> Later<T>::via(Executor* executor) {
+  Promise<T> promise;
+  Later<T> later(std::move(starter_));
+  later.future_ = promise.getFuture();
+  future_->executeWith(executor, std::move(promise));
+  return later;
+}
+
+template <class T>
+Future<T> Later<T>::launch() {
+  starter_.setValue();
+  return std::move(*future_);
+}
+
+template <class T>
+void Later<T>::fireAndForget() {
+  future_->setContinuation([] (Try<T>&& t) {}); // detach
+  starter_.setValue();
+}
+
+}}
diff --git a/folly/wangle/Later.h b/folly/wangle/Later.h
new file mode 100644 (file)
index 0000000..213b527
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * 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 "folly/wangle/Executor.h"
+#include "folly/wangle/Future.h"
+#include "folly/Optional.h"
+
+namespace folly { namespace wangle {
+
+template <typename T> struct isLaterOrFuture;
+template <typename T> struct isLater;
+
+/*
+ * Since wangle primitives (promise/future) are not thread safe, it is difficult
+ * to build complex asynchronous workflows. A Later allows you to build such a
+ * workflow before actually launching it so that continuations can be set in a
+ * threadsafe manner.
+ *
+ * The interface to add additional work is the same as future: a then() method
+ * that can take either a type T, a Future<T>, or a Later<T>
+ *
+ * Thread transitions are done by using executors and calling the via() method.
+ *
+ * Here is an example of a workflow:
+ *
+ * Later<ClientRequest> later(std::move(request));
+ *
+ * auto future = later.
+ *   .via(cpuExecutor)
+ *   .then([=](Try<ClientRequest>&& t) { return doCpuWork(t.value()); })
+ *   .via(diskExecutor)
+ *   .then([=](Try<CpuResponse>&& t) { return doDiskWork(t.value()); })
+ *   .via(serverExecutor)
+ *   .then([=]Try<DiskResponse>&& t) { return sendClientResponse(t.value()); })
+ *   .launch();
+ *
+ * Although this workflow traverses many threads, we are able to string
+ * continuations together in a threadsafe manner.
+ *
+ * Laters can also be used to wrap preexisting asynchronous modules that were
+ * not built with wangle in mind. You can create a Later with a function that
+ * takes a callback as input. The function will not actually be called until
+ * launch(), allowing you to string then() statements on top of the callback.
+ */
+template <class T>
+class Later {
+ public:
+  typedef T value_type;
+
+  template <class U = void,
+            class = typename std::enable_if<std::is_void<U>::value>::type,
+            class = typename std::enable_if<std::is_same<T, U>::value>::type>
+  Later();
+
+  template <class U,
+            class = typename std::enable_if<!std::is_void<U>::value>::type,
+            class = typename std::enable_if<std::is_same<T, U>::value>::type>
+  explicit Later(U&& input);
+
+  /*
+   * then() adds additional work to the end of the workflow. If the lambda
+   * provided to then() returns a future, that future must be fulfilled in the
+   * same thread of the last set executor (either at constructor or from a call
+   * to via()).
+   */
+  template <class F>
+  typename std::enable_if<
+    !isLaterOrFuture<typename std::result_of<F(Try<T>&&)>::type>::value,
+    Later<typename std::result_of<F(Try<T>&&)>::type> >::type
+  then(F&& fn);
+
+  template <class F>
+  typename std::enable_if<
+    isFuture<typename std::result_of<F(Try<T>&&)>::type>::value,
+    Later<typename std::result_of<F(Try<T>&&)>::type::value_type> >::type
+  then(F&& fn);
+
+  /*
+   * If the function passed to then() returns a Later<T>, calls to then() will
+   * be chained to the new Later before launching the new Later.
+   *
+   * This can be used to build asynchronous modules that can be called from a
+   * user thread and completed in a callback thread. Callbacks can be set up
+   * ahead of time without thread safety issues.
+   *
+   * Using the Later(std::function<void(std::function<void(T&&)>)>&& fn)
+   * constructor, you can wrap existing asynchronous modules with a Later and
+   * can chain it to wangle asynchronous workflows via this call.
+   */
+  template <class F>
+  typename std::enable_if<
+    isLater<typename std::result_of<F(Try<T>&&)>::type>::value,
+    Later<typename std::result_of<F(Try<T>&&)>::type::value_type> >::type
+  then(F&& fn);
+
+  /*
+   * Resets the executor - all then() calls made after the call to via() will be
+   * made in the new executor.
+   */
+  Later<T> via(Executor* executor);
+
+  /*
+   * Starts the workflow. The function provided in the constructor will be
+   * called in the executor provided in the constructor. All proximate then()
+   * calls will be made, potentially changing threads if a via() call is made.
+   * The future returned will be fulfilled in the last executor.
+   *
+   * Thread safety issues of Futures still apply. If you want to wait on the
+   * Future, it must be done in the thread that will fulfill it. If you do not
+   * plan to use the result of the Future, use fireAndForget()
+   */
+  Future<T> launch();
+
+  /*
+   * Same as launch, only no Future is returned. This guarantees thread safe
+   * cleanup of the internal Futures, even if the Later completes in a different
+   * thread than the thread that calls fireAndForget().
+   */
+  void fireAndForget();
+
+ private:
+  Promise<void> starter_;
+  folly::Optional<Future<T>> future_;
+
+  struct hide { };
+
+  explicit Later(Promise<void>&& starter);
+
+  template <class U>
+  friend class Later;
+};
+
+}}
+
+#include "Later-inl.h"
diff --git a/folly/wangle/ManualExecutor.cpp b/folly/wangle/ManualExecutor.cpp
new file mode 100644 (file)
index 0000000..8caf358
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * 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 "ManualExecutor.h"
+
+#include <string.h>
+
+#include <stdexcept>
+
+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(std::function<void()>&& callback) {
+  std::lock_guard<std::mutex> lock(lock_);
+  runnables_.push(callback);
+  sem_post(&sem_);
+}
+
+size_t ManualExecutor::run() {
+  size_t count;
+  size_t n;
+  std::function<void()> runnable;
+
+  {
+    std::lock_guard<std::mutex> lock(lock_);
+    n = runnables_.size();
+  }
+
+  for (count = 0; count < n; count++) {
+    {
+      std::lock_guard<std::mutex> lock(lock_);
+      if (runnables_.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_);
+
+      runnable = std::move(runnables_.front());
+      runnables_.pop();
+    }
+    runnable();
+  }
+
+  return count;
+}
+
+void ManualExecutor::wait() {
+  while (true) {
+    {
+      std::lock_guard<std::mutex> lock(lock_);
+      if (!runnables_.empty())
+        break;
+    }
+
+    auto ret = sem_wait(&sem_);
+    if (ret == 0) {
+      break;
+    }
+    if (errno != EINVAL) {
+      throw std::runtime_error(std::string("sem_wait: ") + strerror(errno));
+    }
+  }
+}
+
+}} // namespace
diff --git a/folly/wangle/ManualExecutor.h b/folly/wangle/ManualExecutor.h
new file mode 100644 (file)
index 0000000..93c2591
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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 "folly/wangle/Executor.h"
+#include <semaphore.h>
+#include <memory>
+#include <mutex>
+#include <queue>
+
+namespace folly { namespace wangle {
+
+  class ManualExecutor : public Executor {
+   public:
+    ManualExecutor();
+
+    void add(std::function<void()>&&) override;
+
+    /// Do work. Returns the number of runnables that were executed (maybe 0).
+    /// Non-blocking.
+    size_t run();
+
+    /// Wait for work to do.
+    void wait();
+
+    /// Wait for work to do, and do it.
+    void makeProgress() {
+      wait();
+      run();
+    }
+
+   private:
+    std::mutex lock_;
+    std::queue<std::function<void()>> runnables_;
+    sem_t sem_;
+  };
+
+}}
diff --git a/folly/wangle/Promise-inl.h b/folly/wangle/Promise-inl.h
new file mode 100644 (file)
index 0000000..6e7e494
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * 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 <atomic>
+#include <thread>
+
+#include "WangleException.h"
+#include "detail.h"
+
+namespace folly { namespace wangle {
+
+template <class T>
+Promise<T>::Promise() : retrieved_(false), obj_(new detail::FutureObject<T>())
+{}
+
+template <class T>
+Promise<T>::Promise(Promise<T>&& other) :
+retrieved_(other.retrieved_), obj_(other.obj_) {
+  other.obj_ = nullptr;
+}
+
+template <class T>
+Promise<T>& Promise<T>::operator=(Promise<T>&& other) {
+  std::swap(obj_, other.obj_);
+  std::swap(retrieved_, other.retrieved_);
+  return *this;
+}
+
+template <class T>
+void Promise<T>::throwIfFulfilled() {
+  if (!obj_)
+    throw PromiseAlreadySatisfied();
+}
+
+template <class T>
+void Promise<T>::throwIfRetrieved() {
+  if (retrieved_)
+    throw FutureAlreadyRetrieved();
+}
+
+template <class T>
+Promise<T>::~Promise() {
+  if (obj_) {
+    setException(BrokenPromise());
+  }
+}
+
+template <class T>
+Future<T> Promise<T>::getFuture() {
+  throwIfRetrieved();
+  throwIfFulfilled();
+  retrieved_ = true;
+  return Future<T>(obj_);
+}
+
+template <class T>
+template <class E>
+void Promise<T>::setException(E const& e) {
+  throwIfFulfilled();
+  setException(std::make_exception_ptr<E>(e));
+}
+
+template <class T>
+void Promise<T>::setException(std::exception_ptr const& e) {
+  throwIfFulfilled();
+  obj_->setException(e);
+  if (!retrieved_) {
+    delete obj_;
+  }
+  obj_ = nullptr;
+}
+
+template <class T>
+void Promise<T>::fulfilTry(Try<T>&& t) {
+  throwIfFulfilled();
+  obj_->fulfil(std::move(t));
+  if (!retrieved_) {
+    delete obj_;
+  }
+  obj_ = nullptr;
+}
+
+template <class T>
+template <class M>
+void Promise<T>::setValue(M&& v) {
+  static_assert(!std::is_same<T, void>::value,
+                "Use setValue() instead");
+
+  throwIfFulfilled();
+  obj_->fulfil(Try<T>(std::forward<M>(v)));
+  if (!retrieved_) {
+    delete obj_;
+  }
+  obj_ = nullptr;
+}
+
+template <class T>
+void Promise<T>::setValue() {
+  static_assert(std::is_same<T, void>::value,
+                "Use setValue(value) instead");
+
+  throwIfFulfilled();
+  obj_->fulfil(Try<void>());
+  if (!retrieved_) {
+    delete obj_;
+  }
+  obj_ = nullptr;
+}
+
+template <class T>
+template <class F>
+void Promise<T>::fulfil(const F& func) {
+  fulfilHelper(func);
+}
+
+template <class T>
+template <class F>
+typename std::enable_if<
+  std::is_convertible<typename std::result_of<F()>::type, T>::value &&
+  !std::is_same<T, void>::value>::type
+inline Promise<T>::fulfilHelper(const F& func) {
+  throwIfFulfilled();
+  try {
+    setValue(func());
+  } catch (...) {
+    setException(std::current_exception());
+  }
+}
+
+template <class T>
+template <class F>
+typename std::enable_if<
+  std::is_same<typename std::result_of<F()>::type, void>::value &&
+  std::is_same<T, void>::value>::type
+inline Promise<T>::fulfilHelper(const F& func) {
+  throwIfFulfilled();
+  try {
+    func();
+    setValue();
+  } catch (...) {
+    setException(std::current_exception());
+  }
+}
+
+}}
diff --git a/folly/wangle/Promise.h b/folly/wangle/Promise.h
new file mode 100644 (file)
index 0000000..f2a354f
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * 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 "Try.h"
+#include "Future.h"
+
+namespace folly { namespace wangle {
+
+// forward declaration
+template <class T> class Future;
+
+template <class T>
+class Promise {
+public:
+  Promise();
+  ~Promise();
+
+  // not copyable
+  Promise(Promise const&) = delete;
+  Promise& operator=(Promise const&) = delete;
+
+  // movable
+  Promise(Promise<T>&&);
+  Promise& operator=(Promise<T>&&);
+
+  /** Return a Future tied to the shared state. This can be called only
+    once, thereafter Future already retrieved exception will be raised. */
+  Future<T> 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 <class E> void setException(E const&);
+
+  /** Fulfil this Promise (only for Promise<void>) */
+  void setValue();
+
+  /** Set the value (use perfect forwarding for both move and copy) */
+  template <class M>
+  void setValue(M&& value);
+
+  void fulfilTry(Try<T>&& 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 <class F>
+  void fulfil(const F& func);
+
+private:
+  typedef typename Future<T>::objPtr objPtr;
+
+  // Whether the Future has been retrieved (a one-time operation).
+  bool retrieved_;
+
+  // shared state object
+  objPtr obj_;
+
+  void throwIfFulfilled();
+  void throwIfRetrieved();
+
+  template <class F>
+  typename std::enable_if<
+    std::is_convertible<typename std::result_of<F()>::type, T>::value &&
+    !std::is_same<T, void>::value>::type
+  fulfilHelper(const F& func);
+
+  template <class F>
+  typename std::enable_if<
+    std::is_same<typename std::result_of<F()>::type, void>::value &&
+    std::is_same<T, void>::value>::type
+  fulfilHelper(const F& func);
+};
+
+}}
+
+#include "Promise-inl.h"
diff --git a/folly/wangle/README b/folly/wangle/README
new file mode 100644 (file)
index 0000000..76d482b
--- /dev/null
@@ -0,0 +1 @@
+Please see https://our.intern.facebook.com/intern/dex/wangle/
diff --git a/folly/wangle/ThreadGate.cpp b/folly/wangle/ThreadGate.cpp
new file mode 100644 (file)
index 0000000..e27142d
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * 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 "ThreadGate.h"
+#include <stdexcept>
+
+namespace folly { namespace wangle {
+
+void ThreadGate::makeProgress()
+{
+  throw std::logic_error("This ThreadGate doesn't know how to "
+                         "make progress.");
+}
+
+}} // namespace
diff --git a/folly/wangle/ThreadGate.h b/folly/wangle/ThreadGate.h
new file mode 100644 (file)
index 0000000..00929d2
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+ * 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 <memory>
+#include "Future.h"
+
+namespace folly { namespace wangle {
+
+/**
+  Yo dawg, I heard you like asynchrony so I put asynchrony in your asynchronous
+  framework.
+
+  Wangle's futures and promises are not thread safe. Counterintuitive as this
+  may seem at first, this is very intentional. Making futures and promises
+  threadsafe drastically reduces their performance.
+
+  On the other hand, an asynchronous framework isn't much use if you can't do
+  asynchronous things in other threads. So we use the ThreadGate strategy to
+  decouple the threads and their futures with a form of message passing.
+
+  There are two actors, the east thread which does the asynchronous operation
+  (the server) and the west thread that wants the asynchronous operation done
+  (the client).
+
+  The client calls gate<T>(fn), which returns a Future<T>. Practically speaking
+  the returned Future<T> is the same as the Future<T> returned by fn. But
+  there are actually two futures involved - the original Future which will be
+  generated by fn (called the east Future), and the Future actually returned
+  by gate<T>(fn) (called the west Future).
+
+  These two futures are decoupled, and although the fulfilment of the east
+  Future eventually causes fulfilment of the west Future, those fulfilments
+  happen in their own threads.
+
+  In order to make and use a ThreadGate, you need to provide a strategy for
+  executing code in the east and west threads. These strategies may be
+  different. The only requirement is a threadsafe method
+  `void add(function<void()>&&)`. You may find the executors in
+  Executor.h handy, but ensure that you are using them
+  threadsafely.
+
+  In order for your ThreadGate to do anything, you need to drive those
+  executors somehow. An event loop is a natural fit. A thread pool might be
+  made to work. You could use a busy loop to make a very expensive space
+  heater. 0MQ would be pleasant.
+
+  Another pattern supported by the ThreadGate is the single-thread pattern. In
+  this pattern, non-blocking I/O drives the asynchronous operation, and
+  futures are fulfilled in an event loop callback. In this scenario,
+  ThreadGate is largely superfluous, and the executors would likely just
+  execute code immediately and inline (and therefore not need to be driven, or
+  threadsafe). But a Waiter strategy that makes progress by driving the event
+  loop one iteration would allow for gate-and-wait code which is agnostic to
+  the small detail that everything happens in one thread. It would also make
+  Future change toward a multithreaded architecture easier, as you need only
+  change the components of the ThreadGate which your client code is already
+  using.
+  */
+class ThreadGate {
+public:
+  virtual ~ThreadGate() {}
+
+  /**
+    Returns a Future that will be fulfilled after the Future that will be
+    returned by fn() has been fulfilled, with the same value or exception
+    (moved).
+
+    There's a lot of nuance in that sentence. Let's break it down.
+
+    fn kicks off the asynchronous operation (makes the east Promise), and must
+    be executed in the east thread because the east thread is where the east
+    Promise will be fulfilled. Since gate is being called from the west
+    thread, we must gate fn using the east executor. fn is not executed
+    immediately, it is queued up and will be executed by the east thread as it
+    drives the executor.
+
+    We create the west Promise and return its Future.
+
+    When the east thread executes its task, fn is called and the resulting
+    Future gets a callback that will gate another task back to the west.
+
+    Sometime later, the asynchronous operation completes and the east Promise
+    is fulfilled. Then the east Future executes its callback, which adds a
+    task to the west executor that task is to fulfil the west Promise with the
+    same Try<T>, and it will execute in the west thread.
+
+    At this point, the west Future is still unfulfilled, even though the east
+    Future has been fulfilled and its callback has finished executing. Only
+    when the west executor is driven to execute that task, the west Future
+    will be completed and its callbacks called.
+
+    In summary, both east and west need to have plans to drive their
+    executors, or nothing will actually happen. When the executors are driven,
+    then everything flows.    */
+  template <class T>
+  Future<T> gate(std::function<Future<T>()>&& fn) {
+    Promise<T> pWest;
+    Future<T> fWest = pWest.getFuture();
+
+    gate(std::move(fn), std::move(pWest));
+    return fWest;
+  }
+
+  /**
+   * This version of gate is to support use cases where the calling thread is
+   * not the west thread. Here is an example use case.
+   *
+   *  Promise<T> pWest;
+   *  Future<T> fWest = pWest.getFuture();
+   *
+   *  // Set up callbacks for west from a thread that is not west.
+   *  fWest.then(...).then(...);
+   *
+   *  threadGate.gate(..., std::move(pWest));
+   *
+   * This function assumes that it is safe to call addEast from a thread that is
+   * not the west thread.
+   */
+  template <class T>
+  void gate(std::function<Future<T>()>&& fn,
+            Promise<T>&& p) {
+    folly::MoveWrapper<Promise<T>> pWest(std::move(p));
+    folly::MoveWrapper<std::function<Future<T>()>> fnm(std::move(fn));
+    this->addEast([pWest, fnm, this]() mutable {
+      (*fnm)().then([pWest, this](Try<T>&& t) mutable {
+        folly::MoveWrapper<Try<T>> tm(std::move(t));
+        this->addWest([pWest, tm]() mutable {
+          pWest->fulfilTry(std::move(*tm));
+        });
+      });
+    });
+  }
+
+  /**
+    If your workflow calls for synchronizing with a
+    west Future, then you may call waitFor, but if your west thread is
+    event-driven you will probably not need to call waitFor.
+
+    In order for waitFor to behave properly, you must ensure that the Waiter's
+    makeProgress method causes some progress to be made on the west thread,
+    i.e. drives the west executor either directly or indirectly.
+
+    (Naturally, progress needs to be made on the east thread as well. i.e. the
+    east executor is driven, the asynchronous operation happens, and its
+    Promise is fulfilled. It is likely that none of this concerns the consumer
+    of waitFor.)
+
+    This is the only function that uses the Waiter. It is never called
+    internally. Therefore, if you never use waitFor you can safely provide a
+    DummyWaiter.
+    */
+  template <class T>
+  void waitFor(Future<T> const& f) {
+    while (!f.isReady()) {
+      this->makeProgress();
+    }
+  }
+
+  template <class T>
+  typename std::add_lvalue_reference<T>::type
+  value(Future<T>& f) {
+    waitFor<T>(f);
+    return f.value();
+  }
+
+  template <class T>
+  typename std::add_lvalue_reference<const T>::type
+  value(Future<T> const& f) {
+    waitFor<T>(f);
+    return f.value();
+  }
+
+  virtual void addEast(std::function<void()>&&) = 0;
+  virtual void addWest(std::function<void()>&&) = 0;
+  virtual void makeProgress();
+};
+
+}} // namespace
diff --git a/folly/wangle/Try-inl.h b/folly/wangle/Try-inl.h
new file mode 100644 (file)
index 0000000..68b4121
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * 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 <stdexcept>
+
+#include "WangleException.h"
+
+namespace folly { namespace wangle {
+
+template <class T>
+Try<T>::Try(Try<T>&& t) : contains_(t.contains_) {
+  if (contains_ == VALUE) {
+    new (&value_)T(std::move(t.value_));
+  } else if (contains_ == EXCEPTION) {
+    new (&e_)std::exception_ptr(t.e_);
+  }
+}
+
+template <class T>
+Try<T>& Try<T>::operator=(Try<T>&& t) {
+  this->~Try();
+  contains_ = t.contains_;
+  if (contains_ == VALUE) {
+    new (&value_)T(std::move(t.value_));
+  } else if (contains_ == EXCEPTION) {
+    new (&e_)std::exception_ptr(t.e_);
+  }
+  return *this;
+}
+
+template <class T>
+Try<T>::~Try() {
+  if (contains_ == VALUE) {
+    value_.~T();
+  } else if (contains_ == EXCEPTION) {
+    e_.~exception_ptr();
+  }
+}
+
+template <class T>
+T& Try<T>::value() {
+  throwIfFailed();
+  return value_;
+}
+
+template <class T>
+const T& Try<T>::value() const {
+  throwIfFailed();
+  return value_;
+}
+
+template <class T>
+void Try<T>::throwIfFailed() const {
+  if (contains_ != VALUE) {
+    if (contains_ == EXCEPTION) {
+      std::rethrow_exception(e_);
+    } else {
+      throw UsingUninitializedTry();
+    }
+  }
+}
+
+void Try<void>::throwIfFailed() const {
+  if (!hasValue_) {
+    std::rethrow_exception(e_);
+  }
+}
+
+template <typename T>
+inline T moveFromTry(wangle::Try<T>&& t) {
+  return std::move(t.value());
+}
+
+inline void moveFromTry(wangle::Try<void>&& t) {
+  return t.value();
+}
+
+}}
diff --git a/folly/wangle/Try.h b/folly/wangle/Try.h
new file mode 100644 (file)
index 0000000..1e1df07
--- /dev/null
@@ -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.
+ */
+
+#pragma once
+
+namespace folly { namespace wangle {
+
+template <class T>
+class Try {
+  static_assert(!std::is_reference<T>::value,
+                "Try may not be used with reference types");
+
+  enum Contains {
+    VALUE,
+    EXCEPTION,
+    NOTHING,
+  };
+
+ public:
+  typedef T element_type;
+
+  Try() : contains_(NOTHING) {}
+  explicit Try(const T& v) : contains_(VALUE), value_(v) {}
+  explicit Try(T&& v) : contains_(VALUE), value_(std::move(v)) {}
+  explicit Try(std::exception_ptr e) : contains_(EXCEPTION), e_(e) {}
+
+  // move
+  Try(Try<T>&& t);
+  Try& operator=(Try<T>&& t);
+
+  // no copy
+  Try(const Try<T>& t) = delete;
+  Try& operator=(const Try<T>& 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_ == VALUE; }
+  bool hasException() const { return contains_ == EXCEPTION; }
+
+ private:
+  Contains contains_;
+  union {
+    T value_;
+    std::exception_ptr e_;
+  };
+};
+
+template <>
+class Try<void> {
+ 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_; }
+
+ private:
+  bool hasValue_;
+  std::exception_ptr e_;
+};
+
+/**
+ * Extracts value from try and returns it. Throws if try contained an exception.
+ */
+template <typename T>
+T moveFromTry(wangle::Try<T>&& t);
+
+/**
+ * Throws if try contained an exception.
+ */
+void moveFromTry(wangle::Try<void>&& t);
+
+}}
+
+#include "Try-inl.h"
diff --git a/folly/wangle/WangleException.h b/folly/wangle/WangleException.h
new file mode 100644 (file)
index 0000000..40613c5
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * 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 <exception>
+
+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 FutureAlreadyRetrieved : public WangleException {
+  public:
+    explicit FutureAlreadyRetrieved () :
+      WangleException("Future already retrieved") { }
+};
+
+class UsingUninitializedTry : public WangleException {
+  public:
+    explicit UsingUninitializedTry() :
+      WangleException("Using unitialized try") { }
+};
+
+}}
diff --git a/folly/wangle/detail.h b/folly/wangle/detail.h
new file mode 100644 (file)
index 0000000..bf85041
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * 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 <folly/Optional.h>
+#include <stdexcept>
+#include <atomic>
+
+#include "Try.h"
+#include "Promise.h"
+#include "Future.h"
+
+namespace folly { namespace wangle { namespace detail {
+
+/** The shared state object for Future and Promise. */
+template<typename T>
+class FutureObject {
+ public:
+  FutureObject() = default;
+
+  // not copyable
+  FutureObject(FutureObject const&) = delete;
+  FutureObject& operator=(FutureObject const&) = delete;
+
+  // not movable (see comment in the implementation of Future::then)
+  FutureObject(FutureObject&&) = delete;
+  FutureObject& operator=(FutureObject&&) = delete;
+
+  Try<T>& valueTry() {
+    return *value_;
+  }
+
+  template <typename F>
+  void setContinuation(F func) {
+    if (continuation_) {
+      throw std::logic_error("setContinuation called twice");
+    }
+
+    if (value_.hasValue()) {
+      func(std::move(*value_));
+      delete this;
+    } else {
+      continuation_ = std::move(func);
+    }
+  }
+
+  void fulfil(Try<T>&& t) {
+    if (value_.hasValue()) {
+      throw std::logic_error("fulfil called twice");
+    }
+
+    if (continuation_) {
+      continuation_(std::move(t));
+      delete this;
+    } else {
+      value_ = std::move(t);
+    }
+  }
+
+  void setException(std::exception_ptr const& e) {
+    fulfil(Try<T>(e));
+  }
+
+  template <class E> void setException(E const& e) {
+    fulfil(Try<T>(std::make_exception_ptr<E>(e)));
+  }
+
+  bool ready() const {
+    return value_.hasValue();
+  }
+
+  typename std::add_lvalue_reference<T>::type value() {
+    return value_->value();
+  }
+
+ private:
+  folly::Optional<Try<T>> value_;
+  std::function<void(Try<T>&&)> continuation_;
+};
+
+template <typename... Ts>
+struct VariadicContext {
+  VariadicContext() : total(0), count(0) {}
+  Promise<std::tuple<Try<Ts>... > > p;
+  std::tuple<Try<Ts>... > results;
+  size_t total;
+  std::atomic<size_t> count;
+  typedef Future<std::tuple<Try<Ts>...>> type;
+};
+
+template <typename... Ts, typename THead, typename... Fs>
+typename std::enable_if<sizeof...(Fs) == 0, void>::type
+whenAllVariadicHelper(VariadicContext<Ts...> *ctx, THead& head, Fs&... tail) {
+  head.setContinuation([ctx](Try<typename THead::value_type>&& t) {
+    const size_t i = sizeof...(Ts) - sizeof...(Fs) - 1;
+    std::get<i>(ctx->results) = std::move(t);
+    if (++ctx->count == ctx->total) {
+      ctx->p.setValue(std::move(ctx->results));
+      delete ctx;
+    }
+  });
+}
+
+template <typename... Ts, typename THead, typename... Fs>
+typename std::enable_if<sizeof...(Fs) != 0, void>::type
+whenAllVariadicHelper(VariadicContext<Ts...> *ctx, THead& head, Fs&... tail) {
+  head.setContinuation([ctx](Try<typename THead::value_type>&& t) {
+    const size_t i = sizeof...(Ts) - sizeof...(Fs) - 1;
+    std::get<i>(ctx->results) = std::move(t);
+    if (++ctx->count == ctx->total) {
+      ctx->p.setValue(std::move(ctx->results));
+      delete ctx;
+    }
+  });
+  whenAllVariadicHelper(ctx, tail...); // recursive template tail call
+}
+
+template <typename T>
+struct WhenAllContext {
+  explicit WhenAllContext() : count(0), total(0) {}
+  Promise<std::vector<Try<T> > > p;
+  std::vector<Try<T> > results;
+  std::atomic<size_t> count;
+  size_t total;
+};
+
+template <typename T>
+struct WhenAnyContext {
+  explicit WhenAnyContext(size_t n) : done(false), ref_count(n) {};
+  Promise<std::pair<size_t, Try<T>>> p;
+  std::atomic<bool> done;
+  std::atomic<size_t> ref_count;
+  void decref() {
+    if (--ref_count == 0) {
+      delete this;
+    }
+  }
+};
+
+}}} // namespace
diff --git a/folly/wangle/test/FutureTest.cpp b/folly/wangle/test/FutureTest.cpp
new file mode 100644 (file)
index 0000000..cdc8f82
--- /dev/null
@@ -0,0 +1,571 @@
+/*
+ * 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 <algorithm>
+#include <folly/small_vector.h>
+#include <gtest/gtest.h>
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <unistd.h>
+#include "folly/wangle/Executor.h"
+#include "folly/wangle/Future.h"
+
+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<decltype(x), T>::value))
+
+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<A> t_a(std::move(a));
+
+  Try<void> t_void;
+
+  EXPECT_EQ(5, t_a.value().x());
+}
+
+TEST(Future, special) {
+  EXPECT_FALSE(std::is_copy_constructible<Future<int>>::value);
+  EXPECT_FALSE(std::is_copy_assignable<Future<int>>::value);
+  EXPECT_TRUE(std::is_move_constructible<Future<int>>::value);
+  EXPECT_TRUE(std::is_move_assignable<Future<int>>::value);
+}
+
+TEST(Future, then) {
+  bool flag = false;
+
+  makeFuture<int>(42).then([&](Try<int>&& t) {
+                              flag = true;
+                              EXPECT_EQ(42, t.value());
+                            });
+  EXPECT_TRUE(flag); flag = false;
+
+  makeFuture<int>(42)
+    .then([](Try<int>&& t) { return t.value(); })
+    .then([&](Try<int>&& t) { flag = true; EXPECT_EQ(42, t.value()); });
+  EXPECT_TRUE(flag); flag = false;
+
+  makeFuture().then([&](Try<void>&& t) { flag = true; t.value(); });
+  EXPECT_TRUE(flag); flag = false;
+
+  Promise<void> p;
+  auto f = p.getFuture().then([&](Try<void>&& t) { flag = true; });
+  EXPECT_FALSE(flag);
+  EXPECT_FALSE(f.isReady());
+  p.setValue();
+  EXPECT_TRUE(flag);
+  EXPECT_TRUE(f.isReady());
+}
+
+TEST(Future, value) {
+  auto f = makeFuture(unique_ptr<int>(new int(42)));
+  auto up = std::move(f.value());
+  EXPECT_EQ(42, *up);
+
+  EXPECT_THROW(makeFuture<int>(eggs).value(), eggs_t);
+}
+
+TEST(Future, isReady) {
+  Promise<int> p;
+  auto f = p.getFuture();
+  EXPECT_FALSE(f.isReady());
+  p.setValue(42);
+  EXPECT_TRUE(f.isReady());
+  }
+
+TEST(Future, hasException) {
+  EXPECT_TRUE(makeFuture<int>(eggs).valueTry().hasException());
+  EXPECT_FALSE(makeFuture(42).valueTry().hasException());
+}
+
+TEST(Future, hasValue) {
+  EXPECT_TRUE(makeFuture(42).valueTry().hasValue());
+  EXPECT_FALSE(makeFuture<int>(eggs).valueTry().hasValue());
+}
+
+TEST(Future, makeFuture) {
+  EXPECT_TYPE(makeFuture(42), Future<int>);
+  EXPECT_EQ(42, makeFuture(42).value());
+
+  EXPECT_TYPE(makeFuture<float>(42), Future<float>);
+  EXPECT_EQ(42, makeFuture<float>(42).value());
+
+  auto fun = [] { return 42; };
+  EXPECT_TYPE(makeFutureTry(fun), Future<int>);
+  EXPECT_EQ(42, makeFutureTry(fun).value());
+
+  auto failfun = []() -> int { throw eggs; };
+  EXPECT_TYPE(makeFutureTry(failfun), Future<int>);
+  EXPECT_THROW(makeFutureTry(failfun).value(), eggs_t);
+
+  EXPECT_TYPE(makeFuture(), Future<void>);
+}
+
+// Promise
+
+TEST(Promise, special) {
+  EXPECT_FALSE(std::is_copy_constructible<Promise<int>>::value);
+  EXPECT_FALSE(std::is_copy_assignable<Promise<int>>::value);
+  EXPECT_TRUE(std::is_move_constructible<Promise<int>>::value);
+  EXPECT_TRUE(std::is_move_assignable<Promise<int>>::value);
+}
+
+TEST(Promise, getFuture) {
+  Promise<int> p;
+  Future<int> f = p.getFuture();
+  EXPECT_FALSE(f.isReady());
+}
+
+TEST(Promise, setValue) {
+  Promise<int> fund;
+  auto ffund = fund.getFuture();
+  fund.setValue(42);
+  EXPECT_EQ(42, ffund.value());
+
+  struct Foo {
+    string name;
+    int value;
+  };
+
+  Promise<Foo> 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<Foo>();
+  fpod = pod.getFuture();
+  return;
+  pod.setValue(std::move(f2));
+  Foo f3 = fpod.value();
+  EXPECT_EQ(f.name, f3.name);
+  EXPECT_EQ(f.value, f3.value);
+  EXPECT_NE(f.name, f2.name);
+
+  return;
+
+  Promise<unique_ptr<int>> mov;
+  auto fmov = mov.getFuture();
+  mov.setValue(unique_ptr<int>(new int(42)));
+  unique_ptr<int> ptr = std::move(fmov.value());
+  EXPECT_EQ(42, *ptr);
+
+  Promise<void> v;
+  auto fv = v.getFuture();
+  v.setValue();
+  EXPECT_TRUE(fv.isReady());
+}
+
+TEST(Promise, setException) {
+  {
+    Promise<void> p;
+    auto f = p.getFuture();
+    p.setException(eggs);
+    EXPECT_THROW(f.value(), eggs_t);
+  }
+  {
+    Promise<void> p;
+    auto f = p.getFuture();
+    try {
+      throw eggs;
+    } catch (...) {
+      p.setException(std::current_exception());
+    }
+    EXPECT_THROW(f.value(), eggs_t);
+  }
+}
+
+TEST(Promise, fulfil) {
+  {
+    Promise<int> p;
+    auto f = p.getFuture();
+    p.fulfil([] { return 42; });
+    EXPECT_EQ(42, f.value());
+  }
+  {
+    Promise<int> p;
+    auto f = p.getFuture();
+    p.fulfil([]() -> int { throw eggs; });
+    EXPECT_THROW(f.value(), eggs_t);
+  }
+}
+
+TEST(Future, finish) {
+  auto x = std::make_shared<int>(0);
+  Promise<int> p;
+  auto f = p.getFuture().then([x](Try<int>&& t) { *x = t.value(); });
+
+  // The continuation hasn't executed
+  EXPECT_EQ(0, *x);
+
+  // The continuation has a reference to x
+  EXPECT_EQ(2, x.use_count());
+
+  p.setValue(42);
+
+  // the continuation has executed
+  EXPECT_EQ(42, *x);
+
+  // the continuation has been destructed
+  // and has released its reference to x
+  EXPECT_EQ(1, x.use_count());
+}
+
+TEST(Future, unwrap) {
+  Promise<int> a;
+  Promise<int> 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<int> f = fa.then([&](Try<int>&& ta) {
+    auto va = ta.value();
+    flag1 = true;
+    return fb.then([va, &flag2](Try<int>&& 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<Promise<int>> promises(10);
+    vector<Future<int>> 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<Promise<int>> promises(4);
+    vector<Future<int>> 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.valueTry().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<Promise<void>> promises(10);
+    vector<Future<void>> futures;
+
+    for (auto& p : promises)
+      futures.push_back(p.getFuture());
+
+    auto allf = whenAll(futures.begin(), futures.end())
+      .then([](Try<vector<Try<void>>>&& 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<Promise<int>> promises(10);
+    vector<Future<int>> 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<Promise<void>> promises(10);
+    vector<Future<void>> 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<Promise<int>> promises(10);
+    vector<Future<int>> futures;
+
+    for (auto& p : promises)
+      futures.push_back(p.getFuture());
+
+    auto anyf = whenAny(futures.begin(), futures.end())
+      .then([](Try<pair<size_t, Try<int>>>&& f) {
+        EXPECT_EQ(42, f.value().second.value());
+      });
+
+    promises[3].setValue(42);
+    EXPECT_TRUE(anyf.isReady());
+  }
+}
+
+
+TEST(when, already_completed) {
+  {
+    vector<Future<void>> fs;
+    for (int i = 0; i < 10; i++)
+      fs.push_back(makeFuture());
+
+    whenAll(fs.begin(), fs.end())
+      .then([&](Try<vector<Try<void>>>&& t) {
+        EXPECT_EQ(fs.size(), t.value().size());
+      });
+  }
+  {
+    vector<Future<int>> fs;
+    for (int i = 0; i < 10; i++)
+      fs.push_back(makeFuture(i));
+
+    whenAny(fs.begin(), fs.end())
+      .then([&](Try<pair<size_t, Try<int>>>&& t) {
+        auto& p = t.value();
+        EXPECT_EQ(p.first, p.second.value());
+      });
+  }
+}
+
+TEST(when, whenN) {
+  vector<Promise<void>> promises(10);
+  vector<Future<void>> 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<vector<pair<size_t, Try<void>>>>&& 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) {
+  using folly::small_vector;
+  {
+    small_vector<Future<void>> futures;
+
+    for (int i = 0; i < 10; i++)
+      futures.push_back(makeFuture());
+
+    auto anyf = whenAny(futures.begin(), futures.end());
+  }
+
+  {
+    small_vector<Future<void>> futures;
+
+    for (int i = 0; i < 10; i++)
+      futures.push_back(makeFuture());
+
+    auto allf = whenAll(futures.begin(), futures.end());
+  }
+}
+
+TEST(Future, wait) {
+  Promise<void> p;
+  auto f = p.getFuture();
+  auto t = std::thread([&] {
+    std::this_thread::sleep_for(std::chrono::microseconds(10));
+    p.setValue();
+  });
+
+  f.wait();
+
+  EXPECT_TRUE(f.isReady());
+
+  t.join();
+}
+
+TEST(Future, whenAllVariadic) {
+  Promise<bool> pb;
+  Promise<int> pi;
+  Future<bool> fb = pb.getFuture();
+  Future<int> fi = pi.getFuture();
+  bool flag = false;
+  whenAll(fb, fi)
+    .then([&](Try<std::tuple<Try<bool>, Try<int>>>&& 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<Future<int>> 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<void>&&) -> int { throw std::exception(); });
+  makeFuture().then(
+    [=](Try<void>&&) -> Future<int> { throw std::exception(); });
+}
+
+TEST(Future, throwIfFailed) {
+  makeFuture<void>(eggs)
+    .then([=](Try<void>&& t) {
+      EXPECT_THROW(t.throwIfFailed(), eggs_t);
+    });
+  makeFuture()
+    .then([=](Try<void>&& t) {
+      EXPECT_NO_THROW(t.throwIfFailed());
+    });
+
+  makeFuture<int>(eggs)
+    .then([=](Try<int>&& t) {
+      EXPECT_THROW(t.throwIfFailed(), eggs_t);
+    });
+  makeFuture<int>(42)
+    .then([=](Try<int>&& t) {
+      EXPECT_NO_THROW(t.throwIfFailed());
+    });
+}
diff --git a/folly/wangle/test/LaterTest.cpp b/folly/wangle/test/LaterTest.cpp
new file mode 100644 (file)
index 0000000..37d0a4b
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * 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 <gtest/gtest.h>
+#include <thread>
+
+#include "folly/wangle/ManualExecutor.h"
+#include "folly/wangle/InlineExecutor.h"
+#include "folly/wangle/Later.h"
+
+using namespace folly::wangle;
+
+struct ManualWaiter {
+  explicit ManualWaiter(std::shared_ptr<ManualExecutor> ex) : ex(ex) {}
+
+  void makeProgress() {
+    ex->wait();
+    ex->run();
+  }
+
+  std::shared_ptr<ManualExecutor> ex;
+};
+
+struct LaterFixture : public testing::Test {
+  LaterFixture() :
+    westExecutor(new ManualExecutor),
+    eastExecutor(new ManualExecutor),
+    waiter(new ManualWaiter(westExecutor)),
+    done(false)
+  {
+    t = std::thread([=] {
+      ManualWaiter eastWaiter(eastExecutor);
+      while (!done)
+        eastWaiter.makeProgress();
+      });
+  }
+
+  ~LaterFixture() {
+    done = true;
+    eastExecutor->add([=]() { });
+    t.join();
+  }
+
+  Later<void> later;
+  std::shared_ptr<ManualExecutor> westExecutor;
+  std::shared_ptr<ManualExecutor> eastExecutor;
+  std::shared_ptr<ManualWaiter> waiter;
+  InlineExecutor inlineExecutor;
+  bool done;
+  std::thread t;
+};
+
+TEST(Later, construct_and_launch) {
+  bool fulfilled = false;
+  auto later = Later<void>().then([&](Try<void>&& t) {
+    fulfilled = true;
+    return makeFuture<int>(1);
+  });
+
+  // has not started yet.
+  EXPECT_FALSE(fulfilled);
+
+  EXPECT_EQ(later.launch().value(), 1);
+  EXPECT_TRUE(fulfilled);
+}
+
+TEST(Later, then_value) {
+  auto future = Later<int>(std::move(1))
+    .then([](Try<int>&& t) {
+      return t.value() == 1;
+    })
+    .launch();
+
+  EXPECT_TRUE(future.value());
+}
+
+TEST(Later, then_future) {
+  auto future = Later<int>(1)
+    .then([](Try<int>&& t) {
+      return makeFuture(t.value() == 1);
+    })
+    .launch();
+  EXPECT_TRUE(future.value());
+}
+
+TEST_F(LaterFixture, thread_hops) {
+  auto westThreadId = std::this_thread::get_id();
+  auto future = later.via(eastExecutor.get()).then([=](Try<void>&& t) {
+    EXPECT_NE(std::this_thread::get_id(), westThreadId);
+    return makeFuture<int>(1);
+  }).via(westExecutor.get()
+  ).then([=](Try<int>&& t) {
+    EXPECT_EQ(std::this_thread::get_id(), westThreadId);
+    return t.value();
+  }).launch();
+  while (!future.isReady()) {
+    waiter->makeProgress();
+  }
+  EXPECT_EQ(future.value(), 1);
+}
+
+TEST_F(LaterFixture, chain_laters) {
+  auto westThreadId = std::this_thread::get_id();
+  auto future = later.via(eastExecutor.get()).then([=](Try<void>&& t) {
+    EXPECT_NE(std::this_thread::get_id(), westThreadId);
+    return makeFuture<int>(1);
+  }).then([=](Try<int>&& t) {
+    int val = t.value();
+    return Later<int>(std::move(val)).via(westExecutor.get())
+      .then([=](Try<int>&& t) mutable {
+        EXPECT_EQ(std::this_thread::get_id(), westThreadId);
+        return t.value();
+      });
+  }).then([=](Try<int>&& t) {
+    EXPECT_EQ(std::this_thread::get_id(), westThreadId);
+    return t.value();
+  }).launch();
+
+  while (!future.isReady()) {
+    waiter->makeProgress();
+  }
+  EXPECT_EQ(future.value(), 1);
+}
+
+TEST_F(LaterFixture, fire_and_forget) {
+  auto west = westExecutor.get();
+  later.via(eastExecutor.get()).then([=](Try<void>&& t) {
+    west->add([]() {});
+  }).fireAndForget();
+  waiter->makeProgress();
+}
diff --git a/folly/wangle/test/ThreadGateTest.cpp b/folly/wangle/test/ThreadGateTest.cpp
new file mode 100644 (file)
index 0000000..ef434ac
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * 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 <gtest/gtest.h>
+#include <thread>
+#include <future>
+
+#include "folly/wangle/Executor.h"
+#include "folly/wangle/ManualExecutor.h"
+#include "folly/wangle/ThreadGate.h"
+#include "folly/wangle/GenericThreadGate.h"
+
+using namespace folly::wangle;
+using std::make_shared;
+using std::shared_ptr;
+using std::thread;
+using std::vector;
+
+struct ManualWaiter {
+  explicit ManualWaiter(shared_ptr<ManualExecutor> ex) : ex(ex) {}
+
+  void makeProgress() {
+    ex->wait();
+    ex->run();
+  }
+
+  shared_ptr<ManualExecutor> ex;
+};
+
+struct GenericThreadGateFixture : public testing::Test {
+  GenericThreadGateFixture() :
+    westExecutor(new ManualExecutor),
+    eastExecutor(new ManualExecutor),
+    waiter(new ManualWaiter(westExecutor)),
+    tg(westExecutor, eastExecutor, waiter),
+    done(false)
+  {
+    t = thread([=] {
+      ManualWaiter eastWaiter(eastExecutor);
+      while (!done)
+        eastWaiter.makeProgress();
+    });
+  }
+
+  ~GenericThreadGateFixture() {
+    done = true;
+    tg.gate<void>([] { return makeFuture(); });
+    t.join();
+  }
+
+  shared_ptr<ManualExecutor> westExecutor;
+  shared_ptr<ManualExecutor> eastExecutor;
+  shared_ptr<ManualWaiter> waiter;
+  GenericThreadGate<
+    shared_ptr<ManualExecutor>,
+    shared_ptr<ManualExecutor>,
+    shared_ptr<ManualWaiter>> tg;
+  bool done;
+  thread t;
+};
+
+TEST_F(GenericThreadGateFixture, gate_and_wait) {
+  auto f = tg.gate<void>([] { return makeFuture(); });
+  EXPECT_FALSE(f.isReady());
+
+  tg.waitFor(f);
+  EXPECT_TRUE(f.isReady());
+}
+
+TEST_F(GenericThreadGateFixture, gate_many) {
+  vector<Future<void>> fs;
+  int n = 10;
+
+  for (int i = 0; i < n; i++)
+    fs.push_back(tg.gate<void>([&] { return makeFuture(); }));
+
+  for (auto& f : fs)
+    EXPECT_FALSE(f.isReady());
+
+  auto all = whenAll(fs.begin(), fs.end());
+  tg.waitFor(all);
+}
+
+TEST_F(GenericThreadGateFixture, gate_alternating) {
+  vector<Promise<void>> ps(10);
+  vector<Future<void>> fs;
+  size_t count = 0;
+
+  for (auto& p : ps) {
+    auto* pp = &p;
+    auto f = tg.gate<void>([=] { return pp->getFuture(); });
+
+    // abuse the thread gate to do our dirty work in the other thread
+    tg.gate<void>([=] { pp->setValue(); return makeFuture(); });
+
+    fs.push_back(f.then([&](Try<void>&&) { count++; }));
+  }
+
+  for (auto& f : fs)
+    EXPECT_FALSE(f.isReady());
+  EXPECT_EQ(0, count);
+
+  auto all = whenAll(fs.begin(), fs.end());
+  tg.waitFor(all);
+
+  EXPECT_EQ(ps.size(), count);
+}
+
+TEST(GenericThreadGate, noWaiter) {
+  auto west = make_shared<ManualExecutor>();
+  auto east = make_shared<ManualExecutor>();
+  Promise<void> p;
+  auto dummyFuture = p.getFuture();
+
+  GenericThreadGate<ManualExecutor*, ManualExecutor*>
+    tg(west.get(), east.get());
+
+  EXPECT_THROW(tg.waitFor(dummyFuture), std::logic_error);
+}
+
+TEST_F(GenericThreadGateFixture, gate_with_promise) {
+  Promise<int> p;
+
+  auto westId = std::this_thread::get_id();
+  bool westThenCalled = false;
+  auto f = p.getFuture().then(
+    [westId, &westThenCalled](Try<int>&& t) {
+      EXPECT_EQ(t.value(), 1);
+      EXPECT_EQ(std::this_thread::get_id(), westId);
+      westThenCalled = true;
+      return t.value();
+    });
+
+  bool eastPromiseMade = false;
+  std::async(std::launch::async, [&p, &eastPromiseMade, this]() {
+    // South thread != west thread. p gets set in west thread.
+    tg.gate<int>([&p, &eastPromiseMade, this] {
+                   EXPECT_EQ(t.get_id(), std::this_thread::get_id());
+                   Promise<int> eastPromise;
+                   auto eastFuture = eastPromise.getFuture();
+                   eastPromise.setValue(1);
+                   eastPromiseMade = true;
+                   return eastFuture;
+                 },
+                 std::move(p));
+    });
+
+  tg.waitFor(f);
+  EXPECT_TRUE(westThenCalled);
+  EXPECT_TRUE(eastPromiseMade);
+  EXPECT_EQ(f.value(), 1);
+}
diff --git a/folly/wangle/test/main.cpp b/folly/wangle/test/main.cpp
new file mode 100644 (file)
index 0000000..7dbf27d
--- /dev/null
@@ -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 <gtest/gtest.h>
+
+int main(int argc, char** argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}