Pass the GUID for the SharedMemoryHandle used by base::FieldTrialList.
SharedMemoryHandle now has a GUID used to track shared memory usage across
processes. The SharedMemoryHandle used by base::FieldTrialList is passed
manually by command line flag and Process creation options. This CL appends the
GUID to the command line flag.
BUG=713763
Review-Url: https://codereview.chromium.org/2862123002
Cr-Commit-Position: refs/heads/master@{#470700}
CrOS-Libchrome-Original-Commit: 2525d998c7c50bce5ca17fb934c2aeb8e27e1db0
diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc
index 78602f1..5711aec 100644
--- a/base/metrics/field_trial.cc
+++ b/base/metrics/field_trial.cc
@@ -16,6 +16,7 @@
#include "base/process/memory.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
@@ -208,25 +209,6 @@
}
}
-#if defined(OS_WIN)
-HANDLE CreateReadOnlyHandle(FieldTrialList::FieldTrialAllocator* allocator) {
- HANDLE src = allocator->shared_memory()->handle().GetHandle();
- ProcessHandle process = GetCurrentProcess();
- DWORD access = SECTION_MAP_READ | SECTION_QUERY;
- HANDLE dst;
- if (!::DuplicateHandle(process, src, process, &dst, access, true, 0))
- return kInvalidPlatformFile;
- return dst;
-}
-#endif
-
-#if defined(OS_POSIX) && !defined(OS_NACL)
-int CreateReadOnlyHandle(FieldTrialList::FieldTrialAllocator* allocator) {
- SharedMemoryHandle handle = allocator->shared_memory()->GetReadOnlyHandle();
- return SharedMemory::GetFdFromSharedMemoryHandle(handle);
-}
-#endif
-
void OnOutOfMemory(size_t size) {
#if defined(OS_NACL)
NOTREACHED();
@@ -235,6 +217,23 @@
#endif
}
+#if !defined(OS_NACL)
+// Returns whether the operation succeeded.
+bool DeserializeGUIDFromStringPieces(base::StringPiece first,
+ base::StringPiece second,
+ base::UnguessableToken* guid) {
+ uint64_t high = 0;
+ uint64_t low = 0;
+ if (!base::StringToUint64(first, &high) ||
+ !base::StringToUint64(second, &low)) {
+ return false;
+ }
+
+ *guid = base::UnguessableToken::Deserialize(high, low);
+ return true;
+}
+#endif
+
} // namespace
// statics
@@ -781,9 +780,9 @@
#if defined(OS_WIN)
if (cmd_line.HasSwitch(field_trial_handle_switch)) {
- std::string handle_switch =
+ std::string switch_value =
cmd_line.GetSwitchValueASCII(field_trial_handle_switch);
- bool result = CreateTrialsFromHandleSwitch(handle_switch);
+ bool result = CreateTrialsFromSwitchValue(switch_value);
DCHECK(result);
}
#endif
@@ -793,7 +792,9 @@
// sent over the switch (we don't care about the value). Invalid handles
// occur in some browser tests which don't initialize the allocator.
if (cmd_line.HasSwitch(field_trial_handle_switch)) {
- bool result = CreateTrialsFromDescriptor(fd_key);
+ std::string switch_value =
+ cmd_line.GetSwitchValueASCII(field_trial_handle_switch);
+ bool result = CreateTrialsFromDescriptor(fd_key, switch_value);
DCHECK(result);
}
#endif
@@ -832,21 +833,21 @@
return;
if (kUseSharedMemoryForFieldTrials) {
InstantiateFieldTrialAllocatorIfNeeded();
- if (global_->readonly_allocator_handle_)
- handles->push_back(global_->readonly_allocator_handle_);
+ if (global_->readonly_allocator_handle_.IsValid())
+ handles->push_back(global_->readonly_allocator_handle_.GetHandle());
}
}
#endif
#if defined(OS_POSIX) && !defined(OS_NACL)
// static
-int FieldTrialList::GetFieldTrialHandle() {
+SharedMemoryHandle FieldTrialList::GetFieldTrialHandle() {
if (global_ && kUseSharedMemoryForFieldTrials) {
InstantiateFieldTrialAllocatorIfNeeded();
// We check for an invalid handle where this gets called.
return global_->readonly_allocator_handle_;
}
- return kInvalidPlatformFile;
+ return SharedMemoryHandle();
}
#endif
@@ -873,37 +874,16 @@
InstantiateFieldTrialAllocatorIfNeeded();
// If the readonly handle didn't get duplicated properly, then fallback to
// original behavior.
- if (global_->readonly_allocator_handle_ == kInvalidPlatformFile) {
+ if (!global_->readonly_allocator_handle_.IsValid()) {
AddFeatureAndFieldTrialFlags(enable_features_switch,
disable_features_switch, cmd_line);
return;
}
global_->field_trial_allocator_->UpdateTrackingHistograms();
-
-#if defined(OS_WIN)
- // We need to pass a named anonymous handle to shared memory over the
- // command line on Windows, since the child doesn't know which of the
- // handles it inherited it should open.
- // PlatformFile is typedef'd to HANDLE which is typedef'd to void *. We
- // basically cast the handle into an int (uintptr_t, to be exact), stringify
- // the int, and pass it as a command-line flag. The child process will do
- // the reverse conversions to retrieve the handle. See
- // http://stackoverflow.com/a/153077
- auto uintptr_handle =
- reinterpret_cast<uintptr_t>(global_->readonly_allocator_handle_);
- std::string field_trial_handle = std::to_string(uintptr_handle);
- cmd_line->AppendSwitchASCII(field_trial_handle_switch, field_trial_handle);
-#elif defined(OS_POSIX)
- // On POSIX, we dup the fd into a fixed fd kFieldTrialDescriptor, so we
- // don't have to pass over the handle (it's not even the right handle
- // anyways). But some browser tests don't create the allocator, so we need
- // to be able to distinguish valid and invalid handles. We do that by just
- // checking that the flag is set with a dummy value.
- cmd_line->AppendSwitchASCII(field_trial_handle_switch, "1");
-#else
-#error Unsupported OS
-#endif
+ std::string switch_value = SerializeSharedMemoryHandleMetadata(
+ global_->readonly_allocator_handle_);
+ cmd_line->AppendSwitchASCII(field_trial_handle_switch, switch_value);
return;
}
@@ -1132,22 +1112,81 @@
return entries;
}
+// static
+std::string FieldTrialList::SerializeSharedMemoryHandleMetadata(
+ const SharedMemoryHandle& shm) {
+ std::stringstream ss;
+#if defined(OS_WIN)
+ // Tell the child process the name of the inherited HANDLE.
+ uintptr_t uintptr_handle = reinterpret_cast<uintptr_t>(shm.GetHandle());
+ ss << uintptr_handle << ",";
+#elif !defined(OS_POSIX)
+#error Unsupported OS
+#endif
+
+ base::UnguessableToken guid = shm.GetGUID();
+ ss << guid.GetHighForSerialization() << "," << guid.GetLowForSerialization();
+ return ss.str();
+}
+
#if defined(OS_WIN)
// static
-bool FieldTrialList::CreateTrialsFromHandleSwitch(
- const std::string& handle_switch) {
- int field_trial_handle = std::stoi(handle_switch);
+SharedMemoryHandle FieldTrialList::DeserializeSharedMemoryHandleMetadata(
+ const std::string& switch_value) {
+ std::vector<base::StringPiece> tokens = base::SplitStringPiece(
+ switch_value, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+
+ if (tokens.size() != 3)
+ return SharedMemoryHandle();
+
+ int field_trial_handle = 0;
+ if (!base::StringToInt(tokens[0], &field_trial_handle))
+ return SharedMemoryHandle();
HANDLE handle = reinterpret_cast<HANDLE>(field_trial_handle);
- // TODO(erikchen): Plumb a GUID for this SharedMemoryHandle.
- // https://crbug.com/713763.
- SharedMemoryHandle shm_handle(handle, base::UnguessableToken::Create());
- return FieldTrialList::CreateTrialsFromSharedMemoryHandle(shm_handle);
+
+ base::UnguessableToken guid;
+ if (!DeserializeGUIDFromStringPieces(tokens[1], tokens[2], &guid))
+ return SharedMemoryHandle();
+
+ return SharedMemoryHandle(handle, guid);
}
-#endif
+#endif // defined(OS_WIN)
#if defined(OS_POSIX) && !defined(OS_NACL)
// static
-bool FieldTrialList::CreateTrialsFromDescriptor(int fd_key) {
+SharedMemoryHandle FieldTrialList::DeserializeSharedMemoryHandleMetadata(
+ int fd,
+ const std::string& switch_value) {
+ std::vector<base::StringPiece> tokens = base::SplitStringPiece(
+ switch_value, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+
+ if (tokens.size() != 2)
+ return SharedMemoryHandle();
+
+ base::UnguessableToken guid;
+ if (!DeserializeGUIDFromStringPieces(tokens[0], tokens[1], &guid))
+ return SharedMemoryHandle();
+
+ return SharedMemoryHandle(FileDescriptor(fd, true), guid);
+}
+#endif // defined(OS_POSIX) && !defined(OS_NACL)
+
+#if defined(OS_WIN)
+// static
+bool FieldTrialList::CreateTrialsFromSwitchValue(
+ const std::string& switch_value) {
+ SharedMemoryHandle shm = DeserializeSharedMemoryHandleMetadata(switch_value);
+ if (!shm.IsValid())
+ return false;
+ return FieldTrialList::CreateTrialsFromSharedMemoryHandle(shm);
+}
+#endif // defined(OS_WIN)
+
+#if defined(OS_POSIX) && !defined(OS_NACL)
+// static
+bool FieldTrialList::CreateTrialsFromDescriptor(
+ int fd_key,
+ const std::string& switch_value) {
if (!kUseSharedMemoryForFieldTrials)
return false;
@@ -1158,16 +1197,16 @@
if (fd == -1)
return false;
- // TODO(erikchen): Plumb a GUID for this SharedMemoryHandle.
- // https://crbug.com/713763.
- SharedMemoryHandle shm_handle(FileDescriptor(fd, true),
- base::UnguessableToken::Create());
+ SharedMemoryHandle shm =
+ DeserializeSharedMemoryHandleMetadata(fd, switch_value);
+ if (!shm.IsValid())
+ return false;
- bool result = FieldTrialList::CreateTrialsFromSharedMemoryHandle(shm_handle);
+ bool result = FieldTrialList::CreateTrialsFromSharedMemoryHandle(shm);
DCHECK(result);
return true;
}
-#endif
+#endif // defined(OS_POSIX) && !defined(OS_NACL)
// static
bool FieldTrialList::CreateTrialsFromSharedMemoryHandle(
@@ -1254,7 +1293,7 @@
// Set |readonly_allocator_handle_| so we can pass it to be inherited and
// via the command line.
global_->readonly_allocator_handle_ =
- CreateReadOnlyHandle(global_->field_trial_allocator_.get());
+ global_->field_trial_allocator_->shared_memory()->GetReadOnlyHandle();
#endif
}
diff --git a/base/metrics/field_trial.h b/base/metrics/field_trial.h
index 60a6592..c1f5f6b 100644
--- a/base/metrics/field_trial.h
+++ b/base/metrics/field_trial.h
@@ -72,6 +72,7 @@
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/shared_memory.h"
+#include "base/memory/shared_memory_handle.h"
#include "base/metrics/persistent_memory_allocator.h"
#include "base/observer_list_threadsafe.h"
#include "base/pickle.h"
@@ -573,9 +574,9 @@
#if defined(OS_POSIX) && !defined(OS_NACL)
// On POSIX, we also need to explicitly pass down this file descriptor that
- // should be shared with the child process. Returns kInvalidPlatformFile if no
- // handle exists or was not initialized properly.
- static PlatformFile GetFieldTrialHandle();
+ // should be shared with the child process. Returns an invalid handle if it
+ // was not initialized properly.
+ static base::SharedMemoryHandle GetFieldTrialHandle();
#endif
// Adds a switch to the command line containing the field trial state as a
@@ -645,19 +646,39 @@
DoNotAddSimulatedFieldTrialsToAllocator);
FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest, AssociateFieldTrialParams);
FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest, ClearParamsFromSharedMemory);
+ FRIEND_TEST_ALL_PREFIXES(FieldTrialListTest,
+ SerializeSharedMemoryHandleMetadata);
+
+ // Serialization is used to pass information about the handle to child
+ // processes. It passes a reference to the relevant OS resource, and it passes
+ // a GUID. Serialization and deserialization doesn't actually transport the
+ // underlying OS resource - that must be done by the Process launcher.
+ static std::string SerializeSharedMemoryHandleMetadata(
+ const SharedMemoryHandle& shm);
+#if defined(OS_WIN)
+ static SharedMemoryHandle DeserializeSharedMemoryHandleMetadata(
+ const std::string& switch_value);
+#elif defined(OS_POSIX) && !defined(OS_NACL)
+ static SharedMemoryHandle DeserializeSharedMemoryHandleMetadata(
+ int fd,
+ const std::string& switch_value);
+#endif
#if defined(OS_WIN)
// Takes in |handle_switch| from the command line which represents the shared
// memory handle for field trials, parses it, and creates the field trials.
// Returns true on success, false on failure.
- static bool CreateTrialsFromHandleSwitch(const std::string& handle_switch);
+ // |switch_value| also contains the serialized GUID.
+ static bool CreateTrialsFromSwitchValue(const std::string& switch_value);
#endif
#if defined(OS_POSIX) && !defined(OS_NACL)
// On POSIX systems that use the zygote, we look up the correct fd that backs
// the shared memory segment containing the field trials by looking it up via
// an fd key in GlobalDescriptors. Returns true on success, false on failure.
- static bool CreateTrialsFromDescriptor(int fd_key);
+ // |switch_value| also contains the serialized GUID.
+ static bool CreateTrialsFromDescriptor(int fd_key,
+ const std::string& switch_value);
#endif
// Takes an unmapped SharedMemoryHandle, creates a SharedMemory object from it
@@ -731,7 +752,7 @@
// Readonly copy of the handle to the allocator. Needs to be a member variable
// because it's needed from both CopyFieldTrialStateToFlags() and
// AppendFieldTrialHandleIfNeeded().
- PlatformFile readonly_allocator_handle_ = kInvalidPlatformFile;
+ base::SharedMemoryHandle readonly_allocator_handle_;
// Tracks whether CreateTrialsFromCommandLine() has been called.
bool create_trials_from_command_line_called_ = false;
diff --git a/base/metrics/field_trial_unittest.cc b/base/metrics/field_trial_unittest.cc
index 54672e6..4ca5c11 100644
--- a/base/metrics/field_trial_unittest.cc
+++ b/base/metrics/field_trial_unittest.cc
@@ -1369,4 +1369,23 @@
EXPECT_EQ("value2", shm_params["key2"]);
}
+#if !defined(OS_NACL)
+TEST(FieldTrialListTest, SerializeSharedMemoryHandleMetadata) {
+ std::unique_ptr<base::SharedMemory> shm(new SharedMemory());
+ shm->CreateAndMapAnonymous(4 << 10);
+
+ std::string serialized =
+ FieldTrialList::SerializeSharedMemoryHandleMetadata(shm->handle());
+#if defined(OS_WIN)
+ SharedMemoryHandle deserialized =
+ FieldTrialList::DeserializeSharedMemoryHandleMetadata(serialized);
+#else
+ SharedMemoryHandle deserialized =
+ FieldTrialList::DeserializeSharedMemoryHandleMetadata(-1, serialized);
+#endif
+ EXPECT_EQ(deserialized.GetGUID(), shm->handle().GetGUID());
+ EXPECT_FALSE(deserialized.GetGUID().is_empty());
+}
+#endif // !defined(OS_NACL)
+
} // namespace base