Skip to content

Commit 7bdc42f

Browse files
authored
chore: add callback registration to passert_failure to enable dumping rapidcheck test input (#5532)
Resolves CORE-233. A `passert` failure crashes the process without returning. While `Catch2` has a `SIGABRT` handler to print some details around this, `rapidcheck` does not. As such, if a randomized `rapidcheck` input fails a `passert`, no details about what the input was are printed. Ostensibly we have some means of recovering that failed input using the seed which produced the test case, but this might not be helpful: 1) we don't know if it is platform-independent 2) if there is a non-deterministic or undefined behavior leading to failure, then the seed may not be enough to clue us in This pull request adds a callback registration to `passert_failure` so that we can register a callback in our `rapidcheck` properties to print the last attempted input in the event of a `passert` failure. This way we can recover a bad input with certainty. Example: ``` FATAL TileDB core library internal error: false test/src/unit-sparse-global-order-reader.cc:1396 Last rapidcheck input: (2, 1, 1, [[1, 1]], d1 LT 0x01 0x00 0x00 0x00) ``` --- TYPE: NO_HISTORY DESC: add callback registration to `passert_failure` to enable dumping rapidcheck test input
1 parent e6dfe4f commit 7bdc42f

File tree

3 files changed

+131
-3
lines changed

3 files changed

+131
-3
lines changed

test/src/unit-sparse-global-order-reader.cc

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1311,6 +1311,20 @@ TEST_CASE_METHOD(
13111311
CHECK(retrieved_data == expected_correct_data);
13121312
}
13131313

