Skip to content

Commit d8dfdaf

Browse files
authored
[Support] Introduce new AdvisoryLock interface (#130989)
This PR abstracts the `LockFileManager` API into new `AdvisoryLock` interface. This is so that we can create an alternative implementation for Clang implicitly-built modules that is optimized for single-process environment.
1 parent bd0d28a commit d8dfdaf

File tree

5 files changed

+86
-42
lines changed

5 files changed

+86
-42
lines changed

clang/lib/Frontend/CompilerInstance.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,19 +1502,19 @@ static bool compileModuleAndReadASTBehindLock(
15021502

15031503
// Someone else is responsible for building the module. Wait for them to
15041504
// finish.
1505-
switch (Lock.waitForUnlock()) {
1506-
case llvm::LockFileManager::Res_Success:
1505+
switch (Lock.waitForUnlockFor(std::chrono::seconds(90))) {
1506+
case llvm::WaitForUnlockResult::Success:
15071507
break; // The interesting case.
1508-
case llvm::LockFileManager::Res_OwnerDied:
1508+
case llvm::WaitForUnlockResult::OwnerDied:
15091509
continue; // try again to get the lock.
1510-
case llvm::LockFileManager::Res_Timeout:
1510+
case llvm::WaitForUnlockResult::Timeout:
15111511
// Since ModuleCache takes care of correctness, we try waiting for
15121512
// another process to complete the build so clang does not do it done
15131513
// twice. If case of timeout, build it ourselves.
15141514
Diags.Report(ModuleNameLoc, diag::remark_module_lock_timeout)
15151515
<< Module->Name;
15161516
// Clear the lock file so that future invocations can make progress.
1517-
Lock.unsafeRemoveLockFile();
1517+
Lock.unsafeMaybeUnlock();
15181518
continue;
15191519
}
15201520

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_SUPPORT_ADVISORYLOCK_H
10+
#define LLVM_SUPPORT_ADVISORYLOCK_H
11+
12+
#include "llvm/Support/Error.h"
13+
14+
#include <chrono>
15+
16+
namespace llvm {
17+
/// Describes the result of waiting for the owner to release the lock.
18+
enum class WaitForUnlockResult {
19+
/// The lock was released successfully.
20+
Success,
21+
/// Owner died while holding the lock.
22+
OwnerDied,
23+
/// Reached timeout while waiting for the owner to release the lock.
24+
Timeout,
25+
};
26+
27+
/// A synchronization primitive with weak mutual exclusion guarantees.
28+
/// Implementations of this interface may allow multiple threads/processes to
29+
/// acquire the ownership of the lock simultaneously.
30+
/// Typically, threads/processes waiting for the lock to be unlocked will
31+
/// validate that the computation was performed by the expected thread/process
32+
/// and re-run the computation if not.
33+
class AdvisoryLock {
34+
public:
35+
/// Tries to acquire ownership of the lock without blocking.
36+
///
37+
/// \returns true if ownership of the lock was acquired successfully, false if
38+
/// the lock is already owned by someone else, or \c Error in case of an
39+
/// unexpected failure.
40+
virtual Expected<bool> tryLock() = 0;
41+
42+
/// For a lock owned by someone else, wait until it is unlocked.
43+
///
44+
/// \param MaxSeconds the maximum total wait time in seconds.
45+
virtual WaitForUnlockResult
46+
waitForUnlockFor(std::chrono::seconds MaxSeconds) = 0;
47+
48+
/// For a lock owned by someone else, unlock it. A permitted side-effect is
49+
/// that another thread/process may acquire ownership of the lock before the
50+
/// existing owner unlocks it. This is an unsafe operation.
51+
virtual std::error_code unsafeMaybeUnlock() = 0;
52+
53+
/// Unlocks the lock if its ownership was previously acquired by \c tryLock().
54+
virtual ~AdvisoryLock() = default;
55+
};
56+
} // end namespace llvm
57+
58+
#endif

llvm/include/llvm/Support/LockFileManager.h

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,21 @@
99
#define LLVM_SUPPORT_LOCKFILEMANAGER_H
1010

1111
#include "llvm/ADT/SmallString.h"
12-
#include "llvm/Support/Error.h"
12+
#include "llvm/ADT/StringRef.h"
13+
#include "llvm/Support/AdvisoryLock.h"
1314
#include <optional>
1415
#include <string>
15-
#include <system_error>
1616
#include <variant>
1717

1818
namespace llvm {
19-
class StringRef;
20-
2119
/// Class that manages the creation of a lock file to aid implicit coordination
2220
/// between different processes.
2321
///
2422
/// The implicit coordination works by creating a ".lock" file, using the
2523
/// atomicity of the file system to ensure that only a single process can create
2624
/// that ".lock" file. When the lock file is removed, the owning process has
2725
/// finished the operation.
28-
class LockFileManager {
29-
public:
30-
/// Describes the result of waiting for the owner to release the lock.
31-
enum WaitForUnlockResult {
32-
/// The lock was released successfully.
33-
Res_Success,
34-
/// Owner died while holding the lock.
35-
Res_OwnerDied,
36-
/// Reached timeout while waiting for the owner to release the lock.
37-
Res_Timeout
38-
};
39-
40-
private:
26+
class LockFileManager : public AdvisoryLock {
4127
SmallString<128> FileName;
4228
SmallString<128> LockFileName;
4329
SmallString<128> UniqueLockFileName;
@@ -61,24 +47,24 @@ class LockFileManager {
6147
/// Does not try to acquire the lock.
6248
LockFileManager(StringRef FileName);
6349

64-
/// Unlocks the lock if previously acquired by \c tryLock().
65-
~LockFileManager();
66-
6750
/// Tries to acquire the lock without blocking.
6851
/// \returns true if the lock was successfully acquired, false if the lock is
6952
/// already held by someone else, or \c Error in case of unexpected failure.
70-
Expected<bool> tryLock();
53+
Expected<bool> tryLock() override;
7154

7255
/// For a shared lock, wait until the owner releases the lock.
73-
/// Total timeout for the file to appear is ~1.5 minutes.
56+
///
7457
/// \param MaxSeconds the maximum total wait time in seconds.
75-
WaitForUnlockResult waitForUnlock(const unsigned MaxSeconds = 90);
58+
WaitForUnlockResult
59+
waitForUnlockFor(std::chrono::seconds MaxSeconds) override;
7660

7761
/// Remove the lock file. This may delete a different lock file than
7862
/// the one previously read if there is a race.
79-
std::error_code unsafeRemoveLockFile();
80-
};
63+
std::error_code unsafeMaybeUnlock() override;
8164

65+
/// Unlocks the lock if previously acquired by \c tryLock().
66+
~LockFileManager() override;
67+
};
8268
} // end namespace llvm
8369

8470
#endif // LLVM_SUPPORT_LOCKFILEMANAGER_H

llvm/lib/Support/LockFileManager.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,8 @@ LockFileManager::~LockFileManager() {
264264
sys::DontRemoveFileOnSignal(UniqueLockFileName);
265265
}
266266

267-
LockFileManager::WaitForUnlockResult
268-
LockFileManager::waitForUnlock(const unsigned MaxSeconds) {
267+
WaitForUnlockResult
268+
LockFileManager::waitForUnlockFor(std::chrono::seconds MaxSeconds) {
269269
auto *LockFileOwner = std::get_if<OwnedByAnother>(&Owner);
270270
assert(LockFileOwner &&
271271
"waiting for lock to be unlocked without knowing the owner");
@@ -275,25 +275,25 @@ LockFileManager::waitForUnlock(const unsigned MaxSeconds) {
275275
// algorithm. This improves performance on machines with high core counts
276276
// when the file lock is heavily contended by multiple clang processes
277277
using namespace std::chrono_literals;
278-
ExponentialBackoff Backoff(std::chrono::seconds(MaxSeconds), 10ms, 500ms);
278+
ExponentialBackoff Backoff(MaxSeconds, 10ms, 500ms);
279279

280280
// Wait first as this is only called when the lock is known to be held.
281281
while (Backoff.waitForNextAttempt()) {
282282
// FIXME: implement event-based waiting
283283
if (sys::fs::access(LockFileName.c_str(), sys::fs::AccessMode::Exist) ==
284284
errc::no_such_file_or_directory)
285-
return Res_Success;
285+
return WaitForUnlockResult::Success;
286286

287287
// If the process owning the lock died without cleaning up, just bail out.
288288
if (!processStillExecuting(LockFileOwner->OwnerHostName,
289289
LockFileOwner->OwnerPID))
290-
return Res_OwnerDied;
290+
return WaitForUnlockResult::OwnerDied;
291291
}
292292

293293
// Give up.
294-
return Res_Timeout;
294+
return WaitForUnlockResult::Timeout;
295295
}
296296

297-
std::error_code LockFileManager::unsafeRemoveLockFile() {
297+
std::error_code LockFileManager::unsafeMaybeUnlock() {
298298
return sys::fs::remove(LockFileName);
299299
}

llvm/lib/Target/AMDGPU/AMDGPUSplitModule.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1553,17 +1553,17 @@ PreservedAnalyses AMDGPUSplitModulePass::run(Module &M,
15531553
dbgs() << "[amdgpu-split-module] unable to acquire lockfile, debug "
15541554
"output may be mangled by other processes\n");
15551555
} else if (!Owned) {
1556-
switch (Lock.waitForUnlock()) {
1557-
case LockFileManager::Res_Success:
1556+
switch (Lock.waitForUnlockFor(std::chrono::seconds(90))) {
1557+
case WaitForUnlockResult::Success:
15581558
break;
1559-
case LockFileManager::Res_OwnerDied:
1559+
case WaitForUnlockResult::OwnerDied:
15601560
continue; // try again to get the lock.
1561-
case LockFileManager::Res_Timeout:
1561+
case WaitForUnlockResult::Timeout:
15621562
LLVM_DEBUG(
15631563
dbgs()
15641564
<< "[amdgpu-split-module] unable to acquire lockfile, debug "
15651565
"output may be mangled by other processes\n");
1566-
Lock.unsafeRemoveLockFile();
1566+
Lock.unsafeMaybeUnlock();
15671567
break; // give up
15681568
}
15691569
}

0 commit comments

Comments
 (0)