Bug 1698045 part 1 - Add xpc::SelfHostedShmem to hold shared memory for JS initialization. r=smaug,tcampbell,ipc-reviewers,jld

This change adds the ground work to share content provided by the JS engine of
the Parent process to initialize the JS engine of other threads and Content
processes.

The singleton class xpc::SelfHostedShmem is used to wrap the logic behind
holding the memory. The memory is initialized with `InitFromParent` or
`InitFromChild`. The memory is accessible using either the `Content` or
`Handle`.

The shared memory is transfered through the command line using
`mozilla::ipc::ExportSharedJSInit` and read using
`mozilla::ipc::ImportSharedJSInit` functions. The command line is used, as we
need the shared memory to be avilable for the JS engine initialization. The
command line is composed of a single command named `-jsInit` which is followed
by the handle (on Windows) and the length of the shared content.

The memory associated with the shared memory is cleared in `ShutdownXPCOM` after
closing all threads, and shuting down the JS engine. This is necessary as we
expect the JS engine to borrow content from the shared memory.

Differential Revision: https://phabricator.services.mozilla.com/D110576
This commit is contained in:
Nicolas B. Pierron 2021-05-12 13:57:55 +00:00
parent 1d6ad21b7a
commit a4aef929a1
8 changed files with 353 additions and 0 deletions

View File

@ -2512,6 +2512,12 @@ bool ContentParent::BeginSubprocessLaunch(ProcessPriority aPriority) {
}
mPrefSerializer->AddSharedPrefCmdLineArgs(*mSubprocess, extraArgs);
// The JS engine does some computation during the initialization which can be
// shared across processes. We add command line arguments to pass a file
// handle and its content length, to minimize the startup time of content
// processes.
::mozilla::ipc::ExportSharedJSInit(*mSubprocess, extraArgs);
// Register ContentParent as an observer for changes to any pref
// whose prefix matches the empty string, i.e. all of them. The
// observation starts here in order to capture pref updates that

View File

@ -86,6 +86,8 @@ bool ContentProcess::Init(int aArgc, char* aArgv[]) {
char* prefMapHandle = nullptr;
char* prefsLen = nullptr;
char* prefMapSize = nullptr;
char* jsInitHandle = nullptr;
char* jsInitLen = nullptr;
#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
nsCOMPtr<nsIFile> profileDir;
#endif
@ -141,6 +143,19 @@ bool ContentProcess::Init(int aArgc, char* aArgv[]) {
return false;
}
prefMapSize = aArgv[i];
} else if (strcmp(aArgv[i], "-jsInit") == 0) {
// command line: -jsInit [handle] length
#ifdef XP_WIN
if (++i == aArgc) {
return false;
}
jsInitHandle = aArgv[i];
#endif
if (++i == aArgc) {
return false;
}
jsInitLen = aArgv[i];
} else if (strcmp(aArgv[i], "-safeMode") == 0) {
gSafeMode = true;
@ -177,6 +192,10 @@ bool ContentProcess::Init(int aArgc, char* aArgv[]) {
return false;
}
if (!::mozilla::ipc::ImportSharedJSInit(jsInitHandle, jsInitLen)) {
return false;
}
mContent.Init(IOThreadChild::message_loop(), ParentPid(), *parentBuildID,
IOThreadChild::TakeChannel(), *childID, *isForBrowser);

View File

@ -76,6 +76,15 @@ void SetPrefsFd(int aFd);
void SetPrefMapFd(int aFd);
#endif
// Generate command line argument to spawn a child process. If the shared memory
// is not properly initialized, this would be a no-op.
void ExportSharedJSInit(GeckoChildProcessHost& procHost,
std::vector<std::string>& aExtraOpts);
// Initialize the content used by the JS engine during the initialization of a
// JS::Runtime.
bool ImportSharedJSInit(char* aJsInitHandleStr, char* aJsInitLenStr);
} // namespace ipc
} // namespace mozilla

View File

