Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Install files in parallel #1256

Open
wants to merge 40 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
6cd56a7
Install files in parallel
Thomas1664 Oct 29, 2023
7dad374
optimize + fixes
Thomas1664 Oct 29, 2023
05bc1f0
async create listfile dir
Thomas1664 Oct 29, 2023
ba36960
format
Thomas1664 Oct 29, 2023
4dab20c
Remove unneeded optional
Thomas1664 Oct 30, 2023
dc14e81
fix format of error message
Thomas1664 Oct 30, 2023
c8cd17b
noexcept + constexpr for fs one-liners
Thomas1664 Oct 30, 2023
80af37d
inline + noexcept for one-liners in InstallDir
Thomas1664 Oct 30, 2023
0e09c92
decouple installation from list file creation
Thomas1664 Oct 30, 2023
4fe1e0f
fixes
Thomas1664 Oct 30, 2023
188d451
initialize member
Thomas1664 Oct 30, 2023
65b8c3e
format
Thomas1664 Oct 30, 2023
f7bd6d5
investigate unit test failure
Thomas1664 Oct 30, 2023
23aae9b
Merge branch 'parallel-file-install' of https://github.com/Thomas1664…
Thomas1664 Oct 30, 2023
2cd0e1b
revert
Thomas1664 Oct 30, 2023
0b44c21
format
Thomas1664 Oct 30, 2023
284ac35
format
Thomas1664 Oct 30, 2023
4e787dd
revert
Thomas1664 Oct 30, 2023
c0cba4b
Merge branch 'microsoft:main' into parallel-file-install
Thomas1664 Nov 1, 2023
69fbb7d
invert if
Thomas1664 Nov 1, 2023
e6c094a
unify duplicated if
Thomas1664 Nov 1, 2023
4684372
Merge branch 'microsoft:main' into parallel-file-install
Thomas1664 Nov 4, 2023
99106d3
Fix merge
Thomas1664 Nov 4, 2023
85a9ae9
Merge branch 'microsoft:main' into parallel-file-install
Thomas1664 Nov 9, 2023
d0c1c3a
Merge branch 'microsoft:main' into parallel-file-install
Thomas1664 Nov 12, 2023
599ecf4
Fix comments
Thomas1664 Nov 17, 2023
d04f108
Merge branch 'parallel-file-install' of https://github.com/Thomas1664…
Thomas1664 Nov 17, 2023
376829c
Fix most CR comments
Thomas1664 Nov 28, 2023
68ba04e
format
Thomas1664 Nov 28, 2023
4179d0e
Merge branch 'microsoft:main' into parallel-file-install
Thomas1664 Dec 16, 2023
b211761
Merge branch 'microsoft:main' into parallel-file-install
Thomas1664 Mar 5, 2024
9b676c4
include future
Thomas1664 Mar 5, 2024
670d164
use partition instead of custom sort
Thomas1664 Mar 5, 2024
cac8672
remove compairson operator
Thomas1664 Mar 5, 2024
f109858
format
Thomas1664 Mar 5, 2024
f0048c6
Merge remote-tracking branch 'origin/main' into parallel-file-install
Thomas1664 Mar 18, 2024
247c957
Merge branch 'microsoft:main' into parallel-file-install
Thomas1664 Apr 28, 2024
0927c90
Merge remote-tracking branch 'upstream/main' into parallel-file-install
Thomas1664 Aug 25, 2024
8a44030
Merge branch 'microsoft:main' into parallel-file-install
Thomas1664 Dec 8, 2024
42c406e
Only parallelize symlink_status
Thomas1664 Dec 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions include/vcpkg/base/files.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ namespace vcpkg
}
};

bool is_symlink(FileType s);
bool is_regular_file(FileType s);
bool is_directory(FileType s);
bool exists(FileType s);
constexpr bool is_symlink(FileType s) noexcept { return s == FileType::symlink || s == FileType::junction; }
constexpr bool is_regular_file(FileType s) noexcept { return s == FileType::regular; }
constexpr bool is_directory(FileType s) noexcept { return s == FileType::directory; }
constexpr bool exists(FileType s) noexcept { return s != FileType::not_found && s != FileType::none; }

struct FilePointer
{
Expand Down
6 changes: 3 additions & 3 deletions include/vcpkg/commands.install.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ namespace vcpkg
Path m_listfile;

public:
const Path& destination() const;
const Path& listfile() const;
const Path& destination() const noexcept { return this->m_destination; }
const Path& listfile() const noexcept { return this->m_listfile; }
};

