Fun with Boost and DKit

DKit Laboratory

This morning I finished up a little coding I was doing on DKit and was really happy about the outcome. The thing that really set the stage was the building of boost for OS X 10.7. It was really pretty simple, and it allows me the flexibility to use boost in DKit. Now there was nothing really stopping me before, but without it running on my MacBook Pro, it was a little hard to know that it was going to work.

I suppose I could have pulled out my old Intel laptop and downloaded Ubuntu 12 and put it on there, and run it in a terminal, but I didn't really feel like bring that guy out of the closet, but maybe it's time. I could really use to have Ubuntu 12 working now.

In any case, boost is built, and I was able to make the "hammer" and "drain" thread tests on the LinkedFIFO queues. The idea is that I have a "hammer" that can place items on a queue, and then a drain that can empty a queue. By putting these in different combinations, I can test a multi-producer/single-consumer queue as well as a single-producer/multi-consumer queue.

Starting with the Right Base Class

One of the neat things I did this morning was to make a base abstract template class for all the FIFO queues, as they all (on purpose) have the same basic API. This then allows me to treat all the different implementations as just that - implementations and not as something of significant difference.

It's not too exciting, but I was pretty pleased after I had created it and then used it as the base class for all the FIFO implementations I had in DKit. The really important part was to make sure that the core API methods were pure virtual methods - a.k.a. virtual abstract methods:

  /*******************************************************************
   *
   *                        Accessor Methods
   *
   *******************************************************************/
  /**
   * This method takes an item and places it in the queue - if it can.
   * If so, then it will return 'true', otherwise, it'll return 'false'.
   */
  virtual bool push( const T & anElem ) = 0;
  /**
   * This method updates the passed-in reference with the value on the
   * top of the queue - if it can. If so, it'll return the value and
   * 'true', but if it can't, as in the queue is empty, then the method
   * will return 'false' and the value will be untouched.
   */
  virtual bool pop( T & anElem ) = 0;
  /**
   * This form of the pop() method will throw a std::exception
   * if there is nothing to pop, but otherwise, will return the
   * the first element on the queue. This is a slightly different
   * form that fits a different use-case, and so it's a handy
   * thing to have around at times.
   */
  virtual T pop() = 0;
  /**
   * If there is an item on the queue, this method will return a look
   * at that item without updating the queue. The return value will be
   * 'true' if there is something, but 'false' if the queue is empty.
   */
  virtual bool peek( T & anElem ) = 0;
  /**
   * This form of the peek() method is very much like the non-argument
   * version of the pop() method. If there is something on the top of
   * the queue, this method will return a COPY of it. If not, it will
   * throw a std::exception, that needs to be caught.
   */
  virtual T peek() = 0;
  /**
   * This method will clear out the contents of the queue so if
   * you're storing pointers, then you need to be careful as this
   * could leak.
   */
  virtual void clear() = 0;
  /**
   * This method will return 'true' if there are no items in the
   * queue. Simple.
   */
  virtual bool empty() = 0;
  /**
   * This method will return the total number of items in the
   * queue. Since it's possible that multiple threads are adding
   * to this queue at any one point in time, it's really at BEST
   * a snapshot of the size, and is only completely accurate
   * when the queue is stable.
   */
  virtual size_t size() const = 0;

Then it was easy to use these methods in place of the actual methods in the subclasses. It's just a better way to define the API. With that done, I was able to make the Hammer and Drain because when I needed to reference a Queue, I just used dkit::FIFO and that was good enough. Pretty nice.

Handling the Logging

One of the problems with multi-threaded testing is that the output tends to garble itself pretty totally at times, and it makes it near impossible to determine what was really meant by the output of the code. For example, I had the following in the Drain:

  std::cout << "[Drain::doIt(" << mID << ")] - popped "
            << mCount << " items off the queue" << std::endl;

but because it was dealt with as six different components, there was more than adequate time to have another thread jump into the output stream and have its output intermingle with the message I was trying to send.

So what's a better way? Well… have all the components in one place, like this:

  std::ostringstream msg;
  msg << "[Drain::doIt(" << mID << ")] - popped "
      << mCount << " items off the queue";
  std::cout << msg.str() << std::endl;

What I realized was that this still looks like two items, and the newlines were getting intercepted and causing the output to look bad. The final result I went with was the "all-in-one" idea:

  std::ostringstream msg;
  msg << "[Drain::doIt(" << mID << ")] - popped "
      << mCount << " items off the queue" << std::endl;
  std::cout << msg.str();

At this point, things looked a lot better, and I wasn't getting the scrambled messages. Now this is only good if the console writer is atomic, but it's a lot better bet doing it this way than hoping that the entire streaming operation was going to be atomic. That's just not happening.

Adding a Little Polish

Once I had the LinkedFIFO tests done, and the classes all ready to go, I checked it all in and then updated the docs to reflect the addition of the base class as well as the new dependency on boost and the rationale for it. I'm not sure that many are going to care - it's pretty isolated, if you really don't want to use boost - even though it's on even platform you could imagine, so it's not hard to remove. But it makes life and portability much easier.

It's been a productive morning. I need to go back and put the tests in for the CircularFIFO implementations, but that's not going to be too hard - after all, the same code will work on them that did on the LinkedFIFOs. I just need to move it over and let it run.