// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/disk_cache/simple/simple_backend_impl.h"

#include <algorithm>
#include <cstdlib>
#include <functional>
#include <limits>

#include "base/functional/callback_helpers.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "build/build_config.h"

#if BUILDFLAG(IS_POSIX)
#include <sys/resource.h>
#endif

#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/lazy_instance.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/system/sys_info.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "net/base/net_errors.h"
#include "net/base/prioritized_task_runner.h"
#include "net/disk_cache/backend_cleanup_tracker.h"
#include "net/disk_cache/cache_util.h"
#include "net/disk_cache/simple/simple_entry_format.h"
#include "net/disk_cache/simple/simple_entry_impl.h"
#include "net/disk_cache/simple/simple_file_tracker.h"
#include "net/disk_cache/simple/simple_histogram_macros.h"
#include "net/disk_cache/simple/simple_index.h"
#include "net/disk_cache/simple/simple_index_file.h"
#include "net/disk_cache/simple/simple_synchronous_entry.h"
#include "net/disk_cache/simple/simple_util.h"
#include "net/disk_cache/simple/simple_version_upgrade.h"

using base::FilePath;
using base::Time;

namespace disk_cache {

namespace {

// Maximum fraction of the cache that one entry can consume.
const int kMaxFileRatio = 8;

// Native code entries can be large. Rather than increasing the overall cache
// size, allow an individual entry to occupy up to half of the cache.
const int kMaxNativeCodeFileRatio = 2;

// Overrides the above.
const int64_t kMinFileSizeLimit = 5 * 1024 * 1024;

// Global context of all the files we have open --- this permits some to be
// closed on demand if too many FDs are being used, to avoid running out.
base::LazyInstance<SimpleFileTracker>::Leaky g_simple_file_tracker =
    LAZY_INSTANCE_INITIALIZER;

// Detects if the files in the cache directory match the current disk cache
// backend type and version. If the directory contains no cache, occupies it
// with the fresh structure.
SimpleCacheConsistencyResult FileStructureConsistent(
    BackendFileOperations* file_operations,
    const base::FilePath& path) {
  if (!file_operations->PathExists(path) &&
      !file_operations->CreateDirectory(path)) {
    LOG(ERROR) << "Failed to create directory: " << path.LossyDisplayName();
    return SimpleCacheConsistencyResult::kCreateDirectoryFailed;
  }
  return disk_cache::UpgradeSimpleCacheOnDisk(file_operations, path);
}

// A context used by a BarrierCompletionCallback to track state.
struct BarrierContext {
  explicit BarrierContext(net::CompletionOnceCallback final_callback,
                          int expected)
      : final_callback_(std::move(final_callback)), expected(expected) {}

