[ledger] Avoid calling IsDirectoryAt twice, when getting a LevelDb.

LedgerStorageImpl::GetPageStorage would check if the directory at the
given path existed, in order to return NOT_FOUND if necessary. It would
then call DbFactory::GetOrCreateDb, which checked again if the directory
exists.

In this CL DbFactory::GetOrCreateDb is updated to support only getting
an existing Db, and fail with NOT_FOUND status if it doesn't exist.
This helps remove the extra call to IsDirectoryAt in LedgerStorageImpl.

Test: LevelDbFactoryTest.*
Change-Id: I6b97615179ceae2347e9403b17177f44ee4e1112
diff --git a/bin/ledger/app/page_eviction_manager_impl.cc b/bin/ledger/app/page_eviction_manager_impl.cc
index 7cfb500..aa13c2a 100644
--- a/bin/ledger/app/page_eviction_manager_impl.cc
+++ b/bin/ledger/app/page_eviction_manager_impl.cc
@@ -118,38 +118,39 @@
   // Initializing the DB and marking pages as closed are slow operations and we
   // shouldn't wait for them to finish, before returning from initialization:
   // Start these operations and finalize the initialization completer when done.
-  coroutine_manager_.StartCoroutine(
-      [this](coroutine::CoroutineHandler* handler) {
-        ExpiringToken token = NewExpiringToken();
-        if (!files::CreateDirectoryAt(db_path_.root_fd(), db_path_.path())) {
-          initialization_completer_.Complete(Status::IO_ERROR);
-          return;
-        }
-        storage::Status storage_status;
-        std::unique_ptr<storage::Db> db_instance;
-        if (coroutine::SyncCall(
-                handler,
-                [this](fit::function<void(storage::Status,
-                                          std::unique_ptr<storage::Db>)>
-                           callback) {
-                  db_factory_->GetOrCreateDb(std::move(db_path_),
-                                             std::move(callback));
-                },
-                &storage_status,
-                &db_instance) == coroutine::ContinuationStatus::INTERRUPTED) {
-          initialization_completer_.Complete(Status::INTERNAL_ERROR);
-          return;
-        }
-        if (storage_status != storage::Status::OK) {
-          initialization_completer_.Complete(
-              PageUtils::ConvertStatus(storage_status));
-          return;
-        }
-        db_ = std::make_unique<PageUsageDb>(environment_->clock(),
-                                            std::move(db_instance));
-        Status status = db_->MarkAllPagesClosed(handler);
-        initialization_completer_.Complete(status);
-      });
+  coroutine_manager_.StartCoroutine([this](
+                                        coroutine::CoroutineHandler* handler) {
+    ExpiringToken token = NewExpiringToken();
+    if (!files::CreateDirectoryAt(db_path_.root_fd(), db_path_.path())) {
+      initialization_completer_.Complete(Status::IO_ERROR);
+      return;
+    }
+    storage::Status storage_status;
+    std::unique_ptr<storage::Db> db_instance;
+    if (coroutine::SyncCall(
+            handler,
+            [this](fit::function<void(storage::Status,
+                                      std::unique_ptr<storage::Db>)>
+                       callback) {
+              db_factory_->GetOrCreateDb(
+                  std::move(db_path_), storage::DbFactory::OnDbNotFound::CREATE,
+                  std::move(callback));
+            },
+            &storage_status,
+            &db_instance) == coroutine::ContinuationStatus::INTERRUPTED) {
+      initialization_completer_.Complete(Status::INTERNAL_ERROR);
+      return;
+    }
+    if (storage_status != storage::Status::OK) {
+      initialization_completer_.Complete(
+          PageUtils::ConvertStatus(storage_status));
+      return;
+    }
+    db_ = std::make_unique<PageUsageDb>(environment_->clock(),
+                                        std::move(db_instance));
+    Status status = db_->MarkAllPagesClosed(handler);
+    initialization_completer_.Complete(status);
+  });
   return Status::OK;
 }
 