void install_package_and_write_listfile(const Filesystem& fs,
Expand All @@ -66,7 +66,7 @@ namespace vcpkg

void install_files_and_write_listfile(const Filesystem& fs,
const Path& source_dir,
const std::vector<Path>& files,
std::vector<Path>&& files,
const InstallDir& destination_dir);

InstallResult install_package(const VcpkgPaths& paths,
Expand Down
5 changes: 0 additions & 5 deletions src/vcpkg/base/files.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1366,11 +1366,6 @@ namespace vcpkg

const char* to_printf_arg(const Path& p) noexcept { return p.m_str.c_str(); }

bool is_symlink(FileType s) { return s == FileType::symlink || s == FileType::junction; }
bool is_regular_file(FileType s) { return s == FileType::regular; }
bool is_directory(FileType s) { return s == FileType::directory; }
bool exists(FileType s) { return s != FileType::not_found && s != FileType::none; }

FilePointer::FilePointer(const Path& path) : m_fs(nullptr), m_path(path) { }
FilePointer::FilePointer() noexcept : m_fs(nullptr), m_path{} { }

Expand Down
3 changes: 2 additions & 1 deletion src/vcpkg/commands.export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,8 @@ namespace
files.push_back(paths.installed().root() / suffix);
}

install_files_and_write_listfile(fs, paths.installed().triplet_dir(action.spec.triplet()), files, dirs);
install_files_and_write_listfile(
fs, paths.installed().triplet_dir(action.spec.triplet()), std::move(files), dirs);
}
}

Expand Down
225 changes: 147 additions & 78 deletions src/vcpkg/commands.install.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <vcpkg/base/files.h>
#include <vcpkg/base/hash.h>
#include <vcpkg/base/messages.h>
#include <vcpkg/base/parallel-algorithms.h>
#include <vcpkg/base/system.debug.h>
#include <vcpkg/base/system.h>
#include <vcpkg/base/util.h>
Expand All @@ -28,7 +29,11 @@
#include <vcpkg/vcpkgpaths.h>
#include <vcpkg/xunitwriter.h>

#include <future>
#include <iterator>
#ifdef _WIN32
#include <execution>
#endif

namespace vcpkg
{
Expand All @@ -42,130 +47,194 @@ namespace vcpkg
return dirs;
}

const Path& InstallDir::destination() const { return this->m_destination; }
struct PathAndType
{
Path path;
FileType type = FileType::none;

bool operator<(const PathAndType& other) const noexcept { return path < other.path; }
};

static std::vector<PathAndType> filter_files_to_install(const Filesystem& fs, std::vector<Path>&& files)
{
std::vector<PathAndType> output(files.size());

const Path& InstallDir::listfile() const { return this->m_listfile; }
parallel_transform(files, output.begin(), [&fs](auto&& file) -> PathAndType {
return PathAndType{std::move(file), fs.symlink_status(file, IgnoreErrors{})};
});

Util::erase_remove_if(output, [](const PathAndType& file_and_status) {
// regular file
if (is_regular_file(file_and_status.type))
{
const auto filename = file_and_status.path.filename();
// Don't copy control or manifest files
return filename == FileDotDsStore || filename == "CONTROL" || filename == "vcpkg.json" ||
filename == "BUILD_INFO";
}
// not found
else if (!exists(file_and_status.type))
{
msg::println_error(msgFileNotFound, msg::path = file_and_status.path);
return true;
}
// invalid file type
else if (!is_symlink(file_and_status.type) && !is_directory(file_and_status.type))
{
msg::println_error(msgInvalidFileType, msg::path = file_and_status.path);
return true;
}
return false;
});
#ifdef _WIN32
std::sort(std::execution::par_unseq, output.begin(), output.end());
#else
std::sort(output.begin(), output.end());
#endif
return output;
}

// PRE: files is sorted
// install_dest_dir: The directory where files are installed, without final '/'
static void install_listfile(const Filesystem& fs,
size_t prefix_length,
const Path& install_dest_dir,
const Path& listfile,
View<PathAndType> files)
{
auto install_dest_subdir = install_dest_dir.filename().to_string();
install_dest_subdir.push_back('/');
std::vector<std::string> output(files.size() + 1, install_dest_subdir);

// Note that output and files are off by one because
// the first element of output is the install_dest_dir itself.
for (size_t i = 0; i < files.size(); ++i)
{
const auto suffix = files[i].path.generic_u8string().substr(prefix_length + 1);
auto& this_output = output[i + 1];
this_output.append(suffix);

if (is_directory(files[i].type))
{
// Trailing slash for directories
this_output.push_back('/');
}
}

fs.create_directories(listfile.parent_path(), VCPKG_LINE_INFO);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This races with other file operations you're doing.

fs.write_lines(listfile, output, VCPKG_LINE_INFO);
}