  net::CompletionOnceCallback final_callback_;
  const int expected;
  int count = 0;
  bool had_error = false;
};

void BarrierCompletionCallbackImpl(
    BarrierContext* context,
    int result) {
  DCHECK_GT(context->expected, context->count);
  if (context->had_error)
    return;
  if (result != net::OK) {
    context->had_error = true;
    std::move(context->final_callback_).Run(result);
    return;
  }
  ++context->count;
  if (context->count == context->expected)
    std::move(context->final_callback_).Run(net::OK);
}

// A barrier completion callback is a repeatable callback that waits for
// |count| successful results before invoking |final_callback|. In the case of
// an error, the first error is passed to |final_callback| and all others
// are ignored.
base::RepeatingCallback<void(int)> MakeBarrierCompletionCallback(
    int count,
    net::CompletionOnceCallback final_callback) {
  BarrierContext* context =
      new BarrierContext(std::move(final_callback), count);
  return base::BindRepeating(&BarrierCompletionCallbackImpl,
                             base::Owned(context));
}

// A short bindable thunk that ensures a completion callback is always called
// after running an operation asynchronously. Checks for backend liveness first.
void RunOperationAndCallback(
    base::WeakPtr<SimpleBackendImpl> backend,
    base::OnceCallback<net::Error(net::CompletionOnceCallback)> operation,
    net::CompletionOnceCallback operation_callback) {
  if (!backend)
    return;

  auto split_callback = base::SplitOnceCallback(std::move(operation_callback));
  const int operation_result =
      std::move(operation).Run(std::move(split_callback.first));
  if (operation_result != net::ERR_IO_PENDING && split_callback.second)
    std::move(split_callback.second).Run(operation_result);
}

// Same but for things that work with EntryResult.
void RunEntryResultOperationAndCallback(
    base::WeakPtr<SimpleBackendImpl> backend,
    base::OnceCallback<EntryResult(EntryResultCallback)> operation,
    EntryResultCallback operation_callback) {
  if (!backend)
    return;

  auto split_callback = base::SplitOnceCallback(std::move(operation_callback));
  EntryResult operation_result =
      std::move(operation).Run(std::move(split_callback.first));
  if (operation_result.net_error() != net::ERR_IO_PENDING &&
      split_callback.second) {
    std::move(split_callback.second).Run(std::move(operation_result));
  }
}

void RecordIndexLoad(net::CacheType cache_type,
                     base::TimeTicks constructed_since,
                     int result) {
  const base::TimeDelta creation_to_index = base::TimeTicks::Now() -
                                            constructed_since;
  if (result == net::OK) {
    SIMPLE_CACHE_UMA(TIMES, "CreationToIndex", cache_type, creation_to_index);
  } else {
    SIMPLE_CACHE_UMA(TIMES,
                     "CreationToIndexFail", cache_type, creation_to_index);
  }
}

SimpleEntryImpl::OperationsMode CacheTypeToOperationsMode(net::CacheType type) {
  return (type == net::DISK_CACHE || type == net::GENERATED_BYTE_CODE_CACHE ||
          type == net::GENERATED_NATIVE_CODE_CACHE ||
          type == net::GENERATED_WEBUI_BYTE_CODE_CACHE)
             ? SimpleEntryImpl::OPTIMISTIC_OPERATIONS
             : SimpleEntryImpl::NON_OPTIMISTIC_OPERATIONS;
}

}  // namespace

class SimpleBackendImpl::ActiveEntryProxy
    : public SimpleEntryImpl::ActiveEntryProxy {
 public:
  ~ActiveEntryProxy() override {
    if (backend_) {
      DCHECK_EQ(1U, backend_->active_entries_.count(entry_hash_));
      backend_->active_entries_.erase(entry_hash_);
    }
  }

  static std::unique_ptr<SimpleEntryImpl::ActiveEntryProxy> Create(
      int64_t entry_hash,
      SimpleBackendImpl* backend) {
    return base::WrapUnique(new ActiveEntryProxy(entry_hash, backend));
  }

 private:
  ActiveEntryProxy(uint64_t entry_hash, SimpleBackendImpl* backend)
      : entry_hash_(entry_hash), backend_(backend->AsWeakPtr()) {}

  uint64_t entry_hash_;
  base::WeakPtr<SimpleBackendImpl> backend_;
};

SimpleBackendImpl::SimpleBackendImpl(
    scoped_refptr<BackendFileOperationsFactory> file_operations_factory,
    const FilePath& path,
    scoped_refptr<BackendCleanupTracker> cleanup_tracker,
    SimpleFileTracker* file_tracker,
    int64_t max_bytes,
    net::CacheType cache_type,
    net::NetLog* net_log)
    : Backend(cache_type),
      file_operations_factory_(
          file_operations_factory
              ? std::move(file_operations_factory)
              : base::MakeRefCounted<TrivialFileOperationsFactory>()),
      cleanup_tracker_(std::move(cleanup_tracker)),
      file_tracker_(file_tracker ? file_tracker
                                 : g_simple_file_tracker.Pointer()),
      path_(path),
      orig_max_size_(max_bytes),
      entry_operations_mode_(CacheTypeToOperationsMode(cache_type)),
      post_doom_waiting_(
          base::MakeRefCounted<SimplePostDoomWaiterTable>(cache_type)),
      net_log_(net_log) {
  // Treat negative passed-in sizes same as SetMaxSize would here and in other
  // backends, as default (if first call).
  if (orig_max_size_ < 0)
    orig_max_size_ = 0;
}

SimpleBackendImpl::~SimpleBackendImpl() {
  // Write the index out if there is a pending write from a
  // previous operation.
  if (index_->HasPendingWrite())
    index_->WriteToDisk(SimpleIndex::INDEX_WRITE_REASON_SHUTDOWN);
}

void SimpleBackendImpl::SetTaskRunnerForTesting(
    scoped_refptr<base::SequencedTaskRunner> task_runner) {
  prioritized_task_runner_ =
      base::MakeRefCounted<net::PrioritizedTaskRunner>(kWorkerPoolTaskTraits);
  prioritized_task_runner_->SetTaskRunnerForTesting(  // IN-TEST
      std::move(task_runner));
}

void SimpleBackendImpl::Init(CompletionOnceCallback completion_callback) {
  auto index_task_runner = base::ThreadPool::CreateSequencedTaskRunner(
      {base::MayBlock(), base::WithBaseSyncPrimitives(),
       base::TaskPriority::USER_BLOCKING,
       base::TaskShutdownBehavior::BLOCK_SHUTDOWN});

  prioritized_task_runner_ =
      base::MakeRefCounted<net::PrioritizedTaskRunner>(kWorkerPoolTaskTraits);

  index_ = std::make_unique<SimpleIndex>(
      base::SequencedTaskRunner::GetCurrentDefault(), cleanup_tracker_.get(),
      this, GetCacheType(),
      std::make_unique<SimpleIndexFile>(
          index_task_runner, file_operations_factory_, GetCacheType(), path_));
  index_->ExecuteWhenReady(
      base::BindOnce(&RecordIndexLoad, GetCacheType(), base::TimeTicks::Now()));

  auto file_operations = file_operations_factory_->Create(index_task_runner);
  index_task_runner->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(&SimpleBackendImpl::InitCacheStructureOnDisk,
                     std::move(file_operations), path_, orig_max_size_,
                     GetCacheType()),
      base::BindOnce(&SimpleBackendImpl::InitializeIndex, AsWeakPtr(),
                     std::move(completion_callback)));
}

