Skip to content

[FileSystem] Allow exclusive file lock #114098

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 1 commit into
base: users/cachemeifyoucan/spr/main.filesystem-allow-exclusive-file-lock
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: 6 additions & 2 deletions llvm/include/llvm/Support/FileSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -1184,12 +1184,16 @@ openNativeFileForRead(const Twine &Name, OpenFlags Flags = OF_None,
/// descriptor.
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.
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.
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 @@ -1223,13 +1223,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 @@ -1238,15 +1239,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 @@ -1327,8 +1327,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 @@ -1338,6 +1340,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 @@ -1346,8 +1350,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 @@ -561,6 +562,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
Loading
You are viewing a condensed version of this merge commit. You can view the full changes here.