4

I have two functions:

void prepare() and void finish() that will be called sequentially like:

prepare();
<do something>;
finish(); 
... 
prepare(); 
<do something>; 
finish();

I want to make a simple assertion to simply test that they are in fact being called this way and that they aren't being called concurrently or out-of-order in the application.

This application is a single-threaded application. This is a simple development/testing sanity check to make sure that these functions are being called in-order and that for whatever reason, they aren't being called concurrently. Furthermore, these assertions/sanity checks should be omitted from production code as performance is crucial!

would a simple assert() like this work best?

int test = 0;

void prepare() {
   assert(++test == 1);

   .
   .
   .
}

void finish() {
    assert(--test == 0);

    .
    .
    .
}
chriskirk
  • 761
  • 1
  • 11
  • 22
  • should volatile be used for the int test to mitigate any kind of caching? – chriskirk Feb 01 '11 at 15:50
  • Are there no comments on the int test being qualified as volatile? The way it is now, couldn't test be cached in a register? – chriskirk Feb 01 '11 at 15:53
  • @Fred Nurk can you please elaborate? This particular process doesn't involve the instantiation of new objects, it's simply just a process that occurs within the application. – chriskirk Feb 01 '11 at 16:04
  • @chriskirk: Moved to an answer. – Fred Nurk Feb 01 '11 at 16:06
  • 2
    Re: `volatile` -- please read this, but ignore (or at least take with a brick of salt) the accepted answer, as it implies that `volatile` is useful for multithreaded programming. But it is not. http://stackoverflow.com/questions/4557979/when-to-use-volatile-with-multi-threading – John Dibling Feb 01 '11 at 16:07
  • @chriskirk: You explicitly state in the question that the code is single threaded, if that is so, what potential problem would caching have? (hint: none) how could the two functions be called concurrently? (hint: they cannot) It is either that, or you need to rewrite your question. – David Rodríguez - dribeas Feb 01 '11 at 16:40

6 Answers6

3

There's a race condition here: two concurrent instances of prepare might take the value of test at the same time, then both increment it in a register to both obtain 1, then do the comparison to get true.

Making it volatile is not going to help. Instead, you should put a mutex on test, like so:

boost::mutex mtx;
int test = 0;

void prepare()
{
    boost::mutex::scoped_try_lock lock(&mtx);
    assert(lock.owns_lock());
    assert(test++ == 0);
    // ...
}

void finish()
{
    boost::mutex::scoped_try_lock lock(&mtx);
    assert(lock.owns_lock());
    assert(--test == 0);
}
Community
  • 1
  • 1