void install_package_and_write_listfile(const Filesystem& fs,
const Path& source_dir,
const InstallDir& destination_dir)
{
Checks::check_exit(VCPKG_LINE_INFO,
fs.exists(source_dir, IgnoreErrors{}),
Strings::concat("Source directory ", source_dir, "does not exist"));
Strings::concat("Source directory ", source_dir, " does not exist"));
auto files = fs.get_files_recursive(source_dir, VCPKG_LINE_INFO);
Util::erase_remove_if(files, [](Path& path) { return path.filename() == FileDotDsStore; });
install_files_and_write_listfile(fs, source_dir, files, destination_dir);
install_files_and_write_listfile(fs, source_dir, std::move(files), destination_dir);
}
void install_files_and_write_listfile(const Filesystem& fs,
const Path& source_dir,
const std::vector<Path>& files,
std::vector<Path>&& files,
const InstallDir& destination_dir)
{
std::vector<std::string> output;

const size_t prefix_length = source_dir.native().size();
const Path& destination = destination_dir.destination();
std::string destination_subdirectory = destination.filename().to_string();
const Path& listfile = destination_dir.listfile();

fs.create_directories(destination, VCPKG_LINE_INFO);
const auto listfile_parent = listfile.parent_path();
fs.create_directories(listfile_parent, VCPKG_LINE_INFO);

output.push_back(destination_subdirectory + "/");
for (auto&& file : files)
const auto filtered_files = filter_files_to_install(fs, std::move(files));

auto list_file_future = std::async(std::launch::async | std::launch::deferred, [&]() {
install_listfile(fs, prefix_length, destination, destination_dir.listfile(), filtered_files);
});

// Copy directories
for (const auto& file : filtered_files)
{
if (!is_directory(file.type)) continue;

const Path target = destination / file.path.generic_u8string().substr(prefix_length + 1);
std::error_code ec;
const auto status = fs.symlink_status(file, ec);
fs.create_directory(target, ec);
if (ec)
{
msg::println_warning(format_filesystem_call_error(ec, "symlink_status", {file}));
continue;
msg::println_error(msgInstallFailed, msg::path = target, msg::error_msg = ec.message());
}
}

std::mutex mtx;
// Copy files/symlinks
parallel_for_each(filtered_files, [&](const auto& file_and_status) {
auto& file = file_and_status.path;
auto& status = file_and_status.type;

if (is_directory(status)) return;

const auto filename = file.filename();
if (vcpkg::is_regular_file(status) &&
(filename == "CONTROL" || filename == "vcpkg.json" || filename == "BUILD_INFO"))
if (!is_regular_file(status) && !is_symlink(status))
{
// Do not copy the control file or manifest file
continue;
Checks::unreachable(VCPKG_LINE_INFO);
return;
}

const auto suffix = file.generic_u8string().substr(prefix_length + 1);
const auto target = destination / suffix;
const Path target = destination / file.generic_u8string().substr(prefix_length + 1);

bool use_hard_link = true;
auto this_output = Strings::concat(destination_subdirectory, "/", suffix);
switch (status)
if (fs.exists(target, IgnoreErrors{}))
{
case FileType::directory:
{
fs.create_directory(target, ec);
if (ec)
{
msg::println_error(msgInstallFailed, msg::path = target, msg::error_msg = ec.message());
}

// Trailing backslash for directories
this_output.push_back('/');
output.push_back(std::move(this_output));
break;
std::lock_guard lck(mtx);
msg::println_warning(msgOverwritingFile, msg::path = target);
}
case FileType::regular:
if (is_regular_file(status))
{
if (fs.exists(target, IgnoreErrors{}))
{
msg::println_warning(msgOverwritingFile, msg::path = target);
fs.remove_all(target, IgnoreErrors{});
}
if (use_hard_link)
{
fs.create_hard_link(file, target, ec);
if (ec)
{
Debug::println("Install from packages to installed: Fallback to copy "
"instead creating hard links because of: ",
ec.message());
use_hard_link = false;
}
}
if (!use_hard_link)
{
fs.copy_file(file, target, CopyOptions::overwrite_existing, ec);
}
fs.remove(target, IgnoreErrors{});
}
}

bool use_hard_link = true;

if (is_regular_file(status))
{
if (use_hard_link)
{
std::error_code ec;
fs.create_hard_link(file, target, ec);
if (ec)
{
msg::println_error(msgInstallFailed, msg::path = target, msg::error_msg = ec.message());
std::lock_guard lck(mtx);
Debug::println("Install from packages to installed: Fallback to copy "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug::println is not thread safe.

Copy link
Contributor Author

@Thomas1664 Thomas1664 Nov 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is other parallelized code where this also happens: in cmd_execute_and_capture_output_parallel we call cmd_execute_and_stream_data which calls Debug::print().

"instead creating hard links because of: ",
ec.message());
use_hard_link = false;
}

output.push_back(std::move(this_output));
break;
}
case FileType::symlink:
case FileType::junction:
if (!use_hard_link)
{
if (fs.exists(target, IgnoreErrors{}))
{
msg::println_warning(msgOverwritingFile, msg::path = target);
}

fs.copy_symlink(file, target, ec);
std::error_code ec;
fs.copy_file(file, target, CopyOptions::overwrite_existing, ec);
if (ec)
{
std::lock_guard lck(mtx);
msg::println_error(msgInstallFailed, msg::path = target, msg::error_msg = ec.message());
}

output.push_back(std::move(this_output));
break;
}
default: msg::println_error(msgInvalidFileType, msg::path = file); break;
}
}

std::sort(output.begin(), output.end());
fs.write_lines(listfile, output, VCPKG_LINE_INFO);
else
{
// file is symlink
std::error_code ec;
fs.copy_symlink(file, target, ec);
if (ec)
{
std::lock_guard lck(mtx);
msg::println_error(msgInstallFailed, msg::path = target, msg::error_msg = ec.message());
}
}
});
list_file_future.get();
}

static std::vector<file_pack> extract_files_in_triplet(
Expand Down