bool SimpleBackendImpl::SetMaxSize(int64_t max_bytes) {
  if (max_bytes < 0)
    return false;
  orig_max_size_ = max_bytes;
  index_->SetMaxSize(max_bytes);
  return true;
}

int64_t SimpleBackendImpl::MaxFileSize() const {
  uint64_t file_size_ratio = GetCacheType() == net::GENERATED_NATIVE_CODE_CACHE
                                 ? kMaxNativeCodeFileRatio
                                 : kMaxFileRatio;
  return std::max(
      base::saturated_cast<int64_t>(index_->max_size() / file_size_ratio),
      kMinFileSizeLimit);
}

scoped_refptr<SimplePostDoomWaiterTable> SimpleBackendImpl::OnDoomStart(
    uint64_t entry_hash) {
  post_doom_waiting_->OnDoomStart(entry_hash);
  return post_doom_waiting_;
}

void SimpleBackendImpl::DoomEntries(std::vector<uint64_t>* entry_hashes,
                                    net::CompletionOnceCallback callback) {
  auto mass_doom_entry_hashes = std::make_unique<std::vector<uint64_t>>();
  mass_doom_entry_hashes->swap(*entry_hashes);

  std::vector<uint64_t> to_doom_individually_hashes;

  // For each of the entry hashes, there are two cases:
  // 1. There are corresponding entries in active set, pending doom, or both
  //    sets, and so the hash should be doomed individually to avoid flakes.
  // 2. The hash is not in active use at all, so we can call
  //    SimpleSynchronousEntry::DeleteEntrySetFiles and delete the files en
  //    masse.
  for (int i = mass_doom_entry_hashes->size() - 1; i >= 0; --i) {
    const uint64_t entry_hash = (*mass_doom_entry_hashes)[i];
    if (!active_entries_.count(entry_hash) &&
        !post_doom_waiting_->Has(entry_hash)) {
      continue;
    }

    to_doom_individually_hashes.push_back(entry_hash);

    (*mass_doom_entry_hashes)[i] = mass_doom_entry_hashes->back();
    mass_doom_entry_hashes->resize(mass_doom_entry_hashes->size() - 1);
  }

  base::RepeatingCallback<void(int)> barrier_callback =
      MakeBarrierCompletionCallback(to_doom_individually_hashes.size() + 1,
                                    std::move(callback));
  for (std::vector<uint64_t>::const_iterator
           it = to_doom_individually_hashes.begin(),
           end = to_doom_individually_hashes.end();
       it != end; ++it) {
    const int doom_result = DoomEntryFromHash(*it, barrier_callback);
    DCHECK_EQ(net::ERR_IO_PENDING, doom_result);
    index_->Remove(*it);
  }

  for (std::vector<uint64_t>::const_iterator
           it = mass_doom_entry_hashes->begin(),
           end = mass_doom_entry_hashes->end();
       it != end; ++it) {
    index_->Remove(*it);
    OnDoomStart(*it);
  }

  // Taking this pointer here avoids undefined behaviour from calling
  // std::move() before mass_doom_entry_hashes.get().
  std::vector<uint64_t>* mass_doom_entry_hashes_ptr =
      mass_doom_entry_hashes.get();

  // We don't use priorities (i.e., `prioritized_task_runner_`) here because
  // we don't actually have them here (since this is for eviction based on
  // index).
  auto task_runner =
      base::ThreadPool::CreateSequencedTaskRunner(kWorkerPoolTaskTraits);
  task_runner->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(&SimpleSynchronousEntry::DeleteEntrySetFiles,
                     mass_doom_entry_hashes_ptr, path_,
                     file_operations_factory_->CreateUnbound()),
      base::BindOnce(&SimpleBackendImpl::DoomEntriesComplete, AsWeakPtr(),
                     std::move(mass_doom_entry_hashes), barrier_callback));
}

int32_t SimpleBackendImpl::GetEntryCount() const {
  // TODO(pasko): Use directory file count when index is not ready.
  return index_->GetEntryCount();
}

