[sql] Remove mmap before Raze().
On Windows, file truncation silently fails when the file is memory
mapped. Modify Raze() to disable memory mapping before truncating.
BUG=none
[email protected]
Review-Url: https://codereview.chromium.org/2830263002
Cr-Commit-Position: refs/heads/master@{#466555}
diff --git a/sql/connection.cc b/sql/connection.cc
index 0511e11..c80758f 100644
--- a/sql/connection.cc
+++ b/sql/connection.cc
@@ -1112,6 +1112,15 @@
// page_size" can be used to query such a database.
ScopedWritableSchema writable_schema(db_);
+#if defined(OS_WIN)
+ // On Windows, truncate silently fails when applied to memory-mapped files.
+ // Disable memory-mapping so that the truncate succeeds. Note that other
+ // connections may have memory-mapped the file, so this may not entirely
+ // prevent the problem.
+ // [Source: <https://sqlite.org/mmap.html> plus experiments.]
+ ignore_result(Execute("PRAGMA mmap_size = 0"));
+#endif
+
const char* kMain = "main";
int rc = BackupDatabase(null_db.db_, db_, kMain);
UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RazeDatabase",rc);
diff --git a/sql/connection_unittest.cc b/sql/connection_unittest.cc
index 6ac4f8b..4ee8b028 100644
--- a/sql/connection_unittest.cc
+++ b/sql/connection_unittest.cc
@@ -854,6 +854,42 @@
// closely match real life. That would also allow testing
// RazeWithTimeout().
+// On Windows, truncate silently fails against a memory-mapped file. One goal
+// of Raze() is to truncate the file to remove blocks which generate I/O errors.
+// Test that Raze() turns off memory mapping so that the file is truncated.
+// [This would not cover the case of multiple connections where one of the other
+// connections is memory-mapped. That is infrequent in Chromium.]
+TEST_F(SQLConnectionTest, RazeTruncate) {
+ // The empty database has 0 or 1 pages. Raze() should leave it with exactly 1
+ // page. Not checking directly because auto_vacuum on Android adds a freelist
+ // page.
+ ASSERT_TRUE(db().Raze());
+ int64_t expected_size;
+ ASSERT_TRUE(base::GetFileSize(db_path(), &expected_size));
+ ASSERT_GT(expected_size, 0);
+
+ // Cause the database to take a few pages.
+ const char* kCreateSql = "CREATE TABLE foo (id INTEGER PRIMARY KEY, value)";
+ ASSERT_TRUE(db().Execute(kCreateSql));
+ for (size_t i = 0; i < 24; ++i) {
+ ASSERT_TRUE(
+ db().Execute("INSERT INTO foo (value) VALUES (randomblob(1024))"));
+ }
+ int64_t db_size;
+ ASSERT_TRUE(base::GetFileSize(db_path(), &db_size));
+ ASSERT_GT(db_size, expected_size);
+
+ // Make a query covering most of the database file to make sure that the
+ // blocks are actually mapped into memory. Empirically, the truncate problem
+ // doesn't seem to happen if no blocks are mapped.
+ EXPECT_EQ("24576",
+ ExecuteWithResult(&db(), "SELECT SUM(LENGTH(value)) FROM foo"));
+
+ ASSERT_TRUE(db().Raze());
+ ASSERT_TRUE(base::GetFileSize(db_path(), &db_size));
+ ASSERT_EQ(expected_size, db_size);
+}
+
#if defined(OS_ANDROID)
TEST_F(SQLConnectionTest, SetTempDirForSQL) {