@ -11,6 +11,8 @@
#include "mozilla/UniquePtrExtensions.h"
#include "nsPrintfCString.h"
#include "XPCSelfHostedShmem.h"
namespace mozilla {
namespace ipc {
@ -207,5 +209,111 @@ const FileDescriptor& SharedPreferenceDeserializer::GetPrefMapHandle() const {
return mPrefMapHandle.ref();
}
#ifdef XP_UNIX
// On Unix, file descriptors are per-process. This value is used when mapping
// a parent process handle to a content process handle.
static const int kJSInitFileDescriptor = 11;
#endif
void ExportSharedJSInit(mozilla::ipc::GeckoChildProcessHost& procHost,
std::vector<std::string>& aExtraOpts) {
#ifdef ANDROID
// The code to support Android is added in a follow-up patch.
return;
#else
// Formats a pointer or pointer-sized-integer as a string suitable for passing
// in an arguments list.
auto formatPtrArg = [](auto arg) {
return nsPrintfCString("%zu", uintptr_t(arg));
};
auto& shmem = xpc::SelfHostedShmem::GetSingleton();
const mozilla::UniqueFileHandle& uniqHandle = shmem.Handle();
size_t len = shmem.Content().Length();
// If the file is not found or the content is empty, then we would start the
// content process without this optimization.
if (!uniqHandle || !len) {
return;
}
mozilla::detail::FileHandleType handle = uniqHandle.get();
// command line: -jsInit [handle] length
aExtraOpts.push_back("-jsInit");
# if defined(XP_WIN)
// Record the handle as to-be-shared, and pass it via a command flag.
procHost.AddHandleToShare(HANDLE(handle));
aExtraOpts.push_back(formatPtrArg(HANDLE(handle)).get());
# else
// In contrast, Unix fds are per-process. So remap the fd to a fixed one that
// will be used in the child.
// XXX: bug 1440207 is about improving how fixed fds are used.
//
// Note: on Android, AddFdToRemap() sets up the fd to be passed via a Parcel,
// and the fixed fd isn't used. However, we still need to mark it for
// remapping so it doesn't get closed in the child.
procHost.AddFdToRemap(handle, kJSInitFileDescriptor);
# endif
// Pass the lengths via command line flags.
aExtraOpts.push_back(formatPtrArg(len).get());
#endif
}
bool ImportSharedJSInit(char* aJsInitHandleStr, char* aJsInitLenStr) {
// This is an optimization, and as such we can safely recover if the command
// line argument are not provided.
if (!aJsInitLenStr) {
return true;
}
#ifdef XP_WIN
if (!aJsInitHandleStr) {
return true;
}
#endif
// Parses an arg containing a pointer-sized-integer.
auto parseUIntPtrArg = [](char*& aArg) {
// ContentParent uses %zu to print a word-sized unsigned integer. So even
// though strtoull() returns an unsigned long long int, it will fit in a
// uintptr_t.
return uintptr_t(strtoull(aArg, &aArg, 10));
};
#ifdef XP_WIN
auto parseHandleArg = [&](char*& aArg) {
return HANDLE(parseUIntPtrArg(aArg));
};
base::SharedMemoryHandle handle(parseHandleArg(aJsInitHandleStr));
if (aJsInitHandleStr[0] != '\0') {
return false;
}
#endif
size_t len = parseUIntPtrArg(aJsInitLenStr);
if (aJsInitLenStr[0] != '\0') {
return false;
}
#ifdef XP_UNIX
auto handle = base::FileDescriptor(kJSInitFileDescriptor,
/* auto_close */ true);
#endif
// Initialize the shared memory with the file handle and size of the content
// of the self-hosted Xdr.
auto& shmem = xpc::SelfHostedShmem::GetSingleton();
if (!shmem.InitFromChild(handle, len)) {
NS_ERROR("failed to open shared memory in the child");
return false;
}
return true;
}
} // namespace ipc
} // namespace mozilla

View File

