Skip to content
Permalink
Browse files

base: Introduce generic base::PreReadFile().

https://crrev.com/c/1741782 introduced base::win::PreReadFile(). This CL
moves the function to base::PreReadFile(), and adds a fadvise()-based
implementation for Linux, ChromeOS and Android L+.

(cherry picked from commit b5a0a97)

Bug: 1001838
Change-Id: Ib81175284d883db34535bf80fc13b4f26f0ba1ad
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1781513
Commit-Queue: Victor Costan <pwnall@chromium.org>
Reviewed-by: Greg Thompson <grt@chromium.org>
Reviewed-by: Scott Violet <sky@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#694610}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1794103
Reviewed-by: Victor Costan <pwnall@chromium.org>
Cr-Commit-Position: refs/branch-heads/3904@{chromium#30}
Cr-Branched-From: 675968a-refs/heads/master@{#693954}
  • Loading branch information
pwnall committed Sep 10, 2019
1 parent cc3e0b7 commit f7316d9c8ea280df821e30d3d750a13412ccf06a
@@ -1057,8 +1057,6 @@ jumbo_component("base") {
"win/event_trace_controller.h",
"win/event_trace_provider.cc",
"win/event_trace_provider.h",
"win/file_pre_reader.cc",
"win/file_pre_reader.h",
"win/hstring_compare.cc",
"win/hstring_compare.h",
"win/hstring_reference.cc",
@@ -11,6 +11,7 @@

#include <fstream>
#include <limits>
#include <memory>

#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
@@ -292,6 +293,45 @@ FilePath GetUniquePath(const FilePath& path) {
return path.InsertBeforeExtensionASCII(StringPrintf(" (%d)", uniquifier));
return uniquifier == 0 ? path : base::FilePath();
}

namespace internal {

bool PreReadFileSlow(const FilePath& file_path, int64_t max_bytes) {
DCHECK_GE(max_bytes, 0);

File file(file_path, File::FLAG_OPEN | File::FLAG_READ |
File::FLAG_SEQUENTIAL_SCAN |
File::FLAG_SHARE_DELETE);
if (!file.IsValid())
return false;

constexpr int kBufferSize = 1024 * 1024;
// Ensures the buffer is deallocated at function exit.
std::unique_ptr<char[]> buffer_deleter(new char[kBufferSize]);
char* const buffer = buffer_deleter.get();

while (max_bytes > 0) {
// The static_cast<int> is safe because kBufferSize is int, and both values
// are non-negative. So, the minimum is guaranteed to fit in int.
const int read_size =
static_cast<int>(std::min<int64_t>(max_bytes, kBufferSize));
DCHECK_GE(read_size, 0);
DCHECK_LE(read_size, kBufferSize);

const int read_bytes = file.ReadAtCurrentPos(buffer, read_size);
if (read_bytes < 0)
return false;
if (read_bytes == 0)
break;

max_bytes -= read_bytes;
}

return true;
}

} // namespace internal

#endif // !defined(OS_NACL_NONSFI)

} // namespace base
@@ -12,6 +12,7 @@
#include <stdint.h>
#include <stdio.h>

#include <limits>
#include <set>
#include <string>
#include <vector>
@@ -417,6 +418,31 @@ BASE_EXPORT FilePath GetUniquePath(const FilePath& path);
// false.
BASE_EXPORT bool SetNonBlocking(int fd);

// Hints the OS to prefetch the first |max_bytes| of |file_path| into its cache.
//
// If called at the appropriate time, this can reduce the latency incurred by
// feature code that needs to read the file.
//
// |max_bytes| specifies how many bytes should be pre-fetched. It may exceed the
// file's size. Passing in std::numeric_limits<int64_t>::max() is a convenient
// way to get the entire file pre-fetched.
//
// |is_executable| specifies whether the file is to be prefetched as
// executable code or as data. Windows treats the file backed pages in RAM
// differently, and specifying the wrong value results in two copies in RAM.
//
// Returns false if prefetching definitely failed. A return value of true does
// not guarantee that the entire desired range was prefetched.
//
// Calling this before using ::LoadLibrary() on Windows is more efficient memory
// wise, but we must be sure no other threads try to LoadLibrary() the file
// while we are doing the mapping and prefetching, or the process will get a
// private copy of the DLL via COW.
BASE_EXPORT bool PreReadFile(
const FilePath& file_path,
bool is_executable,
int64_t max_bytes = std::numeric_limits<int64_t>::max());

#if defined(OS_POSIX) || defined(OS_FUCHSIA)

// Creates a pipe. Returns true on success, otherwise false.
@@ -518,6 +544,9 @@ BASE_EXPORT bool CopyAndDeleteDirectory(const FilePath& from_path,
const FilePath& to_path);
#endif // defined(OS_WIN)

// Used by PreReadFile() when no kernel support for prefetching is available.
bool PreReadFileSlow(const FilePath& file_path, int64_t max_bytes);

} // namespace internal
} // namespace base

