Skip to content

Commit 7ca8cdc

Browse files
[VirtualOutputBackend] Support OF_Append output files
Add support for appending output file types with both atomic and non-atomic update to the output file. Atomic append is implemented with a `llvm::LockFileManager` that locks the output file while appending to it.
1 parent 844472e commit 7ca8cdc

File tree

6 files changed

+113
-4
lines changed

6 files changed

+113
-4
lines changed

llvm/include/llvm/Support/VirtualOutputConfig.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
HANDLE_OUTPUT_CONFIG_FLAG(Text, false) // OF_Text.
1818
HANDLE_OUTPUT_CONFIG_FLAG(CRLF, false) // OF_CRLF.
19+
HANDLE_OUTPUT_CONFIG_FLAG(Append, false) // OF_Append.
1920
HANDLE_OUTPUT_CONFIG_FLAG(DiscardOnSignal, true) // E.g., RemoveFileOnSignal.
2021
HANDLE_OUTPUT_CONFIG_FLAG(AtomicWrite, true) // E.g., use temporaries.
2122
HANDLE_OUTPUT_CONFIG_FLAG(ImplyCreateDirectories, true)

llvm/include/llvm/Support/VirtualOutputConfig.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ struct OutputConfig : detail::EmptyBaseClass {
5353
constexpr OutputConfig &setTextWithCRLF(bool Value) {
5454
return Value ? setText().setCRLF() : setBinary();
5555
}
56-
constexpr bool getTextWithCRLF() { return getText() && getCRLF(); }
57-
constexpr bool getBinary() { return !getText(); }
56+
constexpr bool getTextWithCRLF() const { return getText() && getCRLF(); }
57+
constexpr bool getBinary() const { return !getText(); }
5858

5959
/// Updates Text and CRLF flags based on \a sys::fs::OF_Text and \a
6060
/// sys::fs::OF_CRLF in \p Flags. Rejects CRLF without Text (calling

llvm/lib/Support/VirtualOutputBackends.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include "llvm/Support/VirtualOutputBackends.h"
1414
#include "llvm/ADT/ScopeExit.h"
1515
#include "llvm/Support/FileSystem.h"
16+
#include "llvm/Support/LockFileManager.h"
17+
#include "llvm/Support/MemoryBuffer.h"
1618
#include "llvm/Support/Path.h"
1719
#include "llvm/Support/Process.h"
1820
#include "llvm/Support/Signals.h"
@@ -306,6 +308,8 @@ Error OnDiskOutputFile::initializeFD(Optional<int> &FD) {
306308
OF |= sys::fs::OF_TextWithCRLF;
307309
else if (Config.getText())
308310
OF |= sys::fs::OF_Text;
311+
if (Config.getAppend())
312+
OF |= sys::fs::OF_Append;
309313
if (std::error_code EC = sys::fs::openFileForWrite(
310314
OutputPath, NewFD, sys::fs::CD_CreateAlways, OF))
311315
return convertToOutputError(OutputPath, EC);
@@ -448,6 +452,64 @@ Error OnDiskOutputFile::keep() {
448452
if (!TempPath)
449453
return Error::success();
450454

455+
// See if we should append instead of move.
456+
if (Config.getAppend() && OutputPath != "-") {
457+
// Read TempFile for the content to append.
458+
auto Content = MemoryBuffer::getFile(*TempPath);
459+
if (!Content)
460+
return convertToTempFileOutputError(*TempPath, OutputPath,
461+
Content.getError());
462+
while (1) {
463+
// Attempt to lock the output file.
464+
// Only one process is allowed to append to this file at a time.
465+
llvm::LockFileManager Locked(OutputPath);
466+
switch (Locked) {
467+
case llvm::LockFileManager::LFS_Error: {
468+
// If we error acquiring a lock, we cannot ensure appends
469+
// to the trace file are atomic - cannot ensure output correctness.
470+
Locked.unsafeRemoveLockFile();
471+
return convertToOutputError(
472+
OutputPath, std::make_error_code(std::errc::no_lock_available));
473+
}
474+
case llvm::LockFileManager::LFS_Owned: {
475+
// Lock acquired, perform the write and release the lock.
476+
std::error_code EC;
477+
llvm::raw_fd_ostream Out(OutputPath, EC, llvm::sys::fs::OF_Append);
478+
if (EC)
479+
return convertToOutputError(OutputPath, EC);
480+
Out << (*Content)->getBuffer();
481+
Out.close();
482+
Locked.unsafeRemoveLockFile();
483+
if (Out.has_error())
484+
return convertToOutputError(OutputPath, Out.error());
485+
// Remove temp file and done.
486+
(void)sys::fs::remove(*TempPath);
487+
return Error::success();
488+
}
489+
case llvm::LockFileManager::LFS_Shared: {
490+
// Someone else owns the lock on this file, wait.
491+
switch (Locked.waitForUnlock(256)) {
492+
case llvm::LockFileManager::Res_Success:
493+
LLVM_FALLTHROUGH;
494+
case llvm::LockFileManager::Res_OwnerDied: {
495+
continue; // try again to get the lock.
496+
}
497+
case llvm::LockFileManager::Res_Timeout: {
498+
// We could error on timeout to avoid potentially hanging forever, but
499+
// it may be more likely that an interrupted process failed to clear
500+
// the lock, causing other waiting processes to time-out. Let's clear
501+
// the lock and try again right away. If we do start seeing compiler
502+
// hangs in this location, we will need to re-consider.
503+
Locked.unsafeRemoveLockFile();
504+
continue;
505+
}
506+
}
507+
break;
508+
}
509+
}
510+
}
511+
}
512+
451513
if (Config.getOnlyIfDifferent()) {
452514
auto Result = areFilesDifferent(*TempPath, OutputPath);
453515
if (!Result)

llvm/lib/Support/VirtualOutputConfig.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ using namespace llvm::vfs;
1717
OutputConfig &OutputConfig::setOpenFlags(const sys::fs::OpenFlags &Flags) {
1818
// Ignore CRLF on its own as invalid.
1919
using namespace llvm::sys::fs;
20-
return Flags & OF_Text ? setText().setCRLF(Flags & OF_CRLF) : setBinary();
20+
return Flags & OF_Text
21+
? setText().setCRLF(Flags & OF_CRLF).setAppend(Flags & OF_Append)
22+
: setBinary().setAppend(Flags & OF_Append);
2123
}
2224

2325
void OutputConfig::print(raw_ostream &OS) const {

llvm/unittests/Support/VirtualOutputBackendsTest.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,47 @@ TEST(OnDiskBackendTest, OnlyIfDifferent) {
807807
EXPECT_NE(Status1.getUniqueID(), Status3.getUniqueID());
808808
}
809809

810+
TEST(OnDiskBackendTest, Append) {
811+
OnDiskOutputBackendProvider Provider;
812+
auto Backend = Provider.createBackend();
813+
std::string FilePath = Provider.getFilePathToCreate();
814+
OutputConfig Config = OutputConfig().setAppend();
815+
816+
OutputFile O1, O2, O3;
817+
// Write first file.
818+
EXPECT_THAT_ERROR(Backend->createFile(FilePath, Config).moveInto(O1),
819+
Succeeded());
820+
O1 << "some data\n";
821+
EXPECT_THAT_ERROR(O1.keep(), Succeeded());
822+
EXPECT_FALSE(O1.isOpen());
823+
824+
OnDiskFile File1(*Provider.D, FilePath);
825+
EXPECT_TRUE(File1.equalsCurrentContent("some data\n"));
826+
827+
// Append same data.
828+
EXPECT_THAT_ERROR(Backend->createFile(FilePath, Config).moveInto(O2),
829+
Succeeded());
830+
O2 << "more data\n";
831+
EXPECT_THAT_ERROR(O2.keep(), Succeeded());
832+
EXPECT_FALSE(O2.isOpen());
833+
834+
// Check data is appended.
835+
OnDiskFile File2(*Provider.D, FilePath);
836+
EXPECT_TRUE(File2.equalsCurrentContent("some data\nmore data\n"));
837+
838+
// Non atomic append.
839+
EXPECT_THAT_ERROR(
840+
Backend->createFile(FilePath, Config.setNoAtomicWrite()).moveInto(O3),
841+
Succeeded());
842+
O3 << "more more\n";
843+
EXPECT_THAT_ERROR(O3.keep(), Succeeded());
844+
EXPECT_FALSE(O3.isOpen());
845+
846+
// Check data is appended.
847+
OnDiskFile File3(*Provider.D, FilePath);
848+
EXPECT_TRUE(File3.equalsCurrentContent("some data\nmore data\nmore more\n"));
849+
}
850+
810851
TEST(HashingBackendTest, HashOutput) {
811852
HashingOutputBackend<BLAKE3> Backend;
812853
OutputFile O1, O2, O3, O4, O5;

llvm/unittests/Support/VirtualOutputConfigTest.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ TEST(VirtualOutputConfigTest, construct) {
2323
EXPECT_TRUE(OutputConfig().getAtomicWrite());
2424
EXPECT_TRUE(OutputConfig().getImplyCreateDirectories());
2525
EXPECT_FALSE(OutputConfig().getOnlyIfDifferent());
26+
EXPECT_FALSE(OutputConfig().getAppend());
2627

2728
// Test inverted defaults.
2829
EXPECT_TRUE(OutputConfig().getNoText());
@@ -31,6 +32,7 @@ TEST(VirtualOutputConfigTest, construct) {
3132
EXPECT_FALSE(OutputConfig().getNoAtomicWrite());
3233
EXPECT_FALSE(OutputConfig().getNoImplyCreateDirectories());
3334
EXPECT_TRUE(OutputConfig().getNoOnlyIfDifferent());
35+
EXPECT_TRUE(OutputConfig().getNoAppend());
3436
}
3537

3638
TEST(VirtualOutputConfigTest, set) {
@@ -124,7 +126,6 @@ TEST(VirtualOutputConfigTest, OpenFlags) {
124126

125127
// Most flags are not supported / have no effect.
126128
EXPECT_EQ(OutputConfig(), OutputConfig().setOpenFlags(OF_None));
127-
EXPECT_EQ(OutputConfig(), OutputConfig().setOpenFlags(OF_Append));
128129
EXPECT_EQ(OutputConfig(), OutputConfig().setOpenFlags(OF_Delete));
129130
EXPECT_EQ(OutputConfig(), OutputConfig().setOpenFlags(OF_ChildInherit));
130131
EXPECT_EQ(OutputConfig(), OutputConfig().setOpenFlags(OF_UpdateAtime));
@@ -134,6 +135,7 @@ TEST(VirtualOutputConfigTest, OpenFlags) {
134135
OutputConfig(),
135136
OutputConfig().setText(),
136137
OutputConfig().setTextWithCRLF(),
138+
OutputConfig().setAppend(),
137139

138140
// Should be overridden despite being invalid.
139141
OutputConfig().setCRLF(),
@@ -143,6 +145,7 @@ TEST(VirtualOutputConfigTest, OpenFlags) {
143145
EXPECT_EQ(OutputConfig().setText(), Init.setOpenFlags(OF_Text));
144146
EXPECT_EQ(OutputConfig().setTextWithCRLF(),
145147
Init.setOpenFlags(OF_TextWithCRLF));
148+
EXPECT_EQ(OutputConfig().setAppend(), Init.setOpenFlags(OF_Append));
146149
}
147150
}
148151

0 commit comments

Comments
 (0)