forked from mirrors/gecko-dev
Bug 1901745 - Introduce mozilla::dom::quota::RunAfterProcessNextEvent; r=dom-storage-reviewers,jstutte, a=dmeehan
Differential Revision: https://phabricator.services.mozilla.com/D213215
This commit is contained in:
parent
ce16e42511
commit
14094d97cf
5 changed files with 324 additions and 0 deletions
84
dom/quota/ThreadUtils.cpp
Normal file
84
dom/quota/ThreadUtils.cpp
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=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 http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/dom/quota/ThreadUtils.h"
|
||||
|
||||
#include "mozilla/dom/quota/QuotaCommon.h"
|
||||
#include "mozilla/dom/quota/ResultExtensions.h"
|
||||
#include "nsIThreadInternal.h"
|
||||
#include "nsThreadPool.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
namespace mozilla::dom::quota {
|
||||
|
||||
namespace {
|
||||
|
||||
class RunAfterProcessingCurrentEventHelper final : public nsIThreadObserver {
|
||||
public:
|
||||
nsresult Init(std::function<void()>&& aCallback);
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSITHREADOBSERVER
|
||||
|
||||
private:
|
||||
~RunAfterProcessingCurrentEventHelper() = default;
|
||||
|
||||
std::function<void()> mCallback;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
nsresult RunAfterProcessingCurrentEventHelper::Init(
|
||||
std::function<void()>&& aCallback) {
|
||||
nsCOMPtr<nsIThreadInternal> thread = do_QueryInterface(NS_GetCurrentThread());
|
||||
MOZ_ASSERT(thread);
|
||||
|
||||
QM_TRY(MOZ_TO_RESULT(thread->AddObserver(this)));
|
||||
|
||||
mCallback = std::move(aCallback);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(RunAfterProcessingCurrentEventHelper, nsIThreadObserver)
|
||||
|
||||
NS_IMETHODIMP
|
||||
RunAfterProcessingCurrentEventHelper::OnDispatchedEvent() {
|
||||
MOZ_CRASH("Should never be called!");
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
RunAfterProcessingCurrentEventHelper::OnProcessNextEvent(
|
||||
nsIThreadInternal* /* aThread */, bool /* aMayWait */) {
|
||||
MOZ_CRASH("Should never be called!");
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
RunAfterProcessingCurrentEventHelper::AfterProcessNextEvent(
|
||||
nsIThreadInternal* aThread, bool /* aEventWasProcessed */) {
|
||||
MOZ_ASSERT(aThread);
|
||||
|
||||
QM_WARNONLY_TRY(MOZ_TO_RESULT(aThread->RemoveObserver(this)));
|
||||
|
||||
auto callback = std::move(mCallback);
|
||||
callback();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult RunAfterProcessingCurrentEvent(std::function<void()>&& aCallback) {
|
||||
MOZ_DIAGNOSTIC_ASSERT(
|
||||
!nsThreadPool::GetCurrentThreadPool(),
|
||||
"Call to RunAfterProcessingCurrentEvent() from thread pool!");
|
||||
|
||||
auto helper = MakeRefPtr<RunAfterProcessingCurrentEventHelper>();
|
||||
|
||||
QM_TRY(MOZ_TO_RESULT(helper->Init(std::move(aCallback))));
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom::quota
|
||||
46
dom/quota/ThreadUtils.h
Normal file
46
dom/quota/ThreadUtils.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=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 http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DOM_QUOTA_THREADUTILS_H_
|
||||
#define DOM_QUOTA_THREADUTILS_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
enum class nsresult : uint32_t;
|
||||
|
||||
namespace mozilla::dom::quota {
|
||||
|
||||
/**
|
||||
* Add a temporary thread observer and listen for the "AfterProcessNextEvent"
|
||||
* notification. Once the notification is received, remove the temporary thread
|
||||
* observer and call aCallback.
|
||||
* In practice, this calls aCallback immediately after the current thread is
|
||||
* done with running and releasing recently popped event from thread's event
|
||||
* queue.
|
||||
* If called multiple times, all the callbacks will be executed, in the
|
||||
* order in which RunAfterProcessingCurrentEvent() was called.
|
||||
* Use this method if you need to dispatch the same or some other runnable to
|
||||
* another thread in a way which prevents any race conditions (for example
|
||||
* unpredictable releases of objects).
|
||||
* This method should be used only in existing code which can't be easily
|
||||
* converted to use MozPromise which doesn't have the problem with
|
||||
* unpredictable releases of objects, see:
|
||||
* https://searchfox.org/mozilla-central/rev/4582d908c17fbf7924f5699609fe4a12c28ddc4a/xpcom/threads/MozPromise.h#866
|
||||
*
|
||||
* Note: Calling this method from a thread pool is not supported since thread
|
||||
* pools don't fire the "AfterProcessNextEvent" notification. The method has
|
||||
* a diagnostic assertion for that so any calls like that will be caught
|
||||
* in builds with enabled diagnostic assertions. The callback will never
|
||||
* get executed in other builds, such as release builds. The limitation can
|
||||
* be removed completely when thread pool implementation gets support for firing
|
||||
* the "AfterProcessNextEvent".
|
||||
*/
|
||||
nsresult RunAfterProcessingCurrentEvent(std::function<void()>&& aCallback);
|
||||
|
||||
} // namespace mozilla::dom::quota
|
||||
|
||||
#endif // DOM_QUOTA_THREADUTILS_H_
|
||||
|
|
@ -75,6 +75,7 @@ EXPORTS.mozilla.dom.quota += [
|
|||
"StorageHelpers.h",
|
||||
"StreamUtils.h",
|
||||
"StringifyUtils.h",
|
||||
"ThreadUtils.h",
|
||||
"UsageInfo.h",
|
||||
]
|
||||
|
||||
|
|
@ -125,6 +126,7 @@ UNIFIED_SOURCES += [
|
|||
"StorageOriginAttributes.cpp",
|
||||
"StreamUtils.cpp",
|
||||
"StringifyUtils.cpp",
|
||||
"ThreadUtils.cpp",
|
||||
]
|
||||
|
||||
IPDL_SOURCES += [
|
||||
|
|
|
|||
191
dom/quota/test/gtest/TestThreadUtils.cpp
Normal file
191
dom/quota/test/gtest/TestThreadUtils.cpp
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=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 http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/dom/quota/QuotaManager.h"
|
||||
#include "mozilla/dom/quota/ThreadUtils.h"
|
||||
#include "mozilla/gtest/MozAssertions.h"
|
||||
#include "QuotaManagerDependencyFixture.h"
|
||||
|
||||
namespace mozilla::dom::quota::test {
|
||||
|
||||
namespace {
|
||||
|
||||
MozExternalRefCountType GetRefCount(nsISupports* aSupports) {
|
||||
if (!aSupports) {
|
||||
return 0;
|
||||
}
|
||||
aSupports->AddRef();
|
||||
return aSupports->Release();
|
||||
}
|
||||
|
||||
class OneTimeRunnable final : public Runnable {
|
||||
public:
|
||||
enum class State { None, Initial, Created, Destroyed };
|
||||
|
||||
static void Init() {
|
||||
ASSERT_TRUE(sState == State::None || sState == State::Destroyed);
|
||||
ASSERT_FALSE(sCurrent);
|
||||
|
||||
sState = State::Initial;
|
||||
}
|
||||
|
||||
static RefPtr<OneTimeRunnable> Create(std::function<void()>&& aTask) {
|
||||
EXPECT_EQ(sState, State::Initial);
|
||||
|
||||
RefPtr<OneTimeRunnable> runnable = new OneTimeRunnable(std::move(aTask));
|
||||
return runnable;
|
||||
}
|
||||
|
||||
static State GetState() { return sState; }
|
||||
|
||||
static OneTimeRunnable* GetCurrent() { return sCurrent; }
|
||||
|
||||
static MozExternalRefCountType GetCurrentRefCount() {
|
||||
return GetRefCount(static_cast<nsIRunnable*>(sCurrent));
|
||||
}
|
||||
|
||||
NS_IMETHOD
|
||||
Run() override {
|
||||
auto task = std::move(mTask);
|
||||
task();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
explicit OneTimeRunnable(std::function<void()>&& aTask)
|
||||
: Runnable("dom::quota::test::OneTimeRunnable"), mTask(std::move(aTask)) {
|
||||
sCurrent = this;
|
||||
sState = State::Created;
|
||||
}
|
||||
|
||||
~OneTimeRunnable() override {
|
||||
sState = State::Destroyed;
|
||||
sCurrent = nullptr;
|
||||
}
|
||||
|
||||
static Atomic<State> sState;
|
||||
static OneTimeRunnable* sCurrent;
|
||||
|
||||
std::function<void()> mTask;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Atomic<OneTimeRunnable::State> OneTimeRunnable::sState(
|
||||
OneTimeRunnable::State::None);
|
||||
OneTimeRunnable* OneTimeRunnable::sCurrent(nullptr);
|
||||
|
||||
class TestThreadUtils : public QuotaManagerDependencyFixture {
|
||||
public:
|
||||
static void SetUpTestCase() { ASSERT_NO_FATAL_FAILURE(InitializeFixture()); }
|
||||
|
||||
static void TearDownTestCase() { ASSERT_NO_FATAL_FAILURE(ShutdownFixture()); }
|
||||
};
|
||||
|
||||
TEST_F(TestThreadUtils, RunAfterProcessingCurrentEvent_Release) {
|
||||
bool runnableCalled = false;
|
||||
bool callbackCalled = false;
|
||||
|
||||
QuotaManager* quotaManager = QuotaManager::Get();
|
||||
ASSERT_TRUE(quotaManager);
|
||||
|
||||
OneTimeRunnable::Init();
|
||||
|
||||
auto runnable = OneTimeRunnable::Create([&runnableCalled, &callbackCalled]() {
|
||||
runnableCalled = true;
|
||||
|
||||
EXPECT_EQ(OneTimeRunnable::GetState(), OneTimeRunnable::State::Created);
|
||||
EXPECT_EQ(OneTimeRunnable::GetCurrentRefCount(), 2u);
|
||||
|
||||
RunAfterProcessingCurrentEvent([&callbackCalled]() {
|
||||
callbackCalled = true;
|
||||
|
||||
// The runnable shouldn't be yet destroyed because we still have a strong
|
||||
// reference to it in `runnable`.
|
||||
EXPECT_EQ(OneTimeRunnable::GetState(), OneTimeRunnable::State::Created);
|
||||
|
||||
// The runnable should be released once (by the event queue processing
|
||||
// code).
|
||||
EXPECT_EQ(OneTimeRunnable::GetCurrentRefCount(), 1u);
|
||||
});
|
||||
|
||||
EXPECT_EQ(OneTimeRunnable::GetState(), OneTimeRunnable::State::Created);
|
||||
EXPECT_EQ(OneTimeRunnable::GetCurrentRefCount(), 2u);
|
||||
});
|
||||
|
||||
ASSERT_FALSE(runnableCalled);
|
||||
ASSERT_FALSE(callbackCalled);
|
||||
|
||||
// Note that we are keeping ownership of the runnable here.
|
||||
ASSERT_EQ(NS_OK,
|
||||
quotaManager->IOThread()->Dispatch(runnable, NS_DISPATCH_NORMAL));
|
||||
|
||||
// Do a round-trip to the QM IO thread to ensure the test doesn't finish
|
||||
// before the OneTimeRunnable is fully processed.
|
||||
//
|
||||
// Note: In theory, we would use SyncRunnable wrapper here, but the code
|
||||
// reads better when both tests use the same way for blocking the current
|
||||
// thread.
|
||||
ASSERT_NO_FATAL_FAILURE(PerformOnIOThread([]() {}));
|
||||
|
||||
// Check that the runnable and the callback were actually called.
|
||||
ASSERT_TRUE(runnableCalled);
|
||||
ASSERT_TRUE(callbackCalled);
|
||||
}
|
||||
|
||||
TEST_F(TestThreadUtils, RunAfterProcessingCurrentEvent_ReleaseAndDestory) {
|
||||
bool runnableCalled = false;
|
||||
bool callbackCalled = false;
|
||||
|
||||
QuotaManager* quotaManager = QuotaManager::Get();
|
||||
ASSERT_TRUE(quotaManager);
|
||||
|
||||
OneTimeRunnable::Init();
|
||||
|
||||
auto runnable = OneTimeRunnable::Create([&runnableCalled, &callbackCalled]() {
|
||||
runnableCalled = true;
|
||||
|
||||
EXPECT_EQ(OneTimeRunnable::GetState(), OneTimeRunnable::State::Created);
|
||||
EXPECT_EQ(OneTimeRunnable::GetCurrentRefCount(), 1u);
|
||||
|
||||
RunAfterProcessingCurrentEvent([&callbackCalled]() {
|
||||
callbackCalled = true;
|
||||
|
||||
// The runnable should be destroyed because we don't have any other strong
|
||||
// references to it.
|
||||
EXPECT_EQ(OneTimeRunnable::GetState(), OneTimeRunnable::State::Destroyed);
|
||||
|
||||
// The runnable should be released once (by the event queue processing
|
||||
// code).
|
||||
EXPECT_EQ(OneTimeRunnable::GetCurrentRefCount(), 0u);
|
||||
});
|
||||
|
||||
EXPECT_EQ(OneTimeRunnable::GetState(), OneTimeRunnable::State::Created);
|
||||
EXPECT_EQ(OneTimeRunnable::GetCurrentRefCount(), 1u);
|
||||
});
|
||||
|
||||
ASSERT_FALSE(runnableCalled);
|
||||
ASSERT_FALSE(callbackCalled);
|
||||
|
||||
// Note that we are tranferring ownership of the runnable here.
|
||||
ASSERT_EQ(NS_OK, quotaManager->IOThread()->Dispatch(runnable.forget(),
|
||||
NS_DISPATCH_NORMAL));
|
||||
|
||||
// Do a round-trip to the QM IO thread to ensure the test doesn't finish
|
||||
// before the OneTimeRunnable is fully processed.
|
||||
//
|
||||
// Note: SyncRunnable wrapper can't be used here because that would hold our
|
||||
// runnable longer, and we couldn't test that our runnable is destroyed when
|
||||
// the callback for RunAfterProcessingCurrentEvent is executed.
|
||||
ASSERT_NO_FATAL_FAILURE(PerformOnIOThread([]() {}));
|
||||
|
||||
// Check that the runnable and the callback were actually called.
|
||||
ASSERT_TRUE(runnableCalled);
|
||||
ASSERT_TRUE(callbackCalled);
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom::quota::test
|
||||
|
|
@ -34,6 +34,7 @@ UNIFIED_SOURCES = [
|
|||
"TestStorageOriginAttributes.cpp",
|
||||
"TestStringifyUtils.cpp",
|
||||
"TestTelemetry.cpp",
|
||||
"TestThreadUtils.cpp",
|
||||
"TestUsageInfo.cpp",
|
||||
]
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue