33 Forced eviction

Both independent and dependent nodes usually exist in the eviction queue. There is a mechanism to try to force them to be evicted, when there is an application requirement to delete the DGS node. Often a DGS node is a member of some containing object which needs to be destroyed.

Failure to evict a DGS node before it destructs will trip an assertion in ~DGBaseNode():


DGBaseNode::~DGBaseNode()
{
    if (Enabled(DF_IN_EVICTION_QUEUE))
    {
        Tracer() << "ERROR: destroying DGS node " << Name() << " even though it's in the eviction queue\n";

        // Trips if an attempt is made to delete a DGS node that is still in the eviction queue.
        cxAlwaysAssert(false);
    }
}

Prefer objects containing DGS nodes to be deleted by the CSpace GC

It is generally recommended that objects containing DGS nodes be deleted by the CSpace GC, and not for example declared on the frame.

This ensures that they are only deleted when they no longer have any DGS nodes in the eviction queue, because the GC visits all the containing objects of the DGS nodes in the eviction queue.

Example of object on frame needing call to EvictDgsNodes()

In the following example, the call to EvictDgsNodes() is required because the object instance of X is declared on the frame. The DGS nodes within the instance must be evicted before it is allowed to destruct.


$struct X :
    model
    {
        int x1;
        int x2;
        int x3;
    }
{
    $dep int y1 = x1 + x2;
    $dep int y2 = y1 + x3;
};

void Run()
{
    ceda::CSpaceCreator cspace;
    ceda::CSpaceTxn txn;

    // Instance of X is declared on the frame
    X obj;

    obj.x1 = 10;
    obj.x2 = 20;
    obj.x3 = 50;

    // Need to evict DGS nodes in obj before it destructs
    obj.EvictDgsNodes();
}

Xcpp generates EvictDgsNodes() on struct X as follows:

struct X_model
{
    ...
    void EvictDgsNodes() const;

    int x1;
    int x2;
    int x3;
};

template <typename BC>
struct X_model_mixin : public BC
{
    using typename BC::FinalClass;

    void EvictDgsNodes() const
    {
        BC::EvictDgsNodes();
        _model_.EvictDgsNodes();
    }
    ...

    X_model _model_;
};

struct X : public X_model_mixin<ceda::BaseMixin<X,ceda::EmptyBase>>
{
    typedef X_model_mixin<ceda::BaseMixin<X,ceda::EmptyBase>> BaseClass;

    struct Depnode_y1 : ceda::DGDepNode
    {
        ...
    } y1;

    struct Depnode_y2 : ceda::DGDepNode
    {
        ...
    } y2;

    void EvictDgsNodes() const
    {
        BaseClass::EvictDgsNodes();
        y1.TryEvict(false);
        y2.TryEvict(false);
    }
};

void X_model::EvictDgsNodes() const
{
    ceda::DataSourceEvict(&x1);
    ceda::DataSourceEvict(&x2);
    ceda::DataSourceEvict(&x3);
}

The complete generated code is retained as supporting/eviction/generated-code-X.cpp.

Beware of objects having async dependents

The above example of an object on the frame works because there are no asynchronous dependent nodes ($dep variables are synchronous dependent nodes).

It may be possible for the object to be on the frame if EvictDgsNodes() is called with a CSpace lock before the object destructs, as long as all its DGS nodes are evictable at that time according to IsEvictable().

However that can't generally be assumed for objects containing async dependents because an outstanding posted task causes IsEvictable() to return false, and it's not possible to wait for the task to complete while holding a CSpaceLock without a risk of dead-lock.

IView objects in CedaGui framework

Consider the example of an IView object in the CedaGui framework which is being destroyed. In order to allow the object to destruct it is necessary to force the eviction of all its associated DGS nodes.

This means forced evicting of:

  • the DGS nodes associated with its own $indep, $dep and $cache members
  • the DGS nodes associated with its member variables

For this purpose application code can call the method EvictDgsNodes(). For example the CedaGui view framework does this as follows:


    $mixin BaseViewMixin
    {
        void OnDestroyView()
        {
            $this->EvictDgsNodes();
        }
        ...
    }

This method can be chained in mixins. For example


    $mixin DrawImageCtrlMixin
    {
        void EvictDgsNodes() const
        {
            BaseClass::EvictDgsNodes();
            texture_.EvictDgsNodes();
        }

        DependentTexture texture_;
    };

The EmptyBase defined in IObject.h provides the do-nothing base implementation for the mixin chain:


    struct EmptyBase
    {
        void VisitObjects(IObjectVisitor&) const {}
        void VisitPrefs(IPrefVisitor&) const {}
        void EvictDgsNodes() const {}
    };

$evict{...} block on classes to help implement forced eviction

The xc++ language allows $evict{...} blocks to be declared in classes to help implement the EvictDgsNodes method of objects that contains DGS nodes.

For example the following xc++ code:


    $struct Y isa ceda::IObject
    {
        $evict { x.EvictDgsNodes(); }
        X x;
    };

generates the following c++ code:


    struct Y : public ceda::IObjectBaseMixin<Y,ceda::EmptyBase>
    {
        ...

        void EvictDgsNodes_extra() const;

        void EvictDgsNodes() const
        {
            EvictDgsNodes_extra();
            BaseClass::EvictDgsNodes();
        }

        X x;
    };

    void Y::EvictDgsNodes_extra() const
    {
        x.EvictDgsNodes();
    }

What is the point of forced eviction?

Objects that have $indeps, $deps and $cache functions are intended to be heap allocated. They are deleted by the GC when they are unreachable. For that to be possible all the DGS nodes need to be evicted, because DGS nodes in the eviction queue visit their containing objects.

This can easily take too long to happen if we wait for LRU eviction. So in cases where we know an object is no longer useful it's a good idea to help it get gargage collected by manually forcing its DGS nodes to be evicted. This involves calling EvictDgsNodes() on the object.

There is an issue with async DGS nodes - we can't force eviction when there may be a posted task. Waiting for the task to finish can lead to a dead-lock. In that case our current appproach is for the forced eviction to fail, which just means that the memory reclamation will be delayed.