Commit 9b521269 authored by inferno@chromium.org's avatar inferno@chromium.org

Revert 141514 - Remove CachingValueStore in favor of an async ValueStoreFrontend.

Caused massive number of use-after-free crashes on ClusterFuzz.

After actually seeing the use cases for the value store in extension code, I
realized that an async version would be more appropriate. This one will use
less memory as well, since we won't need to keep the cache in memory.

BUG=123366
TEST=no


Review URL: https://chromiumcodereview.appspot.com/10539073

TBR=mpcomplete@chromium.org
Review URL: https://chromiumcodereview.appspot.com/10537116

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@141603 0039d316-1c4b-4281-b951-d872f2087c98
parent 1d3e9176
......@@ -2,42 +2,31 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/value_store/value_store_frontend.h"
#include "chrome/browser/value_store/caching_value_store.h"
#include "chrome/browser/value_store/leveldb_value_store.h"
#include "content/public/browser/browser_thread.h"
using content::BrowserThread;
class ValueStoreFrontend::Backend : public base::RefCountedThreadSafe<Backend> {
class CachingValueStore::Backend : public base::RefCountedThreadSafe<Backend> {
public:
explicit Backend(const FilePath& db_path) : storage_(NULL) {
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&ValueStoreFrontend::Backend::InitOnFileThread,
this, db_path));
}
typedef base::Callback<void(scoped_ptr<base::DictionaryValue>)> ReadyCallback;
Backend() : storage_(NULL) {}
void Get(const std::string& key,
const ValueStoreFrontend::ReadCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
ValueStore::ReadResult result = storage_->Get(key);
// Extract the value from the ReadResult and pass ownership of it to the
// callback.
base::Value* value = NULL;
if (!result->HasError())
result->settings()->RemoveWithoutPathExpansion(key, &value);
scoped_ptr<base::Value> passed_value(value);
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&ValueStoreFrontend::Backend::RunCallback,
this, callback, base::Passed(passed_value.Pass())));
void Init(const FilePath& db_path, ReadyCallback callback) {
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&CachingValueStore::Backend::InitOnFileThread,
this, db_path, callback));
}
void Set(const std::string& key, scoped_ptr<base::Value> value) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
// We don't need the old value, so skip generating changes.
storage_->Set(ValueStore::IGNORE_QUOTA | ValueStore::NO_GENERATE_CHANGES,
// We've already checked that the value has changed, so don't bother
// checking again. Also, skip changes to avoid wasted copies.
storage_->Set(ValueStore::IGNORE_QUOTA |
ValueStore::NO_GENERATE_CHANGES |
ValueStore::NO_CHECK_OLD_VALUE,
key, *value.get());
}
......@@ -57,16 +46,17 @@ class ValueStoreFrontend::Backend : public base::RefCountedThreadSafe<Backend> {
}
}
void InitOnFileThread(const FilePath& db_path) {
void InitOnFileThread(const FilePath& db_path, ReadyCallback callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DCHECK(!storage_);
storage_ = LeveldbValueStore::Create(db_path);
}
void RunCallback(const ValueStoreFrontend::ReadCallback& callback,
scoped_ptr<base::Value> value) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
callback.Run(value.Pass());
ValueStore::ReadResult result = storage_->Get();
if (result->HasError()) {
callback.Run(scoped_ptr<base::DictionaryValue>(NULL));
} else {
callback.Run(result->settings().Pass());
}
}
// The actual ValueStore that handles persisting the data to disk. Used
......@@ -76,36 +66,76 @@ class ValueStoreFrontend::Backend : public base::RefCountedThreadSafe<Backend> {
DISALLOW_COPY_AND_ASSIGN(Backend);
};
ValueStoreFrontend::ValueStoreFrontend(const FilePath& db_path)
: backend_(new Backend(db_path)) {
CachingValueStore::CachingValueStore(const FilePath& db_path)
: backend_(new Backend()) {
backend_->Init(db_path,
base::Bind(&CachingValueStore::OnBackendReady, AsWeakPtr()));
}
ValueStoreFrontend::~ValueStoreFrontend() {
CachingValueStore::~CachingValueStore() {
}
bool CachingValueStore::Get(const std::string& key,
const base::Value** result) {
DCHECK(CalledOnValidThread());
DCHECK(IsInitialized());
base::Value* value = NULL;
if (cache_->GetWithoutPathExpansion(key, &value)) {
*result = value;
return true;
}
return false;
}
void ValueStoreFrontend::Get(const std::string& key,
const ReadCallback& callback) {
void CachingValueStore::Set(const std::string& key, base::Value* value) {
DCHECK(CalledOnValidThread());
DCHECK(IsInitialized());
scoped_ptr<base::Value> new_value(value);
base::Value* old_value = NULL;
if (!cache_->GetWithoutPathExpansion(key, &old_value) ||
!value->Equals(old_value)) {
cache_->SetWithoutPathExpansion(key, new_value.release());
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&ValueStoreFrontend::Backend::Get,
backend_, key, callback));
scoped_ptr<base::Value> passed_value(value->DeepCopy());
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&CachingValueStore::Backend::Set,
backend_, key, base::Passed(passed_value.Pass())));
}
}
void CachingValueStore::Remove(const std::string& key) {
DCHECK(CalledOnValidThread());
DCHECK(IsInitialized());
if (cache_->RemoveWithoutPathExpansion(key, NULL)) {
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&CachingValueStore::Backend::Remove,
backend_, key));
}
}
bool CachingValueStore::IsInitialized() {
DCHECK(CalledOnValidThread());
return !!cache_.get();
}
void ValueStoreFrontend::Set(const std::string& key,
scoped_ptr<base::Value> value) {
void CachingValueStore::AddObserver(Observer* observer) {
DCHECK(CalledOnValidThread());
observers_.AddObserver(observer);
}
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&ValueStoreFrontend::Backend::Set,
backend_, key, base::Passed(value.Pass())));
void CachingValueStore::RemoveObserver(Observer* observer) {
DCHECK(CalledOnValidThread());
observers_.RemoveObserver(observer);
}
void ValueStoreFrontend::Remove(const std::string& key) {
void CachingValueStore::OnBackendReady(
scoped_ptr<base::DictionaryValue> values) {
DCHECK(CalledOnValidThread());
DCHECK(!cache_.get());
cache_.swap(values);
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&ValueStoreFrontend::Backend::Remove,
backend_, key));
FOR_EACH_OBSERVER(Observer, observers_, OnInitializationComplete());
}
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_VALUE_STORE_CACHING_VALUE_STORE_H_
#define CHROME_BROWSER_VALUE_STORE_CACHING_VALUE_STORE_H_
#pragma once
#include <string>
#include "base/file_path.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/threading/non_thread_safe.h"
#include "base/values.h"
// Value store area with caching, backed by a LeveldbValueStore on the FILE
// thread. The key/value store is flat with no path expansion, meaning "foo"
// and "foo.bar" are completely separate keys.
class CachingValueStore
: public base::SupportsWeakPtr<CachingValueStore>,
public base::NonThreadSafe {
public:
class Observer {
public:
virtual ~Observer() {}
// Notification that the Store is ready to use.
virtual void OnInitializationComplete() = 0;
};
explicit CachingValueStore(const FilePath& db_path);
~CachingValueStore();
// Retrieves a value from the cache, returning true if value with the given
// key exists. The returned value is a reference to the value in the cache,
// i.e. no copies are made.
bool Get(const std::string& key, const base::Value** result);
// Sets a value with the given key. Ownership of |value| is transferred to
// the store.
void Set(const std::string& key, base::Value* value);
// Removes the value with the given key.
void Remove(const std::string& key);
// Returns true if the store has finished initializing.
bool IsInitialized();
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
private:
class Backend;
// Called when our backend finishes reading the database.
void OnBackendReady(scoped_ptr<base::DictionaryValue> values);
// A cache of the value store. This is always up-to-date, with changes
// persisted to disk as they are made.
scoped_ptr<base::DictionaryValue> cache_;
// A helper class to manage lifetime of the backing ValueStore, which lives
// on the FILE thread.
scoped_refptr<Backend> backend_;
ObserverList<Observer, true> observers_;
DISALLOW_COPY_AND_ASSIGN(CachingValueStore);
};
#endif // CHROME_BROWSER_VALUE_STORE_CACHING_VALUE_STORE_H_
......@@ -7,7 +7,7 @@
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/scoped_temp_dir.h"
#include "chrome/browser/value_store/value_store_frontend.h"
#include "chrome/browser/value_store/caching_value_store.h"
#include "chrome/common/chrome_paths.h"
#include "content/public/test/test_browser_thread.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -16,9 +16,11 @@ using content::BrowserThread;
// Test suite for CachingValueStore, using a test database with a few simple
// entries.
class ValueStoreFrontendTest : public testing::Test {
class CachingValueStoreTest
: public testing::Test,
public CachingValueStore::Observer {
public:
ValueStoreFrontendTest()
CachingValueStoreTest()
: ui_thread_(BrowserThread::UI, MessageLoop::current()),
file_thread_(BrowserThread::FILE, MessageLoop::current()) {
}
......@@ -37,29 +39,26 @@ class ValueStoreFrontendTest : public testing::Test {
virtual void TearDown() {
MessageLoop::current()->RunAllPending(); // wait for storage to delete
storage_->RemoveObserver(this);
storage_.reset();
}
// Reset the value store, reloading the DB from disk.
void ResetStorage() {
storage_.reset(new ValueStoreFrontend(db_path_));
// CachingValueStore::Observer
virtual void OnInitializationComplete() {
MessageLoop::current()->Quit();
}
bool Get(const std::string& key, scoped_ptr<base::Value>* output) {
storage_->Get(key, base::Bind(&ValueStoreFrontendTest::GetAndWait,
base::Unretained(this), output));
MessageLoop::current()->Run(); // wait for GetAndWait
return !!output->get();
// Reset the value store, reloading the DB from disk.
void ResetStorage() {
if (storage_.get())
storage_->RemoveObserver(this);
storage_.reset(new CachingValueStore(db_path_));
storage_->AddObserver(this);
MessageLoop::current()->Run(); // wait for OnInitializationComplete
}
protected:
void GetAndWait(scoped_ptr<base::Value>* output,
scoped_ptr<base::Value> result) {
*output = result.Pass();
MessageLoop::current()->Quit();
}
scoped_ptr<ValueStoreFrontend> storage_;
scoped_ptr<CachingValueStore> storage_;
ScopedTempDir temp_dir_;
FilePath db_path_;
MessageLoop message_loop_;
......@@ -67,50 +66,48 @@ class ValueStoreFrontendTest : public testing::Test {
content::TestBrowserThread file_thread_;
};
TEST_F(ValueStoreFrontendTest, GetExistingData) {
scoped_ptr<base::Value> value(NULL);
ASSERT_FALSE(Get("key0", &value));
TEST_F(CachingValueStoreTest, GetExistingData) {
const base::Value* value = NULL;
ASSERT_FALSE(storage_->Get("key0", &value));
// Test existing keys in the DB.
{
ASSERT_TRUE(Get("key1", &value));
ASSERT_TRUE(storage_->Get("key1", &value));
std::string result;
ASSERT_TRUE(value->GetAsString(&result));
EXPECT_EQ("value1", result);
}
{
ASSERT_TRUE(Get("key2", &value));
ASSERT_TRUE(storage_->Get("key2", &value));
int result;
ASSERT_TRUE(value->GetAsInteger(&result));
EXPECT_EQ(2, result);
}
}
TEST_F(ValueStoreFrontendTest, ChangesPersistAfterReload) {
storage_->Set("key0",
scoped_ptr<base::Value>(base::Value::CreateIntegerValue(0)));
storage_->Set("key1",
scoped_ptr<base::Value>(base::Value::CreateStringValue("new1")));
TEST_F(CachingValueStoreTest, ChangesPersistAfterReload) {
storage_->Set("key0", base::Value::CreateIntegerValue(0));
storage_->Set("key1", base::Value::CreateStringValue("new1"));
storage_->Remove("key2");
// Reload the DB and test our changes.
ResetStorage();
scoped_ptr<base::Value> value(NULL);
const base::Value* value = NULL;
{
ASSERT_TRUE(Get("key0", &value));
ASSERT_TRUE(storage_->Get("key0", &value));
int result;
ASSERT_TRUE(value->GetAsInteger(&result));
EXPECT_EQ(0, result);
}
{
ASSERT_TRUE(Get("key1", &value));
ASSERT_TRUE(storage_->Get("key1", &value));
std::string result;
ASSERT_TRUE(value->GetAsString(&result));
EXPECT_EQ("new1", result);
}
ASSERT_FALSE(Get("key2", &value));
ASSERT_FALSE(storage_->Get("key2", &value));
}
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_VALUE_STORE_VALUE_STORE_FRONTEND_H_
#define CHROME_BROWSER_VALUE_STORE_VALUE_STORE_FRONTEND_H_
#pragma once
#include <string>
#include "base/callback.h"
#include "base/file_path.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/non_thread_safe.h"
#include "base/values.h"
// A frontend for a LeveldbValueStore, for use on the UI thread.
class ValueStoreFrontend
: public base::SupportsWeakPtr<ValueStoreFrontend>,
public base::NonThreadSafe {
public:
typedef base::Callback<void(scoped_ptr<base::Value>)> ReadCallback;
explicit ValueStoreFrontend(const FilePath& db_path);
~ValueStoreFrontend();
// Retrieves a value from the database asynchronously, passing a copy to
// |callback| when ready. NULL is passed if no matching entry is found.
void Get(const std::string& key, const ReadCallback& callback);
// Sets a value with the given key.
void Set(const std::string& key, scoped_ptr<base::Value> value);
// Removes the value with the given key.
void Remove(const std::string& key);
private:
class Backend;
// A helper class to manage lifetime of the backing ValueStore, which lives
// on the FILE thread.
scoped_refptr<Backend> backend_;
DISALLOW_COPY_AND_ASSIGN(ValueStoreFrontend);
};
#endif // CHROME_BROWSER_VALUE_STORE_VALUE_STORE_FRONTEND_H_
......@@ -4055,6 +4055,8 @@
'browser/user_style_sheet_watcher.h',
'browser/user_style_sheet_watcher_factory.cc',
'browser/user_style_sheet_watcher_factory.h',
'browser/value_store/caching_value_store.cc',
'browser/value_store/caching_value_store.h',
'browser/value_store/failing_value_store.cc',
'browser/value_store/failing_value_store.h',
'browser/value_store/leveldb_value_store.cc',
......@@ -4063,8 +4065,6 @@
'browser/value_store/testing_value_store.h',
'browser/value_store/value_store_change.cc',
'browser/value_store/value_store_change.h',
'browser/value_store/value_store_frontend.cc',
'browser/value_store/value_store_frontend.h',
'browser/value_store/value_store.cc',
'browser/value_store/value_store.h',
'browser/view_type_utils.cc',
......
......@@ -1816,9 +1816,9 @@
'browser/ui/window_snapshot/window_snapshot_mac_unittest.mm',
'browser/chrome_to_mobile_service_unittest.cc',
'browser/user_style_sheet_watcher_unittest.cc',
'browser/value_store/caching_value_store_unittest.cc',
'browser/value_store/leveldb_value_store_unittest.cc',
'browser/value_store/testing_value_store_unittest.cc',
'browser/value_store/value_store_frontend_unittest.cc',
'browser/value_store/value_store_unittest.cc',
'browser/value_store/value_store_unittest.h',
'browser/visitedlink/visitedlink_unittest.cc',
......
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