diff --git a/xpcom/tests/gtest/TestThreadUtils.cpp b/xpcom/tests/gtest/TestThreadUtils.cpp index 86c2f15c8a4f..ddad8bdfea9e 100644 --- a/xpcom/tests/gtest/TestThreadUtils.cpp +++ b/xpcom/tests/gtest/TestThreadUtils.cpp @@ -5,6 +5,7 @@ #include "nsComponentManagerUtils.h" #include "nsThreadUtils.h" #include "mozilla/IdleTaskRunner.h" +#include "mozilla/RefCounted.h" #include "mozilla/UniquePtr.h" #include "gtest/gtest.h" @@ -186,6 +187,10 @@ struct TestCopyMove { int* mMoveCounter; }; +struct TestRefCounted : RefCounted { + MOZ_DECLARE_REFCOUNTED_TYPENAME(TestRefCounted); +}; + static void Expect(const char* aContext, int aCounter, int aMaxExpected) { EXPECT_LE(aCounter, aMaxExpected) << aContext; } @@ -198,24 +203,44 @@ static void ExpectRunnableName(Runnable* aRunnable, const char* aExpectedName) { #endif } -static void TestNewRunnableFunction(bool aNamed) { - // Test NS_NewRunnableFunction with copyable-only function object. +struct BasicRunnableFactory { + static constexpr bool SupportsCopyWithDeletedMove = true; + + template + static auto Create(const char* aName, Function&& aFunc) { + return NS_NewRunnableFunction(aName, std::forward(aFunc)); + } +}; + +struct CancelableRunnableFactory { + static constexpr bool SupportsCopyWithDeletedMove = false; + + template + static auto Create(const char* aName, Function&& aFunc) { + return NS_NewCancelableRunnableFunction(aName, + std::forward(aFunc)); + } +}; + +template +static void TestRunnableFactory(bool aNamed) { + // Test RunnableFactory with copyable-only function object. { int copyCounter = 0; { nsCOMPtr trackedRunnable; { TestCopyWithNoMove tracker(©Counter); - trackedRunnable = - aNamed ? NS_NewRunnableFunction("unused", tracker) - : NS_NewRunnableFunction("TestNewRunnableFunction", tracker); + trackedRunnable = aNamed ? RunnableFactory::Create("unused", tracker) + : RunnableFactory::Create( + "TestNewRunnableFunction", tracker); // Original 'tracker' is destroyed here. } // Verify that the runnable contains a non-destroyed function object. trackedRunnable->Run(); } Expect( - "NS_NewRunnableFunction with copyable-only (and no move) function, " + "RunnableFactory with copyable-only (and no move) function, " "copies", copyCounter, 1); } @@ -227,37 +252,37 @@ static void TestNewRunnableFunction(bool aNamed) { // Passing as rvalue, but using copy. // (TestCopyWithDeletedMove wouldn't allow this.) trackedRunnable = - aNamed ? NS_NewRunnableFunction("unused", - TestCopyWithNoMove(©Counter)) - : NS_NewRunnableFunction("TestNewRunnableFunction", - TestCopyWithNoMove(©Counter)); + aNamed ? RunnableFactory::Create("unused", + TestCopyWithNoMove(©Counter)) + : RunnableFactory::Create("TestNewRunnableFunction", + TestCopyWithNoMove(©Counter)); } trackedRunnable->Run(); } Expect( - "NS_NewRunnableFunction with copyable-only (and no move) function " + "RunnableFactory with copyable-only (and no move) function " "rvalue, copies", copyCounter, 1); } - { + if constexpr (RunnableFactory::SupportsCopyWithDeletedMove) { int copyCounter = 0; { nsCOMPtr trackedRunnable; { TestCopyWithDeletedMove tracker(©Counter); - trackedRunnable = - aNamed ? NS_NewRunnableFunction("unused", tracker) - : NS_NewRunnableFunction("TestNewRunnableFunction", tracker); + trackedRunnable = aNamed ? RunnableFactory::Create("unused", tracker) + : RunnableFactory::Create( + "TestNewRunnableFunction", tracker); } trackedRunnable->Run(); } Expect( - "NS_NewRunnableFunction with copyable-only (and deleted move) " + "RunnableFactory with copyable-only (and deleted move) " "function, copies", copyCounter, 1); } - // Test NS_NewRunnableFunction with movable-only function object. + // Test RunnableFactory with movable-only function object. { int moveCounter = 0; { @@ -265,14 +290,13 @@ static void TestNewRunnableFunction(bool aNamed) { { TestMove tracker(&moveCounter); trackedRunnable = - aNamed ? NS_NewRunnableFunction("unused", std::move(tracker)) - : NS_NewRunnableFunction("TestNewRunnableFunction", - std::move(tracker)); + aNamed ? RunnableFactory::Create("unused", std::move(tracker)) + : RunnableFactory::Create("TestNewRunnableFunction", + std::move(tracker)); } trackedRunnable->Run(); } - Expect("NS_NewRunnableFunction with movable-only function, moves", - moveCounter, 1); + Expect("RunnableFactory with movable-only function, moves", moveCounter, 1); } { int moveCounter = 0; @@ -280,17 +304,17 @@ static void TestNewRunnableFunction(bool aNamed) { nsCOMPtr trackedRunnable; { trackedRunnable = - aNamed ? NS_NewRunnableFunction("unused", TestMove(&moveCounter)) - : NS_NewRunnableFunction("TestNewRunnableFunction", - TestMove(&moveCounter)); + aNamed ? RunnableFactory::Create("unused", TestMove(&moveCounter)) + : RunnableFactory::Create("TestNewRunnableFunction", + TestMove(&moveCounter)); } trackedRunnable->Run(); } - Expect("NS_NewRunnableFunction with movable-only function rvalue, moves", + Expect("RunnableFactory with movable-only function rvalue, moves", moveCounter, 1); } - // Test NS_NewRunnableFunction with copyable&movable function object. + // Test RunnableFactory with copyable&movable function object. { int copyCounter = 0; int moveCounter = 0; @@ -299,16 +323,16 @@ static void TestNewRunnableFunction(bool aNamed) { { TestCopyMove tracker(©Counter, &moveCounter); trackedRunnable = - aNamed ? NS_NewRunnableFunction("unused", std::move(tracker)) - : NS_NewRunnableFunction("TestNewRunnableFunction", - std::move(tracker)); + aNamed ? RunnableFactory::Create("unused", std::move(tracker)) + : RunnableFactory::Create("TestNewRunnableFunction", + std::move(tracker)); } trackedRunnable->Run(); } - Expect("NS_NewRunnableFunction with copyable&movable function, copies", + Expect("RunnableFactory with copyable&movable function, copies", copyCounter, 0); - Expect("NS_NewRunnableFunction with copyable&movable function, moves", - moveCounter, 1); + Expect("RunnableFactory with copyable&movable function, moves", moveCounter, + 1); } { int copyCounter = 0; @@ -317,23 +341,21 @@ static void TestNewRunnableFunction(bool aNamed) { nsCOMPtr trackedRunnable; { trackedRunnable = - aNamed ? NS_NewRunnableFunction( + aNamed ? RunnableFactory::Create( "unused", TestCopyMove(©Counter, &moveCounter)) - : NS_NewRunnableFunction( + : RunnableFactory::Create( "TestNewRunnableFunction", TestCopyMove(©Counter, &moveCounter)); } trackedRunnable->Run(); } - Expect( - "NS_NewRunnableFunction with copyable&movable function rvalue, copies", - copyCounter, 0); - Expect( - "NS_NewRunnableFunction with copyable&movable function rvalue, moves", - moveCounter, 1); + Expect("RunnableFactory with copyable&movable function rvalue, copies", + copyCounter, 0); + Expect("RunnableFactory with copyable&movable function rvalue, moves", + moveCounter, 1); } - // Test NS_NewRunnableFunction with copyable-only lambda capture. + // Test RunnableFactory with copyable-only lambda capture. { int copyCounter = 0; { @@ -342,15 +364,16 @@ static void TestNewRunnableFunction(bool aNamed) { TestCopyWithNoMove tracker(©Counter); // Expect 2 copies (here -> local lambda -> runnable lambda). trackedRunnable = - aNamed ? NS_NewRunnableFunction("unused", - [tracker]() mutable { tracker(); }) - : NS_NewRunnableFunction("TestNewRunnableFunction", - [tracker]() mutable { tracker(); }); + aNamed + ? RunnableFactory::Create("unused", + [tracker]() mutable { tracker(); }) + : RunnableFactory::Create("TestNewRunnableFunction", + [tracker]() mutable { tracker(); }); } trackedRunnable->Run(); } Expect( - "NS_NewRunnableFunction with copyable-only (and no move) capture, " + "RunnableFactory with copyable-only (and no move) capture, " "copies", copyCounter, 2); } @@ -362,15 +385,16 @@ static void TestNewRunnableFunction(bool aNamed) { TestCopyWithDeletedMove tracker(©Counter); // Expect 2 copies (here -> local lambda -> runnable lambda). trackedRunnable = - aNamed ? NS_NewRunnableFunction("unused", - [tracker]() mutable { tracker(); }) - : NS_NewRunnableFunction("TestNewRunnableFunction", - [tracker]() mutable { tracker(); }); + aNamed + ? RunnableFactory::Create("unused", + [tracker]() mutable { tracker(); }) + : RunnableFactory::Create("TestNewRunnableFunction", + [tracker]() mutable { tracker(); }); } trackedRunnable->Run(); } Expect( - "NS_NewRunnableFunction with copyable-only (and deleted move) capture, " + "RunnableFactory with copyable-only (and deleted move) capture, " "copies", copyCounter, 2); } @@ -378,7 +402,7 @@ static void TestNewRunnableFunction(bool aNamed) { // Note: Not possible to use move-only captures. // (Until we can use C++14 generalized lambda captures) - // Test NS_NewRunnableFunction with copyable&movable lambda capture. + // Test RunnableFactory with copyable&movable lambda capture. { int copyCounter = 0; int moveCounter = 0; @@ -387,29 +411,30 @@ static void TestNewRunnableFunction(bool aNamed) { { TestCopyMove tracker(©Counter, &moveCounter); trackedRunnable = - aNamed ? NS_NewRunnableFunction("unused", - [tracker]() mutable { tracker(); }) - : NS_NewRunnableFunction("TestNewRunnableFunction", - [tracker]() mutable { tracker(); }); + aNamed + ? RunnableFactory::Create("unused", + [tracker]() mutable { tracker(); }) + : RunnableFactory::Create("TestNewRunnableFunction", + [tracker]() mutable { tracker(); }); // Expect 1 copy (here -> local lambda) and 1 move (local -> runnable // lambda). } trackedRunnable->Run(); } - Expect("NS_NewRunnableFunction with copyable&movable capture, copies", - copyCounter, 1); - Expect("NS_NewRunnableFunction with copyable&movable capture, moves", - moveCounter, 1); + Expect("RunnableFactory with copyable&movable capture, copies", copyCounter, + 1); + Expect("RunnableFactory with copyable&movable capture, moves", moveCounter, + 1); } } TEST(ThreadUtils, NewRunnableFunction) -{ TestNewRunnableFunction(/*aNamed*/ false); } +{ TestRunnableFactory(/*aNamed*/ false); } TEST(ThreadUtils, NewNamedRunnableFunction) { // The named overload shall behave identical to the non-named counterpart. - TestNewRunnableFunction(/*aNamed*/ true); + TestRunnableFactory(/*aNamed*/ true); // Test naming. { @@ -420,6 +445,38 @@ TEST(ThreadUtils, NewNamedRunnableFunction) } } +TEST(ThreadUtils, NewCancelableRunnableFunction) +{ TestRunnableFactory(/*aNamed*/ false); } + +TEST(ThreadUtils, NewNamedCancelableRunnableFunction) +{ + // The named overload shall behave identical to the non-named counterpart. + TestRunnableFactory(/*aNamed*/ true); + + // Test naming. + { + const char* expectedName = "NamedRunnable"; + RefPtr NamedRunnable = + NS_NewCancelableRunnableFunction(expectedName, [] {}); + ExpectRunnableName(NamedRunnable, expectedName); + } + + // Test release on cancelation. + { + auto foo = MakeRefPtr(); + bool ran = false; + + RefPtr func = + NS_NewCancelableRunnableFunction("unused", [foo, &ran] { ran = true; }); + + EXPECT_EQ(foo->refCount(), 2u); + func->Cancel(); + + EXPECT_EQ(foo->refCount(), 1u); + EXPECT_FALSE(ran); + } +} + static void TestNewRunnableMethod(bool aNamed) { memset(gRunnableExecuted, false, MAX_TESTS * sizeof(bool)); // Scope the smart ptrs so that the runnables need to hold on to whatever they diff --git a/xpcom/threads/nsThreadUtils.h b/xpcom/threads/nsThreadUtils.h index d84d711b67b4..c7b98da76e82 100644 --- a/xpcom/threads/nsThreadUtils.h +++ b/xpcom/threads/nsThreadUtils.h @@ -659,6 +659,47 @@ already_AddRefed NS_NewRunnableFunction( aName, std::forward(aFunction))); } +// Creates a new object implementing nsIRunnable and nsICancelableRunnable, +// which runs a given function on Run and clears the stored function object on a +// call to `Cancel` (and thus destroys all objects it holds). +template +already_AddRefed NS_NewCancelableRunnableFunction( + const char* aName, Function&& aFunc) { + class FuncCancelableRunnable final : public mozilla::CancelableRunnable { + public: + static_assert(std::is_void_v>()())>); + + NS_INLINE_DECL_REFCOUNTING_INHERITED(FuncCancelableRunnable, + CancelableRunnable) + + explicit FuncCancelableRunnable(const char* aName, Function&& aFunc) + : CancelableRunnable{aName}, + mFunc{mozilla::Some(std::forward(aFunc))} {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(mFunc); + + (*mFunc)(); + + return NS_OK; + } + + nsresult Cancel() override { + mFunc.reset(); + return NS_OK; + } + + private: + ~FuncCancelableRunnable() = default; + + mozilla::Maybe> mFunc; + }; + + return mozilla::MakeAndAddRef( + aName, std::forward(aFunc)); +} + namespace mozilla { namespace detail {