Each thread has its own mutable object hierarchy that is proven to be leak-free.
All threads share the common immutable objects that have no loops in topology and thus can be handled with simple ref-counting. Since these objects are immutable, any thread having shared reference to any shared object, can freely traverse all shared references from this object through all shared objects without retaining/releasing/checking them on the go - they are guaranteed to be presented and accessible to this thread regardless of actions of other threads as long as the root reference is retained. This observation has two major implications:
- Vast majority of retain/release actions that are mandatory in data models of other languages can be eradicated at compile time in Argentum.
- All retain/release actions can be postponed, reordered and batched within this thread independently from other threads as long as all retain operations happen before the release operations.
This not only guarantees absence of memory leaks, but also allows to eradicate ref-counting overheads.
Absence of memory leaks in inter-thread communications
Shared references *T
are being passed from one thread to another using a queue. Sender retains object before reference to it is put into the queue, this prevents object from being deleted from the sender thread. Later this object will be destroyed by using the release call from either thread.
Mutable object hierarchies can be passed from one thread to another. For this it must be not owned (newly created, copied or un-parented).
This operation is performed in two stages:
- Object placed to the outgoing thread queue, where it waits for the current thread operation to end. This guarantees that there are no temporary references to this object in the stack frames of the current thread.
- Event dispatcher of the current thread posts this object (a reference to it) to the destination incoming thread queue.
This two-stage process guarantees that when a mutable object changes its thread, there exists no other owning and/or temp references to this object or its sub-objects from its previous thread (and transitively from threads other than a receiving one). This makes this object hierarchy solely a part of the set-of-mutable-objects of the new thread. Since we proved that there are no leaks within one thread, this also proved that passing objects between threads doesn't cause memory leaks, and at any given time all mutable state in the application belongs to exactly one thread. This also proves the absence of data races.
Weak references can be passed between threads. One thread can hold weak references to objects of another thread. Though there is no way of dereferencing such cross-thread weak references. They read as not-bound (nulls) on the lock/check attempts.
Instead they can be used in the following 3 scenarios:
- Weak reference can be passed back to the thread where its target resides, and dereferenced there.
- Weak reference can be directly checked if its target actually exists (which is handy for fast cross-thread operation cancellation).
- Weak reference can be a target of the asynchronous task (which is the only way of the cross-thread communication).
In all 3 scenarios, weak references do not produce temporary pointers to the target objects of the different threads (which prevents inter-thread synchronizations and data races) this also limits the referencing scenarios to the already proven to be leak-free single-threaded cases.
Thread ownership
All thread internal state (its mutable hierarchy and queues) is encapsulated in the Argentum sys_Thread
object. These objects reside in the mutable hierarchies of other threads. There is a system-wide root thread owned by the runtime library. All thread objects - when copied / frozen / passed between threads - produce empty thread objects. This prevents memory leaks when thread is attempted to be shared or passed to its own internal hierarchy. These rules make Thread objects form a tree-like hierarchy without cycles and leaks.