EntryResult SimpleBackendImpl::OpenEntry(const std::string& key,
                                         net::RequestPriority request_priority,
                                         EntryResultCallback callback) {
  const uint64_t entry_hash = simple_util::GetEntryHashKey(key);

  std::vector<SimplePostDoomWaiter>* post_doom = nullptr;
  scoped_refptr<SimpleEntryImpl> simple_entry = CreateOrFindActiveOrDoomedEntry(
      entry_hash, key, request_priority, &post_doom);
  if (!simple_entry) {
    if (post_doom->empty() &&
        entry_operations_mode_ == SimpleEntryImpl::OPTIMISTIC_OPERATIONS) {
      // The entry is doomed, and no other backend operations are queued for the
      // entry, thus the open must fail and it's safe to return synchronously.
      net::NetLogWithSource log_for_entry(net::NetLogWithSource::Make(
          net_log_, net::NetLogSourceType::DISK_CACHE_ENTRY));
      log_for_entry.AddEvent(
          net::NetLogEventType::SIMPLE_CACHE_ENTRY_OPEN_CALL);
      log_for_entry.AddEventWithNetErrorCode(
          net::NetLogEventType::SIMPLE_CACHE_ENTRY_OPEN_END, net::ERR_FAILED);
      return EntryResult::MakeError(net::ERR_FAILED);
    }

    base::OnceCallback<EntryResult(EntryResultCallback)> operation =
        base::BindOnce(&SimpleBackendImpl::OpenEntry, base::Unretained(this),
                       key, request_priority);
    post_doom->emplace_back(base::BindOnce(&RunEntryResultOperationAndCallback,
                                           AsWeakPtr(), std::move(operation),
                                           std::move(callback)));
    return EntryResult::MakeError(net::ERR_IO_PENDING);
  }
  return simple_entry->OpenEntry(std::move(callback));
}

EntryResult SimpleBackendImpl::CreateEntry(
    const std::string& key,
    net::RequestPriority request_priority,
    EntryResultCallback callback) {
  DCHECK_LT(0u, key.size());
  const uint64_t entry_hash = simple_util::GetEntryHashKey(key);

  std::vector<SimplePostDoomWaiter>* post_doom = nullptr;
  scoped_refptr<SimpleEntryImpl> simple_entry = CreateOrFindActiveOrDoomedEntry(
      entry_hash, key, request_priority, &post_doom);

  // If couldn't grab an entry object due to pending doom, see if circumstances
  // are right for an optimistic create.
  if (!simple_entry) {
    simple_entry = MaybeOptimisticCreateForPostDoom(
        entry_hash, key, request_priority, post_doom);
  }

  // If that doesn't work either, retry this once doom is done.
  if (!simple_entry) {
    base::OnceCallback<EntryResult(EntryResultCallback)> operation =
        base::BindOnce(&SimpleBackendImpl::CreateEntry, base::Unretained(this),
                       key, request_priority);
    post_doom->emplace_back(base::BindOnce(&RunEntryResultOperationAndCallback,
                                           AsWeakPtr(), std::move(operation),
                                           std::move(callback)));
    return EntryResult::MakeError(net::ERR_IO_PENDING);
  }

  return simple_entry->CreateEntry(std::move(callback));
}

EntryResult SimpleBackendImpl::OpenOrCreateEntry(
    const std::string& key,
    net::RequestPriority request_priority,
    EntryResultCallback callback) {
  DCHECK_LT(0u, key.size());
  const uint64_t entry_hash = simple_util::GetEntryHashKey(key);

  std::vector<SimplePostDoomWaiter>* post_doom = nullptr;
  scoped_refptr<SimpleEntryImpl> simple_entry = CreateOrFindActiveOrDoomedEntry(
      entry_hash, key, request_priority, &post_doom);

  // If couldn't grab an entry object due to pending doom, see if circumstances
  // are right for an optimistic create.
  if (!simple_entry) {
    simple_entry = MaybeOptimisticCreateForPostDoom(
        entry_hash, key, request_priority, post_doom);
    if (simple_entry) {
      return simple_entry->CreateEntry(std::move(callback));
    } else {
      // If that doesn't work either, retry this once doom is done.
      base::OnceCallback<EntryResult(EntryResultCallback)> operation =
          base::BindOnce(&SimpleBackendImpl::OpenOrCreateEntry,
                         base::Unretained(this), key, request_priority);
      post_doom->emplace_back(
          base::BindOnce(&RunEntryResultOperationAndCallback, AsWeakPtr(),
                         std::move(operation), std::move(callback)));
      return EntryResult::MakeError(net::ERR_IO_PENDING);
    }
  }

  return simple_entry->OpenOrCreateEntry(std::move(callback));
}

