Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect background function being called in critical functions

Tags:

c++

I am working on very large c++ project, it has lot of real time critical functions and also lot of slow background functions. These background functions should not be called from time critical functions. So is there way to detect these background functions being called from critical functions? compile time would be good but anyway I like to detect before these background functions. More info, both slow and critical functions are part of same class and share same header.

Some more information, Critical functions runs under really faster thread (>=10KHz) slower one runs under different slower thread (<=1KHz). Class member variables are protected using critical sections in slow functions since both use same class member variables. That's reason calling slow functions in critical functions will slowdown overall system performance. That's reason I like to find all these kind of functions automatically instead of manual checking.

Thanks....

like image 544
user2409054 Avatar asked May 22 '13 10:05

user2409054


2 Answers

You need to leverage the linker. Separate the "realtime" and slow functions into two modules, and link them in the correct order.

For example, split the files into two directories. Create a lib from each directory (ranlib the object files together) then link your final application using:

c++ -o myapp main.o lib1/slowfns.a lib2/realtime.a

If you try to call anything from slowfns.a in realtime.a, depending on the compiler, it will fail to link (some compilers may need options to enforce this).

In addition, this lets you easily manage compile-time declarations too: make sure that the headers from the slowfns library aren't on the include path when compiling the "realtime" funcitons library for added protection.

like image 139
Nicholas Wilson Avatar answered Sep 16 '22 15:09

Nicholas Wilson


Getting a compile-time detection other than the one proposed by Nicholas Wilson will be extremely hard if not impossible, but assuming "background" really refers to the functions, and not to multiple threads (I saw no mention of threads in the question, so I assume it's just an odd wording) you could trivially use a global flag and a locker object, and either assert or throw an exception. Or, output a debug message. This will, of course, be runtime-only -- but you should be able to very quickly isolate the offenders. It will also be very low overhead for debug builds (almost guaranteed to run from L1 cache), and none for release builds.

Using CaptureStackBackTrace, one should be able to capture the offending function's address, which a tool like addr2line (or whatever the MS equivalent is) can directly translate to a line in your code. There is probably even a toolhelp function that can directly do this translation (though I wouldn't know).

So, something like this (untested!) might do the trick:

namespace global { int slow_flag = 0; }
struct slow_func_locker
{
    slow_func_locker() { ++global::slow_flag; }
    ~slow_func_locker(){ --global::slow_flag; }
};
#indef NDEBUG
  #define REALTIME  if(global::slow_flag) \
  { \
    void* backtrace; \
    CaptureStackBackTrace(0, 1, &backtrace, 0); \
    printf("RT function %s called from %08x\n", __FUNCTION__, backtrace); \
  }
  #define SLOW_FUNC slow_func_locker slow_func_locker_;
#else
  #define REALTIME
  #define SLOW_FUNC
#endif

foo_class::some_realtime_function(...)
{
    REALTIME;
    //...
};

foo_class::some_slow_function(...)
{
    SLOW_FUNC;
    //...
    some_realtime_function(blah); // this will trigger
};

The only real downside (apart from not being compile-time) is you have to mark each and every slow and realtime function with either marker, but since the compiler cannot magically know which is what, there's not much of a choice anyway.

Note that the global "flag" is really a counter, not a flag. The reason for this is that a slow function could immediately call another slow function that returns and clears the flag -- incorrectly assuming a fast function now (the approach with critical sections suggested by xgbi might deadlock in this case!). A counter prevents this from happening. In presence of threads, one might replace int with std::atomic_int, too.

EDIT:
As it is clear now that there are really 2 threads running, and it only matters that one of them (the "fast" thread) does not ever call a "slow" function, there is another simple, working solution (example using Win32 API, but could be done with POSIX either way):

When the "fast" thread starts up (the "slow" thread does not need to do this), store the thread ID somewhere, either as global variable, or as member of the object that contains all the fast/slow functions -- anywhere where it's accessible:

global::fast_thread_id = GetCurrentThreadId();

The macro to bail out on "unwelcome" function calls could then look like:

#define CHECK_FAST_THREAD assert(GetCurrentThreadID() != global::fast_thread_id)

This macro is then added to any "slow" function that should never be called from the "fast" thread. If the fast thread calls a function that it must not call, the assert triggers and it is known which function is called.

like image 35
Damon Avatar answered Sep 17 '22 15:09

Damon