diff options
author | sanjeevr@chromium.org <sanjeevr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-05-20 18:20:29 +0000 |
---|---|---|
committer | sanjeevr@chromium.org <sanjeevr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-05-20 18:20:29 +0000 |
commit | 6658ca866d0e28950179a65179c1a4d6e8e3f8df (patch) | |
tree | 5e7c4767096a10bc71aa0bd4bf2228f1476a564e /chrome/common | |
parent | e45b40f5bfe4f20aece5a8fa25d9a4c6c89f22e1 (diff) | |
download | chromium_src-6658ca866d0e28950179a65179c1a4d6e8e3f8df.zip chromium_src-6658ca866d0e28950179a65179c1a4d6e8e3f8df.tar.gz chromium_src-6658ca866d0e28950179a65179c1a4d6e8e3f8df.tar.bz2 |
Moved ImportantFileWriter to chrome/common in preparation of moving JsonPrefStore to chrome/common.
BUG=None.
TEST=Unit-tests moved and modified.
Review URL: http://codereview.chromium.org/2128014
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@47818 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/common')
-rw-r--r-- | chrome/common/important_file_writer.cc | 150 | ||||
-rw-r--r-- | chrome/common/important_file_writer.h | 112 | ||||
-rw-r--r-- | chrome/common/important_file_writer_unittest.cc | 115 |
3 files changed, 377 insertions, 0 deletions
diff --git a/chrome/common/important_file_writer.cc b/chrome/common/important_file_writer.cc new file mode 100644 index 0000000..3466958 --- /dev/null +++ b/chrome/common/important_file_writer.cc @@ -0,0 +1,150 @@ +// Copyright (c) 2010 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. + +#include "chrome/common/important_file_writer.h" + +#include <stdio.h> + +#include <ostream> +#include <string> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/message_loop_proxy.h" +#include "base/string_util.h" +#include "base/task.h" +#include "base/thread.h" +#include "base/time.h" + +using base::TimeDelta; + +namespace { + +const int kDefaultCommitIntervalMs = 10000; + +class WriteToDiskTask : public Task { + public: + WriteToDiskTask(const FilePath& path, const std::string& data) + : path_(path), + data_(data) { + } + + virtual void Run() { + // Write the data to a temp file then rename to avoid data loss if we crash + // while writing the file. Ensure that the temp file is on the same volume + // as target file, so it can be moved in one step, and that the temp file + // is securely created. + FilePath tmp_file_path; + FILE* tmp_file = file_util::CreateAndOpenTemporaryFileInDir( + path_.DirName(), &tmp_file_path); + if (!tmp_file) { + LogFailure("could not create temporary file"); + return; + } + + size_t bytes_written = fwrite(data_.data(), 1, data_.length(), tmp_file); + if (!file_util::CloseFile(tmp_file)) { + file_util::Delete(tmp_file_path, false); + LogFailure("failed to close temporary file"); + return; + } + if (bytes_written < data_.length()) { + file_util::Delete(tmp_file_path, false); + LogFailure("error writing, bytes_written=" + UintToString( + static_cast<unsigned int>(bytes_written))); + return; + } + + if (file_util::ReplaceFile(tmp_file_path, path_)) { + LogSuccess(); + return; + } + + file_util::Delete(tmp_file_path, false); + LogFailure("could not rename temporary file"); + } + + private: + void LogSuccess() { + LOG(INFO) << "successfully saved " << path_.value(); + } + + void LogFailure(const std::string& message) { + LOG(WARNING) << "failed to write " << path_.value() + << ": " << message; + } + + const FilePath path_; + const std::string data_; + + DISALLOW_COPY_AND_ASSIGN(WriteToDiskTask); +}; + +} // namespace + +ImportantFileWriter::ImportantFileWriter( + const FilePath& path, base::MessageLoopProxy* file_message_loop_proxy) + : path_(path), + file_message_loop_proxy_(file_message_loop_proxy), + serializer_(NULL), + commit_interval_(TimeDelta::FromMilliseconds( + kDefaultCommitIntervalMs)) { + DCHECK(CalledOnValidThread()); + DCHECK(file_message_loop_proxy_.get()); +} + +ImportantFileWriter::~ImportantFileWriter() { + // We're usually a member variable of some other object, which also tends + // to be our serializer. It may not be safe to call back to the parent object + // being destructed. + DCHECK(!HasPendingWrite()); +} + +bool ImportantFileWriter::HasPendingWrite() const { + DCHECK(CalledOnValidThread()); + return timer_.IsRunning(); +} + +void ImportantFileWriter::WriteNow(const std::string& data) { + DCHECK(CalledOnValidThread()); + + if (HasPendingWrite()) + timer_.Stop(); + + // TODO(sanjeevr): Add a DCHECK for the return value of PostTask. + // (Some tests fail if we add the DCHECK and they need to be fixed first). + file_message_loop_proxy_->PostTask(FROM_HERE, + new WriteToDiskTask(path_, data)); +} + +void ImportantFileWriter::ScheduleWrite(DataSerializer* serializer) { + DCHECK(CalledOnValidThread()); + + DCHECK(serializer); + serializer_ = serializer; + + if (!MessageLoop::current()) { + // Happens in unit tests. + DoScheduledWrite(); + return; + } + + if (!timer_.IsRunning()) { + timer_.Start(commit_interval_, this, + &ImportantFileWriter::DoScheduledWrite); + } +} + +void ImportantFileWriter::DoScheduledWrite() { + DCHECK(serializer_); + std::string data; + if (serializer_->SerializeData(&data)) { + WriteNow(data); + } else { + LOG(WARNING) << "failed to serialize data to be saved in " + << path_.value(); + } + serializer_ = NULL; +} diff --git a/chrome/common/important_file_writer.h b/chrome/common/important_file_writer.h new file mode 100644 index 0000000..d4f57a0 --- /dev/null +++ b/chrome/common/important_file_writer.h @@ -0,0 +1,112 @@ +// Copyright (c) 2009 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_COMMON_IMPORTANT_FILE_WRITER_H_ +#define CHROME_COMMON_IMPORTANT_FILE_WRITER_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/non_thread_safe.h" +#include "base/ref_counted.h" +#include "base/time.h" +#include "base/timer.h" + +namespace base { +class MessageLoopProxy; +class Thread; +} + +// Helper to ensure that a file won't be corrupted by the write (for example on +// application crash). Consider a naive way to save an important file F: +// +// 1. Open F for writing, truncating it. +// 2. Write new data to F. +// +// It's good when it works, but it gets very bad if step 2. doesn't complete. +// It can be caused by a crash, a computer hang, or a weird I/O error. And you +// end up with a broken file. +// +// To be safe, we don't start with writing directly to F. Instead, we write to +// to a temporary file. Only after that write is successful, we rename the +// temporary file to target filename. +// +// If you want to know more about this approach and ext3/ext4 fsync issues, see +// http://valhenson.livejournal.com/37921.html +class ImportantFileWriter : public NonThreadSafe { + public: + // Used by ScheduleSave to lazily provide the data to be saved. Allows us + // to also batch data serializations. + class DataSerializer { + public: + virtual ~DataSerializer() {} + + // Should put serialized string in |data| and return true on successful + // serialization. Will be called on the same thread on which + // ImportantFileWriter has been created. + virtual bool SerializeData(std::string* data) = 0; + }; + + // Initialize the writer. + // |path| is the name of file to write. + // |file_message_loop_proxy| is the MessageLoopProxy for a thread on which + // file I/O can be done. + // All non-const methods, ctor and dtor must be called on the same thread. + ImportantFileWriter(const FilePath& path, + base::MessageLoopProxy* file_message_loop_proxy); + + // You have to ensure that there are no pending writes at the moment + // of destruction. + ~ImportantFileWriter(); + + FilePath path() const { return path_; } + + // Returns true if there is a scheduled write pending which has not yet + // been started. + bool HasPendingWrite() const; + + // Save |data| to target filename. Does not block. If there is a pending write + // scheduled by ScheduleWrite, it is cancelled. + void WriteNow(const std::string& data); + + // Schedule a save to target filename. Data will be serialized and saved + // to disk after the commit interval. If another ScheduleWrite is issued + // before that, only one serialization and write to disk will happen, and + // the most recent |serializer| will be used. This operation does not block. + // |serializer| should remain valid through the lifetime of + // ImportantFileWriter. + void ScheduleWrite(DataSerializer* serializer); + + // Serialize data pending to be saved and execute write on backend thread. + void DoScheduledWrite(); + + base::TimeDelta commit_interval() const { + return commit_interval_; + } + + void set_commit_interval(const base::TimeDelta& interval) { + commit_interval_ = interval; + } + + private: + // Path being written to. + const FilePath path_; + + // MessageLoopProxy for the thread on which file I/O can be done. + scoped_refptr<base::MessageLoopProxy> file_message_loop_proxy_; + + // Timer used to schedule commit after ScheduleWrite. + base::OneShotTimer<ImportantFileWriter> timer_; + + // Serializer which will provide the data to be saved. + DataSerializer* serializer_; + + // Time delta after which scheduled data will be written to disk. + base::TimeDelta commit_interval_; + + DISALLOW_COPY_AND_ASSIGN(ImportantFileWriter); +}; + +#endif // CHROME_COMMON_IMPORTANT_FILE_WRITER_H_ diff --git a/chrome/common/important_file_writer_unittest.cc b/chrome/common/important_file_writer_unittest.cc new file mode 100644 index 0000000..3d44ae0 --- /dev/null +++ b/chrome/common/important_file_writer_unittest.cc @@ -0,0 +1,115 @@ +// Copyright (c) 2009 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. + +#include "chrome/common/important_file_writer.h" + +#include "base/compiler_specific.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/scoped_temp_dir.h" +#include "base/thread.h" +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +std::string GetFileContent(const FilePath& path) { + std::string content; + if (!file_util::ReadFileToString(path, &content)) { + NOTREACHED(); + } + return content; +} + +class DataSerializer : public ImportantFileWriter::DataSerializer { + public: + explicit DataSerializer(const std::string& data) : data_(data) { + } + + virtual bool SerializeData(std::string* output) { + output->assign(data_); + return true; + } + + private: + const std::string data_; +}; + +} // namespace + +class ImportantFileWriterTest : public testing::Test { + public: + ImportantFileWriterTest() { } + virtual void SetUp() { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + file_ = temp_dir_.path().AppendASCII("test-file"); + } + + protected: + FilePath file_; + MessageLoop loop_; + + private: + ScopedTempDir temp_dir_; +}; + +TEST_F(ImportantFileWriterTest, Basic) { + ImportantFileWriter writer(file_, + base::MessageLoopProxy::CreateForCurrentThread()); + EXPECT_FALSE(file_util::PathExists(writer.path())); + writer.WriteNow("foo"); + loop_.RunAllPending(); + + ASSERT_TRUE(file_util::PathExists(writer.path())); + EXPECT_EQ("foo", GetFileContent(writer.path())); +} + +TEST_F(ImportantFileWriterTest, ScheduleWrite) { + ImportantFileWriter writer(file_, + base::MessageLoopProxy::CreateForCurrentThread()); + writer.set_commit_interval(base::TimeDelta::FromMilliseconds(25)); + EXPECT_FALSE(writer.HasPendingWrite()); + DataSerializer serializer("foo"); + writer.ScheduleWrite(&serializer); + EXPECT_TRUE(writer.HasPendingWrite()); + MessageLoop::current()->PostDelayedTask(FROM_HERE, + new MessageLoop::QuitTask(), 100); + MessageLoop::current()->Run(); + EXPECT_FALSE(writer.HasPendingWrite()); + ASSERT_TRUE(file_util::PathExists(writer.path())); + EXPECT_EQ("foo", GetFileContent(writer.path())); +} + +TEST_F(ImportantFileWriterTest, DoScheduledWrite) { + ImportantFileWriter writer(file_, + base::MessageLoopProxy::CreateForCurrentThread()); + EXPECT_FALSE(writer.HasPendingWrite()); + DataSerializer serializer("foo"); + writer.ScheduleWrite(&serializer); + EXPECT_TRUE(writer.HasPendingWrite()); + writer.DoScheduledWrite(); + MessageLoop::current()->PostDelayedTask(FROM_HERE, + new MessageLoop::QuitTask(), 100); + MessageLoop::current()->Run(); + EXPECT_FALSE(writer.HasPendingWrite()); + ASSERT_TRUE(file_util::PathExists(writer.path())); + EXPECT_EQ("foo", GetFileContent(writer.path())); +} + +TEST_F(ImportantFileWriterTest, BatchingWrites) { + ImportantFileWriter writer(file_, + base::MessageLoopProxy::CreateForCurrentThread()); + writer.set_commit_interval(base::TimeDelta::FromMilliseconds(25)); + DataSerializer foo("foo"), bar("bar"), baz("baz"); + writer.ScheduleWrite(&foo); + writer.ScheduleWrite(&bar); + writer.ScheduleWrite(&baz); + MessageLoop::current()->PostDelayedTask(FROM_HERE, + new MessageLoop::QuitTask(), 100); + MessageLoop::current()->Run(); + ASSERT_TRUE(file_util::PathExists(writer.path())); + EXPECT_EQ("baz", GetFileContent(writer.path())); +} |