scoped_refptr<SimpleEntryImpl>
SimpleBackendImpl::MaybeOptimisticCreateForPostDoom(
    uint64_t entry_hash,
    const std::string& key,
    net::RequestPriority request_priority,
    std::vector<SimplePostDoomWaiter>* post_doom) {
  scoped_refptr<SimpleEntryImpl> simple_entry;
  // We would like to optimistically have create go ahead, for benefit of
  // HTTP cache use. This can only be sanely done if we are the only op
  // serialized after doom's completion.
  if (post_doom->empty() &&
      entry_operations_mode_ == SimpleEntryImpl::OPTIMISTIC_OPERATIONS) {
    simple_entry = base::MakeRefCounted<SimpleEntryImpl>(
        GetCacheType(), path_, cleanup_tracker_.get(), entry_hash,
        entry_operations_mode_, this, file_tracker_, file_operations_factory_,
        net_log_, GetNewEntryPriority(request_priority));
    simple_entry->SetKey(key);
    simple_entry->SetActiveEntryProxy(
        ActiveEntryProxy::Create(entry_hash, this));
    simple_entry->SetCreatePendingDoom();
    std::pair<EntryMap::iterator, bool> insert_result = active_entries_.insert(
        EntryMap::value_type(entry_hash, simple_entry.get()));
    post_doom->emplace_back(base::BindOnce(
        &SimpleEntryImpl::NotifyDoomBeforeCreateComplete, simple_entry));
    DCHECK(insert_result.second);
  }

  return simple_entry;
}

net::Error SimpleBackendImpl::DoomEntry(const std::string& key,
                                        net::RequestPriority priority,
                                        CompletionOnceCallback callback) {
  const uint64_t entry_hash = simple_util::GetEntryHashKey(key);

  std::vector<SimplePostDoomWaiter>* post_doom = nullptr;
  scoped_refptr<SimpleEntryImpl> simple_entry =
      CreateOrFindActiveOrDoomedEntry(entry_hash, key, priority, &post_doom);
  if (!simple_entry) {
    // At first glance, it appears exceedingly silly to queue up a doom
    // when we get here because the files corresponding to our key are being
    // deleted... but it's possible that one of the things in post_doom is a
    // create for our key, in which case we still have work to do.
    base::OnceCallback<net::Error(CompletionOnceCallback)> operation =
        base::BindOnce(&SimpleBackendImpl::DoomEntry, base::Unretained(this),
                       key, priority);
    post_doom->emplace_back(base::BindOnce(&RunOperationAndCallback,
                                           AsWeakPtr(), std::move(operation),
                                           std::move(callback)));
    return net::ERR_IO_PENDING;
  }

  return simple_entry->DoomEntry(std::move(callback));
}

net::Error SimpleBackendImpl::DoomAllEntries(CompletionOnceCallback callback) {
  return DoomEntriesBetween(Time(), Time(), std::move(callback));
}

net::Error SimpleBackendImpl::DoomEntriesBetween(
    const Time initial_time,
    const Time end_time,
    CompletionOnceCallback callback) {
  index_->ExecuteWhenReady(base::BindOnce(&SimpleBackendImpl::IndexReadyForDoom,
                                          AsWeakPtr(), initial_time, end_time,
                                          std::move(callback)));
  return net::ERR_IO_PENDING;
}

net::Error SimpleBackendImpl::DoomEntriesSince(
    const Time initial_time,
    CompletionOnceCallback callback) {
  return DoomEntriesBetween(initial_time, Time(), std::move(callback));
}

int64_t SimpleBackendImpl::CalculateSizeOfAllEntries(
    Int64CompletionOnceCallback callback) {
  index_->ExecuteWhenReady(
      base::BindOnce(&SimpleBackendImpl::IndexReadyForSizeCalculation,
                     AsWeakPtr(), std::move(callback)));
  return net::ERR_IO_PENDING;
}

int64_t SimpleBackendImpl::CalculateSizeOfEntriesBetween(
    base::Time initial_time,
    base::Time end_time,
    Int64CompletionOnceCallback callback) {
  index_->ExecuteWhenReady(
      base::BindOnce(&SimpleBackendImpl::IndexReadyForSizeBetweenCalculation,
                     AsWeakPtr(), initial_time, end_time, std::move(callback)));
  return net::ERR_IO_PENDING;
}

class SimpleBackendImpl::SimpleIterator final : public Iterator {
 public:
  explicit SimpleIterator(base::WeakPtr<SimpleBackendImpl> backend)
      : backend_(backend) {}

  // From Backend::Iterator:
  EntryResult OpenNextEntry(EntryResultCallback callback) override {
    if (!backend_)
      return EntryResult::MakeError(net::ERR_FAILED);
    CompletionOnceCallback open_next_entry_impl =
        base::BindOnce(&SimpleIterator::OpenNextEntryImpl,
                       weak_factory_.GetWeakPtr(), std::move(callback));
    backend_->index_->ExecuteWhenReady(std::move(open_next_entry_impl));
    return EntryResult::MakeError(net::ERR_IO_PENDING);
  }