@@ -31,6 +31,7 @@
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/numerics/safe_conversions.h"
#include "base/path_service.h"
#include "base/posix/eintr_wrapper.h"
#include "base/stl_util.h"
@@ -1120,6 +1121,34 @@ bool CopyFile(const FilePath& from_path, const FilePath& to_path) {
}
#endif // !defined(OS_MACOSX)

bool PreReadFile(const FilePath& file_path,
bool is_executable,
int64_t max_bytes) {
DCHECK_GE(max_bytes, 0);

// ChromeOS is also covered by OS_LINUX.
// posix_fadvise() is only available in the Android NDK in API 21+. Older
// versions may have the required kernel support, but don't have enough usage
// to justify backporting.
#if defined(OS_LINUX) || (defined(OS_ANDROID) && __ANDROID_API__ >= 21)
File file(file_path, File::FLAG_OPEN | File::FLAG_READ);
if (!file.IsValid())
return false;

if (max_bytes == 0) {
// fadvise() pre-fetches the entire file when given a zero length.
return true;
}

const PlatformFile fd = file.GetPlatformFile();
const ::off_t len = base::saturated_cast<::off_t>(max_bytes);
return posix_fadvise(fd, /*offset=*/0, len, POSIX_FADV_WILLNEED) == 0;
#else
// TODO(pwnall): Fall back to madvise() for macOS.
return internal::PreReadFileSlow(file_path, max_bytes);
#endif // defined(OS_LINUX) || (defined(OS_ANDROID) && __ANDROID_API__ >= 21)
}

// -----------------------------------------------------------------------------

