User:Andrecaldas/SharedPtr

= Introduction: sharing document objects all over! = Each FreeCAD document (App::Document) is composed of document objects (DocumentObject). Each document object should be an autonomous structure that is functional even if it is not inserted in a document. It's behaviour cannot be unexpected just because it is not attached to a document.

Many functions deal with document objects. They are usually passed a pointer to the object. But the more FreeCAD becomes multithreaded, the more we need warranties that a certain object is still functional, even if while processing takes place, the object is concurrently removed from the document, for example.

Today, assuring that a certain object will not be destroyed or removed from the document it belongs to is a complicated task that relies on verifying its internal status (usually in thread-unsafe ways). For example:
 * Afraid of removing an object because of its status.
 * Others need to know that an object is about to be deleted.
 * Variable pcNameInDocument is set to nullptr to indicate some internal status change.
 * Document object is destructed only if it does not belong to a document.
 * Freezes main thread, waiting for parallel processing to finish. The result of the processing will obviously be discarded, though. :-(

Things can become much simpler and much safer if we use shared_ptr. As long as someone is holding a shared_ptr to an object, the object is (almost) guaranteed to be valid. If we design things in such a way that the document is functional even without belonging to a document, everything shall simply work without giving unexpected results (crash).

An example of something that would crash if we were a little more multithreaded: accessing a Document that might not be available anymore.

Pointer management
Instances of Document and DocumentObject should be managed by shared_ptr.

Data inside a DocumentObject can be:
 * Part of the object;
 * Managed by unique_ptr; or
 * Managed by shared_ptr.

Downward reference
Downward reference means ownership. The owning object shall hold a unique_ptr or shared_ptr to the downward reference.

Preferably, once set, those smart pointers should not change. This way, functions that are called in a context where a valid unique_ptr or shared_ptr are assured to exist during the function call can take raw pointers. In particular, functions called by methods in the class that owns a resource can take raw pointers.

Upward reference
Since objects can be detached from their owners in threads different from ours, upward references should be weak_ptr.

Methods that use this weak_ptr shall deal gracefully with weak_ptr::lock returning an invalid lock (shared_ptr). Those methods can either:
 * 1) Return a reasonable default (preferable); or
 * 2) Throw an exception.

Arbitrary reference
To reference an arbitrary object, FreeCAD uses an infrastructure named ObjectIdentifier. I do not understand much about ObjectIdentifier. But I propose a different solution I call "Accessor".

Source code: https://github.com/andre-caldas/FreeCAD/tree/NamedSketcher/src/Base/Accessor

More information: Accessor infrastructure.

A sequence of string identifies an object. Actually, it identifies some data exported by an object. It can be an integer, a string, another object... anything. When the path is resolved, a shared_ptr to the resource is returned. This shared_ptr is not supposed to be stored. It is supposed to be used locally by some function. While the returned shared_ptr exists, the accessed resource is guaranteed to exist. Because of multithreading, It might be removed from the document. It might not satisfy the path anymore... but it is assured to exist.

It is not implemented, yet... but the Accessor can have a URI that can be used to refer to an object in a different document.

Each object shall have a UUID and an optional name that can be set by the user. The UUID is assigned when the object is first created and is stored and restored when the object is serialized (saved to a file) and unserialized.

Python
Python objects are implemented by a "trampoline" class. Those shall use a shared_ptr to hold a reference to the original object. This way, the C++ counter part does not need to care about python reference counter.

Using weak_ptr in python seems not viable. Therefore, care should be taken in a way such that the shared_ptr (probably returned by the Accessor reference to some object) is not stored, but only used locally.

Advantages
TODO: list to pages with further explanation.
 * True thread safety.
 * Non blocking parallel processing.
 * The GUI thread can become very light-weight... making it more responsive.

Asynchronous signal/slot
As far as I understood, in FreeCAD, we use Boost::Signals2 signal/slot infrastructure to synchronize things. However, ideally signals should not need to synchronize too much.

For example, a certain Part (Part::Feature) object has updated its Shape variable. For example, FreeCAD::recompute was called and the Shape was updated in a separate thread. It just has to "inform" FreeCAD to redraw the GUI. Redrawing does not need to be synchronized. There is only one tiny link where synchronization is needed. Read along... ;-)

When the thread "updates the Shape", it does not actually update it... otherwise, we would need exclusive access. Part::Feature::Shape needs to be an atomic shared_ptr (in C++20 and before C++20). The thread would then:
 * 1) Work in a local std::unique_ptr object;
 * 2) Atomically swap the Part::Feature::Shape and the local shape;
 * 3) Inform others about the change through an asynchronous signal.

Who ever holds a copy of the old shape still has a valid "old shape". If the slot takes too long to execute, we might have an even newer Shape!

For this to happen, the Shape in Part::Feature can never be changed. It has to be copied, the copy has to be changed and then atomically swapped. Or... to avoid the copy, there should be a mutex associated to each shape. I don't know how well OCCT deals with multithreading.

Automatic connection management
When dealing with signals and slots, we disconnect them during destruction. This is however not thread safe! We can make the destructors much simpler and disconnection thread safe through the use of std::shared_ptr (it does not have to be boost::shared_ptr). This is called automatic connection management. The signal slot mechanism holds a weak_ptr and the slot is called only if the weak_ptr can be locked. This assures that the slot is called on a valid object, and it also assures the object will remain valid until the slot finishes processing.

When the weak_ptr cannot be locked, this means the object was destructed, is being destructed or is about to be destructed. In this case, the slot is not called. Actually, it is automatically disconnected. FreeCAD should use this in objects that are managed by shared_ptr.