  void OpenNextEntryImpl(EntryResultCallback callback,
                         int index_initialization_error_code) {
    if (!backend_) {
      std::move(callback).Run(EntryResult::MakeError(net::ERR_FAILED));
      return;
    }
    if (index_initialization_error_code != net::OK) {
      std::move(callback).Run(EntryResult::MakeError(
          static_cast<net::Error>(index_initialization_error_code)));
      return;
    }
    if (!hashes_to_enumerate_)
      hashes_to_enumerate_ = backend_->index()->GetAllHashes();

    while (!hashes_to_enumerate_->empty()) {
      uint64_t entry_hash = hashes_to_enumerate_->back();
      hashes_to_enumerate_->pop_back();
      if (backend_->index()->Has(entry_hash)) {
        auto split_callback = base::SplitOnceCallback(std::move(callback));
        callback = std::move(split_callback.first);
        EntryResultCallback continue_iteration = base::BindOnce(
            &SimpleIterator::CheckIterationReturnValue,
            weak_factory_.GetWeakPtr(), std::move(split_callback.second));
        EntryResult open_result = backend_->OpenEntryFromHash(
            entry_hash, std::move(continue_iteration));
        if (open_result.net_error() == net::ERR_IO_PENDING)
          return;
        if (open_result.net_error() != net::ERR_FAILED) {
          std::move(callback).Run(std::move(open_result));
          return;
        }
      }
    }
    std::move(callback).Run(EntryResult::MakeError(net::ERR_FAILED));
  }

  void CheckIterationReturnValue(EntryResultCallback callback,
                                 EntryResult result) {
    if (result.net_error() == net::ERR_FAILED) {
      OpenNextEntry(std::move(callback));
      return;
    }
    std::move(callback).Run(std::move(result));
  }

 private:
  base::WeakPtr<SimpleBackendImpl> backend_;
  std::unique_ptr<std::vector<uint64_t>> hashes_to_enumerate_;
  base::WeakPtrFactory<SimpleIterator> weak_factory_{this};
};

std::unique_ptr<Backend::Iterator> SimpleBackendImpl::CreateIterator() {
  return std::make_unique<SimpleIterator>(AsWeakPtr());
}

void SimpleBackendImpl::GetStats(base::StringPairs* stats) {
  std::pair<std::string, std::string> item;
  item.first = "Cache type";
  item.second = "Simple Cache";
  stats->push_back(item);
}

void SimpleBackendImpl::OnExternalCacheHit(const std::string& key) {
  index_->UseIfExists(simple_util::GetEntryHashKey(key));
}

uint8_t SimpleBackendImpl::GetEntryInMemoryData(const std::string& key) {
  const uint64_t entry_hash = simple_util::GetEntryHashKey(key);
  return index_->GetEntryInMemoryData(entry_hash);
}

void SimpleBackendImpl::SetEntryInMemoryData(const std::string& key,
                                             uint8_t data) {
  const uint64_t entry_hash = simple_util::GetEntryHashKey(key);
  index_->SetEntryInMemoryData(entry_hash, data);
}

void SimpleBackendImpl::InitializeIndex(CompletionOnceCallback callback,
                                        const DiskStatResult& result) {
  if (result.net_error == net::OK) {
    index_->SetMaxSize(result.max_size);
#if BUILDFLAG(IS_ANDROID)
    if (app_status_listener_)
      index_->set_app_status_listener(app_status_listener_);
#endif
    index_->Initialize(result.cache_dir_mtime);
  }
  std::move(callback).Run(result.net_error);
}

void SimpleBackendImpl::IndexReadyForDoom(Time initial_time,
                                          Time end_time,
                                          CompletionOnceCallback callback,
                                          int result) {
  if (result != net::OK) {
    std::move(callback).Run(result);
    return;
  }
  std::unique_ptr<std::vector<uint64_t>> removed_key_hashes(
      index_->GetEntriesBetween(initial_time, end_time).release());
  DoomEntries(removed_key_hashes.get(), std::move(callback));
}

void SimpleBackendImpl::IndexReadyForSizeCalculation(
    Int64CompletionOnceCallback callback,
    int result) {
  int64_t rv = result == net::OK ? index_->GetCacheSize() : result;
  std::move(callback).Run(rv);
}

void SimpleBackendImpl::IndexReadyForSizeBetweenCalculation(
    base::Time initial_time,
    base::Time end_time,
    Int64CompletionOnceCallback callback,
    int result) {
  int64_t rv = result == net::OK
                   ? index_->GetCacheSizeBetween(initial_time, end_time)
                   : result;
  std::move(callback).Run(rv);
}

