Index: llvm/lib/Support/LockFileManager.cpp =================================================================== --- llvm/lib/Support/LockFileManager.cpp +++ llvm/lib/Support/LockFileManager.cpp @@ -23,6 +23,10 @@ #include #include #include +#include +#include +#include + #ifdef _WIN32 #include #endif @@ -30,7 +34,8 @@ #include #endif -#if defined(__APPLE__) && defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && (__MAC_OS_X_VERSION_MIN_REQUIRED > 1050) +#if defined(__APPLE__) && defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \ + (__MAC_OS_X_VERSION_MIN_REQUIRED > 1050) #define USE_OSX_GETHOSTUUID 1 #else #define USE_OSX_GETHOSTUUID 0 @@ -40,6 +45,21 @@ #include #endif +// Could build a better availability check for kevents +#if defined(__APPLE__) && defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \ + (__MAC_OS_X_VERSION_MIN_REQUIRED > 1030) +#define HAVE_KEVENTS 1 +#else +#define HAVE_KEVENTS 0 +#endif + +#if HAVE_KEVENTS +#include +#include +#include +#include +#endif + using namespace llvm; /// Attempt to read the lock file with the given name, if it exists. @@ -294,27 +314,66 @@ if (getState() != LFS_Shared) return Res_Success; -#ifdef _WIN32 - unsigned long Interval = 1; -#else - struct timespec Interval; - Interval.tv_sec = 0; - Interval.tv_nsec = 1000000; -#endif - // Don't wait more than 40s per iteration. Total timeout for the file - // to appear is ~1.5 minutes. - const unsigned MaxSeconds = 40; + // If we don't have an event-based method to wait for the lock file, + // implement randomized exponential backoff, similar to Ethernet collision + // algorithm. This improves performance on machines with high core counts + // when the file lock is heavily contended by multiple clang processes + const unsigned long MinWaitDurationMS = 10; + const unsigned long MaxWaitMultiplier = 50; // 500ms max wait + unsigned long WaitMultiplier = 1; + unsigned long ElapsedTimeSeconds = 0; + // Total timeout for the file to appear is ~1.5 minutes. + const unsigned MaxTotalSeconds = 90; + + std::random_device Device; + std::default_random_engine Engine(Device()); + + auto StartTime = std::chrono::steady_clock::now(); + do { - // Sleep for the designated interval, to allow the owning process time to - // finish up and remove the lock file. - // FIXME: Should we hook in to system APIs to get a notification when the - // lock file is deleted? -#ifdef _WIN32 - Sleep(Interval); -#else - nanosleep(&Interval, nullptr); + // FIXME: implement event-based waiting for Linux & Win32 + bool SuccessfullyWaitedOnFile = false; + +#ifdef HAVE_KEVENTS + // Implement event-based waiting for the lock to become free + // Supported on macOS and various BSD UNIX. + int KernelQueue = kqueue(); + + if (KernelQueue != -1) { + int LockFileDescriptor; + + LockFileDescriptor = open(LockFileName.c_str(), O_EVTONLY); + if (LockFileDescriptor != -1) { + struct kevent EventsToMonitor; + struct kevent EventData; + timespec Timeout; + + Timeout.tv_sec = MaxTotalSeconds; + Timeout.tv_nsec = 0; + + EV_SET(&EventsToMonitor, LockFileDescriptor, EVFILT_VNODE, + EV_ADD | EV_CLEAR, NOTE_DELETE, 0, NULL); + + int EventCount = kevent(KernelQueue, &EventsToMonitor, 1, &EventData, + 1, &Timeout); + + if (EventCount > 0) { + SuccessfullyWaitedOnFile = true; + } + close(LockFileDescriptor); + } + close(KernelQueue); + } #endif + if (!SuccessfullyWaitedOnFile) { + // Sleep for the designated interval, to allow the owning process time to + // finish up and remove the lock file. + std::uniform_int_distribution Distribution(1, WaitMultiplier); + unsigned long WaitDurationMS = MinWaitDurationMS*Distribution(Engine); + std::this_thread::sleep_for(std::chrono::milliseconds(WaitDurationMS)); + } + if (sys::fs::access(LockFileName.c_str(), sys::fs::AccessMode::Exist) == errc::no_such_file_or_directory) { // If the original file wasn't created, somone thought the lock was dead. @@ -327,24 +386,15 @@ if (!processStillExecuting((*Owner).first, (*Owner).second)) return Res_OwnerDied; - // Exponentially increase the time we wait for the lock to be removed. -#ifdef _WIN32 - Interval *= 2; -#else - Interval.tv_sec *= 2; - Interval.tv_nsec *= 2; - if (Interval.tv_nsec >= 1000000000) { - ++Interval.tv_sec; - Interval.tv_nsec -= 1000000000; + WaitMultiplier *= 2; + if (WaitMultiplier > MaxWaitMultiplier) { + WaitMultiplier = MaxWaitMultiplier; } -#endif - } while ( -#ifdef _WIN32 - Interval < MaxSeconds * 1000 -#else - Interval.tv_sec < (time_t)MaxSeconds -#endif - ); + + ElapsedTimeSeconds = std::chrono::duration_cast + (std::chrono::steady_clock::now() - StartTime).count(); + + } while (ElapsedTimeSeconds < MaxTotalSeconds); // Give up. return Res_Timeout;