@ -0,0 +1,115 @@
/* -*- 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 "XPCSelfHostedShmem.h"
// static
mozilla::StaticRefPtr<xpc::SelfHostedShmem>
xpc::SelfHostedShmem::sSelfHostedXdr;
NS_IMPL_ISUPPORTS(xpc::SelfHostedShmem, nsIMemoryReporter)
// static
xpc::SelfHostedShmem& xpc::SelfHostedShmem::GetSingleton() {
MOZ_ASSERT_IF(!sSelfHostedXdr, NS_IsMainThread());
if (!sSelfHostedXdr) {
sSelfHostedXdr = new SelfHostedShmem;
}
return *sSelfHostedXdr;
}
void xpc::SelfHostedShmem::InitMemoryReporter() {
mozilla::RegisterWeakMemoryReporter(this);
}
// static
void xpc::SelfHostedShmem::Shutdown() {
MOZ_ASSERT(NS_IsMainThread());
// NOTE: We cannot call UnregisterWeakMemoryReporter(sSelfHostedXdr) as the
// service is shutdown at the time this call is made. In any cases, this would
// already be done when the memory reporter got destroyed.
sSelfHostedXdr = nullptr;
}
void xpc::SelfHostedShmem::InitFromParent(ContentType aXdr) {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mLen, "Shouldn't call this more than once");
size_t len = aXdr.Length();
auto shm = mozilla::MakeUnique<base::SharedMemory>();
if (NS_WARN_IF(!shm->CreateFreezeable(len))) {
return;
}
if (NS_WARN_IF(!shm->Map(len))) {
return;
}
void* address = shm->memory();
memcpy(address, aXdr.Elements(), aXdr.LengthBytes());
base::SharedMemory roCopy;
if (NS_WARN_IF(!shm->ReadOnlyCopy(&roCopy))) {
return;
}
mMem = std::move(shm);
mHandle = roCopy.TakeHandle();
mLen = len;
}
bool xpc::SelfHostedShmem::InitFromChild(::base::SharedMemoryHandle aHandle,
size_t aLen) {
MOZ_ASSERT(!XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mLen, "Shouldn't call this more than once");
auto shm = mozilla::MakeUnique<base::SharedMemory>();
if (NS_WARN_IF(!shm->SetHandle(aHandle, /* read_only */ true))) {
return false;
}
if (NS_WARN_IF(!shm->Map(aLen))) {
return false;
}
// Note: mHandle remains empty, as content processes are not spawning more
// content processes.
mMem = std::move(shm);
mLen = aLen;
return true;
}
xpc::SelfHostedShmem::ContentType xpc::SelfHostedShmem::Content() const {
if (!mMem) {
MOZ_ASSERT(mLen == 0);
return ContentType();
}
return ContentType(reinterpret_cast<uint8_t*>(mMem->memory()), mLen);
}
const mozilla::UniqueFileHandle& xpc::SelfHostedShmem::Handle() const {
return mHandle;
}
NS_IMETHODIMP
xpc::SelfHostedShmem::CollectReports(nsIHandleReportCallback* aHandleReport,
nsISupports* aData, bool aAnonymize) {
// If this is the parent process, then we have a handle and this instance owns
// the data and shares it with other processes, otherwise this is shared data.
if (XRE_IsParentProcess()) {
// This does not exactly report the amount of data mapped by the system,
// but the space requested when creating the handle.
MOZ_COLLECT_REPORT("explicit/js-non-window/shared-memory/self-hosted-xdr",
KIND_NONHEAP, UNITS_BYTES, mLen,
"Memory used to initialize the JS engine with the "
"self-hosted code encoded by the parent process.");
}
return NS_OK;
}

View File