// static
SimpleBackendImpl::DiskStatResult SimpleBackendImpl::InitCacheStructureOnDisk(
    std::unique_ptr<BackendFileOperations> file_operations,
    const base::FilePath& path,
    uint64_t suggested_max_size,
    net::CacheType cache_type) {
  DiskStatResult result;
  result.max_size = suggested_max_size;
  result.net_error = net::OK;
  SimpleCacheConsistencyResult consistency =
      FileStructureConsistent(file_operations.get(), path);
  SIMPLE_CACHE_UMA(ENUMERATION, "ConsistencyResult", cache_type, consistency);

  // If the cache structure is inconsistent make a single attempt at
  // recovering it.  Previously there were bugs that could cause a partially
  // written fake index file to be left in an otherwise empty cache.  In
  // that case we can delete the index files and start over.  Also, some
  // consistency failures may leave an empty directory directly and we can
  // retry those cases as well.
  if (consistency != SimpleCacheConsistencyResult::kOK) {
    bool deleted_files = disk_cache::DeleteIndexFilesIfCacheIsEmpty(path);
    SIMPLE_CACHE_UMA(BOOLEAN, "DidDeleteIndexFilesAfterFailedConsistency",
                     cache_type, deleted_files);
    if (base::IsDirectoryEmpty(path)) {
      SimpleCacheConsistencyResult orig_consistency = consistency;
      consistency = FileStructureConsistent(file_operations.get(), path);
      SIMPLE_CACHE_UMA(ENUMERATION, "RetryConsistencyResult", cache_type,
                       consistency);
      if (consistency == SimpleCacheConsistencyResult::kOK) {
        SIMPLE_CACHE_UMA(ENUMERATION,
                         "OriginalConsistencyResultBeforeSuccessfulRetry",
                         cache_type, orig_consistency);
      }
    }
    if (deleted_files) {
      SIMPLE_CACHE_UMA(ENUMERATION, "ConsistencyResultAfterIndexFilesDeleted",
                       cache_type, consistency);
    }
  }

  if (consistency != SimpleCacheConsistencyResult::kOK) {
    LOG(ERROR) << "Simple Cache Backend: wrong file structure on disk: "
               << static_cast<int>(consistency)
               << " path: " << path.LossyDisplayName();
    result.net_error = net::ERR_FAILED;
  } else {
    absl::optional<base::File::Info> file_info =
        file_operations->GetFileInfo(path);
    if (!file_info.has_value()) {
      // Something deleted the directory between when we set it up and the
      // mstat; this is not uncommon on some test fixtures which erase their
      // tempdir while some worker threads may still be running.
      LOG(ERROR) << "Simple Cache Backend: cache directory inaccessible right "
                    "after creation; path: "
                 << path.LossyDisplayName();
      result.net_error = net::ERR_FAILED;
    } else {
      result.cache_dir_mtime = file_info->last_modified;
      if (!result.max_size) {
        int64_t available = base::SysInfo::AmountOfFreeDiskSpace(path);
        result.max_size = disk_cache::PreferredCacheSize(available, cache_type);
        DCHECK(result.max_size);
      }
    }
  }
  return result;
}

scoped_refptr<SimpleEntryImpl>
SimpleBackendImpl::CreateOrFindActiveOrDoomedEntry(
    const uint64_t entry_hash,
    const std::string& key,
    net::RequestPriority request_priority,
    std::vector<SimplePostDoomWaiter>** post_doom) {
  DCHECK_EQ(entry_hash, simple_util::GetEntryHashKey(key));

  // If there is a doom pending, we would want to serialize after it.
  *post_doom = post_doom_waiting_->Find(entry_hash);
  if (*post_doom)
    return nullptr;

  std::pair<EntryMap::iterator, bool> insert_result =
      active_entries_.insert(EntryMap::value_type(entry_hash, nullptr));
  EntryMap::iterator& it = insert_result.first;
  const bool did_insert = insert_result.second;
  if (did_insert) {
    SimpleEntryImpl* entry = it->second = new SimpleEntryImpl(
        GetCacheType(), path_, cleanup_tracker_.get(), entry_hash,
        entry_operations_mode_, this, file_tracker_, file_operations_factory_,
        net_log_, GetNewEntryPriority(request_priority));
    entry->SetKey(key);
    entry->SetActiveEntryProxy(ActiveEntryProxy::Create(entry_hash, this));
  }
  // TODO(jkarlin): In case of recycling a half-closed entry, we might want to
  // update its priority.
  DCHECK(it->second);
  // It's possible, but unlikely, that we have an entry hash collision with a
  // currently active entry.
  if (key != it->second->key()) {
    it->second->Doom();
    DCHECK_EQ(0U, active_entries_.count(entry_hash));
    DCHECK(post_doom_waiting_->Has(entry_hash));
    // Re-run ourselves to handle the now-pending doom.
    return CreateOrFindActiveOrDoomedEntry(entry_hash, key, request_priority,
                                           post_doom);
  }
  return base::WrapRefCounted(it->second);
}

