Bug 1313497 - InvokeAsync taking a lambda - r?froydnj draft
authorGerald Squelart <gsquelart@mozilla.com>
Sun, 13 Nov 2016 11:13:07 +1100
changeset 442662 7c6a16da6e345e9ff913f2d23ed5307de1fc9303
parent 442661 f2c53fd7a452d2074f0d2767d425de821e7ec736
child 442663 a0b2f7c231c661312a4da5ce420db870dfe03c4e
push id36776
push usergsquelart@mozilla.com
push dateWed, 23 Nov 2016 01:03:12 +0000
reviewersfroydnj
bugs1313497
milestone53.0a1
Bug 1313497 - InvokeAsync taking a lambda - r?froydnj This new InvokeAsync overload takes a single lambda (or any function object). This is most useful when a method call is not strictly necessary. Avoid obvious copies by refusing lvalue-references. (If one day this is really needed, the implementation is already there, hidden inside `namespace detail`). MozReview-Commit-ID: 57gPBz9kO1q
xpcom/threads/MozPromise.h
--- a/xpcom/threads/MozPromise.h
+++ b/xpcom/threads/MozPromise.h
@@ -894,24 +894,35 @@ public:
   }
 
   bool Exists() const { return !!mRequest; }
 
 private:
   RefPtr<typename PromiseType::Request> mRequest;
 };
 
+template <typename Return>
+struct IsMozPromise
+  : FalseType
+{};
+
+template<typename ResolveValueT, typename RejectValueT, bool IsExclusive>
+struct IsMozPromise<MozPromise<ResolveValueT, RejectValueT, IsExclusive>>
+  : TrueType
+{};
+
 // Asynchronous Potentially-Cross-Thread Method Calls.
 //
-// This machinery allows callers to schedule a promise-returning method to be
-// invoked asynchronously on a given thread, while at the same time receiving
-// a promise upon which to invoke Then() immediately. InvokeAsync dispatches
-// a task to invoke the method on the proper thread and also chain the resulting
-// promise to the one that the caller received, so that resolve/reject values
-// are forwarded through.
+// This machinery allows callers to schedule a promise-returning function
+// (a method and object, or a function object like a lambda) to be invoked
+// asynchronously on a given thread, while at the same time receiving a
+// promise upon which to invoke Then() immediately. InvokeAsync dispatches a
+// task to invoke the function on the proper thread and also chain the
+// resulting promise to the one that the caller received, so that resolve/
+// reject values are forwarded through.
 
 namespace detail {
 
 // Non-templated base class to allow us to use MOZ_COUNT_{C,D}TOR, which cause
 // assertions when used on templated types.
 class MethodCallBase
 {
 public:
@@ -1043,13 +1054,91 @@ InvokeAsync(AbstractThread* aTarget, Thi
                 "Cannot pass reference/pointer types through InvokeAsync, Storages must be provided");
   static_assert(sizeof...(ArgTypes) == sizeof...(ActualArgTypes),
                 "Method's ArgTypes and ActualArgTypes should have equal sizes");
   return detail::InvokeAsyncImpl<StoreCopyPassByRRef<ArgTypes>...>(
            aTarget, aThisVal, aCallerName, aMethod,
            Forward<ActualArgTypes>(aArgs)...);
 }
 
+namespace detail {
+
+template<typename Function, typename PromiseType>
+class ProxyFunctionRunnable : public Runnable
+{
+  typedef typename Decay<Function>::Type FunctionStorage;
+public:
+  template <typename F>
+  ProxyFunctionRunnable(typename PromiseType::Private* aProxyPromise,
+                        F&& aFunction)
+    : mProxyPromise(aProxyPromise)
+    , mFunction(new FunctionStorage(Forward<F>(aFunction))) {}
+
+  NS_IMETHOD Run() override
+  {
+    RefPtr<PromiseType> p = (*mFunction)();
+    mFunction = nullptr;
+    p->ChainTo(mProxyPromise.forget(), "<Proxy Promise>");
+    return NS_OK;
+  }
+
+private:
+  RefPtr<typename PromiseType::Private> mProxyPromise;
+  UniquePtr<FunctionStorage> mFunction;
+};
+
+// Note: The following struct and function are not for public consumption (yet?)
+// as we would prefer all calls to pass on-the-spot lambdas (or at least moved
+// function objects). They could be moved outside of detail if really needed.
+
+// We prefer getting function objects by non-lvalue-ref (to avoid copying them
+// and their captures). This struct is a tag that allows the use of objects
+// through lvalue-refs where necessary.
+struct AllowInvokeAsyncFunctionLVRef {};
+
+// Invoke a function object (e.g., lambda or std/mozilla::function)
+// asynchronously; note that the object will be copied if provided by lvalue-ref.
+// Return a promise that the function should eventually resolve or reject.
+template<typename Function>
+static auto
+InvokeAsync(AbstractThread* aTarget, const char* aCallerName,
+            AllowInvokeAsyncFunctionLVRef, Function&& aFunction)
+  -> decltype(aFunction())
+{
+  static_assert(IsRefcountedSmartPointer<decltype(aFunction())>::value
+                && IsMozPromise<typename RemoveSmartPointer<
+                                           decltype(aFunction())>::Type>::value,
+                "Function object must return RefPtr<MozPromise>");
+  typedef typename RemoveSmartPointer<decltype(aFunction())>::Type PromiseType;
+  typedef detail::ProxyFunctionRunnable<Function, PromiseType> ProxyRunnableType;
+
+  RefPtr<typename PromiseType::Private> p =
+    new (typename PromiseType::Private)(aCallerName);
+  RefPtr<ProxyRunnableType> r =
+    new ProxyRunnableType(p, Forward<Function>(aFunction));
+  MOZ_ASSERT(aTarget->IsDispatchReliable());
+  aTarget->Dispatch(r.forget());
+  return p.forget();
+}
+
+} // namespace detail
+
+// Invoke a function object (e.g., lambda) asynchronously.
+// Return a promise that the function should eventually resolve or reject.
+template<typename Function>
+static auto
+InvokeAsync(AbstractThread* aTarget, const char* aCallerName,
+            Function&& aFunction)
+  -> decltype(aFunction())
+{
+  static_assert(!IsLvalueReference<Function>::value,
+                "Function object must not be passed by lvalue-ref (to avoid "
+                "unplanned copies); Consider move()ing the object.");
+  return detail::InvokeAsync(aTarget, aCallerName,
+                             detail::AllowInvokeAsyncFunctionLVRef(),
+                             Forward<Function>(aFunction));
+}
+
 #undef PROMISE_LOG
 
 } // namespace mozilla
 
 #endif