diff --git a/bin/ledger/storage/fake/fake_db_factory.cc b/bin/ledger/storage/fake/fake_db_factory.cc
index e84bb32..e23c58d 100644
--- a/bin/ledger/storage/fake/fake_db_factory.cc
+++ b/bin/ledger/storage/fake/fake_db_factory.cc
@@ -12,14 +12,21 @@
 namespace fake {
 
 void FakeDbFactory::GetOrCreateDb(
-    ledger::DetachedPath db_path,
+    ledger::DetachedPath db_path, DbFactory::OnDbNotFound on_db_not_found,
     fit::function<void(Status, std::unique_ptr<Db>)> callback) {
-  // Create the path to fake the creation of the Db at the expected destination.
-  if (!files::CreateDirectoryAt(db_path.root_fd(), db_path.path())) {
-    FXL_LOG(ERROR) << "Failed to create the storage directory in "
-                   << db_path.path();
-    callback(Status::INTERNAL_IO_ERROR, nullptr);
-    return;
+  if (!files::IsDirectoryAt(db_path.root_fd(), db_path.path())) {
+    if (on_db_not_found == DbFactory::OnDbNotFound::RETURN) {
+      callback(Status::NOT_FOUND, nullptr);
+      return;
+    }
+    // Create the path to fake the creation of the Db at the expected
+    // destination.
+    if (!files::CreateDirectoryAt(db_path.root_fd(), db_path.path())) {
+      FXL_LOG(ERROR) << "Failed to create the storage directory in "
+                     << db_path.path();
+      callback(Status::INTERNAL_IO_ERROR, nullptr);
+      return;
+    }
   }
   callback(Status::OK, std::make_unique<FakeDb>(dispatcher_));
 }
diff --git a/bin/ledger/storage/fake/fake_db_factory.h b/bin/ledger/storage/fake/fake_db_factory.h
index f463079..7e56a89 100644
--- a/bin/ledger/storage/fake/fake_db_factory.h
+++ b/bin/ledger/storage/fake/fake_db_factory.h
@@ -17,7 +17,7 @@
       : dispatcher_(dispatcher) {}
 
   void GetOrCreateDb(
-      ledger::DetachedPath db_path,
+      ledger::DetachedPath db_path, DbFactory::OnDbNotFound on_db_not_found,
       fit::function<void(Status, std::unique_ptr<Db>)> callback) override;
 
  private:
diff --git a/bin/ledger/storage/impl/ledger_storage_impl.cc b/bin/ledger/storage/impl/ledger_storage_impl.cc
index ee31833..dca3246 100644
--- a/bin/ledger/storage/impl/ledger_storage_impl.cc
+++ b/bin/ledger/storage/impl/ledger_storage_impl.cc
@@ -75,7 +75,7 @@
   auto timed_callback = TRACE_CALLBACK(std::move(callback), "ledger",
                                        "ledger_storage_create_page_storage");
   GetOrCreateDb(GetPathFor(page_id), std::move(page_id),
-                std::move(timed_callback));
+                DbFactory::OnDbNotFound::CREATE, std::move(timed_callback));
 }
 
 void LedgerStorageImpl::GetPageStorage(
@@ -83,13 +83,8 @@
     fit::function<void(Status, std::unique_ptr<PageStorage>)> callback) {
   auto timed_callback = TRACE_CALLBACK(std::move(callback), "ledger",
                                        "ledger_storage_get_page_storage");
-  ledger::DetachedPath path = GetPathFor(page_id);
-  if (!files::IsDirectoryAt(path.root_fd(), path.path())) {
-    timed_callback(Status::NOT_FOUND, nullptr);
-    return;
-  }
-
-  GetOrCreateDb(std::move(path), std::move(page_id), std::move(timed_callback));
+  GetOrCreateDb(GetPathFor(page_id), std::move(page_id),
+                DbFactory::OnDbNotFound::RETURN, std::move(timed_callback));
 }
 
 void LedgerStorageImpl::DeletePageStorage(
@@ -167,9 +162,10 @@
 
 void LedgerStorageImpl::GetOrCreateDb(
     ledger::DetachedPath path, PageId page_id,
+    DbFactory::OnDbNotFound on_db_not_found,
     fit::function<void(Status, std::unique_ptr<PageStorage>)> callback) {
   db_factory_->GetOrCreateDb(
-      std::move(path),
+      std::move(path), on_db_not_found,
       callback::MakeScoped(
           weak_factory_.GetWeakPtr(),
           [this, page_id = std::move(page_id), callback = std::move(callback)](
diff --git a/bin/ledger/storage/impl/ledger_storage_impl.h b/bin/ledger/storage/impl/ledger_storage_impl.h
index 03cf890..b1f77aa 100644
--- a/bin/ledger/storage/impl/ledger_storage_impl.h
+++ b/bin/ledger/storage/impl/ledger_storage_impl.h
@@ -58,6 +58,7 @@
   // given |page_id|.
   void GetOrCreateDb(
       ledger::DetachedPath path, PageId page_id,
+      DbFactory::OnDbNotFound on_db_not_found,
       fit::function<void(Status, std::unique_ptr<PageStorage>)> callback);
 
   ledger::DetachedPath GetPathFor(PageIdView page_id);
diff --git a/bin/ledger/storage/impl/leveldb_factory.cc b/bin/ledger/storage/impl/leveldb_factory.cc
index 16920d5..4434077 100644
--- a/bin/ledger/storage/impl/leveldb_factory.cc
+++ b/bin/ledger/storage/impl/leveldb_factory.cc
@@ -103,7 +103,7 @@
 }
 
 void LevelDbFactory::GetOrCreateDb(
-    ledger::DetachedPath db_path,
+    ledger::DetachedPath db_path, DbFactory::OnDbNotFound on_db_not_found,
     fit::function<void(Status, std::unique_ptr<Db>)> callback) {
   if (files::IsDirectoryAt(db_path.root_fd(), db_path.path())) {
     // If the path exists, there is a LevelDb instance already there. Open and
@@ -112,6 +112,10 @@
                         std::move(callback));
     return;
   }
+  if (on_db_not_found == DbFactory::OnDbNotFound::RETURN) {
+    callback(Status::NOT_FOUND, nullptr);
+    return;
+  }
   // If creating the pre-cached db failed at some point it will likely fail
   // again. Don't retry caching anymore.
   if (cached_db_status_ == Status::OK) {
diff --git a/bin/ledger/storage/impl/leveldb_factory.h b/bin/ledger/storage/impl/leveldb_factory.h
index f6d4d85..43aae40 100644
--- a/bin/ledger/storage/impl/leveldb_factory.h
+++ b/bin/ledger/storage/impl/leveldb_factory.h
@@ -36,7 +36,7 @@
 
   // DbFactory:
   void GetOrCreateDb(
-      ledger::DetachedPath db_path,
+      ledger::DetachedPath db_path, DbFactory::OnDbNotFound on_db_not_found,
       fit::function<void(Status, std::unique_ptr<Db>)> callback) override;
 
  private:
diff --git a/bin/ledger/storage/impl/leveldb_factory_unittest.cc b/bin/ledger/storage/impl/leveldb_factory_unittest.cc
index ddb62c4..e93ec77 100644
--- a/bin/ledger/storage/impl/leveldb_factory_unittest.cc
+++ b/bin/ledger/storage/impl/leveldb_factory_unittest.cc
@@ -60,7 +60,7 @@
   std::unique_ptr<Db> db;
   bool called;
   db_factory_.GetOrCreateDb(
-      db_path_.SubPath("db"),
+      db_path_.SubPath("db"), DbFactory::OnDbNotFound::CREATE,
       callback::Capture(callback::SetWhenCalled(&called), &status, &db));
   RunLoopUntilIdle();
   ASSERT_TRUE(called);
@@ -77,7 +77,7 @@
   // Close the previous instance and open it again.
   db.reset();
   db_factory_.GetOrCreateDb(
-      db_path_.SubPath("db"),
+      db_path_.SubPath("db"), DbFactory::OnDbNotFound::RETURN,
       callback::Capture(callback::SetWhenCalled(&called), &status, &db));
   RunLoopUntilIdle();
   ASSERT_TRUE(called);
@@ -91,6 +91,20 @@
   });
 }
 
+TEST_F(LevelDbFactoryTest, GetDbOnNotFound) {
+  // Try to get a non existing Db and expect a |NOT_FOUND| status.
+  Status status;
+  std::unique_ptr<Db> db;
+  bool called;
+  db_factory_.GetOrCreateDb(
+      db_path_.SubPath("db"), DbFactory::OnDbNotFound::RETURN,
+      callback::Capture(callback::SetWhenCalled(&called), &status, &db));
+  RunLoopUntilIdle();
+  ASSERT_TRUE(called);
+  EXPECT_EQ(Status::NOT_FOUND, status);
+  EXPECT_EQ(nullptr, db);
+}
+
 TEST_F(LevelDbFactoryTest, CreateMultipleDbs) {
   int N = 5;
   Status status;
@@ -104,7 +118,7 @@
     EXPECT_FALSE(files::IsDirectoryAt(path.root_fd(), path.path()));
 
     db_factory_.GetOrCreateDb(
-        path,
+        path, DbFactory::OnDbNotFound::CREATE,
         callback::Capture(callback::SetWhenCalled(&called), &status, &db));
     RunLoopUntilIdle();
     EXPECT_TRUE(called);
@@ -131,6 +145,7 @@
 
     db_factory_.GetOrCreateDb(
         db_path_.SubPath(fxl::NumberToString(i)),
+        DbFactory::OnDbNotFound::CREATE,
         callback::Capture(callback::SetWhenCalled(&called[i]), &statuses[i],
                           &dbs[i]));
   }
@@ -156,13 +171,15 @@
   std::unique_ptr<Db> db2;
 
   db_factory_.GetOrCreateDb(
-      path1, [&](Status status1, std::unique_ptr<Db> db1) {
+      path1, DbFactory::OnDbNotFound::CREATE,
+      [&](Status status1, std::unique_ptr<Db> db1) {
         called1 = true;
         EXPECT_EQ(Status::OK, status1);
         EXPECT_NE(nullptr, db1);
         db_factory_.GetOrCreateDb(
-            path2, callback::Capture(callback::SetWhenCalled(&called2),
-                                     &status2, &db2));
+            path2, DbFactory::OnDbNotFound::CREATE,
+            callback::Capture(callback::SetWhenCalled(&called2), &status2,
+                              &db2));
       });
   RunLoopUntilIdle();
   EXPECT_TRUE(called1);
diff --git a/bin/ledger/storage/public/db_factory.h b/bin/ledger/storage/public/db_factory.h
index 6c027b3..423b4de 100644
--- a/bin/ledger/storage/public/db_factory.h
+++ b/bin/ledger/storage/public/db_factory.h
@@ -15,13 +15,23 @@
 // A factory for Db instances.
 class DbFactory {
  public:
+  // Defines the action to be taken if |GetOrCreate| is called for a path that
+  // doesn't already contain a Db.
+  enum class OnDbNotFound {
+    // |GetOrCreateDb| should return with a |NOT_FOUND| status.
+    RETURN,
+    // |GetOrCreateDb| should create a new Db instance.
+    CREATE
+  };
+
   DbFactory() {}
   virtual ~DbFactory() {}
 
-  // Opens and returns an initialized instance of Db in the given |db_path|. If
-  // the Db doesn't already exist, it creates it.
+  // Opens and returns an initialized instance of Db in the given |db_path|.
+  // Depending on the value of |on_db_not_found|, if the Db doesn't already
+  // exist, it either returns with NOT_FOUND status, or creates a new one.
   virtual void GetOrCreateDb(
-      ledger::DetachedPath db_path,
+      ledger::DetachedPath db_path, OnDbNotFound on_db_not_found,
       fit::function<void(Status, std::unique_ptr<Db>)> callback) = 0;
 };