Commit 58ecc903 authored by Maged Michael's avatar Maged Michael Committed by Facebook Github Bot 1

Methodology for using DeterministicSchedule support for auxiliary data and global invariants

Summary:
Depends on D3648195

This test example is intended to demonstrate the methodology for using DeterministicSchedule support for auxiliary data and global invariants.
The main goal is fine-grained invariant checking, ideally after every shared update.
The secondary goals are:
- Minimize intrusion in the original code. In this proposed methodology, it is adding a friend.
- Minimize duplication of original tested code. Unfortunately, depending on the original code, it seems that significant duplication may be unavoidable if we don't want to change the original code.

This diff is primarily about the methodology for testing already developed code. I plan to apply what we agree on through this diff to the dynamic MPMCQueue code (D3462592).

A future goal to keep in mind is creating a methodology for developing new code with hooks for DSched aux. data and invariant checking integrated in it, in order to minimize or eliminate duplication of tested code.  In past projects, I used non-standard source code (basically algorithm code) that is automatically translatable through scripts and macros to input to a DSched-like tool as well as to compilable code. The main challenge for such a methodology is to allow the original source code to be standard readable C++ code.

Reviewed By: djwatson

Differential Revision: D3675447

fbshipit-source-id: aae2c9f0550af88dc3a5dcbe53318a75a86b6e2b
parent c98aec4e
...@@ -107,34 +107,115 @@ TEST(DeterministicSchedule, buggyAdd) { ...@@ -107,34 +107,115 @@ TEST(DeterministicSchedule, buggyAdd) {
} // for bug } // for bug
} // TEST } // TEST
/// Testing support for auxiliary variables and global invariants /// Test support for auxiliary data and global invariants
///
/** auxiliary variables for atomic counter test */ /// How to use DSched support for auxiliary data and global invariants:
struct AtomicCounterAux { /// 1. Forward declare an annotated shared class
std::vector<int> local_; /// 2. Add the annotated shared class as a friend of the original class(es)
/// to be tested
/// 3. Define auxiliary data
/// 4. Define function(s) for updating auxiliary data to match shared updates
/// 5. Define the annotated shared class
/// It supports an interface to the original class along with auxiliary
/// functions for updating aux data, checking invariants, and/or logging
/// It may have to duplicate the steps of multi-step operations in the
//// original code in order to manage aux data and check invariants after
/// shared accesses other than the first access in an opeeration
/// 6. Define function for checking global invariants and/or logging global
/// state
/// 7. Define function generator(s) for function object(s) that update aux
/// data, check invariants, and/or log state
/// 8. Define TEST using anotated shared data, aux data, and aux functions
using DSched = DeterministicSchedule;
using AuxFn = std::function<void(uint64_t, bool)>;
/** forward declaration of annotated shared class */
class AnnotatedAtomicCounter;
/** original shared class to be tested */
template <typename T, template <typename> class Atom = std::atomic>
class AtomicCounter {
friend AnnotatedAtomicCounter;
public:
explicit AtomicCounter(T val) : counter_(val) {}
void inc() {
counter_.fetch_add(1);
}
void inc_bug() {
int newval = counter_.load() + 1;
counter_.store(newval);
}
T load() {
return counter_.load();
}
private:
Atom<T> counter_ = {0};
};
explicit AtomicCounterAux(int nthr) { /** auxiliary data */
struct AuxData {
explicit AuxData(int nthr) {
local_.resize(nthr, 0); local_.resize(nthr, 0);
} }
std::vector<int> local_;
};
/** aux update function(s) */
void auxUpdateAfterInc(int tid, AuxData& auxdata, bool success) {
if (success) {
auxdata.local_[tid]++;
}
}
/** annotated shared class */
class AnnotatedAtomicCounter {
public:
explicit AnnotatedAtomicCounter(int val) : shared_(val) {}
void inc(AuxFn& auxfn) {
DSched::setAux(auxfn);
/* calls the fine-grained original */
shared_.inc();
}
void inc_bug(AuxFn auxfn) {
/* duplicates the steps of the multi-access original in order to
* annotate the second access */
int newval = shared_.counter_.load() + 1;
DSched::setAux(auxfn);
shared_.counter_.store(newval);
}
int load_direct() {
return shared_.counter_.load_direct();
}
private:
AtomicCounter<int, DeterministicAtomic> shared_;
}; };
/** auxiliary function for checking global invariants and logging using Annotated = AnnotatedAtomicCounter;
* steps of atomic counter test */
void checkAtomicCounter( /** aux log & check function */
int tid, void auxCheck(int tid, uint64_t step, Annotated& annotated, AuxData& auxdata) {
uint64_t step,
DeterministicAtomic<int>& shared,
AtomicCounterAux& aux) {
/* read shared data */ /* read shared data */
int val = shared.load_direct(); int val = annotated.load_direct();
/* read auxiliary variables */ /* read auxiliary data */
int sum = 0; int sum = 0;
for (int v : aux.local_) { for (int v : auxdata.local_) {
sum += v; sum += v;
} }
/* log state */ /* log state */
VLOG(2) << "Step " << step << " -- tid " << tid << " -- shared counter " VLOG(2) << "Step " << step << " -- tid " << tid
<< val << " -- sum increments " << sum; << " -- shared counter= " << val << " -- sum increments= " << sum;
/* check invariant */ /* check invariant */
if (val != sum) { if (val != sum) {
LOG(ERROR) << "Failed after step " << step; LOG(ERROR) << "Failed after step " << step;
...@@ -143,17 +224,11 @@ void checkAtomicCounter( ...@@ -143,17 +224,11 @@ void checkAtomicCounter(
} }
} }
std::function<void(uint64_t, bool)> auxAtomicCounter( /** function generator(s) */
DeterministicAtomic<int>& shared, AuxFn auxAfterInc(int tid, Annotated& annotated, AuxData& auxdata) {
AtomicCounterAux& aux, return [&annotated, &auxdata, tid](uint64_t step, bool success) {
int tid) { auxUpdateAfterInc(tid, auxdata, success);
return [&shared, &aux, tid](uint64_t step, bool success) { auxCheck(tid, step, annotated, auxdata);
// update auxiliary data
if (success) {
aux.local_[tid]++;
}
// check invariants
checkAtomicCounter(tid, step, shared, aux);
}; };
} }
...@@ -170,30 +245,27 @@ TEST(DSchedCustom, atomic_add) { ...@@ -170,30 +245,27 @@ TEST(DSchedCustom, atomic_add) {
CHECK_GT(nthr, 0); CHECK_GT(nthr, 0);
DeterministicAtomic<int> counter{0}; Annotated annotated(0);
AtomicCounterAux auxData(nthr); AuxData auxdata(nthr);
DeterministicSchedule sched(DeterministicSchedule::uniform(seed)); DSched sched(DSched::uniform(seed));
std::vector<std::thread> threads(nthr); std::vector<std::thread> threads(nthr);
for (int tid = 0; tid < nthr; ++tid) { for (int tid = 0; tid < nthr; ++tid) {
threads[tid] = DeterministicSchedule::thread([&, tid]() { threads[tid] = DSched::thread([&, tid]() {
auto auxFn = auxAtomicCounter(counter, auxData, tid); AuxFn auxfn = auxAfterInc(tid, annotated, auxdata);
for (int i = 0; i < niter; ++i) { for (int i = 0; i < niter; ++i) {
if (bug && (tid == 0) && (i % 10 == 0)) { if (bug && (tid == 0) && (i % 10 == 0)) {
int newval = counter.load() + 1; annotated.inc_bug(auxfn);
DeterministicSchedule::setAux(auxFn);
counter.store(newval);
} else { } else {
DeterministicSchedule::setAux(auxFn); annotated.inc(auxfn);
counter.fetch_add(1);
} }
} }
}); });
} }
for (auto& t : threads) { for (auto& t : threads) {
DeterministicSchedule::join(t); DSched::join(t);
} }
EXPECT_EQ(counter.load_direct(), nthr * niter); EXPECT_EQ(annotated.load_direct(), nthr * niter);
} }
int main(int argc, char** argv) { int main(int argc, char** argv) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment