DeleteTest.cpp

// DeleteTest.cpp
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2012

@import "Ceda/cxOperation/UTRoot.h"
@import "Ceda/cxOperation/IWorkingSetMachine.h"
@import "Ceda/cxWorkingSetIpc/WorkingSetIPC.h"
@import "Ceda/cxPersistStore/IPersistStore.h"
@import "Ceda/cxObject/IObjectVisitor.h"
@import "Ceda/cxObject/PrintReflectedVariable.h"
#include "Ceda/cxUtils/HPTime.h"
#include "Ceda/cxUtils/CedaAssert.h"
#include "Ceda/cxUtils/Tracer.h"
#include "Ceda/cxUtils/Environ.h"
#include "Ceda/cxUtils/ScopeWriter.h"

///////////////////////////////////////////////////////////////////////////////////////////////////
// VoterRegistrationSet

$struct VoterRegistration;
ceda::xostream& operator<<(ceda::xostream& os, const ceda::cref<VoterRegistration>& p);

$interface+ ITbuImage : ceda::IObject
{
};

$struct+ Image isa ceda::IPersistable, ITbuImage :
    model
    {
        int32 x;
    }
{
    ~$$()
    {
        Tracer() << "*** ~Image\n";
    }
}; 

$model+ VoterRegistrationModel <<multiline>>
{
    assignable< ceda::xstring > Number;
    xvector< pref< ITbuImage > > Photograph;
};

$struct+ VoterRegistration isa ceda::IPersistable : model VoterRegistrationModel 
{
    ~$$()
    {
        Tracer() << "*** ~VoterRegistration\n";
    }
};


$model+ VoterRegistrationSetModel <<multiline>>
{
    int32 x32;
    xvector< ceda::cref< VoterRegistration > > Registrations;
};

$struct+ VoterRegistrationSet isa ceda::IPersistable : model VoterRegistrationSetModel 
{
    ~$$()
    {
        Tracer() << "*** ~VoterRegistrationSet\n";
    }
};


ceda::xostream& operator<<(ceda::xostream& os, const ceda::cref<VoterRegistration>& p)
{
    if (p) 
    {
        os << (const ceda::prefbase&)p << ' ' << *p; 
    }
    else 
    {
        os << "null";
    }
    return os;
}

/*
Where a VoterRegistration is being removed from the Registrations of a VoterRegistrationSetModel.  
The problem seems to occur with the a VoterRegistration that has prefs/crefs set.  In this case, 
I think that the VoterRegistration needs to have a photo or a fingerprint in it to cause the 
problem.

I will see if I can work out the scenario that shows this problem up and if so, I may be able to 
reproduce it with one of your examples.  I think that to show this up, the persistent objects 
need to be evicted from memory so that the pref is forced to pull the object off disk.

I have a scenario that so far seems repeatable:
    1. Start with an empty store on both the client and server.
    2. Connect them together and boostrat the workingset with an empty VoterRegistrationSet.
    3. Shut down the server so the the client is working off line.
    4. On the client, create a VoterRegistration and add a photo to it.
    5. Delete the VoterRegistration from the VoterRegistrationSet using the generate erase_range.
    6. Shut down the client (intention is to evict everything from memory).
    7. Start up the client
    8. Start up the server
    9. Observe the dangling OID exception with the call stack noted.

It sounds like the working set has recorded changes to the vector of images and not realised that 
the object containing the vector has been deleted. 

Any ideas?

If you fix the problem, please send a diff as I don't want to update my working copy of Ceda at 
the moment.
*/

///////////////////////////////////////////////////////////////////////////////////////////////////
const ceda::xstring UtEntryName("MyApp");

const ceda::WsipcSessionProtocolId protocolId = { "DeleteTest", 1 };

struct Site
{
    explicit Site(const ceda::xstring& path) :
        path(path),
        pstore(nullptr),
        pspace(nullptr),
        ws(nullptr),
        server(nullptr),
        client(nullptr)
    {
    }
    ~Site()
    {
        cxAssert(!pstore);
        cxAssert(!pspace);
        cxAssert(!ws);
        cxAssert(!server);
        cxAssert(!client);
    }
    
    void Create(bool dataSetMaster)
    {
        Tracer() << "Create " << path << "\n";
        cxAssert(!pstore);
        cxAssert(!pspace);
        cxAssert(!ws);
        pstore = ceda::OpenPersistStore(path.c_str(), ceda::OM_CREATE_ALWAYS);
        pspace = ceda::OpenPSpace(pstore, "MyPSpace");
        ceda::SetMilliSecsPerGc( GetCSpace( pspace ), 1 );
        ceda::SetEvictionThreshold( GetCSpace( pspace ), 0 );
        ceda::DeclareThreadPSpace dt(pspace);
        {
            ceda::CSpaceTxn txn;
            ws = ceda::OpenWorkingSetMachine("WS",dataSetMaster);
        }
    }

    void Open(bool dataSetMaster)
    {
        Tracer() << "Open " << path << "\n";
        cxAssert(!pstore);
        cxAssert(!pspace);
        cxAssert(!ws);
        pstore = ceda::OpenPersistStore(path.c_str(), ceda::OM_OPEN_EXISTING);
        pspace = ceda::OpenPSpace(pstore, "MyPSpace");
        ceda::SetMilliSecsPerGc( GetCSpace( pspace ), 1 );
        ceda::SetEvictionThreshold( GetCSpace( pspace ), 0 );
        ceda::DeclareThreadPSpace dt(pspace);
        {
            ceda::CSpaceTxn txn;
            ws = ceda::OpenWorkingSetMachine("WS",dataSetMaster);
        }
    }
    
    template <typename T>
    void BootstrapRoot()
    {
        Tracer() << "Bootstrap " << path << "\n";
        cxAssert(pspace);
        cxAssert(ws);
        ceda::DeclareThreadPSpace dt(pspace);
        ceda::CSpaceTxn txn;
        GetUTRoot(ws).Map[UtEntryName].insert(0,$new T);
    }
    
    void PollQuiescence(ceda::ssize_t count)
    {
        Tracer() << "Polling for quiescence on " << path << "\n";
        cxAssert(pspace);
        cxAssert(ws);
        ceda::DeclareThreadPSpace dt(pspace);
        while(1)
        {
            Sleep(10);
            ceda::CSpaceTxn txn;
            if (HistoryBufferSize(ws) == count) break;
        }
    }
    
    template <typename T>
    void ShowResult() 
    {
        Tracer() << "Showing result on " << path << "\n";
        cxAssert(pspace);
        cxAssert(ws);
        ceda::TraceIndenter indent;
        ceda::DeclareThreadPSpace dt(pspace);
        ceda::CSpaceTxn txn;
        T* t = ceda::GetUTRootEntry<T>(ws,UtEntryName);
        ceda::PrintInstance(Tracer(), t);
    }
    
    void OpenServer()
    {
        Tracer() << "Open server on " << path << "\n";
        cxAssert(!server);
        cxAssert(ws);
        ceda::DeclareThreadPSpace dt(pspace);
        ceda::CSpaceTxn txn;

        ceda::WsipcHostInfo localHostInfo;
        ceda::TcpMsgSessionSettings sessionSettings;
        bool reuse_addr = false;
        int port = 3001;
        auto protocol = ceda::EProtocol::TCP_IPv4;

        server = CreateTcpMsgServer(
            protocol, port, reuse_addr, 
            *CreateWsipcEndPoint(ws, protocolId, localHostInfo), sessionSettings);
    }
    void CloseServer()
    {
        Tracer() << "Close server on " << path << "\n";
        ceda::DeclareThreadPSpace dt(pspace);
        if (server) ceda::Close(server);
        server = nullptr;
    }
    void OpenClient()
    {
        Tracer() << "Open client on " << path << "\n";
        cxAssert(!client);
        cxAssert(ws);
        ceda::DeclareThreadPSpace dt(pspace);
        ceda::CSpaceTxn txn;

        ceda::WsipcHostInfo localHostInfo;
        ceda::TcpMsgSessionSettings sessionSettings;
        const char* host = "127.0.0.1";
        const char* service = "3001";

        client = CreateTcpMsgClient(
            host, service, 
            *CreateWsipcEndPoint(ws, protocolId, localHostInfo), sessionSettings);
    }
    void CloseClient()
    {
        Tracer() << "Close client on " << path << "\n";
        ceda::DeclareThreadPSpace dt(pspace);
        if (client) ceda::Close(client);
        client = nullptr;
    }
    
    void Close()
    {
        Tracer() << "Closing " << path << "\n";
        
        {
            ceda::DeclareThreadPSpace dt(pspace);
        
            if (server) ceda::Close(server);
            server = nullptr;

            if (client) ceda::Close(client);
            client = nullptr;
            
            if (ws) 
            {
                ceda::CSpaceTxn txn;
                ceda::Close(ws);
            }
            ws = nullptr;
        }
        
        if (pspace) ceda::Close(pspace);
        pspace = nullptr;
 
        if (pstore) ceda::Close(pstore);
        pstore = nullptr;
    }