1314+
template <typename... Args>
1315+
struct PAssertFailureCallbackShowRapidcheckInput {
1316+
std::tuple<const Args&...> inputs_;
1317+
1318+
PAssertFailureCallbackShowRapidcheckInput(const Args&... inputs)
1319+
: inputs_(inputs...) {
1320+
}
1321+
1322+
void operator()() const {
1323+
std::cerr << "Last rapidcheck input: ";
1324+
rc::show<decltype(inputs_)>(inputs_, std::cerr);
1325+
}
1326+
};
1327+
13141328
/**
13151329
* Tests that the reader will not yield results out of order across multiple
13161330
* iterations or `submit`s if the fragments are heavily skewed when the memory
@@ -1335,6 +1349,10 @@ TEST_CASE_METHOD(
13351349
const std::vector<templates::Domain<int>>& subarray =
13361350
std::vector<templates::Domain<int>>(),
13371351
tdb_unique_ptr<tiledb::sm::ASTNode> condition = nullptr) {
1352+
PAssertFailureCallbackRegistration showInput(
1353+
PAssertFailureCallbackShowRapidcheckInput(
1354+
fragment_size, num_user_cells, extent, subarray, *condition.get()));
1355+
13381356
// Write a fragment F0 with unique coordinates
13391357
templates::Fragment1D<int, int> fragment0;
13401358
fragment0.dim_.resize(fragment_size);
@@ -1438,6 +1456,10 @@ TEST_CASE_METHOD(
14381456
size_t num_user_cells,
14391457
const std::vector<templates::Domain<int>>& subarray = {},
14401458
tdb_unique_ptr<tiledb::sm::ASTNode> condition = nullptr) {
1459+
PAssertFailureCallbackRegistration showInput(
1460+
PAssertFailureCallbackShowRapidcheckInput(
1461+
fragment_size, num_user_cells, subarray, *condition.get()));
1462+
14411463
templates::Fragment1D<int, int> fragment0;
14421464
templates::Fragment1D<int, int> fragment1;
14431465

@@ -1537,6 +1559,15 @@ TEST_CASE_METHOD(
15371559
const std::vector<templates::Domain<int>>& subarray =
15381560
std::vector<templates::Domain<int>>(),
15391561
tdb_unique_ptr<tiledb::sm::ASTNode> condition = nullptr) {
1562+
PAssertFailureCallbackRegistration showInput(
1563+
PAssertFailureCallbackShowRapidcheckInput(
1564+
num_fragments,
1565+
fragment_size,
1566+
num_user_cells,
1567+
allow_dups,
1568+
subarray,
1569+
*condition.get()));
1570+
15401571
const uint64_t total_budget = 100000;
15411572
const double ratio_coords = 0.2;
15421573

@@ -1656,6 +1687,16 @@ TEST_CASE_METHOD(
16561687
bool allow_dups,
16571688
const Subarray1DType<int>& subarray = {},
16581689
tdb_unique_ptr<tiledb::sm::ASTNode> condition = nullptr) {
1690+
PAssertFailureCallbackRegistration showInput(
1691+
PAssertFailureCallbackShowRapidcheckInput(
1692+
num_fragments,
1693+
fragment_size,
1694+
num_user_cells,
1695+
tile_capacity,
1696+
allow_dups,
1697+
subarray,
1698+
*condition.get()));
1699+
16591700
FxRun1D instance;
16601701
instance.num_user_cells = num_user_cells;
16611702
instance.subarray = subarray;
@@ -1817,6 +1858,18 @@ TEST_CASE_METHOD(
18171858
bool allow_dups,
18181859
const Subarray2DType<int, int>& subarray = {},
18191860
tdb_unique_ptr<tiledb::sm::ASTNode> condition = nullptr) {
1861+
PAssertFailureCallbackRegistration showInput(
1862+
PAssertFailureCallbackShowRapidcheckInput(
1863+
tile_order,
1864+
cell_order,
1865+
num_fragments,
1866+
fragment_size,
1867+
num_user_cells,
1868+
tile_capacity,
1869+
allow_dups,
1870+
subarray,
1871+
*condition.get()));
1872+
18201873
FxRun2D instance;
18211874
instance.num_user_cells = num_user_cells;
18221875
instance.capacity = tile_capacity;
@@ -1981,6 +2034,18 @@ TEST_CASE_METHOD(
19812034
bool allow_dups,
19822035
const Subarray2DType<int, int>& subarray = {},
19832036
tdb_unique_ptr<tiledb::sm::ASTNode> condition = nullptr) {
2037+
PAssertFailureCallbackRegistration showInput(
2038+
PAssertFailureCallbackShowRapidcheckInput(
2039+
tile_order,
2040+
cell_order,
2041+
approximate_memory_tiles,
2042+
num_user_cells,
2043+
num_fragments,
2044+
tile_capacity,
2045+
allow_dups,
2046+
subarray,
2047+
*condition.get()));
2048+
19842049
FxRun2D instance;
19852050
instance.tile_order_ = tile_order;
19862051
instance.cell_order_ = cell_order;
@@ -2128,6 +2193,10 @@ TEST_CASE_METHOD(
21282193
templates::Dimension<Datatype::INT64> dimension,
21292194
const Subarray1DType<int64_t>& subarray = {},
21302195
tdb_unique_ptr<tiledb::sm::ASTNode> condition = nullptr) {
2196+
PAssertFailureCallbackRegistration showInput(
2197+
PAssertFailureCallbackShowRapidcheckInput(
2198+
num_fragments, dimension, subarray, *condition.get()));
2199+
21312200
const size_t fragment_size =
21322201
std::min<size_t>(1024 * 32, dimension.domain.num_cells());
21332202

@@ -3072,7 +3141,11 @@ TEST_CASE_METHOD(
30723141
"fragments",
30733142
"[sparse-global-order][sub-millisecond][rest][rapidcheck]") {
30743143
auto doit = [this]<typename Asserter>(
3075-
const std::vector<uint64_t> fragment_same_timestamp_runs) {
3144+
const std::vector<uint64_t>& fragment_same_timestamp_runs) {
3145+
PAssertFailureCallbackRegistration showInput{
3146+
PAssertFailureCallbackShowRapidcheckInput(
3147+
fragment_same_timestamp_runs)};
3148+
30763149
uint64_t num_fragments = 0;
30773150
for (const auto same_timestamp_run : fragment_same_timestamp_runs) {
30783151
num_fragments += same_timestamp_run;

tiledb/common/assert.cc

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131

3232
#include "tiledb/common/assert.h"
3333

34+
#include <mutex>
35+
#include <vector>
36+
3437
namespace tiledb::common {
3538

3639
AssertFailure::AssertFailure(const std::string& what)
@@ -42,12 +45,43 @@ AssertFailure::AssertFailure(const std::string& what)
4245
throw AssertFailure(file, line, expr);
4346
}
4447

48+
static std::mutex passertFailureCallbackMutex;
49+
static std::vector<std::function<void()>> passertFailureCallbacks;
50+
51+
[[noreturn]] void passert_failure_abort(void) {
52+
{
53+
for (auto& callback : passertFailureCallbacks) {
54+
std::unique_lock<std::mutex> lk(passertFailureCallbackMutex);
55+
callback();
56+
}
57+
}
58+
59+
std::abort();
60+
}
61+
4562
[[noreturn]] void passert_failure(
4663
const char* file, uint64_t line, const char* expr) {
4764
std::cerr << "FATAL TileDB core library internal error: " << expr
4865
<< std::endl;
4966
std::cerr << " " << file << ":" << line << std::endl;
50-
std::abort();
67+
68+
passert_failure_abort();
69+
}
70+
71+
#ifdef TILEDB_ASSERTIONS
72+
73+
PAssertFailureCallbackRegistration::PAssertFailureCallbackRegistration(
74+
std::function<void()>&& callback) {
75+
std::unique_lock<std::mutex> lk(passertFailureCallbackMutex);
76+
passertFailureCallbacks.push_back(
77+
std::forward<std::function<void()>>(callback));
5178
}
5279

80+
PAssertFailureCallbackRegistration::~PAssertFailureCallbackRegistration() {
81+
std::unique_lock<std::mutex> lk(passertFailureCallbackMutex);
82+
passertFailureCallbacks.pop_back();
83+
}
84+
85+
#endif
86+
5387
} // namespace tiledb::common

tiledb/common/assert.h

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888

8989
#include <fmt/format.h>
9090
#include <cstdlib>
91+
#include <functional>
9192
#include <iostream>
9293

9394
#define __SOURCE__ (&__FILE__[__SOURCE_DIR_PATH_SIZE__])
@@ -168,6 +169,11 @@ template <typename... Args>
168169
throw AssertFailure(file, line, expr, fmt, std::forward<Args>(fmt_args)...);
169170
}
170171

172+
/**
173+
* Aborts the process upon `passert` failure.
174+
*/
175+
[[noreturn]] void passert_failure_abort(void);
176+
171177
/**
172178
* Assertion failure which results in a process panic.
173179
* SIGABRT is raised.
@@ -203,9 +209,24 @@ template <typename... Args>
203209
std::cerr << " " << file << ":" << line << std::endl;
204210
std::cerr << " Details: "
205211
<< fmt::format(fmt, std::forward<Args>(fmt_args)...) << std::endl;
206-
std::abort();
212+
213+
passert_failure_abort();
207214
}
208215

216+
/**
217+
* Registers a callback to run upon `passert` failure.
218+
* This can be used to print any diagnostic info prior to aborting the process.
219+
*/
220+
struct PAssertFailureCallbackRegistration {
221+
#ifdef TILEDB_ASSERTIONS
222+
PAssertFailureCallbackRegistration(std::function<void()>&& callback);
223+
~PAssertFailureCallbackRegistration();
224+
#else
225+
PAssertFailureCallbackRegistration(std::function<void()>&&) {
226+
}
227+
#endif
228+
};
229+
209230
} // namespace tiledb::common
210231

211232
#ifdef TILEDB_ASSERTIONS

0 commit comments

Comments
 (0)