mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-10 03:45:46 +00:00
Bug 1463587: Part 2 - Add a shared-memory structured clone key-value store. r=erahm,bz
This class allows one read-write copy of a map in the parent process to share data with multiple read-only copies in child processes. The maps only hold onto data as structured clone blobs, and deserialize them each time a key is read. This commit only provides the bare-bones data structures. Follow-ups will add bindings, change events, and automatic flushes. MozReview-Commit-ID: LimwfmFBNOi --HG-- extra : rebase_source : e43985c39bd1cfd05a2ad536b0d7f74db494a753 extra : source : c27295337b4c16e2a178106a3aa873d2a0e5a1f4
This commit is contained in:
parent
5754c092dc
commit
03bf0b5bdd
@ -7,8 +7,10 @@
|
||||
#include "ProcessGlobal.h"
|
||||
|
||||
#include "nsContentCID.h"
|
||||
#include "mozilla/dom/ContentChild.h"
|
||||
#include "mozilla/dom/MessageManagerBinding.h"
|
||||
#include "mozilla/dom/ResolveSystemBinding.h"
|
||||
#include "mozilla/dom/ipc/SharedMap.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
@ -72,6 +74,16 @@ ProcessGlobal::Get()
|
||||
return global;
|
||||
}
|
||||
|
||||
already_AddRefed<mozilla::dom::ipc::SharedMap>
|
||||
ProcessGlobal::SharedData()
|
||||
{
|
||||
if (ContentChild* child = ContentChild::GetSingleton()) {
|
||||
return do_AddRef(child->SharedData());
|
||||
}
|
||||
auto* ppmm = nsFrameMessageManager::sParentProcessManager;
|
||||
return do_AddRef(ppmm->SharedData()->GetReadOnly());
|
||||
}
|
||||
|
||||
bool
|
||||
ProcessGlobal::WasCreated()
|
||||
{
|
||||
|
@ -25,6 +25,10 @@
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
namespace ipc {
|
||||
class SharedMap;
|
||||
}
|
||||
|
||||
class ProcessGlobal :
|
||||
public nsIMessageSender,
|
||||
public nsMessageManagerScriptExecutor,
|
||||
@ -84,6 +88,8 @@ public:
|
||||
mMessageManager->GetInitialProcessData(aCx, aInitialProcessData, aError);
|
||||
}
|
||||
|
||||
already_AddRefed<ipc::SharedMap> SharedData();
|
||||
|
||||
NS_FORWARD_SAFE_NSIMESSAGESENDER(mMessageManager)
|
||||
|
||||
virtual void LoadScript(const nsAString& aURL);
|
||||
|
@ -48,6 +48,7 @@
|
||||
#include "mozilla/dom/SameProcessMessageQueue.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "mozilla/dom/ToJSValue.h"
|
||||
#include "mozilla/dom/ipc/SharedMap.h"
|
||||
#include "mozilla/dom/ipc/StructuredCloneData.h"
|
||||
#include "mozilla/dom/DOMStringList.h"
|
||||
#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
|
||||
@ -999,6 +1000,19 @@ nsFrameMessageManager::GetInitialProcessData(JSContext* aCx,
|
||||
aInitialProcessData.set(init);
|
||||
}
|
||||
|
||||
WritableSharedMap*
|
||||
nsFrameMessageManager::SharedData()
|
||||
{
|
||||
if (!mChrome || !mIsProcessManager) {
|
||||
MOZ_ASSERT(false, "Should only call this binding method on ppmm");
|
||||
return nullptr;
|
||||
}
|
||||
if (!mSharedData) {
|
||||
mSharedData = new WritableSharedMap();
|
||||
}
|
||||
return mSharedData;
|
||||
}
|
||||
|
||||
already_AddRefed<ProcessMessageManager>
|
||||
nsFrameMessageManager::GetProcessMessageManager(ErrorResult& aError)
|
||||
{
|
||||
|
@ -37,6 +37,11 @@
|
||||
class nsFrameLoader;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace ipc {
|
||||
class FileDescriptor;
|
||||
}
|
||||
|
||||
namespace dom {
|
||||
|
||||
class nsIContentParent;
|
||||
@ -54,6 +59,8 @@ class ProcessMessageManager;
|
||||
|
||||
namespace ipc {
|
||||
|
||||
class WritableSharedMap;
|
||||
|
||||
// Note: we round the time we spend to the nearest millisecond. So a min value
|
||||
// of 1 ms actually captures from 500us and above.
|
||||
static const uint32_t kMinTelemetrySyncMessageManagerLatencyMs = 1;
|
||||
@ -233,6 +240,8 @@ public:
|
||||
JS::MutableHandle<JS::Value> aInitialProcessData,
|
||||
mozilla::ErrorResult& aError);
|
||||
|
||||
mozilla::dom::ipc::WritableSharedMap* SharedData();
|
||||
|
||||
NS_DECL_NSIMESSAGESENDER
|
||||
NS_DECL_NSICONTENTFRAMEMESSAGEMANAGER
|
||||
|
||||
@ -340,6 +349,7 @@ protected:
|
||||
nsTArray<nsString> mPendingScripts;
|
||||
nsTArray<bool> mPendingScriptsGlobalStates;
|
||||
JS::Heap<JS::Value> mInitialProcessData;
|
||||
RefPtr<mozilla::dom::ipc::WritableSharedMap> mSharedData;
|
||||
|
||||
void LoadPendingScripts(nsFrameMessageManager* aManager,
|
||||
nsFrameMessageManager* aChildMM);
|
||||
|
@ -48,6 +48,7 @@
|
||||
#include "mozilla/dom/URLClassifierChild.h"
|
||||
#include "mozilla/dom/WorkerDebugger.h"
|
||||
#include "mozilla/dom/WorkerDebuggerManager.h"
|
||||
#include "mozilla/dom/ipc/SharedMap.h"
|
||||
#include "mozilla/gfx/gfxVars.h"
|
||||
#include "mozilla/gfx/Logging.h"
|
||||
#include "mozilla/psm/PSMContentListener.h"
|
||||
@ -592,7 +593,9 @@ mozilla::ipc::IPCResult
|
||||
ContentChild::RecvSetXPCOMProcessAttributes(const XPCOMInitData& aXPCOMInit,
|
||||
const StructuredCloneData& aInitialData,
|
||||
nsTArray<LookAndFeelInt>&& aLookAndFeelIntCache,
|
||||
nsTArray<SystemFontListEntry>&& aFontList)
|
||||
nsTArray<SystemFontListEntry>&& aFontList,
|
||||
const FileDescriptor& aSharedDataMapFile,
|
||||
const uint32_t& aSharedDataMapSize)
|
||||
{
|
||||
if (!sShutdownCanary) {
|
||||
return IPC_OK();
|
||||
@ -604,6 +607,9 @@ ContentChild::RecvSetXPCOMProcessAttributes(const XPCOMInitData& aXPCOMInit,
|
||||
InitXPCOM(aXPCOMInit, aInitialData);
|
||||
InitGraphicsDeviceData(aXPCOMInit.contentDeviceData());
|
||||
|
||||
mSharedData = new SharedMap(ProcessGlobal::Get(), aSharedDataMapFile,
|
||||
aSharedDataMapSize);
|
||||
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
@ -2378,6 +2384,8 @@ ContentChild::ActorDestroy(ActorDestroyReason why)
|
||||
|
||||
BlobURLProtocolHandler::RemoveDataEntries();
|
||||
|
||||
mSharedData = nullptr;
|
||||
|
||||
mAlertObservers.Clear();
|
||||
|
||||
mIdleObservers.Clear();
|
||||
@ -2557,6 +2565,18 @@ ContentChild::RecvRegisterStringBundles(nsTArray<mozilla::dom::StringBundleDescr
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
ContentChild::RecvUpdateSharedData(const FileDescriptor& aMapFile,
|
||||
const uint32_t& aMapSize,
|
||||
nsTArray<nsCString>&& aChangedKeys)
|
||||
{
|
||||
if (mSharedData) {
|
||||
mSharedData->Update(aMapFile, aMapSize, std::move(aChangedKeys));
|
||||
}
|
||||
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult
|
||||
ContentChild::RecvGeolocationUpdate(nsIDOMGeoPosition* aPosition)
|
||||
{
|
||||
|
@ -66,6 +66,10 @@ class URIParams;
|
||||
|
||||
namespace dom {
|
||||
|
||||
namespace ipc {
|
||||
class SharedMap;
|
||||
}
|
||||
|
||||
class AlertObserver;
|
||||
class ConsoleListener;
|
||||
class ClonedMessageData;
|
||||
@ -164,6 +168,8 @@ public:
|
||||
|
||||
bool IsShuttingDown() const;
|
||||
|
||||
ipc::SharedMap* SharedData() { return mSharedData; };
|
||||
|
||||
static void AppendProcessId(nsACString& aName);
|
||||
|
||||
static void UpdateCookieStatus(nsIChannel *aChannel);
|
||||
@ -396,6 +402,10 @@ public:
|
||||
|
||||
mozilla::ipc::IPCResult RecvRegisterStringBundles(nsTArray<StringBundleDescriptor>&& stringBundles) override;
|
||||
|
||||
mozilla::ipc::IPCResult RecvUpdateSharedData(const FileDescriptor& aMapFile,
|
||||
const uint32_t& aMapSize,
|
||||
nsTArray<nsCString>&& aChangedKeys) override;
|
||||
|
||||
virtual mozilla::ipc::IPCResult RecvGeolocationUpdate(nsIDOMGeoPosition* aPosition) override;
|
||||
|
||||
virtual mozilla::ipc::IPCResult RecvGeolocationError(const uint16_t& errorCode) override;
|
||||
@ -615,7 +625,9 @@ public:
|
||||
RecvSetXPCOMProcessAttributes(const XPCOMInitData& aXPCOMInit,
|
||||
const StructuredCloneData& aInitialData,
|
||||
nsTArray<LookAndFeelInt>&& aLookAndFeelIntCache,
|
||||
nsTArray<SystemFontListEntry>&& aFontList) override;
|
||||
nsTArray<SystemFontListEntry>&& aFontList,
|
||||
const FileDescriptor& aSharedDataMapFile,
|
||||
const uint32_t& aSharedDataMapSize) override;
|
||||
|
||||
virtual mozilla::ipc::IPCResult
|
||||
RecvProvideAnonymousTemporaryFile(const uint64_t& aID, const FileDescOrError& aFD) override;
|
||||
@ -815,6 +827,8 @@ private:
|
||||
nsCOMPtr<nsIDomainPolicy> mPolicy;
|
||||
nsCOMPtr<nsITimer> mForceKillTimer;
|
||||
|
||||
RefPtr<ipc::SharedMap> mSharedData;
|
||||
|
||||
#ifdef MOZ_GECKO_PROFILER
|
||||
RefPtr<ChildProfilerController> mProfilerController;
|
||||
#endif
|
||||
|
@ -61,6 +61,7 @@
|
||||
#include "mozilla/dom/quota/QuotaManagerService.h"
|
||||
#include "mozilla/dom/ServiceWorkerUtils.h"
|
||||
#include "mozilla/dom/URLClassifierParent.h"
|
||||
#include "mozilla/dom/ipc/SharedMap.h"
|
||||
#include "mozilla/embedding/printingui/PrintingParent.h"
|
||||
#include "mozilla/extensions/StreamFilterParent.h"
|
||||
#include "mozilla/gfx/gfxVars.h"
|
||||
@ -2323,8 +2324,12 @@ ContentParent::InitInternal(ProcessPriority aInitialPriority)
|
||||
ScreenManager& screenManager = ScreenManager::GetSingleton();
|
||||
screenManager.CopyScreensToRemote(this);
|
||||
|
||||
ipc::WritableSharedMap* sharedData = nsFrameMessageManager::sParentProcessManager->SharedData();
|
||||
sharedData->Flush();
|
||||
|
||||
Unused << SendSetXPCOMProcessAttributes(xpcomInit, initialData, lnfCache,
|
||||
fontList);
|
||||
fontList, sharedData->CloneMapFile(),
|
||||
sharedData->MapSize());
|
||||
|
||||
nsCOMPtr<nsIChromeRegistry> registrySvc = nsChromeRegistry::GetService();
|
||||
nsChromeRegistryChrome* chromeRegistry =
|
||||
@ -2873,6 +2878,7 @@ ContentParent::Observe(nsISupports* aSubject,
|
||||
|
||||
// Okay to call ShutDownProcess multiple times.
|
||||
ShutDownProcess(SEND_SHUTDOWN_MESSAGE);
|
||||
MarkAsDead();
|
||||
|
||||
// Wait for shutdown to complete, so that we receive any shutdown
|
||||
// data (e.g. telemetry) from the child before we quit.
|
||||
|
@ -466,6 +466,9 @@ child:
|
||||
|
||||
async RegisterStringBundles(StringBundleDescriptor[] stringBundles);
|
||||
|
||||
async UpdateSharedData(FileDescriptor mapFile, uint32_t aSize,
|
||||
nsCString[] changedKeys);
|
||||
|
||||
// nsIPermissionManager messages
|
||||
async AddPermission(Permission permission);
|
||||
async RemoveAllPermissions();
|
||||
@ -513,7 +516,9 @@ child:
|
||||
StructuredCloneData initialData,
|
||||
LookAndFeelInt[] lookAndFeelIntCache,
|
||||
/* used on MacOSX and Linux only: */
|
||||
SystemFontListEntry[] systemFontList);
|
||||
SystemFontListEntry[] systemFontList,
|
||||
FileDescriptor sharedDataMapFile,
|
||||
uint32_t sharedDataMapSize);
|
||||
|
||||
// Notify child that last-pb-context-exited notification was observed
|
||||
async LastPrivateDocShellDestroyed();
|
||||
|
371
dom/ipc/SharedMap.cpp
Normal file
371
dom/ipc/SharedMap.cpp
Normal file
@ -0,0 +1,371 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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 "SharedMap.h"
|
||||
|
||||
#include "MemMapSnapshot.h"
|
||||
#include "ScriptPreloader-inl.h"
|
||||
|
||||
#include "mozilla/dom/ContentParent.h"
|
||||
#include "mozilla/dom/ProcessGlobal.h"
|
||||
|
||||
using namespace mozilla::loader;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace ipc;
|
||||
|
||||
namespace dom {
|
||||
namespace ipc {
|
||||
|
||||
// Align to size of uintptr_t here, to be safe. It's probably not strictly
|
||||
// necessary, though.
|
||||
constexpr size_t kStructuredCloneAlign = sizeof(uintptr_t);
|
||||
|
||||
|
||||
static inline void
|
||||
AlignTo(size_t* aOffset, size_t aAlign)
|
||||
{
|
||||
if (auto mod = *aOffset % aAlign) {
|
||||
*aOffset += aAlign - mod;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SharedMap::SharedMap()
|
||||
{}
|
||||
|
||||
SharedMap::SharedMap(nsIGlobalObject* aGlobal, const FileDescriptor& aMapFile,
|
||||
size_t aMapSize)
|
||||
{
|
||||
mMapFile.reset(new FileDescriptor(aMapFile));
|
||||
mMapSize = aMapSize;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
SharedMap::Has(const nsACString& aName)
|
||||
{
|
||||
return mEntries.Contains(aName);
|
||||
}
|
||||
|
||||
void
|
||||
SharedMap::Get(JSContext* aCx,
|
||||
const nsACString& aName,
|
||||
JS::MutableHandleValue aRetVal,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
auto res = MaybeRebuild();
|
||||
if (res.isErr()) {
|
||||
aRv.Throw(res.unwrapErr());
|
||||
return;
|
||||
}
|
||||
|
||||
Entry* entry = mEntries.Get(aName);
|
||||
if (!entry) {
|
||||
aRetVal.setNull();
|
||||
return;
|
||||
}
|
||||
|
||||
entry->Read(aCx, aRetVal, aRv);
|
||||
}
|
||||
|
||||
void
|
||||
SharedMap::Entry::Read(JSContext* aCx,
|
||||
JS::MutableHandleValue aRetVal,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
if (mData.is<StructuredCloneData>()) {
|
||||
// We have a temporary buffer for a key that was changed after the last
|
||||
// snapshot. Just decode it directly.
|
||||
auto& holder = mData.as<StructuredCloneData>();
|
||||
holder.Read(aCx, aRetVal, aRv);
|
||||
return;
|
||||
}
|
||||
|
||||
// We have a pointer to a shared memory region containing our structured
|
||||
// clone data. Create a temporary buffer to decode that data, and then
|
||||
// discard it so that we don't keep a separate process-local copy around any
|
||||
// longer than necessary.
|
||||
StructuredCloneData holder;
|
||||
if (!holder.CopyExternalData(Data(), Size())) {
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
}
|
||||
|
||||
holder.Read(aCx, aRetVal, aRv);
|
||||
}
|
||||
|
||||
FileDescriptor
|
||||
SharedMap::CloneMapFile()
|
||||
{
|
||||
if (mMap.initialized()) {
|
||||
return mMap.cloneHandle();
|
||||
}
|
||||
return *mMapFile;
|
||||
}
|
||||
|
||||
void
|
||||
SharedMap::Update(const FileDescriptor& aMapFile, size_t aMapSize,
|
||||
nsTArray<nsCString>&& aChangedKeys)
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(!mWritable);
|
||||
|
||||
mMap.reset();
|
||||
if (mMapFile) {
|
||||
*mMapFile = aMapFile;
|
||||
} else {
|
||||
mMapFile.reset(new FileDescriptor(aMapFile));
|
||||
}
|
||||
mMapSize = aMapSize;
|
||||
mEntries.Clear();
|
||||
}
|
||||
|
||||
void
|
||||
SharedMap::Entry::TakeData(StructuredCloneData&& aHolder)
|
||||
{
|
||||
mData = AsVariant(std::move(aHolder));
|
||||
|
||||
mSize = Holder().Data().Size();
|
||||
}
|
||||
|
||||
void
|
||||
SharedMap::Entry::ExtractData(char* aDestPtr, uint32_t aNewOffset)
|
||||
{
|
||||
if (mData.is<StructuredCloneData>()) {
|
||||
char* ptr = aDestPtr;
|
||||
Holder().Data().ForEachDataChunk([&](const char* aData, size_t aSize) {
|
||||
memcpy(ptr, aData, aSize);
|
||||
ptr += aSize;
|
||||
return true;
|
||||
});
|
||||
MOZ_ASSERT(ptr - aDestPtr == mSize);
|
||||
} else {
|
||||
memcpy(aDestPtr, Data(), mSize);
|
||||
}
|
||||
|
||||
mData = AsVariant(aNewOffset);
|
||||
}
|
||||
|
||||
Result<Ok, nsresult>
|
||||
SharedMap::MaybeRebuild()
|
||||
{
|
||||
if (!mMapFile) {
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// This function maps a shared memory region created by Serialize() and reads
|
||||
// its header block to build a new mEntries hashtable of its contents.
|
||||
//
|
||||
// The entries created by this function contain a pointer to this SharedMap
|
||||
// instance, and the offsets and sizes of their structured clone data within
|
||||
// its shared memory region. When needed, that structured clone data is
|
||||
// retrieved directly as indexes into the SharedMap's shared memory region.
|
||||
|
||||
MOZ_TRY(mMap.initWithHandle(*mMapFile, mMapSize));
|
||||
mMapFile.reset();
|
||||
|
||||
// We should be able to pass this range as an initializer list or an immediate
|
||||
// param, but gcc currently chokes on that if optimization is enabled, and
|
||||
// initializes everything to 0.
|
||||
Range<uint8_t> range(&mMap.get<uint8_t>()[0], mMap.size());
|
||||
InputBuffer buffer(range);
|
||||
|
||||
uint32_t count;
|
||||
buffer.codeUint32(count);
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
auto entry = MakeUnique<Entry>(*this);
|
||||
entry->Code(buffer);
|
||||
|
||||
// This buffer was created at runtime, during this session, so any errors
|
||||
// indicate memory corruption, and are fatal.
|
||||
MOZ_RELEASE_ASSERT(!buffer.error());
|
||||
|
||||
// Note: Order of evaluation of function arguments is not guaranteed, so we
|
||||
// can't use entry.release() in place of entry.get() without entry->Name()
|
||||
// sometimes resulting in a null dereference.
|
||||
mEntries.Put(entry->Name(), entry.get());
|
||||
Unused << entry.release();
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
void
|
||||
SharedMap::MaybeRebuild() const
|
||||
{
|
||||
Unused << const_cast<SharedMap*>(this)->MaybeRebuild();
|
||||
}
|
||||
|
||||
WritableSharedMap::WritableSharedMap()
|
||||
: SharedMap()
|
||||
{
|
||||
mWritable = true;
|
||||
// Serialize the initial empty contents of the map immediately so that we
|
||||
// always have a file descriptor to send to callers of CloneMapFile().
|
||||
Unused << Serialize();
|
||||
MOZ_RELEASE_ASSERT(mMap.initialized());
|
||||
}
|
||||
|
||||
SharedMap*
|
||||
WritableSharedMap::GetReadOnly()
|
||||
{
|
||||
if (!mReadOnly) {
|
||||
mReadOnly = new SharedMap(ProcessGlobal::Get(), CloneMapFile(),
|
||||
MapSize());
|
||||
}
|
||||
return mReadOnly;
|
||||
}
|
||||
|
||||
Result<Ok, nsresult>
|
||||
WritableSharedMap::Serialize()
|
||||
{
|
||||
// Serializes a new snapshot of the map, initializes a new read-only shared
|
||||
// memory region with its contents, and updates all entries to point to that
|
||||
// new snapshot.
|
||||
//
|
||||
// The layout of the snapshot is as follows:
|
||||
//
|
||||
// - A header containing a uint32 count field containing the number of
|
||||
// entries in the map, followed by that number of serialized entry headers,
|
||||
// as produced by Entry::Code.
|
||||
//
|
||||
// - A data block containing structured clone data for each of the entries'
|
||||
// values. This data is referenced by absolute byte offsets from the start
|
||||
// of the shared memory region, encoded in each of the entry header values.
|
||||
// Each entry's data is aligned to kStructuredCloneAlign, and therefore may
|
||||
// have alignment padding before it.
|
||||
//
|
||||
// This serialization format is decoded by the MaybeRebuild() method of
|
||||
// read-only SharedMap() instances, and used to populate their mEntries
|
||||
// hashtables.
|
||||
//
|
||||
// Writable instances never read the header blocks, but instead directly
|
||||
// update their Entry instances to point to the appropriate offsets in the
|
||||
// shared memory region created by this function.
|
||||
|
||||
uint32_t count = mEntries.Count();
|
||||
|
||||
size_t dataSize = 0;
|
||||
size_t headerSize = sizeof(count);
|
||||
|
||||
for (auto& entry : IterHash(mEntries)) {
|
||||
headerSize += entry->HeaderSize();
|
||||
|
||||
dataSize += entry->Size();
|
||||
AlignTo(&dataSize, kStructuredCloneAlign);
|
||||
}
|
||||
|
||||
size_t offset = headerSize;
|
||||
AlignTo(&offset, kStructuredCloneAlign);
|
||||
|
||||
OutputBuffer header;
|
||||
header.codeUint32(count);
|
||||
|
||||
MemMapSnapshot mem;
|
||||
MOZ_TRY(mem.Init(offset + dataSize));
|
||||
|
||||
auto ptr = mem.Get<char>();
|
||||
|
||||
for (auto& entry : IterHash(mEntries)) {
|
||||
AlignTo(&offset, kStructuredCloneAlign);
|
||||
|
||||
entry->ExtractData(&ptr[offset], offset);
|
||||
entry->Code(header);
|
||||
|
||||
offset += entry->Size();
|
||||
}
|
||||
|
||||
// FIXME: We should create a separate OutputBuffer class which can encode to
|
||||
// a static memory region rather than dynamically allocating and then
|
||||
// copying.
|
||||
memcpy(ptr.get(), header.Get(), header.cursor());
|
||||
|
||||
// We've already updated offsets at this point. We need this to succeed.
|
||||
mMap.reset();
|
||||
MOZ_RELEASE_ASSERT(mem.Finalize(mMap).isOk());
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
void
|
||||
WritableSharedMap::BroadcastChanges()
|
||||
{
|
||||
if (mChangedKeys.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Serialize().isOk()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsTArray<ContentParent*> parents;
|
||||
ContentParent::GetAll(parents);
|
||||
for (auto& parent : parents) {
|
||||
Unused << parent->SendUpdateSharedData(CloneMapFile(), mMap.size(),
|
||||
mChangedKeys);
|
||||
}
|
||||
|
||||
if (mReadOnly) {
|
||||
mReadOnly->Update(CloneMapFile(), mMap.size(),
|
||||
std::move(mChangedKeys));
|
||||
}
|
||||
|
||||
mChangedKeys.Clear();
|
||||
}
|
||||
|
||||
void
|
||||
WritableSharedMap::Delete(const nsACString& aName)
|
||||
{
|
||||
if (mEntries.Remove(aName)) {
|
||||
KeyChanged(aName);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WritableSharedMap::Set(JSContext* aCx,
|
||||
const nsACString& aName,
|
||||
JS::HandleValue aValue,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
StructuredCloneData holder;
|
||||
|
||||
holder.Write(aCx, aValue, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!holder.BlobImpls().IsEmpty() ||
|
||||
!holder.InputStreams().IsEmpty()) {
|
||||
aRv.Throw(NS_ERROR_INVALID_ARG);
|
||||
return;
|
||||
}
|
||||
|
||||
Entry* entry = mEntries.LookupOrAdd(aName, *this, aName);
|
||||
entry->TakeData(std::move(holder));
|
||||
|
||||
KeyChanged(aName);
|
||||
}
|
||||
|
||||
void
|
||||
WritableSharedMap::Flush()
|
||||
{
|
||||
BroadcastChanges();
|
||||
}
|
||||
|
||||
void
|
||||
WritableSharedMap::KeyChanged(const nsACString& aName)
|
||||
{
|
||||
if (!mChangedKeys.ContainsSorted(aName)) {
|
||||
mChangedKeys.InsertElementSorted(aName);
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS0(SharedMap)
|
||||
|
||||
} // ipc
|
||||
} // dom
|
||||
} // mozilla
|
306
dom/ipc/SharedMap.h
Normal file
306
dom/ipc/SharedMap.h
Normal file
@ -0,0 +1,306 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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_ipc_SharedMap_h
|
||||
#define dom_ipc_SharedMap_h
|
||||
|
||||
#include "mozilla/AutoMemMap.h"
|
||||
#include "mozilla/dom/ipc/StructuredCloneData.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Variant.h"
|
||||
#include "nsClassHashtable.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
class nsIGlobalObject;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
namespace ipc {
|
||||
|
||||
/**
|
||||
* Together, the SharedMap and WritableSharedMap classes allow sharing a
|
||||
* dynamically-updated, shared-memory key-value store across processes.
|
||||
*
|
||||
* The maps may only ever be updated in the parent process, via
|
||||
* WritableSharedMap instances. When that map changes, its entire contents are
|
||||
* serialized into a contiguous shared memory buffer, and broadcast to all child
|
||||
* processes, which in turn update their entire map contents wholesale.
|
||||
*
|
||||
* Keys are arbitrary UTF-8 strings (currently exposed to JavaScript as UTF-16),
|
||||
* and values are structured clone buffers. Values are eagerly encoded whenever
|
||||
* they are updated, and lazily decoded each time they're read.
|
||||
*
|
||||
* Updates are batched. Rather than each key change triggering an immediate
|
||||
* update, combined updates are broadcast after a delay. Currently, this
|
||||
* requires an explicit flush() call, or spawning a new content process. In the
|
||||
* future, it will happen automatically in idle tasks.
|
||||
*
|
||||
*
|
||||
* Whenever a read-only SharedMap is updated, it dispatches a "change" event.
|
||||
* The event contains a "changedKeys" property with a list of all keys which
|
||||
* were changed in the last update batch. Change events are never dispatched to
|
||||
* WritableSharedMap instances.
|
||||
*/
|
||||
class SharedMap : public nsISupports
|
||||
{
|
||||
using FileDescriptor = mozilla::ipc::FileDescriptor;
|
||||
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
SharedMap();
|
||||
|
||||
SharedMap(nsIGlobalObject* aGlobal, const FileDescriptor&, size_t);
|
||||
|
||||
// Returns true if the map contains the given (UTF-8) key.
|
||||
bool Has(const nsACString& name);
|
||||
|
||||
// If the map contains the given (UTF-8) key, decodes and returns a new copy
|
||||
// of its value. Otherwise returns null.
|
||||
void Get(JSContext* cx, const nsACString& name, JS::MutableHandleValue aRetVal,
|
||||
ErrorResult& aRv);
|
||||
|
||||
|
||||
/**
|
||||
* Returns a copy of the read-only file descriptor which backs the shared
|
||||
* memory region for this map. The file descriptor may be passed between
|
||||
* processes, and used to update corresponding instances in child processes.
|
||||
*/
|
||||
FileDescriptor CloneMapFile();
|
||||
|
||||
/**
|
||||
* Returns the size of the memory mapped region that backs this map. Must be
|
||||
* passed to the SharedMap() constructor or Update() method along with the
|
||||
* descriptor returned by CloneMapFile() in order to initialize or update a
|
||||
* child SharedMap.
|
||||
*/
|
||||
size_t MapSize() const { return mMap.size(); }
|
||||
|
||||
/**
|
||||
* Updates this instance to reflect the contents of the shared memory region
|
||||
* in the given map file, and broadcasts a change event for the given set of
|
||||
* changed (UTF-8-encoded) keys.
|
||||
*/
|
||||
void Update(const FileDescriptor& aMapFile, size_t aMapSize,
|
||||
nsTArray<nsCString>&& aChangedKeys);
|
||||
|
||||
|
||||
protected:
|
||||
virtual ~SharedMap() = default;
|
||||
|
||||
class Entry
|
||||
{
|
||||
public:
|
||||
Entry(Entry&&) = delete;
|
||||
|
||||
explicit Entry(SharedMap& aMap, const nsACString& aName = EmptyCString())
|
||||
: mMap(aMap)
|
||||
, mName(aName)
|
||||
, mData(AsVariant(uint32_t(0)))
|
||||
{
|
||||
}
|
||||
|
||||
~Entry() = default;
|
||||
|
||||
/**
|
||||
* Encodes or decodes this entry into or from the given OutputBuffer or
|
||||
* InputBuffer.
|
||||
*/
|
||||
template<typename Buffer>
|
||||
void Code(Buffer& buffer)
|
||||
{
|
||||
DebugOnly<size_t> startOffset = buffer.cursor();
|
||||
|
||||
buffer.codeString(mName);
|
||||
buffer.codeUint32(DataOffset());
|
||||
buffer.codeUint32(mSize);
|
||||
|
||||
MOZ_ASSERT(buffer.cursor() == startOffset + HeaderSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size that this entry will take up in the map header. This
|
||||
* must be equal to the number of bytes encoded by Code().
|
||||
*/
|
||||
size_t HeaderSize() const
|
||||
{
|
||||
return (sizeof(uint16_t) + mName.Length() +
|
||||
sizeof(DataOffset()) +
|
||||
sizeof(mSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the value of this entry to the given structured clone data, of
|
||||
* which it takes ownership. The passed StructuredCloneData object must not
|
||||
* be used after this call.
|
||||
*/
|
||||
void TakeData(StructuredCloneData&&);
|
||||
|
||||
/**
|
||||
* This is called while building a new snapshot of the SharedMap. aDestPtr
|
||||
* must point to a buffer within the new snapshot with Size() bytes reserved
|
||||
* for it, and `aNewOffset` must be the offset of that buffer from the start
|
||||
* of the snapshot's memory region.
|
||||
*
|
||||
* This function copies the raw structured clone data for the entry's value
|
||||
* to the new buffer, and updates its internal state for use with the new
|
||||
* data. Its offset is updated to aNewOffset, and any StructuredCloneData
|
||||
* object it holds is destroyed.
|
||||
*
|
||||
* After this call, the entry is only valid in reference to the new
|
||||
* snapshot, and must not be accessed again until the SharedMap mMap has been
|
||||
* updated to point to it.
|
||||
*/
|
||||
void ExtractData(char* aDestPtr, uint32_t aNewOffset);
|
||||
|
||||
// Returns the UTF-8-encoded name of the entry, which is used as its key in
|
||||
// the map.
|
||||
const nsCString& Name() const { return mName; }
|
||||
|
||||
// Decodes the entry's value into the current Realm of the given JS context
|
||||
// and puts the result in aRetVal on success.
|
||||
void Read(JSContext* aCx, JS::MutableHandleValue aRetVal,
|
||||
ErrorResult& aRv);
|
||||
|
||||
// Returns the byte size of the entry's raw structured clone data.
|
||||
uint32_t Size() const { return mSize; }
|
||||
|
||||
private:
|
||||
// Returns a pointer to the entry value's structured clone data within the
|
||||
// SharedMap's mapped memory region. This is *only* valid shen mData
|
||||
// contains a uint32_t.
|
||||
const char* Data() const
|
||||
{
|
||||
return mMap.Data() + DataOffset();
|
||||
}
|
||||
|
||||
// Returns the offset of the entry value's structured clone data within the
|
||||
// SharedMap's mapped memory region. This is *only* valid shen mData
|
||||
// contains a uint32_t.
|
||||
uint32_t& DataOffset()
|
||||
{
|
||||
return mData.as<uint32_t>();
|
||||
}
|
||||
const uint32_t& DataOffset() const
|
||||
{
|
||||
return mData.as<uint32_t>();
|
||||
}
|
||||
|
||||
// Returns the temporary StructuredCloneData object containing the entry's
|
||||
// value. This is *only* value when mData contains a StructuredCloneDAta
|
||||
// object.
|
||||
const StructuredCloneData& Holder() const
|
||||
{
|
||||
return mData.as<StructuredCloneData>();
|
||||
}
|
||||
|
||||
SharedMap& mMap;
|
||||
|
||||
// The entry's (UTF-8 encoded) name, which serves as its key in the map.
|
||||
nsCString mName;
|
||||
|
||||
/**
|
||||
* This member provides a reference to the entry's structured clone data.
|
||||
* Its type varies depending on the state of the entry:
|
||||
*
|
||||
* - For entries which have been snapshotted into a shared memory region,
|
||||
* this is a uint32_t offset into the parent SharedMap's Data() buffer.
|
||||
*
|
||||
* - For entries which have been changed in a WritableSharedMap instance,
|
||||
* but not serialized to a shared memory snapshot yet, this is a
|
||||
* StructuredCloneData instance, containing a process-local copy of the
|
||||
* data. This will be discarded the next time the map is serialized, and
|
||||
* replaced with a buffer offset, as described above.
|
||||
*/
|
||||
Variant<uint32_t, StructuredCloneData> mData;
|
||||
|
||||
// The size, in bytes, of the entry's structured clone data.
|
||||
uint32_t mSize = 0;
|
||||
};
|
||||
|
||||
// Rebuilds the entry hashtable mEntries from the values serialized in the
|
||||
// current snapshot, if necessary. The hashtable is rebuilt lazily after
|
||||
// construction and after every Update() call, so this function must be called
|
||||
// before any attempt to access mEntries.
|
||||
Result<Ok, nsresult> MaybeRebuild();
|
||||
void MaybeRebuild() const;
|
||||
|
||||
// Note: This header is included by WebIDL binding headers, and therefore
|
||||
// can't include "windows.h". Since FileDescriptor.h does include "windows.h"
|
||||
// on Windows, we can only forward declare FileDescriptor, and can't include
|
||||
// it as an inline member.
|
||||
UniquePtr<FileDescriptor> mMapFile;
|
||||
// The size of the memory-mapped region backed by mMapFile, in bytes.
|
||||
size_t mMapSize = 0;
|
||||
|
||||
mutable nsClassHashtable<nsCStringHashKey, Entry> mEntries;
|
||||
|
||||
// Manages the memory mapping of the current snapshot. This is initialized
|
||||
// lazily after each SharedMap construction or updated, based on the values in
|
||||
// mMapFile and mMapSize.
|
||||
loader::AutoMemMap mMap;
|
||||
|
||||
bool mWritable = false;
|
||||
|
||||
// Returns a pointer to the beginning of the memory mapped snapshot. Entry
|
||||
// offsets are relative to this pointer, and Entry objects access their
|
||||
// structured clone data by indexing this pointer.
|
||||
char* Data() { return mMap.get<char>().get(); }
|
||||
};
|
||||
|
||||
class WritableSharedMap final : public SharedMap
|
||||
{
|
||||
public:
|
||||
WritableSharedMap();
|
||||
|
||||
// Sets the value of the given (UTF-8 encoded) key to a structured clone
|
||||
// snapshot of the given value.
|
||||
void Set(JSContext* cx, const nsACString& name, JS::HandleValue value, ErrorResult& aRv);
|
||||
|
||||
// Deletes the given (UTF-8 encoded) key from the map.
|
||||
void Delete(const nsACString& name);
|
||||
|
||||
// Flushes any queued changes to a new snapshot, and broadcasts it to all
|
||||
// child SharedMap instances.
|
||||
void Flush();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the read-only SharedMap instance corresponding to this
|
||||
* WritableSharedMap for use in the parent process.
|
||||
*/
|
||||
SharedMap* GetReadOnly();
|
||||
|
||||
protected:
|
||||
~WritableSharedMap() override = default;
|
||||
|
||||
private:
|
||||
// The set of (UTF-8 encoded) keys which have changed, or been deleted, since
|
||||
// the last snapshot.
|
||||
nsTArray<nsCString> mChangedKeys;
|
||||
|
||||
RefPtr<SharedMap> mReadOnly;
|
||||
|
||||
// Creates a new snapshot of the map, and updates all Entry instance to
|
||||
// reference its data.
|
||||
Result<Ok, nsresult> Serialize();
|
||||
|
||||
// If there have been any changes since the last snapshot, creates a new
|
||||
// serialization and broadcasts it to all child SharedMap instances.
|
||||
void BroadcastChanges();
|
||||
|
||||
// Marks the given (UTF-8 encoded) key as having changed. This adds it to
|
||||
// mChangedKeys, if not already present. In the future, it will also schedule
|
||||
// a flush the next time the event loop is idle.
|
||||
void KeyChanged(const nsACString& aName);
|
||||
};
|
||||
|
||||
} // ipc
|
||||
} // dom
|
||||
} // mozilla
|
||||
|
||||
#endif // dom_ipc_SharedMap_h
|
@ -29,6 +29,11 @@ namespace mozilla {
|
||||
namespace dom {
|
||||
namespace ipc {
|
||||
|
||||
using mozilla::ipc::AutoIPCStream;
|
||||
using mozilla::ipc::IPCStream;
|
||||
using mozilla::ipc::PBackgroundChild;
|
||||
using mozilla::ipc::PBackgroundParent;
|
||||
|
||||
StructuredCloneData::StructuredCloneData()
|
||||
: StructuredCloneData(StructuredCloneHolder::TransferringSupported)
|
||||
{}
|
||||
|
@ -15,6 +15,7 @@ XPIDL_MODULE = 'dom'
|
||||
|
||||
EXPORTS.mozilla.dom.ipc += [
|
||||
'IdType.h',
|
||||
'SharedMap.h',
|
||||
'SharedStringMap.h',
|
||||
'StructuredCloneData.h',
|
||||
]
|
||||
@ -69,6 +70,7 @@ UNIFIED_SOURCES += [
|
||||
'PermissionMessageUtils.cpp',
|
||||
'PreallocatedProcessManager.cpp',
|
||||
'ProcessPriorityManager.cpp',
|
||||
'SharedMap.cpp',
|
||||
'SharedStringMap.cpp',
|
||||
'StructuredCloneData.cpp',
|
||||
'TabChild.cpp',
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "ScriptPreloader-inl.h"
|
||||
|
||||
#include "mozilla/Unused.h"
|
||||
#include "mozilla/ipc/FileDescriptor.h"
|
||||
#include "nsIFile.h"
|
||||
|
||||
#include <private/pprio.h>
|
||||
@ -142,12 +143,11 @@ AutoMemMap::cloneHandle() const
|
||||
void
|
||||
AutoMemMap::reset()
|
||||
{
|
||||
if (addr && !persistent_) {
|
||||
Unused << NS_WARN_IF(PR_MemUnmap(addr, size()) != PR_SUCCESS);
|
||||
addr = nullptr;
|
||||
}
|
||||
if (fileMap) {
|
||||
if (addr && !persistent_) {
|
||||
Unused << NS_WARN_IF(PR_MemUnmap(addr, size()) != PR_SUCCESS);
|
||||
addr = nullptr;
|
||||
}
|
||||
|
||||
Unused << NS_WARN_IF(PR_CloseFileMap(fileMap) != PR_SUCCESS);
|
||||
fileMap = nullptr;
|
||||
}
|
||||
|
@ -10,7 +10,6 @@
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/RangedPtr.h"
|
||||
#include "mozilla/Result.h"
|
||||
#include "mozilla/ipc/FileDescriptor.h"
|
||||
#include "nsIMemoryReporter.h"
|
||||
|
||||
#include <prio.h>
|
||||
@ -18,6 +17,10 @@
|
||||
class nsIFile;
|
||||
|
||||
namespace mozilla {
|
||||
namespace ipc {
|
||||
class FileDescriptor;
|
||||
}
|
||||
|
||||
namespace loader {
|
||||
|
||||
using mozilla::ipc::FileDescriptor;
|
||||
|
Loading…
Reference in New Issue
Block a user