WCSpace.h

// WCSpace.h
//
// A wrapper on CSpace that adds thread affinity.
//
// David Barrett-Lennard  
//
// (C)opyright Cedanet Pty Ltd 2007

@import "cxObject.h"
@import "IObject.h"
@import "ThreadPtr.h"
@import "CSpace.h"
#include <exception>

///////////////////////////////////////////////////////////////////////////////////////////////////
// Per thread pointer to the CSpace currently affiliated with the thread 

/*
Thread local storage is used to allow each thread to store a pointer to a CSpace to which it is
currently affiliated.  Some of the functions in this header work implicitly on the affiliate CSpace
of the calling thread.

A newly created thread has a nullptr affiliate CSpace, and therefore it is an error to call any of 
the functions that require an affiliate CSpace.
*/

namespace ceda
{
// Set/Get the pointer to the CSpace currently affiliated with the calling thread.
// Returns nullptr if there is no affiliate CSpace.
mDefineThreadPtr(CSpace)

#ifdef CEDA_CHECK_ASSERTIONS
    inline bool HaveLockedCSpace()
    {
        return HaveRecordedLock(GetThreadPtr<CSpace>());
    }

    // The following functions assert:
    //      1)  the given object is not null
    //      2)  the given object is registered in a CSpace
    //      3)  the CSpace associated with the object is set in TLS
    @api bool HaveLockedCSpace(ptr<const IObject> p);
#endif // CEDA_CHECK_ASSERTIONS

// Returns true if p is associated with this thread's associated CSpace.
// Assumes p is not null.
$function+ bool ObjectInThreadCSpace(ptr<const IObject> p);

///////////////////////////////////////////////////////////////////////////////////////////////////
// Garbage collection

/*
All the following functions work implicitly on the CSpace affiliated with the calling thread.  
It is an error to call these functions if there is no affiliate CSpace.
*/

$function+ void AddGcRoot(ptr<const IObject> object);
$function+ void RemoveGcRoot(ptr<const IObject> object);
$function+ void StartGc();
$function+ void StopGc();
$function+ void SynchronousGc();
$function+ int32 GetMilliSecsPerGc();
$function+ void SetMilliSecsPerGc(int32 t);
$function+ ssize_t GetMaxDgsTotalByteSize(ssize_t evictionQueueIndex);
$function+ void SetMaxDgsTotalByteSize(ssize_t evictionQueueIndex, ssize_t totalSizeInBytes);
$function+ ssize_t GetEvictionThreshold();
$function+ void SetEvictionThreshold(ssize_t totalSizeInBytes);

#ifdef CEDA_CHECK_ASSERTIONS
    class CSpacePseudoLock
    {
    public:
        CSpacePseudoLock() : cs_(GetThreadPtr<CSpace>())
        { 
            RecordCSpaceLock(cs_);
        }
        explicit CSpacePseudoLock(CSpace* cs) : cs_(cs)
        { 
            RecordCSpaceLock(cs_);
        }
        ~CSpacePseudoLock()                    
        { 
            RecordCSpaceUnlock(cs_);   
        }
    private:
        CSpace* cs_;
    };
#endif // CEDA_CHECK_ASSERTIONS

///////////////////////////////////////////////////////////////////////////////////////////////////
// CSpaceCreator

// Declared on the frame to create a CSpace and set it as the affilliate CSpace for the calling 
// thread
// TODO: the name of this class seems illogical, the destructor destroys it!
class CSpaceCreator : public DeclareThreadPtr<CSpace>
{
public:
    explicit CSpaceCreator(int32 milliSecsPerGC = CEDA_DEFAULT_MILLISECS_PER_GC) 
    { 
        cs_ = CreateCSpace(milliSecsPerGC);
        SetThreadPtr<CSpace>(cs_);
    }
    ~CSpaceCreator()                    
    { 
        Destroy(cs_); 
    }
    operator CSpace*() { return cs_; }

private:
    CSpace* cs_;
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// CSpaceLock

// Declared on the frame to lock a single CSpace
class CSpaceLock
{
    cxNotCloneable(CSpaceLock)
public:
    CSpaceLock(CSpace* cs, ECSpaceLockMode mode = ECSpaceLockMode::Exclusive) :
        cs_(cs)
    { 
        cxAssert(cs_);
        Lock(cs_, mode); 
    }

    explicit CSpaceLock(ECSpaceLockMode mode = ECSpaceLockMode::Exclusive) :
        cs_(GetThreadPtr<CSpace>())
    { 
        cxAssert(cs_);
        Lock(cs_, mode); 
    }

    ~CSpaceLock()
    {
        /*
        if (uncaughtExceptionCount_ != std::uncaught_exceptions()) 
        {
            // Stack is unwinding, we assume it is too risky to allow
            // a transaction to commit, and instead terminate the process
            cxAlwaysAssert( false );
            std::terminate();
        }
        */
        Unlock(cs_);
    }

private:
    CSpace* cs_;
    //int uncaughtExceptionCount_ = std::uncaught_exceptions();
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// CSpaceTxn

struct CSpaceTxn : CSpaceLock
{
    explicit CSpaceTxn(CSpace* cs) : CSpaceLock(cs,ECSpaceLockMode::Transaction) {}
    CSpaceTxn() : CSpaceLock(ECSpaceLockMode::Transaction) {}
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// CSpaceRelinquishLock

class CSpaceRelinquishLock
{
    cxNotCloneable(CSpaceRelinquishLock)
public:
    explicit CSpaceRelinquishLock(CSpace* cs = GetThreadPtr<CSpace>()) :
        cs_(cs)
    { 
        cxAssert(cs_);
        li_ = BeginUnlock(cs_); 
    }

    ~CSpaceRelinquishLock()
    {
        EndUnlock(cs_,li_);
    }

private:
    CSpace* cs_;
    LockInfo li_;
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// LocalCSpace

struct @api LocalCSpace
{
    LocalCSpace();
    ~LocalCSpace();
    
    CSpace* m_child;
    CSpace* m_parent;
    LocalCSpace* m_prevLocal;
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// LockAndTransfer

struct @api LockAndTransfer
{
    LockAndTransfer();
    ~LockAndTransfer();

    LocalCSpace* m_lc;
};

}