    ceda::xstring path;
    ceda::PersistStore* pstore;
    ceda::PSpace* pspace;
    ceda::WorkingSetMachine* ws;
    ceda::TcpMsgServer* server;
    ceda::TcpMsgClient* client;
};

class CDeleteTest
{
public:
    CDeleteTest() :
        server(ceda::GetCedaTestPath("server.ced")),
        client(ceda::GetCedaTestPath("client.ced"))
    {
    }
    
    void Run()
    {
        /*
        1. Start with an empty store on both the client and server.
        2. Connect them together and boostrap the workingset with an empty VoterRegistrationSet.
        3. Shut down the server so the client is working off line.
        4. On the client, create a VoterRegistration and add a photo to it.
        5. Delete the VoterRegistration from the VoterRegistrationSet using the generated erase_range.
        6. Shut down the client (intention is to evict everything from memory).
        7. Start up the client
        8. Start up the server
        9. Observe the dangling OID exception with the call stack noted.
        */
        
        Sleep( 100 );
        Tracer() << "\n1. Start with an empty store on both the client and server.\n";
        server.Create(true);
        client.Create(false);
        
        Sleep( 100 );
        Tracer() << "\n2. Connect them together and boostrap the workingset with an empty VoterRegistrationSet.\n";
        client.OpenClient();
        server.OpenServer();
        server.BootstrapRoot<VoterRegistrationSet>();
        client.PollQuiescence(1);
        client.ShowResult<VoterRegistrationSet>();
        
        Sleep( 100 );
        Tracer() << "\n3. Shut down the server so the client is working off line.\n";
        //server.CloseServer();
        server.Close();
        
        Sleep( 100 );
        Tracer() << "\n4. On the client, create a VoterRegistration and add a photo to it.\n";
        //for( int i = 0; i < 10; ++i )
        {
            ceda::DeclareThreadPSpace dt(client.pspace);
            ceda::CSpaceTxn txn;

            VoterRegistrationSet* t = ceda::GetUTRootEntry<VoterRegistrationSet>(client.ws,UtEntryName);

            // add the registration
            VoterRegistration* r = $new VoterRegistration;
            t->Registrations.insert(0,r);

            // add the photo
            r->Photograph.erase_range( 0, r->Photograph.read().size() ); // this is done in the TBU code
            ceda::pref<ITbuImage> p = $new Image;
            r->Photograph.insert(0, &p, 1);

            /*ceda::DeclareThreadPSpace dt(client.pspace);
            ceda::CSpaceTxn txn;
            VoterRegistration* r = $new VoterRegistration;
            r->Photograph.insert(0, $new Image);
            
            VoterRegistrationSet* t = ceda::GetUTRootEntry<VoterRegistrationSet>(client.ws,UtEntryName);
            t->Registrations.insert(0,r);*/
        }
        client.ShowResult<VoterRegistrationSet>();

        Sleep( 100 );
        Tracer() << "\n5. Delete the VoterRegistration from the VoterRegistrationSet using the generated erase_range.\n";
        {
            ceda::DeclareThreadPSpace dt(client.pspace);
            ceda::CSpaceTxn txn;
            VoterRegistrationSet* t = ceda::GetUTRootEntry<VoterRegistrationSet>(client.ws,UtEntryName);
            t->Registrations.erase_range(0,1);
        }
        
        client.ShowResult<VoterRegistrationSet>();

        Sleep( 100 );
        Tracer() << "\n6. Shut down the client (intention is to evict everything from memory).\n";
        client.Close();
        
        Sleep( 100 );
        Tracer() << "\n7. Start up the client\n";
        client.Open(false);
        client.OpenClient();
        
        Sleep( 100 );
        Tracer() << "\n8. Start up the server\n";
        server.Open(true);
        server.OpenServer();
        
        Sleep( 100 );
        Tracer() << "\n9. Observe the dangling OID exception with the call stack noted.\n";
        Sleep(2000);

        client.ShowResult<VoterRegistrationSet>();
        server.ShowResult<VoterRegistrationSet>();
        
        client.Close();
        server.Close();
    }

private:
    Site server;
    Site client;
};

void DeleteTest()
{
    Tracer() << "DeleteTest example\n";
    ceda::TraceIndenter indent;
    Tracer() << "dir = " << ceda::GetCedaTestDir() << '\n';
    CDeleteTest test;
    test.Run();
};