Fix flaky tests: add iteration guards, yield in busy-waits, cap thread count

libeigen/eigen!2208

Co-authored-by: Rasmus Munk Larsen <rmlarsen@gmail.com>
This commit is contained in:
Rasmus Munk Larsen
2026-02-24 18:29:07 -08:00
parent 28d090a49c
commit 34092d2788
10 changed files with 92 additions and 61 deletions

View File

@@ -224,10 +224,14 @@ void transformations() {
t4 *= aa3;
VERIFY_IS_APPROX(t3.matrix(), t4.matrix());
do {
v3 = Vector3::Random();
dont_over_optimize(v3);
} while (v3.cwiseAbs().minCoeff() < NumTraits<Scalar>::epsilon());
{
int guard = 0;
do {
v3 = Vector3::Random();
dont_over_optimize(v3);
} while (v3.cwiseAbs().minCoeff() < NumTraits<Scalar>::epsilon() && (++guard) < 100);
VERIFY(guard < 100);
}
Translation3 tv3(v3);
Transform3 t5(tv3);
t4 = tv3;
@@ -381,9 +385,13 @@ void transformations() {
// test transform inversion
t0.setIdentity();
t0.translate(v0);
do {
t0.linear().setRandom();
} while (t0.linear().jacobiSvd().singularValues()(2) < test_precision<Scalar>());
{
int guard = 0;
do {
t0.linear().setRandom();
} while (t0.linear().jacobiSvd().singularValues()(2) < test_precision<Scalar>() && (++guard) < 100);
VERIFY(guard < 100);
}
Matrix4 t044 = Matrix4::Zero();
t044(3, 3) = 1;
t044.block(0, 0, t0.matrix().rows(), 4) = t0.matrix();

View File

@@ -26,16 +26,24 @@ void signed_integer_type_tests(const MatrixType& m) {
MatrixType m1(rows, cols), m2 = MatrixType::Random(rows, cols), mzero = MatrixType::Zero(rows, cols);
do {
m1 = MatrixType::Random(rows, cols);
} while (m1 == mzero || m1 == m2);
{
int guard = 0;
do {
m1 = MatrixType::Random(rows, cols);
} while ((m1 == mzero || m1 == m2) && (++guard) < 100);
VERIFY(guard < 100);
}
// check linear structure
Scalar s1;
do {
s1 = internal::random<Scalar>();
} while (s1 == 0);
{
int guard = 0;
do {
s1 = internal::random<Scalar>();
} while (s1 == 0 && (++guard) < 100);
VERIFY(guard < 100);
}
VERIFY_IS_EQUAL(-(-m1), m1);
VERIFY_IS_EQUAL(-m2 + m1 + m2, m1);
@@ -63,13 +71,21 @@ void integer_type_tests(const MatrixType& m) {
SquareMatrixType identity = SquareMatrixType::Identity(rows, rows), square = SquareMatrixType::Random(rows, rows);
VectorType v1(rows), v2 = VectorType::Random(rows), vzero = VectorType::Zero(rows);
do {
m1 = MatrixType::Random(rows, cols);
} while (m1 == mzero || m1 == m2);
{
int guard = 0;
do {
m1 = MatrixType::Random(rows, cols);
} while ((m1 == mzero || m1 == m2) && (++guard) < 100);
VERIFY(guard < 100);
}
do {
v1 = VectorType::Random(rows);
} while (v1 == vzero || v1 == v2);
{
int guard = 0;
do {
v1 = VectorType::Random(rows);
} while ((v1 == vzero || v1 == v2) && (++guard) < 100);
VERIFY(guard < 100);
}
VERIFY_IS_APPROX(v1, v1);
VERIFY_IS_NOT_APPROX(v1, 2 * v1);

View File

@@ -106,10 +106,10 @@ void lu_invertible() {
MatrixType m1(size, size), m2(size, size), m3(size, size);
FullPivLU<MatrixType> lu;
lu.setThreshold(RealScalar(0.01));
do {
m1 = MatrixType::Random(size, size);
lu.compute(m1);
} while (!lu.isInvertible());
// Create a random diagonally dominant (thus invertible) matrix.
m1 = MatrixType::Random(size, size);
m1.diagonal().array() += RealScalar(2 * size);
lu.compute(m1);
VERIFY_IS_APPROX(m1, lu.reconstructedMatrix());
VERIFY(0 == lu.dimensionOfKernel());

View File

@@ -28,12 +28,9 @@ void inverse_general_4x4(int repeat) {
typedef typename MatrixType::Scalar Scalar;
double error_sum = 0., error_max = 0.;
for (int i = 0; i < repeat; ++i) {
MatrixType m;
bool is_invertible;
do {
m = MatrixType::Random();
is_invertible = Eigen::FullPivLU<MatrixType>(m).isInvertible();
} while (!is_invertible);
// Create a random diagonally dominant (thus invertible) matrix.
MatrixType m = MatrixType::Random();
m.diagonal().array() += Scalar(8); // 2 * 4 for a 4x4 matrix.
MatrixType inv = m.inverse();
double error = double((m * inv - MatrixType::Identity()).norm());
error_sum += error;

View File

@@ -154,10 +154,10 @@ void qr() {
{
MatrixType m2, m3;
Index size = rows;
do {
m1 = MatrixType::Random(size, size);
qr.compute(m1);
} while (!qr.isInvertible());
// Create a random diagonally dominant (thus invertible) matrix.
m1 = MatrixType::Random(size, size);
m1.diagonal().array() += Scalar(2 * size);
qr.compute(m1);
MatrixType m1_inv = qr.inverse();
m3 = m1 * MatrixType::Random(size, cols2);
m2 = qr.solve(m3);

View File

@@ -56,10 +56,10 @@ void qr() {
{
MatrixType m2, m3;
Index size = rows;
do {
m1 = MatrixType::Random(size, size);
qr.compute(m1);
} while (!qr.isInvertible());
// Create a random diagonally dominant (thus invertible) matrix.
m1 = MatrixType::Random(size, size);
m1.diagonal().array() += Scalar(2 * size);
qr.compute(m1);
MatrixType m1_inv = qr.inverse();
m3 = m1 * MatrixType::Random(size, cols2);
m2 = qr.solve(m3);

View File

@@ -52,13 +52,11 @@ void stable_norm(const MatrixType& m) {
Index rows = m.rows();
Index cols = m.cols();
// get a non-zero random factor
Scalar factor = internal::random<Scalar>();
while (numext::abs2(factor) < RealScalar(1e-4)) factor = internal::random<Scalar>();
// Get a random factor bounded away from zero: |factor| >= 0.1.
Scalar factor = internal::random<Scalar>(Scalar(RealScalar(0.1)), Scalar(RealScalar(1)));
Scalar big = factor * ((std::numeric_limits<RealScalar>::max)() * RealScalar(1e-4));
factor = internal::random<Scalar>();
while (numext::abs2(factor) < RealScalar(1e-4)) factor = internal::random<Scalar>();
factor = internal::random<Scalar>(Scalar(RealScalar(0.1)), Scalar(RealScalar(1)));
Scalar small = factor * ((std::numeric_limits<RealScalar>::min)() * RealScalar(1e4));
Scalar one(1);
@@ -217,12 +215,11 @@ void test_empty() {
template <typename Scalar>
void test_hypot() {
typedef typename NumTraits<Scalar>::Real RealScalar;
Scalar factor = internal::random<Scalar>();
while (numext::abs2(factor) < RealScalar(1e-4)) factor = internal::random<Scalar>();
// Get a random factor bounded away from zero: |factor| >= 0.1.
Scalar factor = internal::random<Scalar>(Scalar(RealScalar(0.1)), Scalar(RealScalar(1)));
Scalar big = factor * ((std::numeric_limits<RealScalar>::max)() * RealScalar(1e-4));
factor = internal::random<Scalar>();
while (numext::abs2(factor) < RealScalar(1e-4)) factor = internal::random<Scalar>();
factor = internal::random<Scalar>(Scalar(RealScalar(0.1)), Scalar(RealScalar(1)));
Scalar small = factor * ((std::numeric_limits<RealScalar>::min)() * RealScalar(1e4));
Scalar one(1), zero(0), sqrt2(std::sqrt(2)), nan(std::numeric_limits<RealScalar>::quiet_NaN());

View File

@@ -177,8 +177,8 @@ void svd_min_norm(const MatrixType& m) {
int guard = 0;
do {
m2.setRandom();
} while (SVD_FOR_MIN_NORM(MatrixType2)(m2).setThreshold(test_precision<Scalar>()).rank() != rank && (++guard) < 10);
VERIFY(guard < 10);
} while (SVD_FOR_MIN_NORM(MatrixType2)(m2).setThreshold(test_precision<Scalar>()).rank() != rank && (++guard) < 100);
VERIFY(guard < 100);
RhsType2 rhs2 = RhsType2::Random(rank);
// use QR to find a reference minimal norm solution

View File

@@ -74,7 +74,7 @@ const int TestQueue::kQueueSize;
// fake queues. Ensure that it does not crash, consumers don't deadlock and
// number of blocked and unblocked threads match.
static void test_stress_eventcount() {
const int kThreads = std::thread::hardware_concurrency();
const int kThreads = (std::min)(static_cast<int>(std::thread::hardware_concurrency()), 16);
static const int kEvents = 1 << 16;
static const int kQueues = 10;

View File

@@ -12,6 +12,18 @@
#include "main.h"
#include "Eigen/ThreadPool"
// Spin-wait with yielding until the condition is met, or fail after a timeout.
// Returns true if the condition was met before the deadline.
template <typename Cond>
static bool spin_wait(Cond cond, int timeout_seconds = 60) {
auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(timeout_seconds);
while (!cond()) {
if (std::chrono::steady_clock::now() > deadline) return false;
std::this_thread::yield();
}
return true;
}
static void test_create_destroy_empty_pool() {
// Just create and destroy the pool. This will wind up and tear down worker
// threads. Ensure there are no issues in that logic.
@@ -38,12 +50,12 @@ static void test_parallelism(bool allow_spinning) {
VERIFY_LE(thread_id, kThreads - 1);
running++;
while (phase < 1) {
std::this_thread::yield();
}
done++;
});
}
while (running != kThreads) {
}
VERIFY(spin_wait([&] { return running == kThreads; }));
running = 0;
phase = 1;
// Now, while the previous tasks exit, schedule another kThreads tasks and
@@ -52,6 +64,7 @@ static void test_parallelism(bool allow_spinning) {
tp.Schedule([&, i]() {
running++;
while (phase < 2) {
std::this_thread::yield();
}
// When all tasks are running, half of tasks exit, quarter of tasks
// continue running and quarter of tasks schedule another 2 tasks each.
@@ -62,6 +75,7 @@ static void test_parallelism(bool allow_spinning) {
} else if (i < 3 * kThreads / 4) {
running++;
while (phase < 3) {
std::this_thread::yield();
}
done++;
} else {
@@ -69,6 +83,7 @@ static void test_parallelism(bool allow_spinning) {
tp.Schedule([&]() {
running++;
while (phase < 3) {
std::this_thread::yield();
}
done++;
});
@@ -77,23 +92,21 @@ static void test_parallelism(bool allow_spinning) {
done++;
});
}
while (running != kThreads) {
}
VERIFY(spin_wait([&] { return running == kThreads; }));
running = 0;
phase = 2;
for (int i = 0; i < kThreads / 4; ++i) {
tp.Schedule([&]() {
running++;
while (phase < 3) {
std::this_thread::yield();
}
done++;
});
}
while (running != kThreads) {
}
VERIFY(spin_wait([&] { return running == kThreads; }));
phase = 3;
while (done != 3 * kThreads) {
}
VERIFY(spin_wait([&] { return done == 3 * kThreads; }));
}
}
@@ -136,12 +149,12 @@ static void test_pool_partitions() {
VERIFY_LE(thread_id, kThreads - 1);
++running;
while (phase < 1) {
std::this_thread::yield();
}
++done;
});
}
while (running != kThreads) {
}
VERIFY(spin_wait([&] { return running == kThreads; }));
// Schedule each closure to only run on thread 'i' and verify that it does.
for (int i = 0; i < kThreads; ++i) {
tp.ScheduleWithHint(
@@ -150,6 +163,7 @@ static void test_pool_partitions() {
const int thread_id = tp.CurrentThreadId();
VERIFY_IS_EQUAL(thread_id, i);
while (phase < 2) {
std::this_thread::yield();
}
++done;
},
@@ -157,8 +171,7 @@ static void test_pool_partitions() {
}
running = 0;
phase = 1;
while (running != kThreads) {
}
VERIFY(spin_wait([&] { return running == kThreads; }));
running = 0;
phase = 2;
}