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

Make individual print calls thread safe #1290

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
8 changes: 8 additions & 0 deletions include/vcpkg/base/fwd/system.console.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#pragma once

namespace vcpkg
{
class Console;
extern Console& std_out;
extern Console& std_error;
}
13 changes: 3 additions & 10 deletions include/vcpkg/base/messages.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <vcpkg/base/fmt.h>
#include <vcpkg/base/span.h>
#include <vcpkg/base/stringview.h>
#include <vcpkg/base/system.console.h>

#include <string>
#include <type_traits>
Expand Down Expand Up @@ -191,16 +192,8 @@ namespace vcpkg::msg

inline void print(Color c, const LocalizedString& s) { msg::write_unlocalized_text(c, s); }
inline void print(const LocalizedString& s) { msg::write_unlocalized_text(Color::none, s); }
inline void println(Color c, const LocalizedString& s)
{
msg::write_unlocalized_text(c, s);
msg::write_unlocalized_text(Color::none, "\n");
}
inline void println(const LocalizedString& s)
{
msg::write_unlocalized_text(Color::none, s);
msg::write_unlocalized_text(Color::none, "\n");
}
inline void println(Color c, LocalizedString s) { msg::write_unlocalized_text(c, s.append_raw('\n')); }
inline void println(LocalizedString s) { msg::write_unlocalized_text(Color::none, s.append_raw('\n')); }

[[nodiscard]] LocalizedString format_error(const LocalizedString& s);
template<VCPKG_DECL_MSG_TEMPLATE>
Expand Down
69 changes: 69 additions & 0 deletions include/vcpkg/base/system.console.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#pragma once

#include <vcpkg/base/fwd/messages.h>
#include <vcpkg/base/fwd/span.h>
#include <vcpkg/base/fwd/system.console.h>

#include <vcpkg/base/stringview.h>

#include <mutex>

