[llvm][Support] Add ExponentialBackoff helper (#81206)
This provides a simple way to implement exponential backoff using a do
while loop.
Usage example (also see the change to LockFileManager.cpp):
```
ExponentialBackoff Backoff(10s);
do {
if (tryToDoSomething())
return ItWorked;
} while (Backoff.waitForNextAttempt());
return Timeout;
```
Abstracting this out of `LockFileManager` as the module build daemon
will need it.
This commit is contained in:
65
llvm/include/llvm/Support/ExponentialBackoff.h
Normal file
65
llvm/include/llvm/Support/ExponentialBackoff.h
Normal file
@@ -0,0 +1,65 @@
|
||||
//===- llvm/Support/ExponentialBackoff.h ------------------------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file defines a helper class for implementing exponential backoff.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#ifndef LLVM_EXPONENTIALBACKOFF_H
|
||||
#define LLVM_EXPONENTIALBACKOFF_H
|
||||
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
#include <chrono>
|
||||
#include <random>
|
||||
|
||||
namespace llvm {
|
||||
|
||||
/// A class to help implement exponential backoff.
|
||||
///
|
||||
/// Example usage:
|
||||
/// \code
|
||||
/// ExponentialBackoff Backoff(10s);
|
||||
/// do {
|
||||
/// if (tryToDoSomething())
|
||||
/// return ItWorked;
|
||||
/// } while (Backoff.waitForNextAttempt());
|
||||
/// return Timeout;
|
||||
/// \endcode
|
||||
class ExponentialBackoff {
|
||||
public:
|
||||
using duration = std::chrono::steady_clock::duration;
|
||||
using time_point = std::chrono::steady_clock::time_point;
|
||||
|
||||
/// \param Timeout the maximum wall time this should run for starting when
|
||||
/// this object is constructed.
|
||||
/// \param MinWait the minimum amount of time `waitForNextAttempt` will sleep
|
||||
/// for.
|
||||
/// \param MaxWait the maximum amount of time `waitForNextAttempt` will sleep
|
||||
/// for.
|
||||
ExponentialBackoff(duration Timeout,
|
||||
duration MinWait = std::chrono::milliseconds(10),
|
||||
duration MaxWait = std::chrono::milliseconds(500))
|
||||
: MinWait(MinWait), MaxWait(MaxWait),
|
||||
EndTime(std::chrono::steady_clock::now() + Timeout) {}
|
||||
|
||||
/// Blocks while waiting for the next attempt.
|
||||
/// \returns true if you should try again, false if the timeout has been
|
||||
/// reached.
|
||||
bool waitForNextAttempt();
|
||||
|
||||
private:
|
||||
duration MinWait;
|
||||
duration MaxWait;
|
||||
time_point EndTime;
|
||||
std::random_device RandDev;
|
||||
int64_t CurrentMultiplier = 1;
|
||||
};
|
||||
|
||||
} // end namespace llvm
|
||||
|
||||
#endif // LLVM_EXPONENTIALBACKOFF_H
|
||||
@@ -176,6 +176,7 @@ add_llvm_component_library(LLVMSupport
|
||||
ELFAttributes.cpp
|
||||
Error.cpp
|
||||
ErrorHandling.cpp
|
||||
ExponentialBackoff.cpp
|
||||
ExtensibleRTTI.cpp
|
||||
FileCollector.cpp
|
||||
FileUtilities.cpp
|
||||
|
||||
29
llvm/lib/Support/ExponentialBackoff.cpp
Normal file
29
llvm/lib/Support/ExponentialBackoff.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
//===- llvm/Support/ExponentialBackoff.h ------------------------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "llvm/Support/ExponentialBackoff.h"
|
||||
#include <thread>
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
bool ExponentialBackoff::waitForNextAttempt() {
|
||||
auto Now = std::chrono::steady_clock::now();
|
||||
if (Now >= EndTime)
|
||||
return false;
|
||||
|
||||
duration CurMaxWait = std::min(MinWait * CurrentMultiplier, MaxWait);
|
||||
std::uniform_int_distribution<uint64_t> Dist(MinWait.count(),
|
||||
CurMaxWait.count());
|
||||
// Use random_device directly instead of a PRNG as uniform_int_distribution
|
||||
// often only takes a few samples anyway.
|
||||
duration WaitDuration = std::min(duration(Dist(RandDev)), EndTime - Now);
|
||||
if (CurMaxWait < MaxWait)
|
||||
CurrentMultiplier *= 2;
|
||||
std::this_thread::sleep_for(WaitDuration);
|
||||
return true;
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "llvm/ADT/StringExtras.h"
|
||||
#include "llvm/Support/Errc.h"
|
||||
#include "llvm/Support/ErrorOr.h"
|
||||
#include "llvm/Support/ExponentialBackoff.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
#include "llvm/Support/MemoryBuffer.h"
|
||||
#include "llvm/Support/Process.h"
|
||||
@@ -20,7 +21,6 @@
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <system_error>
|
||||
@@ -295,29 +295,15 @@ LockFileManager::waitForUnlock(const unsigned MaxSeconds) {
|
||||
return Res_Success;
|
||||
|
||||
// Since we don't yet have an event-based method to wait for the lock file,
|
||||
// implement randomized exponential backoff, similar to Ethernet collision
|
||||
// use 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;
|
||||
using namespace std::chrono_literals;
|
||||
ExponentialBackoff Backoff(std::chrono::seconds(MaxSeconds), 10ms, 500ms);
|
||||
|
||||
std::random_device Device;
|
||||
std::default_random_engine Engine(Device());
|
||||
|
||||
auto StartTime = std::chrono::steady_clock::now();
|
||||
|
||||
do {
|
||||
// Wait first as this is only called when the lock is known to be held.
|
||||
while (Backoff.waitForNextAttempt()) {
|
||||
// FIXME: implement event-based waiting
|
||||
|
||||
// Sleep for the designated interval, to allow the owning process time to
|
||||
// finish up and remove the lock file.
|
||||
std::uniform_int_distribution<unsigned long> 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.
|
||||
@@ -329,17 +315,7 @@ LockFileManager::waitForUnlock(const unsigned MaxSeconds) {
|
||||
// If the process owning the lock died without cleaning up, just bail out.
|
||||
if (!processStillExecuting((*Owner).first, (*Owner).second))
|
||||
return Res_OwnerDied;
|
||||
|
||||
WaitMultiplier *= 2;
|
||||
if (WaitMultiplier > MaxWaitMultiplier) {
|
||||
WaitMultiplier = MaxWaitMultiplier;
|
||||
}
|
||||
|
||||
ElapsedTimeSeconds = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::steady_clock::now() - StartTime)
|
||||
.count();
|
||||
|
||||
} while (ElapsedTimeSeconds < MaxSeconds);
|
||||
}
|
||||
|
||||
// Give up.
|
||||
return Res_Timeout;
|
||||
|
||||
@@ -38,6 +38,7 @@ add_llvm_unittest(SupportTests
|
||||
ErrnoTest.cpp
|
||||
ErrorOrTest.cpp
|
||||
ErrorTest.cpp
|
||||
ExponentialBackoffTest.cpp
|
||||
ExtensibleRTTITest.cpp
|
||||
FileCollectorTest.cpp
|
||||
FileOutputBufferTest.cpp
|
||||
|
||||
31
llvm/unittests/Support/ExponentialBackoffTest.cpp
Normal file
31
llvm/unittests/Support/ExponentialBackoffTest.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
//===- unittests/ExponentialBackoffTest.cpp -------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "llvm/Support/ExponentialBackoff.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include <chrono>
|
||||
|
||||
using namespace llvm;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace {
|
||||
|
||||
TEST(ExponentialBackoffTest, Timeout) {
|
||||
auto Start = std::chrono::steady_clock::now();
|
||||
// Use short enough times that this test runs quickly.
|
||||
ExponentialBackoff Backoff(100ms, 1ms, 10ms);
|
||||
do {
|
||||
} while (Backoff.waitForNextAttempt());
|
||||
auto Duration = std::chrono::steady_clock::now() - Start;
|
||||
EXPECT_GE(Duration, 100ms);
|
||||
}
|
||||
|
||||
// Testing individual wait duration is omitted as those tests would be
|
||||
// non-deterministic.
|
||||
|
||||
} // end anonymous namespace
|
||||
Reference in New Issue
Block a user