/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#include <chrono>
#include <thread>
#include "ErrorList.h"
#include "gtest/gtest.h"
#include "mozilla/MozPromise.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "nsISupportsImpl.h"
#include "nsThreadUtils.h"
using namespace mozilla;
// Simple function to be able to distinguish threads in output
size_t tid() {
return std::hash<std::thread::id>{}(std::this_thread::get_id());
}
// Invoking something on a background thread, but getting the completion on the
// main thread.
TEST(MozPromiseExamples, InvokeAsync)
{
bool done =
false;
InvokeAsync(
GetCurrentSerialEventTarget(), __func__,
[]() {
printf(
"[%zu] Doing some work on a background thread...\n", tid());
std::this_thread::sleep_for(std::chrono::milliseconds(100));
printf(
"[%zu] Done...\n", tid());
// Simulate various outcomes:
srand(getpid());
switch (rand() % 4) {
case 0:
return GenericPromise::CreateAndResolve(
true, __func__);
case 1:
return GenericPromise::CreateAndResolve(
false, __func__);
case 2:
return GenericPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY,
__func__);
default:
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
})
->Then(
GetMainThreadSerialEventTarget(), __func__,
[&done](GenericPromise::ResolveOrRejectValue&& aResult) {
if (aResult.IsReject()) {
printf(
"[%zu] Back on the main thread, the task failed: 0x%x\n",
tid(), (
unsigned int)aResult.RejectValue());
done =
true;
return;
}
printf(
"[%zu] back on the main thread, sucess, return value: %s\n",
tid(), aResult.ResolveValue() ?
"true" :
"false");
done =
true;
});
// Process all events and check that `done` was effectively set to true. This
// is just for the purpose of this test.
MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
"xpcom:TEST(MozPromiseExamples, OneOff)"_ns, [&done]() {
return done; }));
EXPECT_TRUE(done);
}
class Something final {
public:
explicit Something(uint32_t aMilliseconds = 100)
: mMilliseconds(aMilliseconds) {}
RefPtr<GenericPromise> DoIt() {
// Do no dispatch the async task twice if still underway.
if (mPromise) {
return mPromise;
}
mPromise = mHolder.Ensure(__func__);
// Kick off some work to another thread...
std::thread([self = RefPtr{
this},
this] {
printf(
"[%zu] Working...\n", tid());
std::this_thread::sleep_for(std::chrono::milliseconds(mMilliseconds));
printf(
"[%zu] Resolving from background thread\n", tid());
self->mHolder.Resolve(
true, __func__);
}).detach();
return mPromise;
}
private:
~Something() =
default;
const uint32_t mMilliseconds;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Something)
RefPtr<GenericPromise> mPromise;
MozPromiseHolder<GenericPromise> mHolder{};
};
// Waiting for something asynchronous to complete, from outside the instance
TEST(MozPromiseExamples, OneOff)
{
RefPtr<Something> thing(
new Something);
bool done =
false;
thing->DoIt()->Then(
GetCurrentSerialEventTarget(), __func__,
[&done, thing](
bool aResult) {
printf(
"[%zu] Success: %s\n", tid(), aResult ?
"true" :
"false");
done =
true;
},
[&done](nsresult aError) {
printf(
"[%zu] Failure: 0x%x\n", tid(), (
unsigned)aError);
done =
true;
});
// Process all events and check that `done` was effectively set to true. This
// is just for the purpose of this test.
MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
"xpcom:TEST(MozPromiseExamples, OneOff)"_ns, [&done]() {
return done; }));
}
class SomethingCancelable final {
public:
RefPtr<GenericPromise> DoIt() {
if (mPromise) {
return mPromise;
}
mPromise = mHolder.Ensure(__func__);
// Kick off some work to another thread...
std::thread([self = RefPtr{
this}] {
printf(
"[%zu] Working...\n", tid());
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// This is printed: despite being canceled, the thread runs normally and
// resolves its promise.
printf(
"[%zu] Resolving from background thread\n", tid());
self->mHolder.Resolve(
true, __func__);
}).detach();
return mPromise;
}
private:
~SomethingCancelable() =
default;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SomethingCancelable)
MozPromiseHolder<GenericPromise> mHolder{};
RefPtr<GenericPromise> mPromise;
MozPromiseRequestHolder<GenericPromise> mRequest;
};
// Kick of an asynchronous job, and cancel it
TEST(MozPromiseExamples, OneOffCancelable)
{
RefPtr<SomethingCancelable> thing(
new SomethingCancelable);
// Start a job that takes 100ms
MozPromiseRequestHolder<GenericPromise> holder;
thing->DoIt()
->Then(GetCurrentSerialEventTarget(), __func__,
[&holder] {
holder.Complete();
// This is never printed: in this example we disconnect the
// request before completion.
printf(
"[%zu] Async work finished", tid());
})
->Track(holder);
// But cancel it after just 10ms
std::this_thread::sleep_for(std::chrono::milliseconds(10));
holder.Disconnect();
}
// Waiting for multiple asynchronous tasks to complete, from outside the
// instance
TEST(MozPromiseExamples, MultipleWaits)
{
nsTArray<RefPtr<Something>> things;
uint32_t count = 10;
while (count--) {
things.AppendElement(
new Something(count * 10));
}
bool done =
false;
nsTArray<RefPtr<GenericPromise>> promises;
for (
auto& thing : things) {
promises.AppendElement(thing->DoIt());
}
GenericPromise::All(GetCurrentSerialEventTarget(), promises)
->Then(
GetCurrentSerialEventTarget(), __func__,
[&done](nsTArray<
bool>&& aResults) {
nsCString results;
for (
auto b : aResults) {
results.AppendPrintf(
"%s, ", b ?
"true" :
"false");
}
printf(
"[%zu] All succeeded: %s\n", tid(), results.get());
done =
true;
},
[&done](nsresult aError) {
printf(
"[%zu] One failed: 0x%x\n", tid(), (
unsigned)aError);
done =
true;
});
// Process all events and check that `done` was effectively set to true. This
// is just for the purpose of this test.
MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
"xpcom:TEST(MozPromiseExamples, OneOff)"_ns, [&done]() {
return done; }));
}
RefPtr<GenericPromise> SyncOperation(uint32_t aConstraint) {
printf(
"[%zu] SyncOperation(%" PRIu32
")\n", tid(), aConstraint);
if (aConstraint > 5) {
return GenericPromise::CreateAndReject(NS_ERROR_UNEXPECTED, __func__);
}
return GenericPromise::CreateAndResolve(
true, __func__);
}
// This test uses various MozPromise facilities and prints a message to the
// console, to show how the scheduling works.
TEST(MozPromiseExamples, SyncReturn)
{
bool done =
false;
// Dispatch a runnable to the current even loop, for the sole purpose of
// understanding ordering.
NS_DispatchToCurrentThread(NS_NewRunnableFunction(
"Initial runnable", [] {
printf(
"[%zu] Dispatched before sync promise operation\n", tid());
}));
// SyncOperation synchronously returns a resolved promise. However, `Then`
// works by dispatching so the printf will happen after InitialRunnable.
SyncOperation(3)->Then(
GetCurrentSerialEventTarget(), __func__,
[](
bool aResult) {
printf(
"[%zu] Sync promise value: %s\n", tid(),
aResult ?
"true" :
"false");
},
[](nsresult aError) {
printf(
"[%zu] Error: 0x%x\n", tid(), (
unsigned)aError);
});
// Now call the same method, but invoke it async on the current event queue.
// The resolve will also be in its own event loop task. It follows that this
// will be printed after the "Final Runnable" below.
// MozPromise can be put in tail dispatch mode,or sync mode, and in those
// cases, the ordering will be different.
InvokeAsync(GetCurrentSerialEventTarget(), __func__,
[]() {
return SyncOperation(4); })
->Then(
GetCurrentSerialEventTarget(), __func__,
[&done](
bool aResult) {
printf(
"[%zu] Sync promise value (InvokeAsync): %s\n", tid(),
aResult ?
"true" :
"false");
done =
true;
},
[](nsresult aError) {
printf(
"[%zu] Error (InvokeAsync): 0x%x\n", tid(),
(
unsigned)aError);
});
// Dispatch a runnable to the current even loop, for the sole purpose of
// understanding ordering.
NS_DispatchToCurrentThread(NS_NewRunnableFunction(
"Final runnable", [] {
printf(
"[%zu] Dispatched after sync promise operation\n", tid());
}));
// The output will be as such (omitting the thread ids):
// [...] SyncOperation(3)
// [...] Dispatched before sync promise operation
// [...] Sync promise value: true
// [...] SyncOperation(4)
// [...] Dispatched after sync promise operation
// [...] Sync promise value (InvokeAsync): true
// Process all events and check that `done` was effectively set to true. This
// is just for the purpose of this test.
MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
"xpcom:TEST(MozPromiseExamples, OneOff)"_ns, [&done]() {
return done; }));
}
using IntPromise = MozPromise<
int, nsresult,
true>;
using UintPromise = MozPromise<
unsigned, nsresult,
true>;
class SomethingSync {
public:
RefPtr<GenericPromise> DoSomethingSync() {
return GenericPromise::CreateAndResolve(
true,
"Returning true");
}
};
TEST(MozPromiseExamples, Chaining)
{
bool done =
false;
SomethingSync s;
// Do something that returns a bool, then chain it to a promise that returns
// an int, then to a promise that returns an unsigned.
s.DoSomethingSync()
->Then(GetCurrentSerialEventTarget(), __func__,
[](GenericPromise::ResolveOrRejectValue&& aValue) {
if (aValue.IsResolve()) {
// Depending on the value of the bool, find the proper signed
// integer value.
return IntPromise::CreateAndResolve(
aValue.ResolveValue() ? 3 : 5,
"Example IntPromise Resolver");
}
return IntPromise::CreateAndReject(
aValue.RejectValue(),
"Example IntPromise Rejecter");
})
->Then(GetCurrentSerialEventTarget(), __func__,
[&done](IntPromise::ResolveOrRejectValue&& aValue) {
if (aValue.IsResolve()) {
// Depending on the value of the signed integer, find the
// proper unsigned integer value.
done =
true;
return UintPromise::CreateAndResolve(
static_cast<
unsigned>(aValue.ResolveValue()),
"Example UintPromise Resolver");
}
return UintPromise::CreateAndReject(
aValue.RejectValue(),
"Example UintPromise Rejecter");
});
// Process all events and check that `done` was effectively set to true. This
// is just for the purpose of this test.
MOZ_ALWAYS_TRUE(
SpinEventLoopUntil(
"xpcom:TEST(MozPromiseExamples, Chaining)"_ns,
[&done]() {
return done; }));
}
// - converting an async legacy callback interface to a modern MozPromise
// version with MozPromiseHolder.