namespace vcpkg
{
class Console
{
public:
#ifdef _WIN32
explicit Console(unsigned long std_device) noexcept;
#else
explicit Console(int fd) noexcept;
#endif
~Console() = default;

// This function is safe to call from multiple threads.
// When called from multiple threads, calls are atomic with respect to other callers
void print(Color color, StringView text)
{
std::lock_guard<std::mutex> lck{mtx};
print_unlocked(color, text);
flush();
}

// This function is safe to call from multiple threads.
// When called from multiple threads, calls are atomic with respect to other callers
// If a line doesn't end with \n, a \n is automatically printed.
void print_lines(View<std::pair<Color, StringView>> lines);

// This function is safe to call from multiple threads.
// When called from multiple threads, calls are atomic with respect to other callers
void println(Color color, StringView text)
{
std::lock_guard<std::mutex> lck{mtx};
print_unlocked(color, text);
print_unlocked(Color::none, "\n");
flush();
}

// This function is safe to call from multiple threads.
// When called from multiple threads, calls are atomic with respect to other callers
void println(Color color, std::string&& text)
{
text.push_back('\n');
print(color, text);
}

private:
void print_unlocked(Color color, StringView text) noexcept;
void write(const char* text, size_t count) noexcept;
void flush() noexcept;
bool check_is_terminal() noexcept;

std::mutex mtx;
#ifdef _WIN32
void* fd;
#else
int fd;
#endif
bool is_terminal;
};
} // namespace vcpkg
133 changes: 2 additions & 131 deletions src/vcpkg/base/messages.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -284,138 +284,9 @@ namespace vcpkg
}
namespace vcpkg::msg
{
// basic implementation - the write_unlocalized_text_to_stdout
#if defined(_WIN32)
static bool is_console(HANDLE h)
{
DWORD mode = 0;
// GetConsoleMode succeeds iff `h` is a console
// we do not actually care about the mode of the console
return GetConsoleMode(h, &mode);
}

static void check_write(BOOL success)
{
if (!success)
{
::fwprintf(stderr, L"[DEBUG] Failed to write to stdout: %lu\n", GetLastError());
std::abort();
}
}
static DWORD size_to_write(::size_t size) { return size > MAXDWORD ? MAXDWORD : static_cast<DWORD>(size); }

static void write_unlocalized_text_impl(Color c, StringView sv, HANDLE the_handle, bool is_console)
{
if (sv.empty()) return;

if (is_console)
{
WORD original_color = 0;
if (c != Color::none)
{
CONSOLE_SCREEN_BUFFER_INFO console_screen_buffer_info{};
::GetConsoleScreenBufferInfo(the_handle, &console_screen_buffer_info);
original_color = console_screen_buffer_info.wAttributes;
::SetConsoleTextAttribute(the_handle, static_cast<WORD>(c) | (original_color & 0xF0));
}

auto as_wstr = Strings::to_utf16(sv);

const wchar_t* pointer = as_wstr.data();
::size_t size = as_wstr.size();

while (size != 0)
{
DWORD written = 0;
check_write(::WriteConsoleW(the_handle, pointer, size_to_write(size), &written, nullptr));
pointer += written;
size -= written;
}

if (c != Color::none)
{
::SetConsoleTextAttribute(the_handle, original_color);
}
}
else
{
const char* pointer = sv.data();
::size_t size = sv.size();

while (size != 0)
{
DWORD written = 0;
check_write(::WriteFile(the_handle, pointer, size_to_write(size), &written, nullptr));
pointer += written;
size -= written;
}
}
}
void write_unlocalized_text_to_stdout(Color c, StringView sv) { return std_out.print(c, sv); }

void write_unlocalized_text_to_stdout(Color c, StringView sv)
{
static const HANDLE stdout_handle = ::GetStdHandle(STD_OUTPUT_HANDLE);
static const bool stdout_is_console = is_console(stdout_handle);
return write_unlocalized_text_impl(c, sv, stdout_handle, stdout_is_console);
}

void write_unlocalized_text_to_stderr(Color c, StringView sv)
{
static const HANDLE stderr_handle = ::GetStdHandle(STD_ERROR_HANDLE);
static const bool stderr_is_console = is_console(stderr_handle);
return write_unlocalized_text_impl(c, sv, stderr_handle, stderr_is_console);
}
#else
static void write_all(const char* ptr, size_t to_write, int fd)
{
while (to_write != 0)
{
auto written = ::write(fd, ptr, to_write);
if (written == -1)
{
::fprintf(stderr, "[DEBUG] Failed to print to stdout: %d\n", errno);
std::abort();
}
ptr += written;
to_write -= written;
}
}

static void write_unlocalized_text_impl(Color c, StringView sv, int fd, bool is_a_tty)
{
static constexpr char reset_color_sequence[] = {'\033', '[', '0', 'm'};

if (sv.empty()) return;

bool reset_color = false;
if (is_a_tty && c != Color::none)
{
reset_color = true;

const char set_color_sequence[] = {'\033', '[', '9', static_cast<char>(c), 'm'};
write_all(set_color_sequence, sizeof(set_color_sequence), fd);
}

write_all(sv.data(), sv.size(), fd);

if (reset_color)
{
write_all(reset_color_sequence, sizeof(reset_color_sequence), fd);
}
}

void write_unlocalized_text_to_stdout(Color c, StringView sv)
{
static bool is_a_tty = ::isatty(STDOUT_FILENO);
return write_unlocalized_text_impl(c, sv, STDOUT_FILENO, is_a_tty);
}

void write_unlocalized_text_to_stderr(Color c, StringView sv)
{
static bool is_a_tty = ::isatty(STDERR_FILENO);
return write_unlocalized_text_impl(c, sv, STDERR_FILENO, is_a_tty);
}
#endif
void write_unlocalized_text_to_stderr(Color c, StringView sv) { return std_error.print(c, sv); }

OutputStream default_output_stream = OutputStream::StdOut;

Expand Down
152 changes: 152 additions & 0 deletions src/vcpkg/base/system.console.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#include <vcpkg/base/span.h>
#include <vcpkg/base/stringview.h>
#include <vcpkg/base/system.console.h>

#ifdef _WIN32
#include <Windows.h>
#else
#include <unistd.h>
#endif

namespace vcpkg
{
#ifdef _WIN32
Console::Console(unsigned long std_device) noexcept
: fd(::GetStdHandle(std_device)), is_terminal(this->check_is_terminal())
{
}
#else
Console::Console(int fd) noexcept : fd(fd), is_terminal(this->check_is_terminal()) { }
#endif

void Console::flush() noexcept
{
#ifdef _WIN32
::FlushFileBuffers(fd);
#else
::fsync(fd);
#endif
}

bool Console::check_is_terminal() noexcept
{
#ifdef _WIN32
DWORD mode = 0;
// GetConsoleMode succeeds if `h` is a console
// we do not actually care about the mode of the console
return GetConsoleMode(fd, &mode);
#else
return ::isatty(fd) == 1;
#endif
}

void Console::print_lines(View<std::pair<Color, StringView>> lines)
{
std::lock_guard<std::mutex> lck{mtx};

for (auto&& [color, text] : lines)
{
print_unlocked(color, text);

if (text.back() != '\n')
{
print_unlocked(Color::none, "\n");
}
}
}

#ifdef _WIN32
void Console::print_unlocked(Color c, StringView sv) noexcept
{
if (sv.empty()) return;

WORD original_color = 0;

if (is_terminal && c != Color::none)
{
CONSOLE_SCREEN_BUFFER_INFO console_screen_buffer_info{};
::GetConsoleScreenBufferInfo(fd, &console_screen_buffer_info);
original_color = console_screen_buffer_info.wAttributes;
::SetConsoleTextAttribute(fd, static_cast<WORD>(c) | (original_color & 0xF0));
}

write(sv.data(), sv.size());

if (is_terminal && c != Color::none)
{
::SetConsoleTextAttribute(fd, original_color);
}
}

static constexpr DWORD size_to_write(size_t size) noexcept
{
return size > MAXDWORD ? MAXDWORD : static_cast<DWORD>(size);
}

void Console::write(const char* text, size_t count) noexcept
{
while (count != 0)
{
DWORD written = 0;
if (!::WriteFile(fd, text, size_to_write(count), &written, nullptr))
{
::fwprintf(stderr, L"[DEBUG] Failed to write to stdout: %lu\n", GetLastError());
std::abort();
}
text += written;
count -= written;
}
}

#else

void Console::print_unlocked(Color c, StringView sv) noexcept
{
static constexpr char reset_color_sequence[] = {'\033', '[', '0', 'm'};

if (sv.empty()) return;

bool reset_color = false;
// Only write color sequence if file descriptor is a terminal
if (is_terminal && c != Color::none)
{
reset_color = true;

const char set_color_sequence[] = {'\033', '[', '9', static_cast<char>(c), 'm'};
write(set_color_sequence, sizeof(set_color_sequence));
}

write(sv.data(), sv.size());

if (reset_color)
{
write(reset_color_sequence, sizeof(reset_color_sequence));
}
}

void Console::write(const char* text, size_t count) noexcept
{
while (count != 0)
{
auto written = ::write(fd, text, count);
if (written == -1)
{
::fprintf(stderr, "[DEBUG] Failed to print to stdout: %d\n", errno);
std::abort();
}
text += written;
count -= written;
}
}
#endif
#ifdef _WIN32
Console std_out_instance(STD_OUTPUT_HANDLE);
Console std_error_instance(STD_ERROR_HANDLE);
#else
Console std_out_instance(STDOUT_FILENO);
Console std_error_instance(STDERR_FILENO);
#endif

Console& std_out = std_out_instance;
Console& std_error = std_error_instance;
} // namespace vcpkg
Loading