Fred Foo
  • 355,277
  • 75
  • 744
  • 836
  • @chriskirk: No, for reasons outlined here: http://stackoverflow.com/questions/2484980/why-is-volatile-not-considered-useful-in-multithreaded-c-or-c-programming – Fred Foo Feb 01 '11 at 16:00
  • Hum... I don't think this program is a multi-threaded one. Is it? – peoro Feb 01 '11 at 16:02
  • @peoro: the OP asked about concurrent accesses. – Fred Foo Feb 01 '11 at 16:04
  • Yes, there is a race condition. What platform is this? – ThomasMcLeod Feb 01 '11 at 16:05
  • 1
    This application is *not supposed* to be concurrent. This routine is not suppose to support concurrency or re-entrancy. This whole post is asking what the best way to assert() this would be in development/testing mode (not in production). Just like a basic sanity check while developing. – chriskirk Feb 01 '11 at 16:24
  • A scoped lock only does half the job. The mutex needs to be held until finish is called. – Fred Nurk Feb 01 '11 at 16:25
  • @chriskirk: If the entire program is single-threaded, then you don't need to check for concurrent accesses as you said in your question. Then your original code suffices. – Fred Foo Feb 01 '11 at 16:28
  • Updated the original question, hope things will be cleared up! Sorry for any confusion! – chriskirk Feb 01 '11 at 16:28
  • @larsmans: You can simply [hold the mutex throughout](http://stackoverflow.com/questions/4864645/simple-assert-for-ordered-non-re-entrant-calling/4864891#4864891) instead of the separate counter. – Fred Nurk Feb 01 '11 at 16:32
3

You might want to change

int test = 0;

to

#ifndef NDEBUG
int test = 0;
#endif

to satisfy your requirement that "any code relating to this test should be omitted from production".

Raedwald
  • 46,613
  • 43
  • 151
  • 237
3

you probably want:

int test = 0;

void prepare() {
    // enter critical section
    assert(test++ == 0);

    .
    .
    .
    // leave critical section 
}

void finish() {
    // enter critical section
    assert(--test == 0);

    .
    .
    .
    // leave critical section
}
ThomasMcLeod
  • 7,603
  • 4
  • 42
  • 80
  • This is a conventional c++ reference counting idiom. But it doesn't sove the concurrency problem. What platform are you on? – ThomasMcLeod Feb 01 '11 at 15:58
  • this exercise is being done to just check that prepare() and finish() are being called properly. It's a single threaded app on 64-bit Linux using gcc 4.1.2. – chriskirk Feb 01 '11 at 16:05
  • @chris, if it's single threaded, then you do not need thread synchronization. Please edit your question to remove references to concurrency, and remove the concurrency and reentrancy tags. – ThomasMcLeod Feb 01 '11 at 16:12
1

Your code is OK, unless you need to allow nesting prepare and finish calls.

If nesting is not allowed, you could use a bool instead of an int:

bool locked = false;;

void prepare() {
    assert( ! locked );
    locked = true;
    ...
}

void finish() {
    assert( locked );
    locked = false;
    ...
}
peoro
  • 25,562
  • 20
  • 98
  • 150
  • Well, I think it would be nicer to have it all in a single assert to eliminate the need of having #if NDEBUG. What do you think? – chriskirk Feb 01 '11 at 15:55
  • @chriskirk: You can still do it: `assert( (locked=!locked)==true );` in `prepare and `assert( (locked=!locked)==false );` in `finish`. Anyway I'd prefer using `#if NDEBUG`, in my opinion it's easier to understand the code this way, but it's just a matter of taste. I think that in most circumstances using a `bool` could cost less than using an `int`, plus the code logic is clearer, in my opinion. – peoro Feb 01 '11 at 16:01
1

Since you're using C++, why not use RAII? You'd still need to check for re-entrant use, but RAII simplifies things considerably. Combined with larsmans' mutex and Raedwald's elimination in NDEBUG:

struct Frobber {
  Frobber() {
    assert(mtx.try_lock());
#ifndef NDEBUG
    try {  // in case prepare throws
#endif
      prepare();
#ifndef NDEBUG
    }
    catch (...) {
      mtx.unlock();
      throw;
    }
#endif
  }

  void something();
  // And the other actions that can be performed between preparation and finishing.

  ~Frobber() {
    finish();
#ifndef NDEBUG
    mtx.unlock();
#endif
  }

private:
#ifndef NDEBUG
  static boost::mutex mtx;
#endif

  Frobber(Frobber const&);  // not defined; 0x: = delete
  Frobber& operator=(Frobber const&);  // not defined; 0x: = delete
};
#ifndef NDEBUG
boost::mutex Frobber::mtx;
#endif

void example() {
  Frobber blah;  // instead of prepare()
  blah.something();
  // implicit finish()
}

Inside example, you simply cannot do something without first preparing, and finishing will always happen, even if an exception is thrown.

Side note about NDEBUG: if you use it this way, make sure it is either always defined or always undefined in all translation units, as opposed to how it's used for assert (allowing it to be defined and undefined at various points).

Community
  • 1
  • 1
Fred Nurk
  • 13,952
  • 4
  • 37
  • 63
  • Sorry, I forgot to mention this application is very latency-sensitive and hence RAII wouldn't be appropriate. Thanks for the suggestion, and it's good under typical application scenarios and requirements. – chriskirk Feb 01 '11 at 16:13
  • @chriskirk: How do you think the above code will adversely affect latency? What overhead do you see compared to the non-RAII case? – Fred Nurk Feb 01 '11 at 16:23
1

If you put <do something>; into a class you can reduce the need for a check at all:

Just have the constructor call prepare and the destructor call finish. Then it's automatically enforced that they're called appropriately.

Note that concurrency and nesting issues still apply: If you want to prevent nesting then you'd still need some sort of global state (static class member?) to keep track of that, and if it's used in more than one thread access to that counter would need to be mutex protected.

Also note that you could also make private operator new/delete to prevent someone from creating one on the heap and not destroying it.

Mark B
  • 95,107
  • 10
  • 109
  • 188
  • Private operator new/delete doesn't prevent someone from creating one on the heap. Sometimes you just have to accept people can break your code if they try. – Fred Nurk Feb 01 '11 at 16:27