I have the following interfaces:
class T {
public:
// Called in parallel
virtual unsigned validate () = 0;
// Called with a lock taken out
virtual unsigned update () = 0;
};
template <typename DataType>
class Cache {
public:
// Gets the requested object.
// If it doesn't exist in memory, go to SQL.
unsigned fetch (DataType &data);
// Gets the requested object.
// If it's not in memory, returns NOT_FOUND.
unsigned find (DataType &data);
};
What I'd like to achieve: I would like to be able to have compilation fail if fetch
is called during update
. Effectively, I'd like to disable the function statically, based on the call site. Something like,
std::enable_if <callsite_is_not_implementation_of_T_update, unsigned>
fetch (DataType &data);
Usage would work something like this:
class A_T : public T {
public:
virtual unsigned validate () {
global_cache.fetch (object); // OK
}
virtual unsigned update () {
global_cache.find (object); // Also OK
global_cache.fetch (object); // FAIL!
}
};
There are approximately 500 implementations of T
in my project.
The application loops in many threads and calls validate
for many instances of T
in parallel. Then a global lock is taken out, and update
is called. Hence, the speed of update
is crucial. The general attitude is to take whatever time you need during validate
, but update
should be as lean as possible.
My problem is with the use ofCache
. A Cache
is basically an in-memory cache of data objects from SQL.
The policy is to never call Cache::fetch
during update
because of the potential SQL round-trip while holding a lock. We're all working hard to foster this mindset within the team. Unfortunately, some of these still sneak in, and get past code review. We only notice them when the system is under heavy load and everything grinds to a halt.
I'd like to develop a safety net and prevent this kind of thing from being allowed at all. What I'd like to achieve is to have compilation fail if Cache::fetch
is called from T::update
.
I don't mind if it can be worked around. The point is to have it as a barrier; the only way you could make a mistake is to intentionally do it.
I've made it a little bit of the way there, although not quite what I'm really after. For instance, I'd prefer not to have to change every single call to fetch
.
template <typename Impl>
class cache_key {
cache_key() { }
friend unsigned Impl::validate();
};
#define CACHE_KEY cache_key<std::remove_pointer<decltype(this)>::type> ()
So now Cache::fetch
looks like:
unsigned fetch (DataType &object, const cache_key &key);
And an implementation of T
might look like this:
class A_T : public T {
public:
virtual unsigned validate () {
global_cache.fetch (object, CACHE_KEY); // OK
}
virtual unsigned update () {
global_cache.fetch (object, CACHE_KEY); // Can't do it!
}
};
Complete HTML/CSS Course 2022 To stop the execution of a function in JavaScript, use the clearTimeout() method. This function call clears any timer set by the setTimeout() functions.
A static method can call only other static methods; it cannot call a non-static method. A static method can be called directly from the class, without having to create an instance of the class. A static method can only access static variables; it cannot access instance variables.
exit() where you want to stop.
It is important to understand that each of the functions we write can be used and called from other functions we write. This is one of the most important ways that computer scientists take a large problem and break it down into a group of smaller problems.
I'm not aware of compilation time error generation, but it can be done to generate run-time error with updates only to base class.
A way to do it is to call update through a non-virtual proxy function in a base class, which would set state to the base class to detect that we are in update and therefore fetch should not be called.
class updateWatcher()
{
public:
updateWatcher(bool *valIn) : val(valIn) {*val=true;}
~updateWatcher() {*val=false;}
private:
bool* val;
}
class T {
public:
// Called in parallel
virtual unsigned validate () = 0;
unsigned updateProxy()
{
updateWatcher(&inUpdate); //exception safe tracker we are in update
return update();
}
void
protected:
// Called with a lock taken out
virtual unsigned update () = 0;
bool inUpdate; // tells if we are in update or not
};
class A_T : public T {
public:
virtual unsigned validate () {
global_cache.fetch (object,inUpdate); // OK
}
virtual unsigned update () {
global_cache.find (object); // Also OK
global_cache.fetch (object,inUpdate); // FAIL (put assert in global_cache.fetch) !
}
};
This will not produce compilation but run-time error, benefit is that there is no need to update any implementations (except replacing all global_cache.fetch (...); by global_cache.fetch (...,inUpdate); and calls to update() to updateProxy(); in all implementations, which can be automated efficiently). You can then integrate some automated tests as part of the build environment to catch the asserts.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With