13 ThreadBlockerWhileUsed

ThreadBlockerWhileUsed is defined purely in a header file.

This is a problematic, deprecated approach which involves trying to block a thread until a reference count is decremented down to zero.

It was being used to block a thread trying to close an object that had pending asynchronous I/O requests.

class X
{
    X() : blocker_(1) {}
    
    void Close()
    {
        ...
        --blocker_;
        blocker_.Wait();
        delete this;
    }

    void PostSomeAsyncOperation()
    {
        ++blocker_;
        post([this]() 
            { 
                // handle completion 
                --blocker_; 
            });
    }

    ThreadBlockerWhileUsed blocker_;
};

ThreadBlockerWhileUsed.h

ThreadBlockerWhileUsed provides a thread safe counter which can be incremented and decremented (with both prefix and postfix ++, -- operators). It allows for waiting until the counter is zero. It assumes the counter never goes below zero.

// ThreadBlockerWhileUsed.h
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2013

#pragma once
#ifndef Ceda_cxThread_ThreadBlockerWhileUsed_H
#define Ceda_cxThread_ThreadBlockerWhileUsed_H

#include "cxThread.h"
#include "Ceda/cxUtils/CedaAssert.h"
#include <mutex>
#include <condition_variable>

namespace ceda
{
class ThreadBlockerWhileUsed
{
    cxNotCloneable(ThreadBlockerWhileUsed)
    
public:
    explicit ThreadBlockerWhileUsed(int count = 0) : count_(count) 
    {
        // todo temporarily commented out because TcpClient is broken
        //cxAssert(count_ > 0);
    }

    ~ThreadBlockerWhileUsed()
    {
        // todo: this tripped on JigsawServer on Linux box
        cxAssert(count_ == 0);
    }

    int operator++() 
    {
        std::lock_guard<std::mutex> lock(mutex_);
        // todo temporarily commented out because TcpClient is broken
        //cxAssert(count_ > 0);                       // Can't allow count to be zero because this implementation assumes there is at most one transition to zero
        ++count_;
        return count_;
    }

    int operator--()
    {
        std::lock_guard<std::mutex> lock(mutex_);
        cxAssert(count_ > 0);
        if (--count_ == 0)
        {
            // It is important that this notification be performed while holding a lock on the mutex,
            // even though a thread waiting on the condition may wake up only to immediately block again.
            // For example, consider that the thread calling Wait() would exit the program if the condition is 
            // satisfied, causing destruction of the condition_variable.  Then a spurious wakeup after mutex 
            // unlock but before notify would result in notify called on a destroyed object.
            cv_.notify_all();
        }
        return count_;
    }

    // Postfix
    int operator++(int) { return operator++()-1; }
    int operator--(int) { return operator--()+1; }

    void Wait()
    {
        std::unique_lock<std::mutex> lock(mutex_);
        cv_.wait(lock,[this]{return count_ == 0;});
    }
private:
    std::mutex mutex_;
    std::condition_variable cv_;
    int count_;
};
} // namespace ceda
#endif // include guard