Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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: 6 additions & 2 deletions llvm/include/llvm/Support/FileSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -1194,12 +1194,16 @@ 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),
bool Exclusive = true);

/// 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 Exclusive if \p true use exclusive/writer lock, otherwise use
/// shared/reader lock.
LLVM_ABI std::error_code lockFile(int FD, bool Exclusive = true);

/// Unlock the file.
///
Expand Down
11 changes: 7 additions & 4 deletions llvm/lib/Support/Unix/Path.inc
Original file line number Diff line number Diff line change
Expand Up @@ -1224,13 +1224,14 @@ Expected<size_t> readNativeFileSlice(file_t FD, MutableArrayRef<char> Buf,
return NumRead;
}

std::error_code tryLockFile(int FD, std::chrono::milliseconds Timeout) {
std::error_code tryLockFile(int FD, std::chrono::milliseconds Timeout,
bool Exclusive) {
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;
Lock.l_type = Exclusive ? F_WRLCK : F_RDLCK;
Lock.l_whence = SEEK_SET;
Lock.l_start = 0;
Lock.l_len = 0;
Expand All @@ -1239,15 +1240,17 @@ 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, bool Exclusive) {
struct flock Lock;
memset(&Lock, 0, sizeof(Lock));
Lock.l_type = F_WRLCK;
Lock.l_type = Exclusive ? F_WRLCK : F_RDLCK;
Lock.l_whence = SEEK_SET;
Lock.l_start = 0;
Lock.l_len = 0;
Expand Down
12 changes: 8 additions & 4 deletions llvm/lib/Support/Windows/Path.inc
Original file line number Diff line number Diff line change
Expand Up @@ -1329,8 +1329,10 @@ Expected<size_t> 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,
bool Exclusive) {
DWORD Flags = Exclusive ? LOCKFILE_EXCLUSIVE_LOCK : 0;
Flags |= LOCKFILE_FAIL_IMMEDIATELY;
OVERLAPPED OV = {};
file_t File = convertFDToNativeFile(FD);
auto Start = std::chrono::steady_clock::now();
Expand All @@ -1340,6 +1342,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;
}
Expand All @@ -1348,8 +1352,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, bool Exclusive) {
DWORD Flags = Exclusive ? LOCKFILE_EXCLUSIVE_LOCK : 0;
OVERLAPPED OV = {};
file_t File = convertFDToNativeFile(FD);
if (::LockFileEx(File, Flags, 0, MAXDWORD, MAXDWORD, &OV))
Expand Down
76 changes: 76 additions & 0 deletions llvm/unittests/Support/ProgramTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -573,6 +574,81 @@ 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));

std::error_code ErrC =
fs::tryLockFile(FD2, std::chrono::seconds(0), /*Exclusive=*/true);
EXPECT_TRUE(ErrC);
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::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;

Expand Down