зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1658419 - Create a class that implements sync'd cross-process shared memory r=handyman
This class (which may be useful outside of just gamepad code) provides an API to share an object across processes in a synchronized manner. Eventually it will be used to share gamepad data (axes, buttons, etc) immediately with child processes for performance reasons. Differential Revision: https://phabricator.services.mozilla.com/D100215
This commit is contained in:
Родитель
c87dcc5e4c
Коммит
69f092758b
|
@ -0,0 +1,297 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
/* Implements a piece of cross-process shared memory that can be shared with
|
||||
* many processes simultaniously, and is synchronized by a cross-process mutex.
|
||||
*
|
||||
* In almost all cases, IPDL and its machinery should be preferred to this.
|
||||
* This class should only be used if you really know you need it, and
|
||||
* precautions need to be taken to avoid introducing security bugs.
|
||||
*
|
||||
* Specifically:
|
||||
* - Don't assume that any process other than the main process is trustworthy,
|
||||
* since exploits may exist in graphics drivers, video codecs, etc.
|
||||
* - Don't assume the other side can't modify the shared memory even while
|
||||
* you hold the lock.
|
||||
* - Don't assume that if you read the same piece of data twice, that it will
|
||||
* return the same value.
|
||||
* - Don't assume the other side will ever actually release the lock.
|
||||
* IE using RunWithLock() on the main thread is probably a bad idea.
|
||||
*
|
||||
* If possible, design things so that the trusted side is on a separate
|
||||
* thread and is write-only and you avoid those problems altogether. If you,
|
||||
* must read, consider taking a "snapshot" of the shared memory by copying it
|
||||
* to a local variable, operating on that, and then copying it back to shared
|
||||
* memory when done.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DOM_GAMEPAD_SYNCHRONIZEDSHAREDMEMORY_H_
|
||||
#define DOM_GAMEPAD_SYNCHRONIZEDSHAREDMEMORY_H_
|
||||
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include <cinttypes>
|
||||
#include <type_traits>
|
||||
|
||||
namespace mozilla::ipc {
|
||||
class IProtocol;
|
||||
} // namespace mozilla::ipc
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
// IPDL structure that knows how to serialize this over IPC
|
||||
class SynchronizedSharedMemoryRemoteInfo;
|
||||
|
||||
// Details that you don't need to know to use this class
|
||||
//
|
||||
// This mostly represents an interface/implementation firewall (Pimpl Idiom)
|
||||
// because the implementation is per-platform and we want to keep this file
|
||||
// free of platform-specific headers.
|
||||
//
|
||||
// It is implemented in terms of "void*" because template classes with template
|
||||
// methods can't hide their implementations in .cpp files. The main class is a
|
||||
// template wrapper for this that exposes it as a typesafe API for type `T`.
|
||||
//
|
||||
// See SynchronizedSharedMemory below for usage of the public API
|
||||
|
||||
class SynchronizedSharedMemoryDetail {
|
||||
public:
|
||||
static Maybe<SynchronizedSharedMemoryDetail> CreateNew(uint32_t aSize);
|
||||
~SynchronizedSharedMemoryDetail();
|
||||
|
||||
void LockMutex();
|
||||
void UnlockMutex();
|
||||
|
||||
void* GetPtr() const;
|
||||
|
||||
// Allows this to be transferred across IPDL
|
||||
static Maybe<SynchronizedSharedMemoryDetail> CreateFromRemoteInfo(
|
||||
const SynchronizedSharedMemoryRemoteInfo& aIPCInfo);
|
||||
bool GenerateRemoteInfo(const mozilla::ipc::IProtocol* aActor,
|
||||
SynchronizedSharedMemoryRemoteInfo* aOut);
|
||||
|
||||
// Allow move
|
||||
SynchronizedSharedMemoryDetail(SynchronizedSharedMemoryDetail&&) noexcept;
|
||||
SynchronizedSharedMemoryDetail& operator=(
|
||||
SynchronizedSharedMemoryDetail&&) noexcept;
|
||||
|
||||
// Disallow copy
|
||||
SynchronizedSharedMemoryDetail(const SynchronizedSharedMemoryDetail&) =
|
||||
delete;
|
||||
SynchronizedSharedMemoryDetail& operator=(
|
||||
const SynchronizedSharedMemoryDetail&) = delete;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
|
||||
SynchronizedSharedMemoryDetail();
|
||||
explicit SynchronizedSharedMemoryDetail(UniquePtr<Impl> aImpl);
|
||||
|
||||
UniquePtr<Impl> mImpl;
|
||||
|
||||
// Testing-related helper
|
||||
template <typename T>
|
||||
friend class SynchronizedSharedMemory;
|
||||
bool GenerateTestRemoteInfo(SynchronizedSharedMemoryRemoteInfo* aOut);
|
||||
};
|
||||
|
||||
// Represents a piece of cross-process synchronized shared memory
|
||||
//
|
||||
// Can create a piece of shared memory to hold an object of type T,
|
||||
// pass it over IPDL to another actor, and can perform synchronized
|
||||
// cross-process access of the object
|
||||
//
|
||||
// See top of file for warnings about security.
|
||||
//
|
||||
// The type, T, must be TriviallyCopyable since sharing a piece of memory
|
||||
// between 2 processes is akin to memcopy-ing its bytes from one program to
|
||||
// another. StandardLayout may not be strictly necessary, but conforming to the
|
||||
// platform C ABI is probably a good idea when sharing across processes.
|
||||
//
|
||||
// Although not statically enforcable, generally the object should not contain
|
||||
// pointers, since they are usually meaningless across processes.
|
||||
template <typename T>
|
||||
class SynchronizedSharedMemory {
|
||||
static_assert(std::is_trivially_copyable<T>::value,
|
||||
"SynchronizedSharedMemory can only be used with types that are "
|
||||
"trivially copyable");
|
||||
static_assert(std::is_standard_layout<T>::value,
|
||||
"SynchronizedSharedMemory can only be used with types that are "
|
||||
"standard layout");
|
||||
|
||||
public:
|
||||
// Construct a new object of type T in cross-process shareable memory
|
||||
//
|
||||
// Returns `Nothing` if an error occurs
|
||||
//
|
||||
// # Example
|
||||
// ```
|
||||
// struct MyObject {
|
||||
// int i;
|
||||
// };
|
||||
// auto maybeSharedMemory = SynchronizedSharedMemory<MyObject>::CreateNew();
|
||||
// MOZ_ASSERT(maybeSharedMemory);
|
||||
// ```
|
||||
template <typename... Args>
|
||||
static Maybe<SynchronizedSharedMemory> CreateNew(Args&&... args) {
|
||||
auto maybeDetail = SynchronizedSharedMemoryDetail::CreateNew(sizeof(T));
|
||||
if (!maybeDetail) {
|
||||
return Nothing{};
|
||||
}
|
||||
new ((*maybeDetail).GetPtr()) T(std::forward<Args>(args)...);
|
||||
return Some(SynchronizedSharedMemory(std::move(*maybeDetail)));
|
||||
}
|
||||
|
||||
// Run the given function on the current thread while holding a lock on the
|
||||
// cross-process mutex
|
||||
//
|
||||
// Guarantees that no well-behaved users** will race or forget to unlock when
|
||||
// accessing this object, even from other processes.
|
||||
//
|
||||
// ** Of course, proper locking/unlocking can never be enforced in a
|
||||
// compromised process. See the security warning at the top of this file
|
||||
//
|
||||
// # Example
|
||||
// ```
|
||||
// ===== In one process =====
|
||||
//
|
||||
// auto maybeSharedMemory = SynchronizedSharedMemory<int>::CreateNew();
|
||||
// maybeSharedMemory.RunWithLock([] (int* pInt) {
|
||||
// *pInt = 5;
|
||||
// });
|
||||
//
|
||||
// // ===== In another process =====
|
||||
//
|
||||
// int curValue = 0;
|
||||
// maybeSharedMemory.RunWithLock([&] (int* pInt) {
|
||||
// curValue = *pInt;
|
||||
// });
|
||||
//
|
||||
// MOZ_ASSERT(curValue == 5);
|
||||
// ```
|
||||
template <typename Fn, typename... Args>
|
||||
void RunWithLock(Fn&& aFn, Args&&... args) {
|
||||
mDetail.LockMutex();
|
||||
|
||||
// Justification for volatile: We need to make a local copy of structure T
|
||||
// so a compromised remote process can't exploit us by ignoring the lock
|
||||
// and writing after we've performed validation and trust the data, but
|
||||
// before we actually use it to do something privileged
|
||||
//
|
||||
// Without volatile, the optimizer may be allowed to evade the copy
|
||||
// and continue to act on the original remote object (among other dangerous
|
||||
// things)
|
||||
//
|
||||
T localCopy;
|
||||
volatile_memcpy(&localCopy, mDetail.GetPtr(), sizeof(T));
|
||||
|
||||
(std::forward<Fn>(aFn))(&localCopy, std::forward<Args>(args)...);
|
||||
|
||||
volatile_memcpy(mDetail.GetPtr(), &localCopy, sizeof(T));
|
||||
|
||||
mDetail.UnlockMutex();
|
||||
}
|
||||
|
||||
// Create from info generated using GenerateRemoteInfo()
|
||||
//
|
||||
// This is the other end to GenerateRemoteInfo(). It receives the
|
||||
// serialized `SynchronizedSharedMemoryRemoteInfo` structure sent over IPDL
|
||||
// and uses it to attach to the shared object.
|
||||
//
|
||||
// Returns `Nothing` if an error occurs.
|
||||
//
|
||||
// # Example
|
||||
// ```
|
||||
// // ===== One side of IPDL =====
|
||||
//
|
||||
// bool SetupSharedMemory(mozilla::ipc::PMyProtocol& aProtocol) {
|
||||
// auto maybeRemoteInfo = mSharedMemory.GenerateRemoteInfo(&aProtocol);
|
||||
// if (!maybeRemoteInfo) {
|
||||
// return false;
|
||||
// }
|
||||
// return aProtocol.SendSharedMemoryInfo(*maybeRemoteInfo);
|
||||
// }
|
||||
//
|
||||
// // ===== Other side of IPDL =====
|
||||
//
|
||||
// mozilla::ipc::IPCResult MyProtocol::RecvSharedMemoryInfo(
|
||||
// const SynchronizedSharedMemoryRemoteInfo& aRemoteInfo) {
|
||||
// auto maybeSharedMemory = SynchronizedSharedMemory::CreateFromRemoteInfo(
|
||||
// aRemoteInfo);
|
||||
// if (!maybeSharedMemory) {
|
||||
// return IPC_FAIL_NO_REASON(this);
|
||||
// }
|
||||
// mSharedMemory = std::move(*maybeSharedMemory);
|
||||
// return IPC_OK();
|
||||
// }
|
||||
// ```
|
||||
static Maybe<SynchronizedSharedMemory> CreateFromRemoteInfo(
|
||||
const SynchronizedSharedMemoryRemoteInfo& aIPCInfo) {
|
||||
auto maybeDetail =
|
||||
SynchronizedSharedMemoryDetail::CreateFromRemoteInfo(aIPCInfo);
|
||||
if (!maybeDetail) {
|
||||
return Nothing{};
|
||||
}
|
||||
return Some(SynchronizedSharedMemory(std::move(*maybeDetail)));
|
||||
}
|
||||
|
||||
// Generate remote info needed to share object with specified actor
|
||||
//
|
||||
// Note that the returned information is likely specific to the actor. This
|
||||
// should be called individually for each actor that will use the shared
|
||||
// object.
|
||||
//
|
||||
// See `CreateFromRemoteInfo` for usage example
|
||||
bool GenerateRemoteInfo(const mozilla::ipc::IProtocol* aActor,
|
||||
SynchronizedSharedMemoryRemoteInfo* aOut) {
|
||||
return mDetail.GenerateRemoteInfo(aActor, aOut);
|
||||
}
|
||||
|
||||
// Allow move
|
||||
SynchronizedSharedMemory(SynchronizedSharedMemory&&) noexcept = default;
|
||||
SynchronizedSharedMemory& operator=(SynchronizedSharedMemory&&) noexcept =
|
||||
default;
|
||||
|
||||
// Disallow copy
|
||||
SynchronizedSharedMemory(const SynchronizedSharedMemory&) = delete;
|
||||
SynchronizedSharedMemory& operator=(const SynchronizedSharedMemory&) = delete;
|
||||
|
||||
~SynchronizedSharedMemory() = default;
|
||||
|
||||
private:
|
||||
SynchronizedSharedMemory() = default;
|
||||
explicit SynchronizedSharedMemory(SynchronizedSharedMemoryDetail aDetail)
|
||||
: mDetail(std::move(aDetail)) {}
|
||||
|
||||
void volatile_memcpy(void* aDst, const void* aSrc, size_t aSize) {
|
||||
const volatile unsigned char* src =
|
||||
static_cast<const volatile unsigned char*>(aSrc);
|
||||
volatile unsigned char* dst = static_cast<volatile unsigned char*>(aDst);
|
||||
size_t remaining = aSize;
|
||||
|
||||
while (remaining) {
|
||||
*dst = *src;
|
||||
++src;
|
||||
++dst;
|
||||
--remaining;
|
||||
}
|
||||
}
|
||||
|
||||
SynchronizedSharedMemoryDetail mDetail;
|
||||
|
||||
// The following is for testing of this API. These should be accessed
|
||||
// from `SynchronizedSharedMemoryTestFriend`. This is used because generating
|
||||
// a mock IProtocol actor is a fair amount of work.
|
||||
friend class SynchronizedSharedMemoryTestFriend;
|
||||
bool GenerateTestRemoteInfo(SynchronizedSharedMemoryRemoteInfo* aOut) {
|
||||
return mDetail.GenerateTestRemoteInfo(aOut);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
#endif // DOM_GAMEPAD_SYNCHRONIZEDSHAREDMEMORY_H_
|
|
@ -0,0 +1,64 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
// This is the implementation of SynchronizedSharedMemory for unimplemented
|
||||
// platforms
|
||||
|
||||
#include "mozilla/dom/SynchronizedSharedMemory.h"
|
||||
#include "mozilla/dom/SynchronizedSharedMemoryRemoteInfo.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
class SynchronizedSharedMemoryDetail::Impl {};
|
||||
|
||||
// static
|
||||
Maybe<SynchronizedSharedMemoryDetail> SynchronizedSharedMemoryDetail::CreateNew(
|
||||
uint32_t) {
|
||||
return Nothing{};
|
||||
}
|
||||
|
||||
void SynchronizedSharedMemoryDetail::LockMutex() {
|
||||
MOZ_CRASH("Should never be called");
|
||||
}
|
||||
|
||||
void SynchronizedSharedMemoryDetail::UnlockMutex() {
|
||||
MOZ_CRASH("Should never be called");
|
||||
}
|
||||
|
||||
void* SynchronizedSharedMemoryDetail::GetPtr() const {
|
||||
MOZ_CRASH("Should never be called");
|
||||
}
|
||||
|
||||
// static
|
||||
Maybe<SynchronizedSharedMemoryDetail>
|
||||
SynchronizedSharedMemoryDetail::CreateFromRemoteInfo(
|
||||
const SynchronizedSharedMemoryRemoteInfo&) {
|
||||
return Nothing{};
|
||||
}
|
||||
|
||||
bool SynchronizedSharedMemoryDetail::GenerateRemoteInfo(
|
||||
const mozilla::ipc::IProtocol*, SynchronizedSharedMemoryRemoteInfo*) {
|
||||
MOZ_CRASH("Should never be called");
|
||||
}
|
||||
|
||||
bool SynchronizedSharedMemoryDetail::GenerateTestRemoteInfo(
|
||||
SynchronizedSharedMemoryRemoteInfo*) {
|
||||
MOZ_CRASH("Should never be called");
|
||||
}
|
||||
|
||||
SynchronizedSharedMemoryDetail::SynchronizedSharedMemoryDetail() = default;
|
||||
SynchronizedSharedMemoryDetail::~SynchronizedSharedMemoryDetail() = default;
|
||||
|
||||
SynchronizedSharedMemoryDetail::SynchronizedSharedMemoryDetail(
|
||||
UniquePtr<Impl> aImpl)
|
||||
: mImpl(std::move(aImpl)) {}
|
||||
|
||||
SynchronizedSharedMemoryDetail::SynchronizedSharedMemoryDetail(
|
||||
SynchronizedSharedMemoryDetail&&) noexcept = default;
|
||||
SynchronizedSharedMemoryDetail& SynchronizedSharedMemoryDetail::operator=(
|
||||
SynchronizedSharedMemoryDetail&&) noexcept = default;
|
||||
|
||||
} // namespace mozilla::dom
|
|
@ -0,0 +1,12 @@
|
|||
/* 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/. */
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
struct SynchronizedSharedMemoryRemoteInfo {
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
|
@ -33,6 +33,7 @@ EXPORTS.mozilla.dom += [
|
|||
"ipc/GamepadMessageUtils.h",
|
||||
"ipc/GamepadTestChannelChild.h",
|
||||
"ipc/GamepadTestChannelParent.h",
|
||||
"SynchronizedSharedMemory.h",
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES = [
|
||||
|
@ -64,10 +65,19 @@ elif CONFIG["OS_ARCH"] == "Linux":
|
|||
else:
|
||||
UNIFIED_SOURCES += ["fallback/FallbackGamepad.cpp"]
|
||||
|
||||
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
|
||||
IPDL_SOURCES += ["windows/SynchronizedSharedMemoryRemoteInfo.ipdlh"]
|
||||
SOURCES += ["windows/SynchronizedSharedMemory.cpp"]
|
||||
else:
|
||||
IPDL_SOURCES += ["fallback/SynchronizedSharedMemoryRemoteInfo.ipdlh"]
|
||||
SOURCES += ["fallback/SynchronizedSharedMemory.cpp"]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
"ipc",
|
||||
]
|
||||
|
||||
TEST_DIRS += ["tests/gtest"]
|
||||
|
||||
include("/ipc/chromium/chromium-config.mozbuild")
|
||||
|
||||
FINAL_LIBRARY = "xul"
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
#include "mozilla/dom/SynchronizedSharedMemory.h"
|
||||
#include "mozilla/dom/SynchronizedSharedMemoryRemoteInfo.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
// Dead-simple test for the shared memory fallback: As long as CreateNew()
|
||||
// returns `Nothing`, there really isn't anything else that can be done with
|
||||
// the API
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
// Ensure that we can't create SynchronizedSharedMemory
|
||||
TEST(SynchronizedSharedMemoryTest, NoCreate)
|
||||
{
|
||||
auto maybeSharedMemory = SynchronizedSharedMemory<int>::CreateNew();
|
||||
ASSERT_TRUE(!maybeSharedMemory);
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
|
@ -0,0 +1,183 @@
|
|||
#include "mozilla/dom/SynchronizedSharedMemory.h"
|
||||
#include "mozilla/dom/SynchronizedSharedMemoryRemoteInfo.h"
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <windows.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
// Helpers that have private access to SynchronizedSharedMemory
|
||||
// (As it turns out, creating an entire IPDL actor tree just for a test is
|
||||
// hard)
|
||||
class SynchronizedSharedMemoryTestFriend {
|
||||
public:
|
||||
template <typename T>
|
||||
static bool GenerateRemoteInfo(SynchronizedSharedMemory<T>& aSharedMemory,
|
||||
SynchronizedSharedMemoryRemoteInfo* aOut) {
|
||||
return aSharedMemory.GenerateTestRemoteInfo(aOut);
|
||||
}
|
||||
};
|
||||
|
||||
// A basic test that ensures that reading/writing even works
|
||||
TEST(SynchronizedSharedMemoryTest, Basic)
|
||||
{
|
||||
auto maybeLocalObject = SynchronizedSharedMemory<int>::CreateNew(0);
|
||||
ASSERT_TRUE(maybeLocalObject);
|
||||
|
||||
SynchronizedSharedMemoryRemoteInfo remoteInfo;
|
||||
ASSERT_TRUE(SynchronizedSharedMemoryTestFriend::GenerateRemoteInfo(
|
||||
*maybeLocalObject, &remoteInfo));
|
||||
|
||||
auto maybeRemoteObject =
|
||||
SynchronizedSharedMemory<int>::CreateFromRemoteInfo(remoteInfo);
|
||||
ASSERT_TRUE(maybeRemoteObject);
|
||||
|
||||
(*maybeLocalObject).RunWithLock([](int* ptr, int value) { *ptr = value; }, 5);
|
||||
|
||||
int value = 0;
|
||||
|
||||
(*maybeRemoteObject).RunWithLock([&value](int* ptr) { value = *ptr; });
|
||||
|
||||
ASSERT_EQ(value, 5);
|
||||
}
|
||||
|
||||
// Multi-threaded test (to try and simulate multi-process use case)
|
||||
//
|
||||
// Create a shared object, create a bunch of threads each with their own
|
||||
// remote handle to the object, and have each thread perform an action
|
||||
// that would be unsafe if locking were not working properly.
|
||||
//
|
||||
// In this case, the threads are incrementing the elements of an array by a
|
||||
// a constant value, and the array is intentionally large enough that it can't
|
||||
// fit in a cache line.
|
||||
//
|
||||
// Each thread will assert that it never sees a version of the array where all
|
||||
// the elements aren't equal, and the main thread won't stop the test until it's
|
||||
// seen a minimum amount of changes made to the entire array without any
|
||||
// inconsistency.
|
||||
TEST(SynchronizedSharedMemoryTest, Multithreaded)
|
||||
{
|
||||
struct TestStruct {
|
||||
// Big enough that it can't fit into a single cache line (512 bits on x86)
|
||||
std::array<uint64_t, 16> data;
|
||||
|
||||
void WriteValueToAll(uint64_t value) {
|
||||
for (auto& x : data) {
|
||||
x = value;
|
||||
}
|
||||
}
|
||||
|
||||
void AddValueToAll(uint64_t value) {
|
||||
for (auto& x : data) {
|
||||
x += value;
|
||||
}
|
||||
}
|
||||
|
||||
void AssertAllEqual(uint64_t value) const {
|
||||
for (size_t i = 0; i < (data.size() - 1); ++i) {
|
||||
ASSERT_EQ(data[i], data[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
void AssertEq(const TestStruct& other) const {
|
||||
for (size_t i = 0; i < data.size(); ++i) {
|
||||
ASSERT_EQ(data[i], other.data[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using SharedType = SynchronizedSharedMemory<TestStruct>;
|
||||
|
||||
// Some useful settings for the test
|
||||
|
||||
// Number of worker threads to spin up
|
||||
constexpr size_t kNumThreads = 4;
|
||||
// Value to start each element at
|
||||
constexpr uint64_t kTestStartValue = 0x1234;
|
||||
// Value to increment each element by
|
||||
constexpr uint64_t kTestIncrementAmount = 0x5678;
|
||||
// Number of changes the main thread must see before passing
|
||||
constexpr uint32_t kMinimumChangesToWitness = 10000;
|
||||
|
||||
// Initial value to test that the implementation correctly handles copy
|
||||
// construction of the contained object
|
||||
constexpr TestStruct kInitialStruct = {0, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 9, 10, 11, 12, 13, 14, 15};
|
||||
|
||||
// Create the shared object, ensure it constructed properly, set all elements
|
||||
// to kTestStartValue, and assert that it worked
|
||||
|
||||
auto maybeLocalObject = SharedType::CreateNew(kInitialStruct);
|
||||
ASSERT_TRUE(maybeLocalObject);
|
||||
|
||||
maybeLocalObject->RunWithLock([&](TestStruct* shared) {
|
||||
shared->AssertEq(kInitialStruct);
|
||||
shared->WriteValueToAll(kTestStartValue);
|
||||
shared->AssertAllEqual(kTestStartValue);
|
||||
});
|
||||
|
||||
// This variable will be set on the main thread when it has seen enough
|
||||
// changes happen, causing the threads to end
|
||||
std::atomic_bool done{false};
|
||||
|
||||
// Fire up the worker threads, each with their own "remote info", and have
|
||||
// them add a constant to each element in the array while holding the lock
|
||||
// If any thread ever witnesses a situation where all elements aren't equal,
|
||||
// the test is failed
|
||||
std::array<std::thread, kNumThreads> workerThreads;
|
||||
for (auto& t : workerThreads) {
|
||||
SynchronizedSharedMemoryRemoteInfo remoteInfo;
|
||||
ASSERT_TRUE(SynchronizedSharedMemoryTestFriend::GenerateRemoteInfo(
|
||||
*maybeLocalObject, &remoteInfo));
|
||||
|
||||
t = std::thread([remoteInfo, &done] {
|
||||
auto maybeRemoteObject = SharedType::CreateFromRemoteInfo(remoteInfo);
|
||||
ASSERT_TRUE(maybeRemoteObject);
|
||||
|
||||
while (!done.load(std::memory_order_relaxed)) {
|
||||
maybeRemoteObject->RunWithLock([&](TestStruct* shared) {
|
||||
shared->AssertAllEqual(shared->data[0]);
|
||||
shared->AddValueToAll(kTestIncrementAmount);
|
||||
shared->AssertAllEqual(shared->data[0]);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// The main thread will just keep locking the mutex and sampling the state
|
||||
// of the array, ensuring all elements are equal and keeping track of the
|
||||
// number of times it's seen the value change.
|
||||
// Once it's seen `kMinimumChangesToWitness` changes occur, it will set
|
||||
// `done` to signal all the threads to stop and join with them.
|
||||
|
||||
uint64_t numChangesWitnessed = 0;
|
||||
uint64_t lastValueWitnessed = kTestStartValue;
|
||||
|
||||
while (numChangesWitnessed < kMinimumChangesToWitness) {
|
||||
// Grab the current value of the array and ensure they're all equal
|
||||
uint64_t valueWitnessed = 0;
|
||||
maybeLocalObject->RunWithLock([&](TestStruct* shared) {
|
||||
valueWitnessed = shared->data[0];
|
||||
shared->AssertAllEqual(valueWitnessed);
|
||||
});
|
||||
|
||||
// If the value has changed, take note
|
||||
if (lastValueWitnessed != valueWitnessed) {
|
||||
++numChangesWitnessed;
|
||||
lastValueWitnessed = valueWitnessed;
|
||||
}
|
||||
}
|
||||
|
||||
// We passed! Shut it all down
|
||||
done.store(true, std::memory_order_relaxed);
|
||||
|
||||
for (auto& t : workerThreads) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
|
@ -0,0 +1,16 @@
|
|||
# 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/.
|
||||
|
||||
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
|
||||
SOURCES = [
|
||||
"TestSynchronizedSharedMemoryWindows.cpp",
|
||||
]
|
||||
else:
|
||||
SOURCES = [
|
||||
"TestSynchronizedSharedMemoryFallback.cpp",
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = "xul-gtest"
|
||||
|
||||
include("/ipc/chromium/chromium-config.mozbuild")
|
|
@ -0,0 +1,156 @@
|
|||
/* -*- 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_GAMEPAD_GAMEPADWINDOWSUTIL_H_
|
||||
#define DOM_GAMEPAD_GAMEPADWINDOWSUTIL_H_
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include <utility>
|
||||
#include <windows.h>
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
// UniqueHandle - A generic RAII wrapper for handle-like objects
|
||||
//
|
||||
// A type that behaves like UniquePtr, but for arbitrary handle types.
|
||||
// The type, HandleType, only needs to be copyable (like all C primitive types
|
||||
// and structures). The `Traits` template parameter will define the `HandleType`
|
||||
// and the following 3 or 4 operations on the basic type:
|
||||
//
|
||||
// static HandleType Invalid()
|
||||
// Must return an instance of the object that is considered invalid
|
||||
// according to `IsValid()`. Note that UniqueHandle doesn't assume that
|
||||
// there is a sentinal value that is invalid -- It only cares that
|
||||
// IsValid(Invalid()) is false
|
||||
//
|
||||
// static bool IsValid(HandleType)
|
||||
// Returns true if the handle is valid, false otherwise
|
||||
//
|
||||
// static void Close(HandleType)
|
||||
// Called on destruction to close the handle (only called if IsValid())
|
||||
//
|
||||
// static bool IsEqual(HandleType, HandleType)
|
||||
// OPTIONAL -- If you want the UniqueHandle type to support == and !=
|
||||
// operators, implement this. It doesn't affect anything else.
|
||||
//
|
||||
// (Note that the functions can take the handle by value or by const-ref)
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// struct FdHandleTraits {
|
||||
// using HandleType = int;
|
||||
// static HandleType Invalid() { return -1; }
|
||||
// static void Close(HandleType handle) { close(handle); }
|
||||
// static bool IsValid(HandleType handle) { return handle >= 0; }
|
||||
// static bool IsEqual(HandleType a, HandleType b) { return a == b; }
|
||||
// };
|
||||
//
|
||||
// UniqueHandle<FdHandleTraits> uniqueFile(open(...));
|
||||
//
|
||||
template <typename Traits>
|
||||
class UniqueHandle {
|
||||
public:
|
||||
using HandleType = typename Traits::HandleType;
|
||||
|
||||
UniqueHandle() : mHandle(Traits::Invalid()) {
|
||||
MOZ_ASSERT(!Traits::IsValid(mHandle));
|
||||
}
|
||||
explicit UniqueHandle(HandleType t) : mHandle(std::move(t)) {
|
||||
MOZ_ASSERT(!Traits::IsValid(Traits::Invalid()));
|
||||
}
|
||||
|
||||
UniqueHandle(UniqueHandle&& other) : UniqueHandle() { swap(*this, other); }
|
||||
|
||||
~UniqueHandle() {
|
||||
if (Traits::IsValid(mHandle)) {
|
||||
Traits::Close(mHandle);
|
||||
mHandle = Traits::Invalid();
|
||||
}
|
||||
}
|
||||
|
||||
UniqueHandle& operator=(UniqueHandle&& other) {
|
||||
UniqueHandle temp(std::move(other));
|
||||
swap(*this, temp);
|
||||
return *this;
|
||||
}
|
||||
|
||||
HandleType Release() {
|
||||
using std::swap;
|
||||
HandleType result = Traits::Invalid();
|
||||
swap(result, mHandle);
|
||||
return result;
|
||||
}
|
||||
|
||||
explicit operator bool() const { return Traits::IsValid(mHandle); }
|
||||
|
||||
HandleType Get() const { return mHandle; }
|
||||
|
||||
friend void swap(UniqueHandle& a, UniqueHandle& b) {
|
||||
// Well-behaved types *should* properly handle self-swap, but let's be
|
||||
// defensive just in case
|
||||
if (&a != &b) {
|
||||
using std::swap;
|
||||
swap(a.mHandle, b.mHandle);
|
||||
}
|
||||
}
|
||||
|
||||
// If the traits don't provide an isEqual() function, this will get SFINAE'd
|
||||
// out and equality tests won't be supported
|
||||
template <typename = decltype(Traits::IsEqual(Traits::Invalid(),
|
||||
Traits::Invalid()))>
|
||||
friend bool operator==(const UniqueHandle& a, const UniqueHandle& b) {
|
||||
return Traits::IsEqual(a.mHandle, b.mHandle);
|
||||
}
|
||||
|
||||
template <typename = decltype(Traits::IsEqual(Traits::Invalid(),
|
||||
Traits::Invalid()))>
|
||||
friend bool operator!=(const UniqueHandle& a, const UniqueHandle& b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
UniqueHandle(const UniqueHandle&) = delete;
|
||||
UniqueHandle& operator=(const UniqueHandle&) = delete;
|
||||
|
||||
private:
|
||||
HandleType mHandle;
|
||||
};
|
||||
|
||||
// UniqueHandle trait for NT Kernel handles.
|
||||
// Has `Tag` to create a strong type for each kind of NT handle.
|
||||
template <typename Tag>
|
||||
struct NTKernelHandleTraits {
|
||||
using HandleType = HANDLE;
|
||||
static HandleType Invalid() { return nullptr; }
|
||||
static bool IsValid(HandleType handle) { return !!handle; }
|
||||
static void Close(HandleType handle) {
|
||||
MOZ_ALWAYS_TRUE(::CloseHandle(handle));
|
||||
}
|
||||
static bool IsEqual(const HandleType& a, const HandleType& b) {
|
||||
return a == b;
|
||||
}
|
||||
};
|
||||
|
||||
// Create strong handle traits for several different NT handle types
|
||||
struct NTMutexHandleTag {};
|
||||
struct NTEventHandleTag {};
|
||||
struct NTFileMappingHandleTag {};
|
||||
|
||||
using NTMutexHandleTraits = NTKernelHandleTraits<NTMutexHandleTag>;
|
||||
using NTEventHandleTraits = NTKernelHandleTraits<NTEventHandleTag>;
|
||||
using NTFileMappingHandleTraits = NTKernelHandleTraits<NTFileMappingHandleTag>;
|
||||
|
||||
// UniqueHandle trait for NT file mapping
|
||||
struct NTFileViewHandleTraits {
|
||||
using HandleType = HANDLE;
|
||||
static void* Invalid() { return nullptr; }
|
||||
static bool IsValid(void* p) { return !!p; }
|
||||
static void Close(void* p) { MOZ_ALWAYS_TRUE(::UnmapViewOfFile(p)); }
|
||||
static bool IsEqual(const void* a, const void* b) { return a == b; }
|
||||
};
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
#endif // DOM_GAMEPAD_GAMEPADWINDOWSUTIL_H_
|
|
@ -0,0 +1,213 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
// This is the implementation of SynchronizedSharedMemory for Windows.
|
||||
|
||||
#include "mozilla/dom/SynchronizedSharedMemory.h"
|
||||
#include "mozilla/dom/SynchronizedSharedMemoryRemoteInfo.h"
|
||||
#include "mozilla/ipc/ProtocolUtils.h"
|
||||
#include "GamepadWindowsUtil.h"
|
||||
#include <utility>
|
||||
#include <windows.h>
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
// This Impl class is where most of the actual logic is for creating/using
|
||||
// the shared memory is located. The public member functions of
|
||||
// SynchronizedSharedMemoryDetail mostly just pass the call through to this.
|
||||
class SynchronizedSharedMemoryDetail::Impl {
|
||||
public:
|
||||
static UniquePtr<Impl> CreateNew(uint32_t aSize) {
|
||||
UniqueHandle<NTMutexHandleTraits> mutex(::CreateMutex(
|
||||
nullptr /*no ACL*/, FALSE /*not owned*/, nullptr /*no name*/));
|
||||
if (!mutex) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UniqueHandle<NTFileMappingHandleTraits> fileMapping(
|
||||
::CreateFileMapping(INVALID_HANDLE_VALUE /*memory-only*/,
|
||||
nullptr /*no ACL*/, PAGE_READWRITE, 0 /*sizeHigh*/,
|
||||
aSize /*sizeLow*/, nullptr /*no name*/));
|
||||
if (!fileMapping) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UniqueHandle<NTFileViewHandleTraits> sharedPtr(::MapViewOfFile(
|
||||
fileMapping.Get(), FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/,
|
||||
0 /*offsetLow*/, 0 /*Map entire region*/));
|
||||
if (!sharedPtr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return UniquePtr<Impl>(new Impl(std::move(mutex), std::move(fileMapping),
|
||||
std::move(sharedPtr)));
|
||||
}
|
||||
static UniquePtr<Impl> CreateFromRemote(
|
||||
const SynchronizedSharedMemoryRemoteInfo& aRemoteCreationInfo) {
|
||||
UniqueHandle<NTMutexHandleTraits> mutex(
|
||||
reinterpret_cast<HANDLE>(aRemoteCreationInfo.mutexHandle()));
|
||||
UniqueHandle<NTFileMappingHandleTraits> fileMapping(
|
||||
reinterpret_cast<HANDLE>(aRemoteCreationInfo.sharedFileHandle()));
|
||||
|
||||
if (!mutex || !fileMapping) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UniqueHandle<NTFileViewHandleTraits> sharedPtr(::MapViewOfFile(
|
||||
fileMapping.Get(), FILE_MAP_ALL_ACCESS, 0 /*offsetHigh*/,
|
||||
0 /*offsetLow*/, 0 /*Map entire region*/));
|
||||
if (!sharedPtr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return UniquePtr<Impl>(new Impl(std::move(mutex), std::move(fileMapping),
|
||||
std::move(sharedPtr)));
|
||||
}
|
||||
|
||||
~Impl() {
|
||||
MOZ_ASSERT(mMutex);
|
||||
MOZ_ASSERT(mFileMapping);
|
||||
MOZ_ASSERT(mSharedPtr);
|
||||
}
|
||||
|
||||
bool GenerateRemoteInfo(const mozilla::ipc::IProtocol* aActor,
|
||||
SynchronizedSharedMemoryRemoteInfo* aOut) {
|
||||
MOZ_ASSERT(mMutex);
|
||||
MOZ_ASSERT(mFileMapping);
|
||||
|
||||
DWORD targetPID = aActor ? aActor->OtherPid() : ::GetCurrentProcessId();
|
||||
|
||||
HANDLE remoteMutexHandle = nullptr;
|
||||
if (!mozilla::ipc::DuplicateHandle(mMutex.Get(), targetPID,
|
||||
&remoteMutexHandle, 0,
|
||||
DUPLICATE_SAME_ACCESS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HANDLE remoteSharedFileHandle = nullptr;
|
||||
if (!mozilla::ipc::DuplicateHandle(mFileMapping.Get(), targetPID,
|
||||
&remoteSharedFileHandle, 0,
|
||||
DUPLICATE_SAME_ACCESS)) {
|
||||
// There's no reasonable way to prevent leaking remoteMutexHandle here,
|
||||
// since we don't own it
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(remoteMutexHandle);
|
||||
MOZ_ASSERT(remoteSharedFileHandle);
|
||||
|
||||
(*aOut) = SynchronizedSharedMemoryRemoteInfo{
|
||||
reinterpret_cast<WindowsHandle>(remoteMutexHandle),
|
||||
reinterpret_cast<WindowsHandle>(remoteSharedFileHandle),
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LockMutex() {
|
||||
MOZ_ALWAYS_TRUE(::WaitForSingleObject(mMutex.Get(), INFINITE) !=
|
||||
WAIT_FAILED);
|
||||
}
|
||||
|
||||
void UnlockMutex() { MOZ_ALWAYS_TRUE(::ReleaseMutex(mMutex.Get())); }
|
||||
|
||||
void* GetPtr() const { return mSharedPtr.Get(); }
|
||||
|
||||
// Disallow copying/moving
|
||||
Impl(const Impl&) = delete;
|
||||
Impl(Impl&&) = delete;
|
||||
Impl& operator=(const Impl&) = delete;
|
||||
Impl& operator=(Impl&&) = delete;
|
||||
|
||||
private:
|
||||
Impl(UniqueHandle<NTMutexHandleTraits> aMutex,
|
||||
UniqueHandle<NTFileMappingHandleTraits> aFileMapping,
|
||||
UniqueHandle<NTFileViewHandleTraits> aSharedPtr)
|
||||
: mMutex(std::move(aMutex)),
|
||||
mFileMapping(std::move(aFileMapping)),
|
||||
mSharedPtr(std::move(aSharedPtr)) {
|
||||
MOZ_ASSERT(mMutex);
|
||||
MOZ_ASSERT(mFileMapping);
|
||||
MOZ_ASSERT(mSharedPtr);
|
||||
}
|
||||
|
||||
UniqueHandle<NTMutexHandleTraits> mMutex;
|
||||
UniqueHandle<NTFileMappingHandleTraits> mFileMapping;
|
||||
UniqueHandle<NTFileViewHandleTraits> mSharedPtr;
|
||||
};
|
||||
|
||||
//////////// Everything below this line is Pimpl boilerplate ///////////////////
|
||||
|
||||
// static
|
||||
Maybe<SynchronizedSharedMemoryDetail> SynchronizedSharedMemoryDetail::CreateNew(
|
||||
uint32_t aSize) {
|
||||
MOZ_RELEASE_ASSERT(aSize);
|
||||
|
||||
UniquePtr<Impl> impl = Impl::CreateNew(aSize);
|
||||
if (!impl) {
|
||||
return Nothing{};
|
||||
}
|
||||
|
||||
return Some(SynchronizedSharedMemoryDetail(std::move(impl)));
|
||||
}
|
||||
|
||||
void SynchronizedSharedMemoryDetail::LockMutex() {
|
||||
MOZ_RELEASE_ASSERT(mImpl);
|
||||
mImpl->LockMutex();
|
||||
}
|
||||
|
||||
void SynchronizedSharedMemoryDetail::UnlockMutex() {
|
||||
MOZ_RELEASE_ASSERT(mImpl);
|
||||
mImpl->UnlockMutex();
|
||||
}
|
||||
|
||||
void* SynchronizedSharedMemoryDetail::GetPtr() const {
|
||||
MOZ_RELEASE_ASSERT(mImpl);
|
||||
return mImpl->GetPtr();
|
||||
}
|
||||
|
||||
// static
|
||||
Maybe<SynchronizedSharedMemoryDetail>
|
||||
SynchronizedSharedMemoryDetail::CreateFromRemoteInfo(
|
||||
const SynchronizedSharedMemoryRemoteInfo& aIPCInfo) {
|
||||
UniquePtr<Impl> impl = Impl::CreateFromRemote(aIPCInfo);
|
||||
if (!impl) {
|
||||
return Nothing{};
|
||||
}
|
||||
|
||||
return Some(SynchronizedSharedMemoryDetail(std::move(impl)));
|
||||
}
|
||||
|
||||
bool SynchronizedSharedMemoryDetail::GenerateRemoteInfo(
|
||||
const mozilla::ipc::IProtocol* aActor,
|
||||
SynchronizedSharedMemoryRemoteInfo* aOut) {
|
||||
MOZ_RELEASE_ASSERT(mImpl);
|
||||
|
||||
return mImpl->GenerateRemoteInfo(aActor, aOut);
|
||||
}
|
||||
|
||||
bool SynchronizedSharedMemoryDetail::GenerateTestRemoteInfo(
|
||||
SynchronizedSharedMemoryRemoteInfo* aOut) {
|
||||
MOZ_RELEASE_ASSERT(mImpl);
|
||||
|
||||
return mImpl->GenerateRemoteInfo(nullptr, aOut);
|
||||
}
|
||||
|
||||
SynchronizedSharedMemoryDetail::SynchronizedSharedMemoryDetail() = default;
|
||||
SynchronizedSharedMemoryDetail::~SynchronizedSharedMemoryDetail() = default;
|
||||
|
||||
SynchronizedSharedMemoryDetail::SynchronizedSharedMemoryDetail(
|
||||
UniquePtr<Impl> aImpl)
|
||||
: mImpl(std::move(aImpl)) {
|
||||
MOZ_ASSERT(mImpl);
|
||||
}
|
||||
|
||||
SynchronizedSharedMemoryDetail::SynchronizedSharedMemoryDetail(
|
||||
SynchronizedSharedMemoryDetail&&) noexcept = default;
|
||||
SynchronizedSharedMemoryDetail& SynchronizedSharedMemoryDetail::operator=(
|
||||
SynchronizedSharedMemoryDetail&&) noexcept = default;
|
||||
|
||||
} // namespace mozilla::dom
|
|
@ -0,0 +1,16 @@
|
|||
/* 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/. */
|
||||
|
||||
using mozilla::WindowsHandle from "mozilla/ipc/IPCTypes.h";
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
struct SynchronizedSharedMemoryRemoteInfo {
|
||||
WindowsHandle mutexHandle;
|
||||
WindowsHandle sharedFileHandle;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
Загрузка…
Ссылка в новой задаче