EntryResult SimpleBackendImpl::OpenEntryFromHash(uint64_t entry_hash,
                                                 EntryResultCallback callback) {
  std::vector<SimplePostDoomWaiter>* post_doom =
      post_doom_waiting_->Find(entry_hash);
  if (post_doom) {
    base::OnceCallback<EntryResult(EntryResultCallback)> operation =
        base::BindOnce(&SimpleBackendImpl::OpenEntryFromHash,
                       base::Unretained(this), entry_hash);
    // TODO(https://crbug.com/1019682) The cancellation behavior looks wrong.
    post_doom->emplace_back(base::BindOnce(&RunEntryResultOperationAndCallback,
                                           AsWeakPtr(), std::move(operation),
                                           std::move(callback)));
    return EntryResult::MakeError(net::ERR_IO_PENDING);
  }

  auto has_active = active_entries_.find(entry_hash);
  if (has_active != active_entries_.end()) {
    return OpenEntry(has_active->second->key(), net::HIGHEST,
                     std::move(callback));
  }

  auto simple_entry = base::MakeRefCounted<SimpleEntryImpl>(
      GetCacheType(), path_, cleanup_tracker_.get(), entry_hash,
      entry_operations_mode_, this, file_tracker_, file_operations_factory_,
      net_log_, GetNewEntryPriority(net::HIGHEST));
  EntryResultCallback backend_callback =
      base::BindOnce(&SimpleBackendImpl::OnEntryOpenedFromHash, AsWeakPtr(),
                     entry_hash, simple_entry, std::move(callback));
  return simple_entry->OpenEntry(std::move(backend_callback));
}

net::Error SimpleBackendImpl::DoomEntryFromHash(
    uint64_t entry_hash,
    CompletionOnceCallback callback) {
  std::vector<SimplePostDoomWaiter>* post_doom =
      post_doom_waiting_->Find(entry_hash);
  if (post_doom) {
    base::OnceCallback<net::Error(CompletionOnceCallback)> operation =
        base::BindOnce(&SimpleBackendImpl::DoomEntryFromHash,
                       base::Unretained(this), entry_hash);
    post_doom->emplace_back(base::BindOnce(&RunOperationAndCallback,
                                           AsWeakPtr(), std::move(operation),
                                           std::move(callback)));
    return net::ERR_IO_PENDING;
  }

  auto active_it = active_entries_.find(entry_hash);
  if (active_it != active_entries_.end())
    return active_it->second->DoomEntry(std::move(callback));

  // There's no pending dooms, nor any open entry. We can make a trivial
  // call to DoomEntries() to delete this entry.
  std::vector<uint64_t> entry_hash_vector;
  entry_hash_vector.push_back(entry_hash);
  DoomEntries(&entry_hash_vector, std::move(callback));
  return net::ERR_IO_PENDING;
}

void SimpleBackendImpl::OnEntryOpenedFromHash(
    uint64_t hash,
    const scoped_refptr<SimpleEntryImpl>& simple_entry,
    EntryResultCallback callback,
    EntryResult result) {
  if (result.net_error() != net::OK) {
    std::move(callback).Run(std::move(result));
    return;
  }

  std::pair<EntryMap::iterator, bool> insert_result =
      active_entries_.insert(EntryMap::value_type(hash, simple_entry.get()));
  EntryMap::iterator& it = insert_result.first;
  const bool did_insert = insert_result.second;
  if (did_insert) {
    // There was no active entry corresponding to this hash. We've already put
    // the entry opened from hash in the |active_entries_|. We now provide the
    // proxy object to the entry.
    it->second->SetActiveEntryProxy(ActiveEntryProxy::Create(hash, this));
    std::move(callback).Run(std::move(result));
  } else {
    // The entry was made active while we waiting for the open from hash to
    // finish. The entry created from hash needs to be closed, and the one
    // in |active_entries_| can be returned to the caller.
    Entry* entry_from_result = result.ReleaseEntry();
    DCHECK_EQ(entry_from_result, simple_entry.get());
    simple_entry->Close();
    EntryResult reopen_result = it->second->OpenEntry(std::move(callback));
    DCHECK_EQ(reopen_result.net_error(), net::ERR_IO_PENDING);
  }
}

void SimpleBackendImpl::DoomEntriesComplete(
    std::unique_ptr<std::vector<uint64_t>> entry_hashes,
    CompletionOnceCallback callback,
    int result) {
  for (const uint64_t& entry_hash : *entry_hashes)
    post_doom_waiting_->OnDoomComplete(entry_hash);
  std::move(callback).Run(result);
}

uint32_t SimpleBackendImpl::GetNewEntryPriority(
    net::RequestPriority request_priority) {
  // Lower priority is better, so give high network priority the least bump.
  return ((net::RequestPriority::MAXIMUM_PRIORITY - request_priority) * 10000) +
         entry_count_++;
}

}  // namespace disk_cache
