LLVM 22.0.0git
LockFileManager.cpp
Go to the documentation of this file.
1//===--- LockFileManager.cpp - File-level Locking Utility------------------===//
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
12#include "llvm/Config/llvm-config.h" // for LLVM_ON_UNIX
13#include "llvm/Support/Errc.h"
22#include <cerrno>
23#include <chrono>
24#include <ctime>
25#include <memory>
26#include <system_error>
27#include <tuple>
28
29#ifdef _WIN32
30#include <windows.h>
31#endif
32#if LLVM_ON_UNIX
33#include <unistd.h>
34#endif
35
36#if defined(__APPLE__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > 1050)
37#define USE_OSX_GETHOSTUUID 1
38#else
39#define USE_OSX_GETHOSTUUID 0
40#endif
41
42#if USE_OSX_GETHOSTUUID
43#include <uuid/uuid.h>
44#endif
45
46using namespace llvm;
47
48/// Attempt to read the lock file with the given name, if it exists.
49///
50/// \param LockFileName The name of the lock file to read.
51///
52/// \returns The process ID of the process that owns this lock file
53std::optional<LockFileManager::OwnedByAnother>
54LockFileManager::readLockFile(StringRef LockFileName) {
55 // Read the owning host and PID out of the lock file. If it appears that the
56 // owning process is dead, the lock file is invalid.
58 MemoryBuffer::getFile(LockFileName);
59 if (!MBOrErr) {
60 sys::fs::remove(LockFileName);
61 return std::nullopt;
62 }
63 MemoryBuffer &MB = *MBOrErr.get();
64
65 StringRef Hostname;
66 StringRef PIDStr;
67 std::tie(Hostname, PIDStr) = getToken(MB.getBuffer(), " ");
68 PIDStr = PIDStr.substr(PIDStr.find_first_not_of(' '));
69 int PID;
70 if (!PIDStr.getAsInteger(10, PID)) {
71 OwnedByAnother Owner;
72 Owner.OwnerHostName = Hostname;
73 Owner.OwnerPID = PID;
74 if (processStillExecuting(Owner.OwnerHostName, Owner.OwnerPID))
75 return Owner;
76 }
77
78 // Delete the lock file. It's invalid anyway.
79 sys::fs::remove(LockFileName);
80 return std::nullopt;
81}
82
83static std::error_code getHostID(SmallVectorImpl<char> &HostID) {
84 HostID.clear();
85
86#if USE_OSX_GETHOSTUUID
87 // On OS X, use the more stable hardware UUID instead of hostname.
88 struct timespec wait = {1, 0}; // 1 second.
89 uuid_t uuid;
90 if (gethostuuid(uuid, &wait) != 0)
91 return errnoAsErrorCode();
92
93 uuid_string_t UUIDStr;
94 uuid_unparse(uuid, UUIDStr);
95 StringRef UUIDRef(UUIDStr);
96 HostID.append(UUIDRef.begin(), UUIDRef.end());
97
98#elif LLVM_ON_UNIX
99 char HostName[256];
100 HostName[255] = 0;
101 HostName[0] = 0;
102 gethostname(HostName, 255);
103 StringRef HostNameRef(HostName);
104 HostID.append(HostNameRef.begin(), HostNameRef.end());
105
106#else
107 StringRef Dummy("localhost");
108 HostID.append(Dummy.begin(), Dummy.end());
109#endif
110
111 return std::error_code();
112}
113
114bool LockFileManager::processStillExecuting(StringRef HostID, int PID) {
115#if LLVM_ON_UNIX && !defined(__ANDROID__)
116 SmallString<256> StoredHostID;
117 if (getHostID(StoredHostID))
118 return true; // Conservatively assume it's executing on error.
119
120 // Check whether the process is dead. If so, we're done.
121 if (StoredHostID == HostID && getsid(PID) == -1 && errno == ESRCH)
122 return false;
123#endif
124
125 return true;
126}
127
128namespace {
129
130/// An RAII helper object ensure that the unique lock file is removed.
131///
132/// Ensures that if there is an error or a signal before we finish acquiring the
133/// lock, the unique file will be removed. And if we successfully take the lock,
134/// the signal handler is left in place so that signals while the lock is held
135/// will remove the unique lock file. The caller should ensure there is a
136/// matching call to sys::DontRemoveFileOnSignal when the lock is released.
137class RemoveUniqueLockFileOnSignal {
138 StringRef Filename;
139 bool RemoveImmediately;
140public:
141 RemoveUniqueLockFileOnSignal(StringRef Name)
142 : Filename(Name), RemoveImmediately(true) {
143 sys::RemoveFileOnSignal(Filename, nullptr);
144 }
145
146 ~RemoveUniqueLockFileOnSignal() {
147 if (!RemoveImmediately) {
148 // Leave the signal handler enabled. It will be removed when the lock is
149 // released.
150 return;
151 }
152 sys::fs::remove(Filename);
154 }
155
156 void lockAcquired() { RemoveImmediately = false; }
157};
158
159} // end anonymous namespace
160
161LockFileManager::LockFileManager(StringRef FileName)
162 : FileName(FileName), Owner(OwnerUnknown{}) {}
163
165 auto BypassSandbox = sys::sandbox::scopedDisable();
166
167 assert(std::holds_alternative<OwnerUnknown>(Owner) &&
168 "lock has already been attempted");
169
170 SmallString<128> AbsoluteFileName(FileName);
171 if (std::error_code EC = sys::fs::make_absolute(AbsoluteFileName))
172 return createStringError(EC, "failed to obtain absolute path for " +
173 AbsoluteFileName);
174 LockFileName = AbsoluteFileName;
175 LockFileName += ".lock";
176
177 // If the lock file already exists, don't bother to try to create our own
178 // lock file; it won't work anyway. Just figure out who owns this lock file.
179 if (auto LockFileOwner = readLockFile(LockFileName)) {
180 Owner = std::move(*LockFileOwner);
181 return false;
182 }
183
184 // Create a lock file that is unique to this instance.
185 UniqueLockFileName = LockFileName;
186 UniqueLockFileName += "-%%%%%%%%";
187 int UniqueLockFileID;
188 if (std::error_code EC = sys::fs::createUniqueFile(
189 UniqueLockFileName, UniqueLockFileID, UniqueLockFileName))
190 return createStringError(EC, "failed to create unique file " +
191 UniqueLockFileName);
192
193 // Clean up the unique file on signal or scope exit.
194 RemoveUniqueLockFileOnSignal RemoveUniqueFile(UniqueLockFileName);
195
196 // Write our process ID to our unique lock file.
197 {
198 SmallString<256> HostID;
199 if (auto EC = getHostID(HostID))
200 return createStringError(EC, "failed to get host id");
201
202 raw_fd_ostream Out(UniqueLockFileID, /*shouldClose=*/true);
203 Out << HostID << ' ' << sys::Process::getProcessId();
204 Out.close();
205
206 if (Out.has_error()) {
207 // We failed to write out PID, so report the error and fail.
208 Error Err = createStringError(Out.error(),
209 "failed to write to " + UniqueLockFileName);
210 // Don't call report_fatal_error.
211 Out.clear_error();
212 return std::move(Err);
213 }
214 }
215
216 while (true) {
217 // Create a link from the lock file name. If this succeeds, we're done.
218 std::error_code EC =
219 sys::fs::create_link(UniqueLockFileName, LockFileName);
220 if (!EC) {
221 RemoveUniqueFile.lockAcquired();
222 Owner = OwnedByUs{};
223 return true;
224 }
225
226 if (EC != errc::file_exists)
227 return createStringError(EC, "failed to create link " + LockFileName +
228 " to " + UniqueLockFileName);
229
230 // Someone else managed to create the lock file first. Read the process ID
231 // from the lock file.
232 if (auto LockFileOwner = readLockFile(LockFileName)) {
233 Owner = std::move(*LockFileOwner);
234 return false;
235 }
236
237 if (!sys::fs::exists(LockFileName)) {
238 // The previous owner released the lock file before we could read it.
239 // Try to get ownership again.
240 continue;
241 }
242
243 // There is a lock file that nobody owns; try to clean it up and get
244 // ownership.
245 if ((EC = sys::fs::remove(LockFileName)))
246 return createStringError(EC, "failed to remove lockfile " +
247 UniqueLockFileName);
248 }
249}
250
252 auto BypassSandbox = sys::sandbox::scopedDisable();
253
254 if (!std::holds_alternative<OwnedByUs>(Owner))
255 return;
256
257 // Since we own the lock, remove the lock file and our own unique lock file.
258 sys::fs::remove(LockFileName);
259 sys::fs::remove(UniqueLockFileName);
260 // The unique file is now gone, so remove it from the signal handler. This
261 // matches a sys::RemoveFileOnSignal() in LockFileManager().
262 sys::DontRemoveFileOnSignal(UniqueLockFileName);
263}
264
266LockFileManager::waitForUnlockFor(std::chrono::seconds MaxSeconds) {
267 auto BypassSandbox = sys::sandbox::scopedDisable();
268
269 auto *LockFileOwner = std::get_if<OwnedByAnother>(&Owner);
270 assert(LockFileOwner &&
271 "waiting for lock to be unlocked without knowing the owner");
272
273 // Since we don't yet have an event-based method to wait for the lock file,
274 // use randomized exponential backoff, similar to Ethernet collision
275 // algorithm. This improves performance on machines with high core counts
276 // when the file lock is heavily contended by multiple clang processes
277 using namespace std::chrono_literals;
278 ExponentialBackoff Backoff(MaxSeconds, 10ms, 500ms);
279
280 // Wait first as this is only called when the lock is known to be held.
281 while (Backoff.waitForNextAttempt()) {
282 // FIXME: implement event-based waiting
283 if (sys::fs::access(LockFileName.c_str(), sys::fs::AccessMode::Exist) ==
286
287 // If the process owning the lock died without cleaning up, just bail out.
288 if (!processStillExecuting(LockFileOwner->OwnerHostName,
289 LockFileOwner->OwnerPID))
291 }
292
293 // Give up.
295}
296
298 auto BypassSandbox = sys::sandbox::scopedDisable();
299
300 return sys::fs::remove(LockFileName);
301}
assert(UImm &&(UImm !=~static_cast< T >(0)) &&"Invalid immediate!")
basic Basic Alias true
static ManagedStatic< DebugCounterOwner > Owner
Provides ErrorOr<T> smart pointer.
static std::error_code getHostID(SmallVectorImpl< char > &HostID)
Provides a library for accessing information about this process and other processes on the operating ...
This file defines the SmallVector class.
This file contains some functions that are useful when dealing with strings.
Represents either an error or a value T.
Definition ErrorOr.h:56
reference get()
Definition ErrorOr.h:149
Lightweight error class with error context and mandatory checking.
Definition Error.h:159
Tagged union holding either a T or a Error.
Definition Error.h:485
A class to help implement exponential backoff.
LLVM_ABI bool waitForNextAttempt()
Blocks while waiting for the next attempt.
std::error_code unsafeMaybeUnlock() override
Remove the lock file.
WaitForUnlockResult waitForUnlockFor(std::chrono::seconds MaxSeconds) override
For a shared lock, wait until the owner releases the lock.
Expected< bool > tryLock() override
Tries to acquire the lock without blocking.
~LockFileManager() override
Unlocks the lock if previously acquired by tryLock().
This interface provides simple read-only access to a block of memory, and provides simple methods for...
StringRef getBuffer() const
static ErrorOr< std::unique_ptr< MemoryBuffer > > getFile(const Twine &Filename, bool IsText=false, bool RequiresNullTerminator=true, bool IsVolatile=false, std::optional< Align > Alignment=std::nullopt)
Open the specified file as a MemoryBuffer, returning a new MemoryBuffer if successful,...
SmallString - A SmallString is just a SmallVector with methods and accessors that make it work better...
Definition SmallString.h:26
This class consists of common code factored out of the SmallVector class to reduce code duplication b...
void append(ItTy in_start, ItTy in_end)
Add the specified range to the end of the SmallVector.
StringRef - Represent a constant reference to a string, i.e.
Definition StringRef.h:55
bool getAsInteger(unsigned Radix, T &Result) const
Parse the current string as an integer of the specified radix.
Definition StringRef.h:472
constexpr StringRef substr(size_t Start, size_t N=npos) const
Return a reference to the substring from [Start, Start + N).
Definition StringRef.h:573
iterator begin() const
Definition StringRef.h:112
iterator end() const
Definition StringRef.h:114
LLVM_ABI size_t find_first_not_of(char C, size_t From=0) const
Find the first character in the string that is not C or npos if not found.
A raw_ostream that writes to a file descriptor.
bool has_error() const
Return the value of the flag in this raw_fd_ostream indicating whether an output error has been encou...
std::error_code error() const
void close()
Manually flush the stream and close the file.
void clear_error()
Set the flag read by has_error() to false.
static LLVM_ABI Pid getProcessId()
Get the process's identifier.
LLVM_ABI std::error_code access(const Twine &Path, AccessMode Mode)
Can the file be accessed?
LLVM_ABI bool exists(const basic_file_status &status)
Does file exist?
Definition Path.cpp:1086
LLVM_ABI std::error_code create_link(const Twine &to, const Twine &from)
Create a link from from to to.
LLVM_ABI std::error_code createUniqueFile(const Twine &Model, int &ResultFD, SmallVectorImpl< char > &ResultPath, OpenFlags Flags=OF_None, unsigned Mode=all_read|all_write)
Create a uniquely named file.
Definition Path.cpp:874
LLVM_ABI std::error_code remove(const Twine &path, bool IgnoreNonExisting=true)
Remove path.
LLVM_ABI std::error_code make_absolute(SmallVectorImpl< char > &path)
Make path an absolute path.
Definition Path.cpp:958
ScopedSetting scopedDisable()
Definition IOSandbox.h:36
LLVM_ABI void DontRemoveFileOnSignal(StringRef Filename)
This function removes a file from the list of files to be removed on signal delivery.
LLVM_ABI bool RemoveFileOnSignal(StringRef Filename, std::string *ErrMsg=nullptr)
This function registers signal handlers to ensure that if a signal gets delivered that the named file...
This is an optimization pass for GlobalISel generic memory operations.
LLVM_ABI std::pair< StringRef, StringRef > getToken(StringRef Source, StringRef Delimiters=" \t\n\v\f\r")
getToken - This function extracts one token from source, ignoring any leading characters that appear ...
Error createStringError(std::error_code EC, char const *Fmt, const Ts &... Vals)
Create formatted StringError object.
Definition Error.h:1305
@ no_such_file_or_directory
Definition Errc.h:65
@ file_exists
Definition Errc.h:48
WaitForUnlockResult
Describes the result of waiting for the owner to release the lock.
@ Success
The lock was released successfully.
@ OwnerDied
Owner died while holding the lock.
@ Timeout
Reached timeout while waiting for the owner to release the lock.
std::error_code errnoAsErrorCode()
Helper to get errno as an std::error_code.
Definition Error.h:1240