diff --git a/llvm/include/llvm/Support/FileSystem.h b/llvm/include/llvm/Support/FileSystem.h index ee7a77c578747..a21b0a272d2b0 100644 --- a/llvm/include/llvm/Support/FileSystem.h +++ b/llvm/include/llvm/Support/FileSystem.h @@ -1171,6 +1171,12 @@ LLVM_ABI Expected openNativeFileForRead(const Twine &Name, OpenFlags Flags = OF_None, SmallVectorImpl *RealPath = nullptr); +/// An enumeration for the lock kind. +enum class LockKind { + Exclusive, // Exclusive/writer lock + Shared // Shared/reader lock +}; + /// Try to locks the file during the specified time. /// /// This function implements advisory locking on entire file. If it returns @@ -1184,6 +1190,7 @@ openNativeFileForRead(const Twine &Name, OpenFlags Flags = OF_None, /// @param Timeout Time in milliseconds that the process should wait before /// reporting lock failure. Zero value means try to get lock only /// once. +/// @param Kind The kind of the lock used (exclusive/shared). /// @returns errc::success if lock is successfully obtained, /// errc::no_lock_available if the file cannot be locked, or platform-specific /// error_code otherwise. @@ -1194,12 +1201,15 @@ openNativeFileForRead(const Twine &Name, OpenFlags Flags = OF_None, /// descriptor. LLVM_ABI std::error_code tryLockFile(int FD, - std::chrono::milliseconds Timeout = std::chrono::milliseconds(0)); + std::chrono::milliseconds Timeout = std::chrono::milliseconds(0), + LockKind Kind = LockKind::Exclusive); /// Lock the file. /// /// This function acts as @ref tryLockFile but it waits infinitely. -LLVM_ABI std::error_code lockFile(int FD); +/// \param FD file descriptor to use for locking. +/// \param Kind of lock to used (exclusive/shared). +LLVM_ABI std::error_code lockFile(int FD, LockKind Kind = LockKind::Exclusive); /// Unlock the file. /// diff --git a/llvm/lib/Support/Unix/Path.inc b/llvm/lib/Support/Unix/Path.inc index 31fb1e8fe9b75..2f563e2899b56 100644 --- a/llvm/lib/Support/Unix/Path.inc +++ b/llvm/lib/Support/Unix/Path.inc @@ -1230,13 +1230,21 @@ Expected readNativeFileSlice(file_t FD, MutableArrayRef Buf, return NumRead; } -std::error_code tryLockFile(int FD, std::chrono::milliseconds Timeout) { +std::error_code tryLockFile(int FD, std::chrono::milliseconds Timeout, + LockKind Kind) { auto Start = std::chrono::steady_clock::now(); auto End = Start + Timeout; do { struct flock Lock; memset(&Lock, 0, sizeof(Lock)); - Lock.l_type = F_WRLCK; + switch (Kind) { + case LockKind::Exclusive: + Lock.l_type = F_WRLCK; + break; + case LockKind::Shared: + Lock.l_type = F_RDLCK; + break; + } Lock.l_whence = SEEK_SET; Lock.l_start = 0; Lock.l_len = 0; @@ -1245,15 +1253,24 @@ std::error_code tryLockFile(int FD, std::chrono::milliseconds Timeout) { int Error = errno; if (Error != EACCES && Error != EAGAIN) return std::error_code(Error, std::generic_category()); + if (Timeout.count() == 0) + break; usleep(1000); } while (std::chrono::steady_clock::now() < End); return make_error_code(errc::no_lock_available); } -std::error_code lockFile(int FD) { +std::error_code lockFile(int FD, LockKind Kind) { struct flock Lock; memset(&Lock, 0, sizeof(Lock)); - Lock.l_type = F_WRLCK; + switch (Kind) { + case LockKind::Exclusive: + Lock.l_type = F_WRLCK; + break; + case LockKind::Shared: + Lock.l_type = F_RDLCK; + break; + } Lock.l_whence = SEEK_SET; Lock.l_start = 0; Lock.l_len = 0; diff --git a/llvm/lib/Support/Windows/Path.inc b/llvm/lib/Support/Windows/Path.inc index 9001c19c057cf..6672d8e0ec777 100644 --- a/llvm/lib/Support/Windows/Path.inc +++ b/llvm/lib/Support/Windows/Path.inc @@ -1337,8 +1337,10 @@ Expected readNativeFileSlice(file_t FileHandle, return readNativeFileImpl(FileHandle, Buf, &Overlapped); } -std::error_code tryLockFile(int FD, std::chrono::milliseconds Timeout) { - DWORD Flags = LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY; +std::error_code tryLockFile(int FD, std::chrono::milliseconds Timeout, + LockKind Kind) { + DWORD Flags = Kind == LockKind::Exclusive ? LOCKFILE_EXCLUSIVE_LOCK : 0; + Flags |= LOCKFILE_FAIL_IMMEDIATELY; OVERLAPPED OV = {}; file_t File = convertFDToNativeFile(FD); auto Start = std::chrono::steady_clock::now(); @@ -1348,6 +1350,8 @@ std::error_code tryLockFile(int FD, std::chrono::milliseconds Timeout) { return std::error_code(); DWORD Error = ::GetLastError(); if (Error == ERROR_LOCK_VIOLATION) { + if (Timeout.count() == 0) + break; ::Sleep(1); continue; } @@ -1356,8 +1360,8 @@ std::error_code tryLockFile(int FD, std::chrono::milliseconds Timeout) { return mapWindowsError(ERROR_LOCK_VIOLATION); } -std::error_code lockFile(int FD) { - DWORD Flags = LOCKFILE_EXCLUSIVE_LOCK; +std::error_code lockFile(int FD, LockKind Kind) { + DWORD Flags = Kind == LockKind::Exclusive ? LOCKFILE_EXCLUSIVE_LOCK : 0; OVERLAPPED OV = {}; file_t File = convertFDToNativeFile(FD); if (::LockFileEx(File, Flags, 0, MAXDWORD, MAXDWORD, &OV)) diff --git a/llvm/unittests/Support/ProgramTest.cpp b/llvm/unittests/Support/ProgramTest.cpp index 693b53b0a9781..d30bf458f233c 100644 --- a/llvm/unittests/Support/ProgramTest.cpp +++ b/llvm/unittests/Support/ProgramTest.cpp @@ -10,6 +10,7 @@ #include "llvm/Config/llvm-config.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/ConvertUTF.h" +#include "llvm/Support/ExponentialBackoff.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include "llvm/Support/Signals.h" @@ -573,6 +574,88 @@ TEST_F(ProgramEnvTest, TestLockFile) { sys::fs::remove(LockedFile); } +TEST_F(ProgramEnvTest, TestLockFileExclusive) { + using namespace llvm::sys; + using namespace std::chrono_literals; + + if (const char *LockedFile = getenv("LLVM_PROGRAM_TEST_LOCKED_FILE")) { + // Child process. + int FD2; + ASSERT_NO_ERROR(fs::openFileForReadWrite(LockedFile, FD2, + fs::CD_OpenExisting, fs::OF_None)); + + // File should currently be non-exclusive locked by the main process, thus + // trying to acquire exclusive lock will fail and trying to acquire + // non-exclusive will succeed. + EXPECT_TRUE( + fs::tryLockFile(FD2, std::chrono::seconds(0), fs::LockKind::Exclusive)); + + EXPECT_FALSE( + fs::tryLockFile(FD2, std::chrono::seconds(0), fs::LockKind::Shared)); + + close(FD2); + // Write a file to indicate just finished. + std::string FinishFile = std::string(LockedFile) + "-finished"; + int FD3; + ASSERT_NO_ERROR(fs::openFileForReadWrite(FinishFile, FD3, fs::CD_CreateNew, + fs::OF_None)); + close(FD3); + exit(0); + } + + // Create file that will be locked. + SmallString<64> LockedFile; + int FD1; + ASSERT_NO_ERROR( + fs::createUniqueDirectory("TestLockFileExclusive", LockedFile)); + sys::path::append(LockedFile, "file"); + ASSERT_NO_ERROR( + fs::openFileForReadWrite(LockedFile, FD1, fs::CD_CreateNew, fs::OF_None)); + + std::string Executable = + sys::fs::getMainExecutable(TestMainArgv0, &ProgramTestStringArg1); + StringRef argv[] = {Executable, + "--gtest_filter=ProgramEnvTest.TestLockFileExclusive"}; + + // Add LLVM_PROGRAM_TEST_LOCKED_FILE to the environment of the child. + std::string EnvVar = "LLVM_PROGRAM_TEST_LOCKED_FILE="; + EnvVar += LockedFile.str(); + addEnvVar(EnvVar); + + // Lock the file. + ASSERT_NO_ERROR( + fs::tryLockFile(FD1, std::chrono::seconds(0), fs::LockKind::Exclusive)); + + std::string Error; + bool ExecutionFailed; + ProcessInfo PI2 = ExecuteNoWait(Executable, argv, getEnviron(), {}, 0, &Error, + &ExecutionFailed); + ASSERT_FALSE(ExecutionFailed) << Error; + ASSERT_TRUE(Error.empty()); + ASSERT_NE(PI2.Pid, ProcessInfo::InvalidPid) << "Invalid process id"; + + std::string FinishFile = std::string(LockedFile) + "-finished"; + // Wait till child process writes the file to indicate the job finished. + bool Finished = false; + ExponentialBackoff Backoff(5s); // timeout 5s. + do { + if (fs::exists(FinishFile)) { + Finished = true; + break; + } + } while (Backoff.waitForNextAttempt()); + + ASSERT_TRUE(Finished); + ASSERT_NO_ERROR(fs::unlockFile(FD1)); + ProcessInfo WaitResult = llvm::sys::Wait(PI2, /*SecondsToWait=*/1, &Error); + ASSERT_TRUE(Error.empty()); + ASSERT_EQ(0, WaitResult.ReturnCode); + ASSERT_EQ(WaitResult.Pid, PI2.Pid); + sys::fs::remove(LockedFile); + sys::fs::remove(FinishFile); + sys::fs::remove_directories(sys::path::parent_path(LockedFile)); +} + TEST_F(ProgramEnvTest, TestExecuteWithNoStacktraceHandler) { using namespace llvm::sys;