Commit a6267a6d authored by Mike Wittman's avatar Mike Wittman Committed by Commit Bot

[Sampling profiler] Fix ModuleCache module partitioning

std::remove_if may not preserve removed elements if they aren't already
at the end of the container. Use std::stable_partition instead to
get this behavior and avoid destroying modules prematurely.

Bug: 1127466
Change-Id: I90175785e45e6ceea91d04ec332e263eece6f4eb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2406525
Commit-Queue: Mike Wittman <wittman@chromium.org>
Auto-Submit: Mike Wittman <wittman@chromium.org>
Reviewed-by: default avatarEtienne Pierre-Doray <etiennep@chromium.org>
Cr-Commit-Position: refs/heads/master@{#806805}
parent ce5684bc
...@@ -54,38 +54,41 @@ std::vector<const ModuleCache::Module*> ModuleCache::GetModules() const { ...@@ -54,38 +54,41 @@ std::vector<const ModuleCache::Module*> ModuleCache::GetModules() const {
} }
void ModuleCache::UpdateNonNativeModules( void ModuleCache::UpdateNonNativeModules(
const std::vector<const Module*>& to_remove, const std::vector<const Module*>& defunct_modules,
std::vector<std::unique_ptr<const Module>> to_add) { std::vector<std::unique_ptr<const Module>> new_modules) {
// Insert the modules to remove into a set to support O(log(n)) lookup below. // Insert the modules to remove into a set to support O(log(n)) lookup below.
flat_set<const Module*> to_remove_set(to_remove.begin(), to_remove.end()); flat_set<const Module*> defunct_modules_set(defunct_modules.begin(),
defunct_modules.end());
// Reorder the modules to be removed to the last slots in the set, then move // Reorder the modules to be removed to the last slots in the set, then move
// them to the inactive modules, then erase the moved-from modules from the // them to the inactive modules, then erase the moved-from modules from the
// set. The flat_set docs endorse using base::EraseIf() which performs the // set. This is a variation on the standard erase-remove idiom, which is
// same operations -- exclusive of the moves -- so this is OK even though it // explicitly endorsed for implementing erase behavior on flat_sets.
// might seem like we're messing with the internal set representation.
// //
// remove_if is O(m*log(r)) where m is the number of current modules and r is // stable_partition is O(m*log(r)) where m is the number of current modules
// the number of modules to remove. insert and erase are both O(r). // and r is the number of modules to remove. insert and erase are both O(r).
auto first_module_to_remove = std::remove_if( auto first_module_defunct_modules = std::stable_partition(
non_native_modules_.begin(), non_native_modules_.end(), non_native_modules_.begin(), non_native_modules_.end(),
[&to_remove_set](const std::unique_ptr<const Module>& module) { [&defunct_modules_set](const std::unique_ptr<const Module>& module) {
return to_remove_set.find(module.get()) != to_remove_set.end(); return defunct_modules_set.find(module.get()) ==
defunct_modules_set.end();
}); });
// All modules requested to be removed should have been found. // All modules requested to be removed should have been found.
DCHECK_EQ(static_cast<ptrdiff_t>(to_remove.size()), DCHECK_EQ(
std::distance(first_module_to_remove, non_native_modules_.end())); static_cast<ptrdiff_t>(defunct_modules.size()),
std::distance(first_module_defunct_modules, non_native_modules_.end()));
inactive_non_native_modules_.insert( inactive_non_native_modules_.insert(
inactive_non_native_modules_.end(), inactive_non_native_modules_.end(),
std::make_move_iterator(first_module_to_remove), std::make_move_iterator(first_module_defunct_modules),
std::make_move_iterator(non_native_modules_.end())); std::make_move_iterator(non_native_modules_.end()));
non_native_modules_.erase(first_module_to_remove, non_native_modules_.end()); non_native_modules_.erase(first_module_defunct_modules,
non_native_modules_.end());
// Insert the modules to be added. This operation is O((m + a) + a*log(a)) // Insert the modules to be added. This operation is O((m + a) + a*log(a))
// where m is the number of current modules and a is the number of modules to // where m is the number of current modules and a is the number of modules to
// be added. // be added.
non_native_modules_.insert(std::make_move_iterator(to_add.begin()), non_native_modules_.insert(std::make_move_iterator(new_modules.begin()),
std::make_move_iterator(to_add.end())); std::make_move_iterator(new_modules.end()));
} }
void ModuleCache::AddCustomNativeModule(std::unique_ptr<const Module> module) { void ModuleCache::AddCustomNativeModule(std::unique_ptr<const Module> module) {
......
...@@ -84,14 +84,14 @@ class BASE_EXPORT ModuleCache { ...@@ -84,14 +84,14 @@ class BASE_EXPORT ModuleCache {
// GetModuleForAddress() will return the non-native module rather than the // GetModuleForAddress() will return the non-native module rather than the
// native module for the memory region it occupies. // native module for the memory region it occupies.
// //
// Modules in |to_remove| are removed from the set of active modules; // Modules in |defunct_modules| are removed from the set of active modules;
// specifically they no longer participate in the GetModuleForAddress() // specifically they no longer participate in the GetModuleForAddress()
// lookup. They continue to exist for the lifetime of the ModuleCache, // lookup. They continue to exist for the lifetime of the ModuleCache,
// however, so that existing references to them remain valid. Modules in // however, so that existing references to them remain valid. Modules in
// |to_add| are added to the set of active non-native modules. // |new_modules| are added to the set of active non-native modules.
void UpdateNonNativeModules( void UpdateNonNativeModules(
const std::vector<const Module*>& to_remove, const std::vector<const Module*>& defunct_modules,
std::vector<std::unique_ptr<const Module>> to_add); std::vector<std::unique_ptr<const Module>> new_modules);
// Adds a custom native module to the cache. This is intended to support // Adds a custom native module to the cache. This is intended to support
// native modules that require custom handling. In general, native modules // native modules that require custom handling. In general, native modules
......
...@@ -226,6 +226,28 @@ MAYBE_TEST(ModuleCacheTest, UpdateNonNativeModulesRemoveModuleIsNotDestroyed) { ...@@ -226,6 +226,28 @@ MAYBE_TEST(ModuleCacheTest, UpdateNonNativeModulesRemoveModuleIsNotDestroyed) {
EXPECT_TRUE(was_destroyed); EXPECT_TRUE(was_destroyed);
} }
// Regression test to validate that when modules are partitioned into modules to
// keep and modules to remove, the modules to remove are not destroyed.
// https://crbug.com/1127466 case 2.
MAYBE_TEST(ModuleCacheTest, UpdateNonNativeModulesPartitioning) {
int destroyed_count = 0;
const auto record_destroyed = [&destroyed_count]() { ++destroyed_count; };
{
ModuleCache cache;
std::vector<std::unique_ptr<const ModuleCache::Module>> modules;
modules.push_back(std::make_unique<FakeModule>(
1, 1, false, BindLambdaForTesting(record_destroyed)));
const ModuleCache::Module* module1 = modules.back().get();
modules.push_back(std::make_unique<FakeModule>(
2, 1, false, BindLambdaForTesting(record_destroyed)));
cache.UpdateNonNativeModules({}, std::move(modules));
cache.UpdateNonNativeModules({module1}, {});
EXPECT_EQ(0, destroyed_count);
}
EXPECT_EQ(2, destroyed_count);
}
MAYBE_TEST(ModuleCacheTest, UpdateNonNativeModulesReplace) { MAYBE_TEST(ModuleCacheTest, UpdateNonNativeModulesReplace) {
ModuleCache cache; ModuleCache cache;
// Replace a module with another larger module at the same base address. // Replace a module with another larger module at the same base address.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment