Base class for a concurrent state machine.
This example tests a key-value store that has a lost-update bug. The increment operation reads a value and then writes the incremented value in a separate operation. Thus, two workers can overwrite an update. The invariant compares the stored total with the number of completed increments.
class ConcurrentKVStore {
public:
std::optional<int64_t> get(int key) const {
std::lock_guard<std::mutex> lock(mutex_);
auto value = values_.find(key);
if (value == values_.end()) {
return std::nullopt;
}
return value->second;
}
void put(int key, int64_t value) {
std::lock_guard<std::mutex> lock(mutex_);
values_[key] = value;
}
bool put_if_absent(int key, int64_t value) {
std::lock_guard<std::mutex> lock(mutex_);
return values_.emplace(key, value).second;
}
void increment(int key) {
int64_t value = get(key).value_or(0);
std::this_thread::yield();
put(key, value + 1);
}
std::map<int, int64_t> snapshot() const {
std::lock_guard<std::mutex> lock(mutex_);
return values_;
}
private:
mutable std::mutex mutex_;
std::map<int, int64_t> values_;
};
struct ConcurrentKV
: hegel::stateful::ConcurrentStateMachine<ConcurrentKV> {
ConcurrentKVStore store;
hegel::stateful::ConcurrentPool<int> keys;
std::atomic<int64_t> increments{0};
explicit ConcurrentKV(hegel::TestCase& tc) : keys(tc) {}
std::vector<hegel::stateful::ConcurrentRule<ConcurrentKV>>
rules() {
return {
{"register", "operations",
[](hegel::TestCase& tc, ConcurrentKV& m) {
{.min_value = 0, .max_value = 3}));
if (m.store.put_if_absent(key, 0)) {
m.keys.add(tc, key);
}
}},
{"increment", "operations",
[](hegel::TestCase& tc, ConcurrentKV& m) {
m.store.increment(key);
m.increments.fetch_add(1);
}},
{"read", "operations",
[](hegel::TestCase& tc, ConcurrentKV& m) {
int64_t value = m.store.get(key).value_or(0);
tc.
note(
"read " + std::to_string(key) +
" -> " +
std::to_string(value));
}},
{"snapshot", "snapshot",
[](hegel::TestCase& tc, ConcurrentKV& m) {
tc.
note(
"snapshot holds " +
std::to_string(m.store.snapshot().size()) +
" keys");
}},
};
}
std::vector<hegel::stateful::Invariant<ConcurrentKV>>
invariants()
const {
return {{"no_lost_updates", [](const ConcurrentKV& m) {
int64_t stored = 0;
for (const auto& entry : m.store.snapshot()) {
stored += entry.second;
}
int64_t performed = m.increments.load();
if (stored != performed) {
throw std::runtime_error(
"the store lost an increment");
}
}}};
}
};
ConcurrentKV machine(tc);
}
void note(std::string_view message) const
Record a message that will be printed on the final replay of a failing test case.
T draw(const generators::Generator< T > &gen) const
Draw a random value from a generator.
Definition core.h:264
std::vector< Invariant< Derived > > invariants() const
Returns the invariants of the machine.
Definition stateful.h:270
#define HEGEL_TEST(name,...)
Define a Hegel property test.
Definition hegel.h:575
Generator< T > integers(IntegersParams< T > params={})
Generate random integers. For a given integral type T, produces values in the range [std::numeric_lim...
Definition numeric.h:159
void run_concurrent(M &machine, TestCase &tc, int64_t min_concurrency, int64_t max_concurrency)
Executes a concurrent stateful test with rules from machine.
Definition stateful.h:855
Generator< T > values_reusable(Pool< T > &p)
Returns a value from the pool without removing it.
Definition stateful.h:480