Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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.