views:

142

answers:

1

Hello all :)

I'm looking at a simple class I have to manage critical sections and locks, and I'd like to cover this with test cases. Does this make sense, and how would one go about doing it? It's difficult because the only way to verify the class works is to setup very complicated threading scenarios, and even then there's not a good way to test for a leak of a Critical Section in Win32. Is there a more direct way to make sure it's working correctly?

Here's the code:

CriticalSection.hpp:

#pragma once
#include <windows.h>
#include <boost/shared_ptr.hpp>

namespace WindowsAPI { namespace Threading {

    class CriticalSectionImpl;
    class CriticalLock;
    class CriticalAttemptedLock;

    class CriticalSection
    {
        friend class CriticalLock;
        friend class CriticalAttemptedLock;
        boost::shared_ptr<CriticalSectionImpl> impl;
        void Enter();
        bool TryEnter();
        void Leave();
    public:
        CriticalSection();
    };

    class CriticalLock
    {
        CriticalSection &ref;
    public:
        CriticalLock(CriticalSection& sectionToLock) : ref(sectionToLock) { ref.Enter(); };
        ~CriticalLock() { ref.Leave(); };
    };

    class CriticalAttemptedLock
    {
        CriticalSection &ref;
        bool valid;
    public:
        CriticalAttemptedLock(CriticalSection& sectionToLock) : ref(sectionToLock), valid(ref.TryEnter()) {};
        bool LockHeld() { return valid; };
        ~CriticalAttemptedLock() { if (valid) ref.Leave(); };
    };

}}

CriticalSection.cpp:

#include "CriticalSection.hpp"

namespace WindowsAPI { namespace Threading {

class CriticalSectionImpl
{
    friend class CriticalSection;
    CRITICAL_SECTION sectionStructure;
    CriticalSectionImpl() { InitializeCriticalSection(&sectionStructure); };
    void Enter() { EnterCriticalSection(&sectionStructure); };
    bool TryEnter() { if (TryEnterCriticalSection(&sectionStructure)) return true; else return false; };
    void Leave() { LeaveCriticalSection(&sectionStructure); };
public:
    ~CriticalSectionImpl() { DeleteCriticalSection(&sectionStructure); };
};

void CriticalSection::Enter() { impl->Enter(); };
bool CriticalSection::TryEnter() { return impl->TryEnter(); };
void CriticalSection::Leave() { impl->Leave(); };
CriticalSection::CriticalSection() : impl(new CriticalSectionImpl) {} ;

}}
+3  A: 