namespace internal {
@@ -3927,6 +3927,65 @@ TEST_F(FileUtilTest, GetUniquePathNumberTooManyFiles) {
// Verify that the limit has been reached.
EXPECT_EQ(GetUniquePathNumber(some_file), -1);
}

TEST_F(FileUtilTest, PreReadFile_ExistingFile_NoSize) {
FilePath text_file = temp_dir_.GetPath().Append(FPL("text_file"));
CreateTextFile(text_file, bogus_content);

EXPECT_TRUE(PreReadFile(text_file, /*is_executable=*/false));
}

TEST_F(FileUtilTest, PreReadFile_ExistingFile_ExactSize) {
FilePath text_file = temp_dir_.GetPath().Append(FPL("text_file"));
CreateTextFile(text_file, bogus_content);

EXPECT_TRUE(PreReadFile(text_file, /*is_executable=*/false,
base::size(bogus_content)));
}

TEST_F(FileUtilTest, PreReadFile_ExistingFile_OverSized) {
FilePath text_file = temp_dir_.GetPath().Append(FPL("text_file"));
CreateTextFile(text_file, bogus_content);

EXPECT_TRUE(PreReadFile(text_file, /*is_executable=*/false,
base::size(bogus_content) * 2));
}

TEST_F(FileUtilTest, PreReadFile_ExistingFile_UnderSized) {
FilePath text_file = temp_dir_.GetPath().Append(FPL("text_file"));
CreateTextFile(text_file, bogus_content);

EXPECT_TRUE(PreReadFile(text_file, /*is_executable=*/false,
base::size(bogus_content) / 2));
}

TEST_F(FileUtilTest, PreReadFile_ExistingFile_ZeroSize) {
FilePath text_file = temp_dir_.GetPath().Append(FPL("text_file"));
CreateTextFile(text_file, bogus_content);

EXPECT_TRUE(PreReadFile(text_file, /*is_executable=*/false, /*max_bytes=*/0));
}

TEST_F(FileUtilTest, PreReadFile_ExistingEmptyFile_NoSize) {
FilePath text_file = temp_dir_.GetPath().Append(FPL("text_file"));
CreateTextFile(text_file, L"");
// The test just asserts that this doesn't crash. The Windows implementation
// fails in this case, due to the base::MemoryMappedFile implementation and
// the limitations of ::MapViewOfFile().
PreReadFile(text_file, /*is_executable=*/false);
}

TEST_F(FileUtilTest, PreReadFile_ExistingEmptyFile_ZeroSize) {
FilePath text_file = temp_dir_.GetPath().Append(FPL("text_file"));
CreateTextFile(text_file, L"");
EXPECT_TRUE(PreReadFile(text_file, /*is_executable=*/false, /*max_bytes=*/0));
}

TEST_F(FileUtilTest, PreReadFile_InexistentFile) {
FilePath inexistent_file = temp_dir_.GetPath().Append(FPL("inexistent_file"));
EXPECT_FALSE(PreReadFile(inexistent_file, /*is_executable=*/false));
}

#endif // !defined(OS_NACL_NONSFI)

// Test that temp files obtained racily are all unique (no interference between
@@ -20,9 +20,11 @@

#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/memory_mapped_file.h"
#include "base/guid.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/safe_conversions.h"
#include "base/process/process_handle.h"
#include "base/rand_util.h"
#include "base/stl_util.h"
@@ -927,6 +929,60 @@ bool SetNonBlocking(int fd) {
return false;
}

namespace {

// ::PrefetchVirtualMemory() is only available on Windows 8 and above. Chrome
// supports Windows 7, so we need to check for the function's presence
// dynamically.
using PrefetchVirtualMemoryPtr = decltype(&::PrefetchVirtualMemory);

// Returns null if ::PrefetchVirtualMemory() is not available.
PrefetchVirtualMemoryPtr GetPrefetchVirtualMemoryPtr() {
HMODULE kernel32_dll = ::GetModuleHandleA("kernel32.dll");
return reinterpret_cast<decltype(&::PrefetchVirtualMemory)>(
GetProcAddress(kernel32_dll, "PrefetchVirtualMemory"));
}

} // namespace

bool PreReadFile(const FilePath& file_path,
bool is_executable,
int64_t max_bytes) {
DCHECK_GE(max_bytes, 0);

// On Win8 and higher use ::PrefetchVirtualMemory(). This is better than a
// simple data file read, more from a RAM perspective than CPU. This is
// because reading the file as data results in double mapping to
// Image/executable pages for all pages of code executed.
static PrefetchVirtualMemoryPtr prefetch_virtual_memory =
GetPrefetchVirtualMemoryPtr();

if (prefetch_virtual_memory == nullptr)
return internal::PreReadFileSlow(file_path, max_bytes);

if (max_bytes == 0) {
// PrefetchVirtualMemory() fails when asked to read zero bytes.
// base::MemoryMappedFile::Initialize() fails on an empty file.
return true;
}

// PrefetchVirtualMemory() fails if the file is opened with write access.
MemoryMappedFile::Access access = is_executable
? MemoryMappedFile::READ_CODE_IMAGE
: MemoryMappedFile::READ_ONLY;
MemoryMappedFile mapped_file;
if (!mapped_file.Initialize(file_path, access))
return false;

const ::SIZE_T length =
std::min(base::saturated_cast<::SIZE_T>(max_bytes),
base::saturated_cast<::SIZE_T>(mapped_file.length()));
::_WIN32_MEMORY_RANGE_ENTRY address_range = {mapped_file.data(), length};
return (*prefetch_virtual_memory)(::GetCurrentProcess(),
/*NumberOfEntries=*/1, &address_range,
/*Flags=*/0);
}

// -----------------------------------------------------------------------------

namespace internal {

This file was deleted.

0 comments on commit f7316d9

Please sign in to comment.
You can’t perform that action at this time.