@ -0,0 +1,89 @@
/* -*- 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 xpcselfhostedshmem_h___
#define xpcselfhostedshmem_h___
#include "base/shared_memory.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/Span.h"
#include "mozilla/StaticPtr.h"
#include "nsIMemoryReporter.h"
#include "nsIObserver.h"
#include "nsIThread.h"
namespace xpc {
// This class is a singleton which holds a file-mapping of the Self Hosted
// Stencil XDR, to be shared by the parent process with all the child processes.
//
// It is either initialized by the parent process by monitoring when the Self
// hosted stencil is produced, or by the content process by reading a shared
// memory-mapped file.
class SelfHostedShmem final : public nsIMemoryReporter {
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIMEMORYREPORTER
// NOTE: This type is identical to JS::SelfHostedCache, but we repeat it to
// avoid including JS engine API in ipc code.
using ContentType = mozilla::Span<uint8_t>;
static SelfHostedShmem& GetSingleton();
// Initialize this singleton with the content of the Self-hosted Stencil XDR.
// This will be used to initialize the shared memory which would hold a copy.
//
// This function is not thread-safe and should be call at most once and from
// the main thread.
void InitFromParent(ContentType aXdr);
// Initialize this singleton with the content coming from the parent process,
// using a file handle which maps to the memory pages of the parent process.
//
// This function is not thread-safe and should be call at most once and from
// the main thread.
[[nodiscard]] bool InitFromChild(base::SharedMemoryHandle aHandle,
size_t aLen);
// Return a span over the read-only XDR content of the self-hosted stencil.
ContentType Content() const;
// Return the file handle which is under which the content is mapped.
const mozilla::UniqueFileHandle& Handle() const;
// Register this class to the memory reporter service.
void InitMemoryReporter();
// Unregister this class from the memory reporter service, and delete the
// memory associated with the shared memory. As the memory is borrowed by the
// JS engine, this function should be called after JS_Shutdown.
//
// NOTE: This is not using the Observer service which would call Shutdown
// while JS code might still be running during shutdown process.
static void Shutdown();
private:
SelfHostedShmem() = default;
~SelfHostedShmem() = default;
static mozilla::StaticRefPtr<SelfHostedShmem> sSelfHostedXdr;
// read-only file Handle used to transfer from the parent process to content
// processes.
mozilla::UniqueFileHandle mHandle;
// Shared memory used by JS runtime initialization.
mozilla::UniquePtr<base::SharedMemory> mMem;
// Length of the content within the shared memory.
size_t mLen = 0;
};
} // namespace xpc
#endif // !xpcselfhostedshmem_h___

View File

@ -9,6 +9,7 @@ EXPORTS += [
"XPCJSMemoryReporter.h",
"xpcObjectHelper.h",
"xpcpublic.h",
"XPCSelfHostedShmem.h",
]
UNIFIED_SOURCES += [
@ -30,6 +31,7 @@ UNIFIED_SOURCES += [
"XPCMaps.cpp",
"XPCModule.cpp",
"XPCRuntimeService.cpp",
"XPCSelfHostedShmem.cpp",
"XPCShellImpl.cpp",
"XPCString.cpp",
"XPCThrower.cpp",

View File

@ -99,6 +99,7 @@
#include "jsapi.h"
#include "js/Initialization.h"
#include "XPCSelfHostedShmem.h"
#include "gfxPlatform.h"
@ -473,6 +474,7 @@ NS_InitXPCOM(nsIServiceManager** aResult, nsIFile* aBinDirectory,
// The memory reporter manager is up and running -- register our reporters.
RegisterStrongMemoryReporter(new ICUReporter());
RegisterStrongMemoryReporter(new OggReporter());
xpc::SelfHostedShmem::GetSingleton().InitMemoryReporter();
mozilla::Telemetry::Init();
@ -731,6 +733,9 @@ nsresult ShutdownXPCOM(nsIServiceManager* aServMgr) {
sInitializedJS = false;
}
// Release shared memory which might be borrowed by the JS engine.
xpc::SelfHostedShmem::Shutdown();
// After all threads have been joined and the component manager has been shut
// down, any remaining objects that could be holding NSS resources (should)
// have been released, so we can safely shut down NSS.