6

I'm trying to implement a cache which must be (obviously) optimized for getting and recycling items.

There is one challenge though: The cache may be invalidated from time to time.

From an architecture perspective, what I did so far is to implement the cache comprising a pool. The pool itself is actually a lock-free object pool, well tested and working.

One problem is to allow the pool itself to be swapped with an updated version. I think I've got the swapping working (reference: of Lock-free swap of two unique_ptr<T>).

But how is it possible to handle lock-free get and set using a pool that can be swapped during operation (getPreparedDefaultRule() and recyclePreparedDefaultRule() below respectively)?

I'm almost at the point to assume it is not possible. Though I'd be glad to be proven wrong.

template <typename Data, typename Delegate>
class DefaultRulePoolCache {
public:
    DefaultRulePoolCache() : _atomicDefaultRulePool(nullptr), _defaultRulePool(nullptr) {
        
    }
    
    std::unique_ptr<detail::PreparedRule<Data, Delegate>> getPreparedDefaultRule() {
        auto atomicPool = _atomicDefaultRulePool.load();
        if (!atomicPool) {
            return nullptr;
        }
        return atomicPool->getPreparedRule();
    }
    
    void recyclePreparedDefaultRule(std::unique_ptr<detail::PreparedRulePool<Data, Delegate>> preparedRule) {
        auto atomicPool = _atomicDefaultRulePool.load();
        if (!atomicPool) {
            return;
        }
        hdmCheck(preparedRule) << "Cannot recycle null rule.";
        // we return the prepared rule only to the pool in case it's the current version of the default rul
        if (atomicPool->getTag() == preparedRule->getTag()) {
            atomicPool->recyclePreparedRule(std::move(preparedRule));
        }
    }
    
    void setDefaultRulePool(std::shared_ptr<detail::PreparedRulePool<Data, Delegate>> defaultRulePool) {
        std::lock_guard<std::mutex> lock(_defaultRulePoolMutex);
        atomicSwapPool(defaultRulePool);
    }
    
    void removeDefaultRulePool() {
        std::lock_guard<std::mutex> lock(_defaultRulePoolMutex);
        atomicSwapPool(nullptr);
    }

private:
    std::atomic<detail::PreparedRulePool<Data, Delegate>*> _atomicDefaultRulePool;
    std::shared_ptr<detail::PreparedRulePool<data::Location, KVOLocationValuesDelegate>> _defaultRulePool;    
    std::mutex _defaultRulePoolMutex;

    
    void atomicSwapPool(std::shared_ptr<detail::PreparedRulePool<Data, Delegate>> defaultRulePool) {
       std::atomic_exchange(&_atomicDefaultRulePool, defaultRulePool.get());
        // destroy the old pool
        _defaultRulePool = nullptr;
        // asign the new pool
        _defaultRulePool = defaultRulePool;
    }
};
benjist
  • 2,740
  • 3
  • 31
  • 58
  • swapping the whole pool "with a new version" sounds like RCU could be what you're looking for. https://en.wikipedia.org/wiki/Read-copy-update. One of the keys to that working is to delay deallocation until all possible readers have finished looking at this copy of the data structure. RCU is good for read-mostly data that changes infrequently, although you might be able to use it with incrementally-growing data structures and only actually copy/update when stuff needs to go away. – Peter Cordes Nov 14 '21 at 18:38

0 Answers0