Bug 1471025: Part 3a - Pass shared preference map to (non-Android) content processes at startup. r?jld,njn draft
authorKris Maglione <maglione.k@gmail.com>
Mon, 02 Jul 2018 15:40:38 -0700
changeset 815374 778ba8b508184b21c746449cc9740438a0d3b7ed
parent 815373 b45a4d852ad13d2fd05805e912c273174075f4bc
child 815375 2e574a3f1c4234758d9dd2f6b222f64c3aeb1971
push id115503
push usermaglione.k@gmail.com
push dateSat, 07 Jul 2018 19:50:37 +0000
reviewersjld, njn
bugs1471025
milestone63.0a1
Bug 1471025: Part 3a - Pass shared preference map to (non-Android) content processes at startup. r?jld,njn This adds an additional file descriptor to the set sent at content process startup, for the read-only preference map that we share between all content processes. This forms the base set of preferences. The other preferences FD contains changes that the content process will need to apply on top of the snapshot. MozReview-Commit-ID: 6hc0HIxFmHg
dom/ipc/ContentParent.cpp
dom/ipc/ContentProcess.cpp
modules/libpref/Preferences.h
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2018,16 +2018,19 @@ ContentParent::LaunchSubprocess(ProcessP
   char idStr[21];
   SprintfLiteral(idStr, "%" PRId64, static_cast<uint64_t>(mChildID));
   extraArgs.push_back(idStr);
   extraArgs.push_back(IsForBrowser() ? "-isForBrowser" : "-notForBrowser");
 
   // Prefs information is passed via anonymous shared memory to avoid bloating
   // the command line.
 
+  size_t prefMapSize;
+  auto prefMapHandle = Preferences::EnsureSnapshot(&prefMapSize).ClonePlatformHandle();
+
   // Serialize the early prefs.
   nsAutoCStringN<1024> prefs;
   Preferences::SerializePreferences(prefs);
 
   // Set up the shared memory.
   base::SharedMemory shm;
   if (!shm.Create(prefs.Length())) {
     NS_ERROR("failed to create shared memory in the parent");
@@ -2038,38 +2041,50 @@ ContentParent::LaunchSubprocess(ProcessP
     NS_ERROR("failed to map shared memory in the parent");
     MarkAsDead();
     return false;
   }
 
   // Copy the serialized prefs into the shared memory.
   memcpy(static_cast<char*>(shm.memory()), prefs.get(), prefs.Length());
 
+  // 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));
+  };
+
 #if defined(XP_WIN)
   // Record the handle as to-be-shared, and pass it via a command flag. This
   // works because Windows handles are system-wide.
   HANDLE prefsHandle = shm.handle();
   mSubprocess->AddHandleToShare(prefsHandle);
+  mSubprocess->AddHandleToShare(prefMapHandle.get());
   extraArgs.push_back("-prefsHandle");
-  extraArgs.push_back(
-    nsPrintfCString("%zu", reinterpret_cast<uintptr_t>(prefsHandle)).get());
+  extraArgs.push_back(formatPtrArg(prefsHandle).get());
+  extraArgs.push_back("-prefMapHandle");
+  extraArgs.push_back(formatPtrArg(prefMapHandle.get()).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.
   mSubprocess->AddFdToRemap(shm.handle().fd, kPrefsFileDescriptor);
+  mSubprocess->AddFdToRemap(prefMapHandle.get(), kPrefMapFileDescriptor);
 #endif
 
-  // Pass the length via a command flag.
+  // Pass the lengths via command line flags.
   extraArgs.push_back("-prefsLen");
-  extraArgs.push_back(nsPrintfCString("%zu", uintptr_t(prefs.Length())).get());
+  extraArgs.push_back(formatPtrArg(prefs.Length()).get());
+
+  extraArgs.push_back("-prefMapSize");
+  extraArgs.push_back(formatPtrArg(prefMapSize).get());
 
   // Scheduler prefs need to be handled differently because the scheduler needs
   // to start up in the content process before the normal preferences service.
   nsCString schedulerPrefs = Scheduler::GetPrefs();
   extraArgs.push_back("-schedulerPrefs");
   extraArgs.push_back(schedulerPrefs.get());
 
   if (gSafeMode) {
--- a/dom/ipc/ContentProcess.cpp
+++ b/dom/ipc/ContentProcess.cpp
@@ -92,23 +92,39 @@ SetPrefsFd(int aFd)
 #endif
 
 bool
 ContentProcess::Init(int aArgc, char* aArgv[])
 {
   Maybe<uint64_t> childID;
   Maybe<bool> isForBrowser;
   Maybe<base::SharedMemoryHandle> prefsHandle;
+  Maybe<FileDescriptor> prefMapHandle;
   Maybe<size_t> prefsLen;
+  Maybe<size_t> prefMapSize;
   Maybe<const char*> schedulerPrefs;
   Maybe<const char*> parentBuildID;
 #if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
   nsCOMPtr<nsIFile> profileDir;
 #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 a 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));
+  };
+#endif
+
   for (int i = 1; i < aArgc; i++) {
     if (!aArgv[i]) {
       continue;
     }
 
     if (strcmp(aArgv[i], "-appdir") == 0) {
       if (++i == aArgc) {
         return false;
@@ -132,35 +148,53 @@ ContentProcess::Init(int aArgc, char* aA
     } else if (strcmp(aArgv[i], "-notForBrowser") == 0) {
       isForBrowser = Some(false);
 
 #ifdef XP_WIN
     } else if (strcmp(aArgv[i], "-prefsHandle") == 0) {
       if (++i == aArgc) {
         return false;
       }
-      // ContentParent uses %zu to print a word-sized unsigned integer. So
-      // even though strtoull() returns a long long int, it will fit in a
-      // uintptr_t.
       char* str = aArgv[i];
-      prefsHandle = Some(reinterpret_cast<HANDLE>(strtoull(str, &str, 10)));
+      prefsHandle = Some(parseHandleArg(str));
+      if (str[0] != '\0') {
+        return false;
+      }
+
+    } else if (strcmp(aArgv[i], "-prefMapHandle") == 0) {
+      if (++i == aArgc) {
+        return false;
+      }
+      char* str = aArgv[i];
+      // The FileDescriptor constructor will clone this handle when constructed,
+      // so store it in a UniquePlatformHandle to make sure the original gets
+      // closed.
+      FileDescriptor::UniquePlatformHandle handle(parseHandleArg(str));
+      prefMapHandle.emplace(handle.get());
       if (str[0] != '\0') {
         return false;
       }
 #endif
 
     } else if (strcmp(aArgv[i], "-prefsLen") == 0) {
       if (++i == aArgc) {
         return false;
       }
-      // ContentParent uses %zu to print a word-sized unsigned integer. So
-      // even though strtoull() returns a long long int, it will fit in a
-      // uintptr_t.
       char* str = aArgv[i];
-      prefsLen = Some(strtoull(str, &str, 10));
+      prefsLen = Some(parseUIntPtrArg(str));
+      if (str[0] != '\0') {
+        return false;
+      }
+
+    } else if (strcmp(aArgv[i], "-prefMapSize") == 0) {
+      if (++i == aArgc) {
+        return false;
+      }
+      char* str = aArgv[i];
+      prefMapSize = Some(parseUIntPtrArg(str));
       if (str[0] != '\0') {
         return false;
       }
 
     } else if (strcmp(aArgv[i], "-schedulerPrefs") == 0) {
       if (++i == aArgc) {
         return false;
       }
@@ -192,28 +226,40 @@ ContentProcess::Init(int aArgc, char* aA
 
 #ifdef ANDROID
   // Android is different; get the FD via gPrefsFd instead of a fixed fd.
   MOZ_RELEASE_ASSERT(gPrefsFd != -1);
   prefsHandle = Some(base::FileDescriptor(gPrefsFd, /* auto_close */ true));
 #elif XP_UNIX
   prefsHandle = Some(base::FileDescriptor(kPrefsFileDescriptor,
                                           /* auto_close */ true));
+
+  // The FileDescriptor constructor will clone this handle when constructed,
+  // so store it in a UniquePlatformHandle to make sure the original gets
+  // closed.
+  FileDescriptor::UniquePlatformHandle handle(kPrefMapFileDescriptor);
+  prefMapHandle.emplace(handle.get());
 #endif
 
   // Did we find all the mandatory flags?
   if (childID.isNothing() ||
       isForBrowser.isNothing() ||
       prefsHandle.isNothing() ||
       prefsLen.isNothing() ||
+      prefMapHandle.isNothing() ||
+      prefMapSize.isNothing() ||
       schedulerPrefs.isNothing() ||
       parentBuildID.isNothing()) {
     return false;
   }
 
+  // Init the shared-memory base preference mapping first, so that only changed
+  // preferences wind up in heap memory.
+  Preferences::InitSnapshot(prefMapHandle.ref(), *prefMapSize);
+
   // Set up early prefs from the shared memory.
   base::SharedMemory shm;
   if (!shm.SetHandle(*prefsHandle, /* read_only */ true)) {
     NS_ERROR("failed to open shared memory in the child");
     return false;
   }
   if (!shm.Map(*prefsLen)) {
     NS_ERROR("failed to map shared memory in the child");
--- a/modules/libpref/Preferences.h
+++ b/modules/libpref/Preferences.h
@@ -56,18 +56,32 @@ enum class PrefType : uint8_t
   String = 1,
   Int = 2,
   Bool = 3,
 };
 
 #endif
 
 #ifdef XP_UNIX
+// We need to send two shared memory descriptors to every child process:
+//
+// 1) A read-only/write-protected snapshot of the initial state of the
+//    preference database. This memory is shared between all processes, and
+//    therefore cannot be modified once it has been created.
+//
+// 2) A set of changes on top of the snapshot, containing the current values of
+//    all preferences which have changed since it was created.
+//
+// Since the second set will be different for every process, and the first set
+// cannot be modified, it is unfortunately not possible to combine them into a
+// single file descriptor.
+//
 // XXX: bug 1440207 is about improving how fixed fds such as this are used.
 static const int kPrefsFileDescriptor = 8;
+static const int kPrefMapFileDescriptor = 9;
 #endif
 
 // Keep this in sync with PrefType in parser/src/lib.rs.
 enum class PrefValueKind : uint8_t
 {
   Default,
   User
 };