Skip to content

Commit edff3ff

Browse files
authored
[llvm][Support] Add ExponentialBackoff helper (llvm#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.
1 parent 22d2f3a commit edff3ff

File tree

6 files changed

+134
-31
lines changed

6 files changed

+134
-31
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//===- llvm/Support/ExponentialBackoff.h ------------------------*- C++ -*-===//
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+
// This file defines a helper class for implementing exponential backoff.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
#ifndef LLVM_EXPONENTIALBACKOFF_H
13+
#define LLVM_EXPONENTIALBACKOFF_H
14+
15+
#include "llvm/ADT/STLExtras.h"
16+
#include "llvm/Support/Error.h"
17+
#include <chrono>
18+
#include <random>
19+
20+
namespace llvm {
21+
22+
/// A class to help implement exponential backoff.
23+
///
24+
/// Example usage:
25+
/// \code
26+
/// ExponentialBackoff Backoff(10s);
27+
/// do {
28+
/// if (tryToDoSomething())
29+
/// return ItWorked;
30+
/// } while (Backoff.waitForNextAttempt());
31+
/// return Timeout;
32+
/// \endcode
33+
class ExponentialBackoff {
34+
public:
35+
using duration = std::chrono::steady_clock::duration;
36+
using time_point = std::chrono::steady_clock::time_point;
37+
38+
/// \param Timeout the maximum wall time this should run for starting when
39+
/// this object is constructed.
40+
/// \param MinWait the minimum amount of time `waitForNextAttempt` will sleep
41+
/// for.
42+
/// \param MaxWait the maximum amount of time `waitForNextAttempt` will sleep
43+
/// for.
44+
ExponentialBackoff(duration Timeout,
45+
duration MinWait = std::chrono::milliseconds(10),
46+
duration MaxWait = std::chrono::milliseconds(500))
47+
: MinWait(MinWait), MaxWait(MaxWait),
48+
EndTime(std::chrono::steady_clock::now() + Timeout) {}
49+
50+
/// Blocks while waiting for the next attempt.
51+
/// \returns true if you should try again, false if the timeout has been
52+
/// reached.
53+
bool waitForNextAttempt();
54+
55+
private:
56+
duration MinWait;
57+
duration MaxWait;
58+
time_point EndTime;
59+
std::random_device RandDev;
60+
int64_t CurrentMultiplier = 1;
61+
};
62+
63+
} // end namespace llvm
64+
65+
#endif // LLVM_EXPONENTIALBACKOFF_H

llvm/lib/Support/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ add_llvm_component_library(LLVMSupport
176176
ELFAttributes.cpp
177177
Error.cpp
178178
ErrorHandling.cpp
179+
ExponentialBackoff.cpp
179180
ExtensibleRTTI.cpp
180181
FileCollector.cpp
181182
FileUtilities.cpp
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//===- llvm/Support/ExponentialBackoff.h ------------------------*- C++ -*-===//
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+
#include "llvm/Support/ExponentialBackoff.h"
10+
#include <thread>
11+
12+
using namespace llvm;
13+
14+
bool ExponentialBackoff::waitForNextAttempt() {
15+
auto Now = std::chrono::steady_clock::now();
16+
if (Now >= EndTime)
17+
return false;
18+
19+
duration CurMaxWait = std::min(MinWait * CurrentMultiplier, MaxWait);
20+
std::uniform_int_distribution<uint64_t> Dist(MinWait.count(),
21+
CurMaxWait.count());
22+
// Use random_device directly instead of a PRNG as uniform_int_distribution
23+
// often only takes a few samples anyway.
24+
duration WaitDuration = std::min(duration(Dist(RandDev)), EndTime - Now);
25+
if (CurMaxWait < MaxWait)
26+
CurrentMultiplier *= 2;
27+
std::this_thread::sleep_for(WaitDuration);
28+
return true;
29+
}

llvm/lib/Support/LockFileManager.cpp

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "llvm/ADT/StringExtras.h"
1212
#include "llvm/Support/Errc.h"
1313
#include "llvm/Support/ErrorOr.h"
14+
#include "llvm/Support/ExponentialBackoff.h"
1415
#include "llvm/Support/FileSystem.h"
1516
#include "llvm/Support/MemoryBuffer.h"
1617
#include "llvm/Support/Process.h"
@@ -20,7 +21,6 @@
2021
#include <chrono>
2122
#include <ctime>
2223
#include <memory>
23-
#include <random>
2424
#include <sys/stat.h>
2525
#include <sys/types.h>
2626
#include <system_error>
@@ -295,29 +295,15 @@ LockFileManager::waitForUnlock(const unsigned MaxSeconds) {
295295
return Res_Success;
296296

297297
// Since we don't yet have an event-based method to wait for the lock file,
298-
// implement randomized exponential backoff, similar to Ethernet collision
298+
// use randomized exponential backoff, similar to Ethernet collision
299299
// algorithm. This improves performance on machines with high core counts
300300
// when the file lock is heavily contended by multiple clang processes
301-
const unsigned long MinWaitDurationMS = 10;
302-
const unsigned long MaxWaitMultiplier = 50; // 500ms max wait
303-
unsigned long WaitMultiplier = 1;
304-
unsigned long ElapsedTimeSeconds = 0;
301+
using namespace std::chrono_literals;
302+
ExponentialBackoff Backoff(std::chrono::seconds(MaxSeconds), 10ms, 500ms);
305303

306-
std::random_device Device;
307-
std::default_random_engine Engine(Device());
308-
309-
auto StartTime = std::chrono::steady_clock::now();
310-
311-
do {
304+
// Wait first as this is only called when the lock is known to be held.
305+
while (Backoff.waitForNextAttempt()) {
312306
// FIXME: implement event-based waiting
313-
314-
// Sleep for the designated interval, to allow the owning process time to
315-
// finish up and remove the lock file.
316-
std::uniform_int_distribution<unsigned long> Distribution(1,
317-
WaitMultiplier);
318-
unsigned long WaitDurationMS = MinWaitDurationMS * Distribution(Engine);
319-
std::this_thread::sleep_for(std::chrono::milliseconds(WaitDurationMS));
320-
321307
if (sys::fs::access(LockFileName.c_str(), sys::fs::AccessMode::Exist) ==
322308
errc::no_such_file_or_directory) {
323309
// If the original file wasn't created, somone thought the lock was dead.
@@ -329,17 +315,7 @@ LockFileManager::waitForUnlock(const unsigned MaxSeconds) {
329315
// If the process owning the lock died without cleaning up, just bail out.
330316
if (!processStillExecuting((*Owner).first, (*Owner).second))
331317
return Res_OwnerDied;
332-
333-
WaitMultiplier *= 2;
334-
if (WaitMultiplier > MaxWaitMultiplier) {
335-
WaitMultiplier = MaxWaitMultiplier;
336-
}
337-
338-
ElapsedTimeSeconds = std::chrono::duration_cast<std::chrono::seconds>(
339-
std::chrono::steady_clock::now() - StartTime)
340-
.count();
341-
342-
} while (ElapsedTimeSeconds < MaxSeconds);
318+
}
343319

344320
// Give up.
345321
return Res_Timeout;

llvm/unittests/Support/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ add_llvm_unittest(SupportTests
3838
ErrnoTest.cpp
3939
ErrorOrTest.cpp
4040
ErrorTest.cpp
41+
ExponentialBackoffTest.cpp
4142
ExtensibleRTTITest.cpp
4243
FileCollectorTest.cpp
4344
FileOutputBufferTest.cpp
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//===- unittests/ExponentialBackoffTest.cpp -------------------------------===//
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+
#include "llvm/Support/ExponentialBackoff.h"
10+
#include "gtest/gtest.h"
11+
#include <chrono>
12+
13+
using namespace llvm;
14+
using namespace std::chrono_literals;
15+
16+
namespace {
17+
18+
TEST(ExponentialBackoffTest, Timeout) {
19+
auto Start = std::chrono::steady_clock::now();
20+
// Use short enough times that this test runs quickly.
21+
ExponentialBackoff Backoff(100ms, 1ms, 10ms);
22+
do {
23+
} while (Backoff.waitForNextAttempt());
24+
auto Duration = std::chrono::steady_clock::now() - Start;
25+
EXPECT_GE(Duration, 100ms);
26+
}
27+
28+
// Testing individual wait duration is omitted as those tests would be
29+
// non-deterministic.
30+
31+
} // end anonymous namespace

0 commit comments

Comments
 (0)