Skip to content

[libc++] Fix unnecessary flushes in std::print() on POSIX #70321

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 3 additions & 5 deletions libcxx/include/__ostream/print.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ _LIBCPP_EXPORTED_FROM_ABI FILE* __get_ostream_file(ostream& __os);
# if _LIBCPP_HAS_UNICODE
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
_LIBCPP_HIDE_FROM_ABI void __vprint_unicode(ostream& __os, string_view __fmt, format_args __args, bool __write_nl) {
# if _LIBCPP_AVAILABILITY_HAS_PRINT == 0
# if _LIBCPP_AVAILABILITY_HAS_PRINT == 0 || !defined(_LIBCPP_WIN32API)
return std::__vprint_nonunicode(__os, __fmt, __args, __write_nl);
# else
FILE* __file = std::__get_ostream_file(__os);
Expand All @@ -120,10 +120,8 @@ _LIBCPP_HIDE_FROM_ABI void __vprint_unicode(ostream& __os, string_view __fmt, fo
# endif // _LIBCPP_HAS_EXCEPTIONS
ostream::sentry __s(__os);
if (__s) {
# ifndef _LIBCPP_WIN32API
__print::__vprint_unicode_posix(__file, __fmt, __args, __write_nl, true);
# elif _LIBCPP_HAS_WIDE_CHARACTERS
__print::__vprint_unicode_windows(__file, __fmt, __args, __write_nl, true);
# if _LIBCPP_HAS_WIDE_CHARACTERS
__print::__vprint_unicode_windows(__file, __fmt, __args, __write_nl);
# else
# error "Windows builds with wchar_t disabled are not supported."
# endif
Expand Down
27 changes: 9 additions & 18 deletions libcxx/include/print
Original file line number Diff line number Diff line change
Expand Up @@ -233,26 +233,11 @@ __vprint_nonunicode(FILE* __stream, string_view __fmt, format_args __args, bool
// terminal when the output is redirected. Typically during testing the
// output is redirected to be able to capture it. This makes it hard to
// test this code path.
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
_LIBCPP_HIDE_FROM_ABI inline void
__vprint_unicode_posix(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) {
// TODO PRINT Should flush errors throw too?
if (__is_terminal)
std::fflush(__stream);

__print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl);
}

# if _LIBCPP_HAS_WIDE_CHARACTERS
template <class = void> // TODO PRINT template or availability markup fires too eagerly (http://llvm.org/PR61563).
_LIBCPP_HIDE_FROM_ABI inline void
__vprint_unicode_windows(FILE* __stream, string_view __fmt, format_args __args, bool __write_nl, bool __is_terminal) {
if (!__is_terminal)
return __print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl);

// TODO PRINT Should flush errors throw too?
std::fflush(__stream);

__vprint_unicode_windows([[maybe_unused]] FILE* __stream, string_view __fmt, format_args __args, bool __write_nl) {
string __str = std::vformat(__fmt, __args);
// UTF-16 uses the same number or less code units than UTF-8.
// However the size of the code unit is 16 bits instead of 8 bits.
Expand Down Expand Up @@ -313,9 +298,15 @@ __vprint_unicode([[maybe_unused]] FILE* __stream,
// Windows there is a different API. This API requires transcoding.

# ifndef _LIBCPP_WIN32API
__print::__vprint_unicode_posix(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream));
__print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl);
# elif _LIBCPP_HAS_WIDE_CHARACTERS
__print::__vprint_unicode_windows(__stream, __fmt, __args, __write_nl, __print::__is_terminal(__stream));
if (__print::__is_terminal(__stream)) {
// TODO PRINT Should flush errors throw too?
std::fflush(__stream);
__print::__vprint_unicode_windows(__stream, __fmt, __args, __write_nl);
} else {
__print::__vprint_nonunicode(__stream, __fmt, __args, __write_nl);
}
# else
# error "Windows builds with wchar_t disabled are not supported."
# endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
// When std::print is unavailable, we don't rely on an implementation of
// std::__is_terminal and we always assume a non-unicode and non-terminal
// output.
// XFAIL: availability-print-missing
// XFAIL: availability-print-missing && target={{.*}}-windows{{.*}}

// Clang modules do not work with the definiton of _LIBCPP_TESTING_PRINT_IS_TERMINAL
// XFAIL: clang-modules-build
// XFAIL: clang-modules-build && target={{.*}}-windows{{.*}}
// <ostream>

// Tests the implementation of
Expand Down Expand Up @@ -81,14 +81,22 @@ static void test_is_terminal_file_stream() {
assert(stream.is_open());
assert(stream.good());
std::print(stream, "test");
#ifdef _WIN32
assert(is_terminal_calls == 1);
#else
assert(is_terminal_calls == 0);
#endif
}
{
std::ofstream stream(filename);
assert(stream.is_open());
assert(stream.good());
std::print(stream, "test");
#ifdef _WIN32
assert(is_terminal_calls == 2);
#else
assert(is_terminal_calls == 0);
#endif
}
}

Expand All @@ -105,7 +113,11 @@ static void test_is_terminal_rdbuf_derived_from_filebuf() {

std::ostream stream(&buf);
std::print(stream, "test");
#ifdef _WIN32
assert(is_terminal_calls == 1);
#else
assert(is_terminal_calls == 0);
#endif
}

// When the stream is cout, clog, or cerr, its FILE* may be a terminal. Validate
Expand All @@ -115,15 +127,27 @@ static void test_is_terminal_std_cout_cerr_clog() {
is_terminal_result = false;
{
std::print(std::cout, "test");
#ifdef _WIN32
assert(is_terminal_calls == 1);
#else
assert(is_terminal_calls == 0);
#endif
}
{
std::print(std::cerr, "test");
#ifdef _WIN32
assert(is_terminal_calls == 2);
#else
assert(is_terminal_calls == 0);
#endif
}
{
std::print(std::clog, "test");
#ifdef _WIN32
assert(is_terminal_calls == 3);
#else
assert(is_terminal_calls == 0);
#endif
}
}

Expand Down Expand Up @@ -156,7 +180,11 @@ static void test_is_terminal_is_flushed() {
// A terminal sync is called.
is_terminal_result = true;
std::print(stream, "");
#ifdef _WIN32
assert(buf.sync_calls == 1); // only called from the destructor of the sentry
#else
assert(buf.sync_calls == 0);
#endif
}

int main(int, char**) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
// Tests the implementation of
// void __print::__vprint_unicode_windows(FILE* __stream, string_view __fmt,
// format_args __args, bool __write_nl,
// bool __is_terminal);
// );
//
// In the library when the stdout is redirected to a file it is no
// longer considered a terminal and the special terminal handling is no
Expand Down Expand Up @@ -59,43 +59,19 @@ scoped_test_env env;
std::string filename = env.create_file("output.txt");

static void test_basics() {
FILE* file = std::fopen(filename.c_str(), "wb");
assert(file);

// Test writing to a "non-terminal" stream does not call WriteConsoleW.
std::__print::__vprint_unicode_windows(file, "Hello", std::make_format_args(), false, false);
assert(std::ftell(file) == 5);

// It's not possible to reliably test whether writing to a "terminal" stream
// flushes before writing. Testing flushing a closed stream worked on some
// platforms, but was unreliable.
calling = true;
std::__print::__vprint_unicode_windows(file, " world", std::make_format_args(), false, true);
std::__print::__vprint_unicode_windows(stdout, " world", std::make_format_args(), false);
}

// When the output is a file the data is written as-is.
// When the output is a "terminal" invalid UTF-8 input is flagged.
static void test(std::wstring_view output, std::string_view input) {
// *** File ***
FILE* file = std::fopen(filename.c_str(), "wb");
assert(file);

std::__print::__vprint_unicode_windows(file, input, std::make_format_args(), false, false);
assert(std::ftell(file) == static_cast<long>(input.size()));
std::fclose(file);

file = std::fopen(filename.c_str(), "rb");
assert(file);

std::vector<char> buffer(input.size());
size_t read = fread(buffer.data(), 1, buffer.size(), file);
assert(read == input.size());
assert(input == std::string_view(buffer.begin(), buffer.end()));
std::fclose(file);

// *** Terminal ***
expected = output;
std::__print::__vprint_unicode_windows(file, input, std::make_format_args(), false, true);
std::__print::__vprint_unicode_windows(stdout, input, std::make_format_args(), false);
}

static void test() {
Expand Down
Loading