Skip to content

Commit d8c0734

Browse files
authored
[Support] Move raw_ostream::tie to raw_fd_ostream (#97396)
Originally, tie was introduced by D81156 to flush stdout before writing to stderr. 0308975 reverted this due to race conditions. Nonetheless, it does cost performance, causing an extra check in the "cold" path, which is actually the hot path for raw_svector_ostream. Given that this feature is only used for errs(), move it to raw_fd_ostream so that it no longer affects performance of other stream classes.
1 parent 4d2ae88 commit d8c0734

File tree

5 files changed

+71
-54
lines changed

5 files changed

+71
-54
lines changed

clang-tools-extra/clangd/index/remote/server/Server.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -499,8 +499,6 @@ int main(int argc, char *argv[]) {
499499
}
500500

501501
llvm::errs().SetBuffered();
502-
// Don't flush stdout when logging for thread safety.
503-
llvm::errs().tie(nullptr);
504502
auto Logger = makeLogger(LogPrefix.getValue(), llvm::errs());
505503
clang::clangd::LoggingSession LoggingSession(*Logger);
506504

clang-tools-extra/clangd/tool/ClangdMain.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -840,8 +840,6 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
840840
// Use buffered stream to stderr (we still flush each log message). Unbuffered
841841
// stream can cause significant (non-deterministic) latency for the logger.
842842
llvm::errs().SetBuffered();
843-
// Don't flush stdout when logging, this would be both slow and racy!
844-
llvm::errs().tie(nullptr);
845843
StreamLogger Logger(llvm::errs(), LogLevel);
846844
LoggingSession LoggingSession(Logger);
847845
// Write some initial logs before we start doing any real work.

llvm/include/llvm/Support/raw_ostream.h

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,6 @@ class raw_ostream {
8282
char *OutBufStart, *OutBufEnd, *OutBufCur;
8383
bool ColorEnabled = false;
8484

85-
/// Optional stream this stream is tied to. If this stream is written to, the
86-
/// tied-to stream will be flushed first.
87-
raw_ostream *TiedStream = nullptr;
88-
8985
enum class BufferKind {
9086
Unbuffered = 0,
9187
InternalBuffer,
@@ -360,10 +356,6 @@ class raw_ostream {
360356

361357
bool colors_enabled() const { return ColorEnabled; }
362358

363-
/// Tie this stream to the specified stream. Replaces any existing tied-to
364-
/// stream. Specifying a nullptr unties the stream.
365-
void tie(raw_ostream *TieTo) { TiedStream = TieTo; }
366-
367359
//===--------------------------------------------------------------------===//
368360
// Subclass Interface
369361
//===--------------------------------------------------------------------===//
@@ -422,9 +414,6 @@ class raw_ostream {
422414
/// flushing. The result is affected by calls to enable_color().
423415
bool prepare_colors();
424416

425-
/// Flush the tied-to stream (if present) and then write the required data.
426-
void flush_tied_then_write(const char *Ptr, size_t Size);
427-
428417
virtual void anchor();
429418
};
430419

@@ -475,6 +464,10 @@ class raw_fd_ostream : public raw_pwrite_stream {
475464
bool IsRegularFile = false;
476465
mutable std::optional<bool> HasColors;
477466

467+
/// Optional stream this stream is tied to. If this stream is written to, the
468+
/// tied-to stream will be flushed first.
469+
raw_ostream *TiedStream = nullptr;
470+
478471
#ifdef _WIN32
479472
/// True if this fd refers to a Windows console device. Mintty and other
480473
/// terminal emulators are TTYs, but they are not consoles.
@@ -553,6 +546,13 @@ class raw_fd_ostream : public raw_pwrite_stream {
553546

554547
bool has_colors() const override;
555548

549+
/// Tie this stream to the specified stream. Replaces any existing tied-to
550+
/// stream. Specifying a nullptr unties the stream. This is intended for to
551+
/// tie errs() to outs(), so that outs() is flushed whenever something is
552+
/// written to errs(), preventing weird and hard-to-test output when stderr
553+
/// is redirected to stdout.
554+
void tie(raw_ostream *TieTo) { TiedStream = TieTo; }
555+
556556
std::error_code error() const { return EC; }
557557

558558
/// Return the value of the flag in this raw_fd_ostream indicating whether an

llvm/lib/Support/raw_ostream.cpp

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -221,15 +221,15 @@ void raw_ostream::flush_nonempty() {
221221
assert(OutBufCur > OutBufStart && "Invalid call to flush_nonempty.");
222222
size_t Length = OutBufCur - OutBufStart;
223223
OutBufCur = OutBufStart;
224-
flush_tied_then_write(OutBufStart, Length);
224+
write_impl(OutBufStart, Length);
225225
}
226226

227227
raw_ostream &raw_ostream::write(unsigned char C) {
228228
// Group exceptional cases into a single branch.
229229
if (LLVM_UNLIKELY(OutBufCur >= OutBufEnd)) {
230230
if (LLVM_UNLIKELY(!OutBufStart)) {
231231
if (BufferMode == BufferKind::Unbuffered) {
232-
flush_tied_then_write(reinterpret_cast<char *>(&C), 1);
232+
write_impl(reinterpret_cast<char *>(&C), 1);
233233
return *this;
234234
}
235235
// Set up a buffer and start over.
@@ -249,7 +249,7 @@ raw_ostream &raw_ostream::write(const char *Ptr, size_t Size) {
249249
if (LLVM_UNLIKELY(size_t(OutBufEnd - OutBufCur) < Size)) {
250250
if (LLVM_UNLIKELY(!OutBufStart)) {
251251
if (BufferMode == BufferKind::Unbuffered) {
252-
flush_tied_then_write(Ptr, Size);
252+
write_impl(Ptr, Size);
253253
return *this;
254254
}
255255
// Set up a buffer and start over.
@@ -265,7 +265,7 @@ raw_ostream &raw_ostream::write(const char *Ptr, size_t Size) {
265265
if (LLVM_UNLIKELY(OutBufCur == OutBufStart)) {
266266
assert(NumBytes != 0 && "undefined behavior");
267267
size_t BytesToWrite = Size - (Size % NumBytes);
268-
flush_tied_then_write(Ptr, BytesToWrite);
268+
write_impl(Ptr, BytesToWrite);
269269
size_t BytesRemaining = Size - BytesToWrite;
270270
if (BytesRemaining > size_t(OutBufEnd - OutBufCur)) {
271271
// Too much left over to copy into our buffer.
@@ -306,12 +306,6 @@ void raw_ostream::copy_to_buffer(const char *Ptr, size_t Size) {
306306
OutBufCur += Size;
307307
}
308308

309-
void raw_ostream::flush_tied_then_write(const char *Ptr, size_t Size) {
310-
if (TiedStream)
311-
TiedStream->flush();
312-
write_impl(Ptr, Size);
313-
}
314-
315309
// Formatted output.
316310
raw_ostream &raw_ostream::operator<<(const format_object_base &Fmt) {
317311
// If we have more than a few bytes left in our output buffer, try
@@ -742,6 +736,9 @@ static bool write_console_impl(int FD, StringRef Data) {
742736
#endif
743737

744738
void raw_fd_ostream::write_impl(const char *Ptr, size_t Size) {
739+
if (TiedStream)
740+
TiedStream->flush();
741+
745742
assert(FD >= 0 && "File already closed.");
746743
pos += Size;
747744

llvm/unittests/Support/raw_ostream_test.cpp

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -388,9 +388,14 @@ TEST(raw_ostreamTest, flush_tied_to_stream_on_write) {
388388
TiedTo.SetBuffered();
389389
TiedTo << "a";
390390

391-
std::string Buffer;
392-
raw_string_ostream TiedStream(Buffer);
391+
SmallString<64> Path;
392+
int FD;
393+
ASSERT_FALSE(sys::fs::createTemporaryFile("tietest", "", FD, Path));
394+
FileRemover Cleanup(Path);
395+
raw_fd_ostream TiedStream(FD, /*ShouldClose=*/false);
396+
TiedStream.SetUnbuffered();
393397
TiedStream.tie(&TiedTo);
398+
394399
// Sanity check that the stream hasn't already been flushed.
395400
EXPECT_EQ("", TiedToBuffer);
396401

@@ -435,30 +440,60 @@ TEST(raw_ostreamTest, flush_tied_to_stream_on_write) {
435440
TiedStream << "pq";
436441
EXPECT_EQ("acego", TiedToBuffer);
437442

438-
// Streams can be tied to each other safely.
443+
// Calling tie with nullptr unties stream.
444+
TiedStream.SetUnbuffered();
445+
TiedStream.tie(nullptr);
446+
TiedTo << "y";
447+
TiedStream << "0";
448+
EXPECT_EQ("acego", TiedToBuffer);
449+
450+
TiedTo.flush();
439451
TiedStream.flush();
440-
Buffer = "";
452+
}
453+
454+
static void checkFileData(StringRef FileName, StringRef GoldenData) {
455+
ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
456+
MemoryBuffer::getFileOrSTDIN(FileName);
457+
EXPECT_FALSE(BufOrErr.getError());
458+
459+
EXPECT_EQ((*BufOrErr)->getBufferSize(), GoldenData.size());
460+
EXPECT_EQ(memcmp((*BufOrErr)->getBufferStart(), GoldenData.data(),
461+
GoldenData.size()),
462+
0);
463+
}
464+
465+
TEST(raw_ostreamTest, raw_fd_ostream_mutual_ties) {
466+
SmallString<64> PathTiedTo;
467+
int FDTiedTo;
468+
ASSERT_FALSE(
469+
sys::fs::createTemporaryFile("tietest1", "", FDTiedTo, PathTiedTo));
470+
FileRemover CleanupTiedTo(PathTiedTo);
471+
raw_fd_ostream TiedTo(FDTiedTo, /*ShouldClose=*/false);
472+
473+
SmallString<64> PathTiedStream;
474+
int FDTiedStream;
475+
ASSERT_FALSE(sys::fs::createTemporaryFile("tietest2", "", FDTiedStream,
476+
PathTiedStream));
477+
FileRemover CleanupTiedStream(PathTiedStream);
478+
raw_fd_ostream TiedStream(FDTiedStream, /*ShouldClose=*/false);
479+
480+
// Streams can be tied to each other safely.
481+
TiedStream.tie(&TiedTo);
482+
TiedStream.SetBuffered();
483+
TiedStream.SetBufferSize(2);
441484
TiedTo.tie(&TiedStream);
442485
TiedTo.SetBufferSize(2);
443486
TiedStream << "r";
444487
TiedTo << "s";
445-
EXPECT_EQ("", Buffer);
446-
EXPECT_EQ("acego", TiedToBuffer);
488+
checkFileData(PathTiedStream.str(), "");
489+
checkFileData(PathTiedTo.str(), "");
447490
TiedTo << "tuv";
448-
EXPECT_EQ("r", Buffer);
491+
checkFileData(PathTiedStream.str(), "r");
449492
TiedStream << "wxy";
450-
EXPECT_EQ("acegostuv", TiedToBuffer);
451-
// The x remains in the buffer, since it was written after the flush of
493+
checkFileData(PathTiedTo.str(), "stuv");
494+
// The y remains in the buffer, since it was written after the flush of
452495
// TiedTo.
453-
EXPECT_EQ("rwx", Buffer);
454-
TiedTo.tie(nullptr);
455-
456-
// Calling tie with nullptr unties stream.
457-
TiedStream.SetUnbuffered();
458-
TiedStream.tie(nullptr);
459-
TiedTo << "y";
460-
TiedStream << "0";
461-
EXPECT_EQ("acegostuv", TiedToBuffer);
496+
checkFileData(PathTiedStream.str(), "rwx");
462497

463498
TiedTo.flush();
464499
TiedStream.flush();
@@ -478,17 +513,6 @@ TEST(raw_ostreamTest, reserve_stream) {
478513
EXPECT_EQ("11111111111111111111hello1world", Str);
479514
}
480515

481-
static void checkFileData(StringRef FileName, StringRef GoldenData) {
482-
ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
483-
MemoryBuffer::getFileOrSTDIN(FileName);
484-
EXPECT_FALSE(BufOrErr.getError());
485-
486-
EXPECT_EQ((*BufOrErr)->getBufferSize(), GoldenData.size());
487-
EXPECT_EQ(memcmp((*BufOrErr)->getBufferStart(), GoldenData.data(),
488-
GoldenData.size()),
489-
0);
490-
}
491-
492516
TEST(raw_ostreamTest, writeToOutputFile) {
493517
SmallString<64> Path;
494518
int FD;

0 commit comments

Comments
 (0)