Here are three options and personally I favour the last one...

  • You could create a 'critical section factory' interface that can be passed to your constructor. This would have functions that wrapped the API level functions that you need to use. You could then mock this interface up and pass the mock to the code when under test and you can be sure that the right API functions are called. You would, generally, also have a constructor that didn't take this interface and that instead initialised itself with a static instance of the factory that called directly to the API. Normal creation of the objects wouldn't be affected (as you have them use a default implementation) but you can instrument when under test. This is the standard dependency injection route and results in you being able to parameterise from above. The downside of all this is that you have a layer of indirection and you need to store a pointer to the factory in each instance (so you're probably losing out in both space and time).
  • Alternatively you could try and mock the API out from underneath... A long time ago I looked into testing this kind of low level API usage with API hooking; the idea being that if I hooked the actual Win32 API calls I could develop a 'mock API layer' which would be used in the same way as more normal Mock Objects but would rely on "parameterise from below" rather than parameterise from above. Whilst this worked and I got quite a long way into the project, it was very complex to ensure that you were only mocking the code under test. The good thing about this approach was that I could cause the API calls to fail under controlled conditions in my test; this allowed me to test failure paths which were otherwise VERY difficult to exercise.
  • The third approach is to accept that some code is not testable with reasonable resources and that dependency injection isn't always suitable. Make the code as simple as you can, eyeball it, write tests for the bits that you can and move on. This is what I tend to do in situations like this.

However....

I'm dubious of your design choice. Firstly there's too much going on in the class (IMHO). The reference counting and the locking are orthogonal. I'd split them apart so that I had a simple class that did critical section management and then built on it I found I really needed reference counting... Secondly there's the reference counting and the design of your lock functions; rather than returning an object that releases the lock in its dtor why not simply have an object that you create on the stack to create a scoped lock. This would remove much of the complexity. In fact you could end up with a critical section class that's as simple as this:

CCriticalSection::CCriticalSection()
{
   ::InitializeCriticalSection(&m_crit);
}

CCriticalSection::~CCriticalSection()
{
   ::DeleteCriticalSection(&m_crit);
}

#if(_WIN32_WINNT >= 0x0400)
bool CCriticalSection::TryEnter()
{
   return ToBool(::TryEnterCriticalSection(&m_crit));
}
#endif

void CCriticalSection::Enter()
{
   ::EnterCriticalSection(&m_crit);
}

void CCriticalSection::Leave()
{
   ::LeaveCriticalSection(&m_crit);
}

Which fits with my idea of this kind of code being simple enough to eyeball rather than introducing complex testing ...

You could then have a scoped locking class such as:

CCriticalSection::Owner::Owner(
   ICriticalSection &crit)
   : m_crit(crit)
{
   m_crit.Enter();
}

CCriticalSection::Owner::~Owner()
{
   m_crit.Leave();
}

You'd use it like this

void MyClass::DoThing()
{
   ICriticalSection::Owner lock(m_criticalSection);

   // We're locked whilst 'lock' is in scope...
}

Of course my code isn't using TryEnter() or doing anything complex but there's nothing to stop your simple RAII classes from doing more; though, IMHO, I think TryEnter() is actually required VERY rarely.

Len Holgate
Few problems with this. #1. CRITICAL_SECTION structures cannot be moved or copied. That's the reason for the refcounting in the first place. Therefore some form of refcounting here is essential because I don't want clients to have to worry about memory management of the class. #2: I like your handling of the lock much better. Thank you very much :) #3: I'm going to look at somehow mocking the API functions -- which is ironic because half the point of the critical section object is to be able to test code using it.
Billy ONeal
I've never found #1 to be an issue; perhaps it's just the way that I use my locks. I simply add an instance of `CCriticalSection` to a class that needs to be able to lock areas of itself and then use the RAII 'owner' class to manage the lock lifetime. For the objects that have their own locks I rarely have a copy ctor or assignment op; it just never makes sense, so I don't have a problem and don't need the ref count...
Len Holgate
I suppose I could just make the critical section object noncopyable but for now I'm in a scenario where I actually need the critical section object to have "`shared_ptr`" semantics. If the critical sections didn't need special teardown I'd just use a `shared_ptr` and be done with it, but unfortunately they do :(
Billy ONeal
Personally I'd think long and hard about what it means to have an object that can be copied and which can be locked and where the copy shares the lock of the original...
Len Holgate
I have a class which manages generation of a report. Each segment of the report takes a long time to produce, and is often I/O bound, so I want them to run in parallel. A section needs to be shared among the threads because when each thread finishes it's task, it `push_back`s to a vector of completed report strings. Once all the threads finish their jobs, the final report is assembled. Therefore each thread, as well as the component that does final assembly, needs access to the critical section.
Billy ONeal
IMHO, each thread needs access to the "result collection" and the result collection needs to be lockable. I expect the structure of the code is such that you set things up, start some threads, wait for them to finish, and then produce the final report. You can create the "result collection" on the stack when you set things up, pass a pointer of it to each thread, and have it lock itself internally when each thread calls "AddResults()" on it. None of this needs copyable locks and all of it is pretty standard stuff... There's no need for `shared_ptr`. The main thread owns the results.
Len Holgate
Except then my collection would need to have locking semantics. I've edited the question to the solution I settled on -- I just used a pimpl-idiom to move the refcounting into shared_ptr where it belongs.
Billy ONeal
So you wrap the collection in a class which contains a lock and exposes just the methods that you need for it to do its job... The collection that you NEED SHOULD have locking semantics; so build one that does...
Len Holgate