One weakness of the currently released Rust version. Is if the user makes a mistake about what sodium objects are referenced in the lambda expression, then memory could be freed while still in use, thus causing a seg fault. (The program could misleading think there is a cycle)
JavaScript does not face this problem because there are actually two garbage collectors. One provided by the language, and one implemented in the vertex for freeing cycles (lack of weak ref).
The same solution could be used in C++/Rust. And even if the user makes a mistake or lies about what sodium objects are referenced in C++/Rust, then no seg fault will occur.
We can use smart pointers on the outside like we do, and implement the GC the way we do in TypeScript on the inside of out Vertex/Node. Then when a cycle is detected, instead of freeing the vertex/node memory we just disconnect the cycle and let the outer smart pointer do the freeing for us (std::shard_ptr
(C++) / Arc
(Rust)).
I'm having a go at it now in a wip-gc-node branch in the experimental rust version of sodium.
I've realise too that lazy construction/deconstruction can not work in C++/Rust. Because the Sources we have in the current typescript version would cause a memory cycle (which is already handled by the outer JavaScript garbage collector for us). So construction must be eager like in Java/C#. It just means our GcNode should start with reference count 1 instead of 0, and we use the IIRA provided by C++/Rust to do the increment/decrement of the reference count for us (for the cycle aware gc node).
We'd need to construct two types of sodium Nodes. One we can call Node
, it will use IIRA to increment/decrement its internal GcNode
reference counts. The other one we can call WeakNode
, it is used in the target list in out Node
, the difference is WeakNode
will not increment/decrement its internal GcNode
, but the data structure is identical.
accessing WeakNode
after the node has been freed will not be a problem because there will be two layers of reference counting.
Interestingly collect_white in our four color gc algorithm needs to increment the reference count just like scan_black does. As the Node
deconstructor will cause a negative reference count when freeing after collect white. This goes against the paper. But I the paper is targeting compilers and not IIRA-based libraries for the algorithm. For compilers the end-user of the language does not worry about increments or decrements at all.
I take back what I said about lazy construction. It is still do-able with working memory management. Just easier not to.
calling scan_black always (even when ref count is zero), then marking white when ref_count was zero seems to work for double reference counting.
3 node cycle test:
running 1 test
[2020-04-11T03:56:53Z TRACE sodium_rust_alt::impl_::gc_node] start: collect_cycles
[2020-04-11T03:56:53Z TRACE sodium_rust_alt::impl_::gc_node] start: mark_roots
[2020-04-11T03:56:53Z TRACE sodium_rust_alt::impl_::gc_node] mark_gray: start: visiting children of gc node 0
[2020-04-11T03:56:53Z TRACE sodium_rust_alt::impl_::gc_node] mark_gray: gc node 2 dec ref count
[2020-04-11T03:56:53Z TRACE sodium_rust_alt::impl_::gc_node] mark_gray: start: visiting children of gc node 2
[2020-04-11T03:56:53Z TRACE sodium_rust_alt::impl_::gc_node] mark_gray: gc node 1 dec ref count
[2020-04-11T03:56:53Z TRACE sodium_rust_alt::impl_::gc_node] mark_gray: start: visiting children of gc node 1
[2020-04-11T03:56:53Z TRACE sodium_rust_alt::impl_::gc_node] mark_gray: gc node 0 dec ref count
[2020-04-11T03:56:53Z TRACE sodium_rust_alt::impl_::gc_node] mark_gray: end: visting children of gc node 1
[2020-04-11T03:56:53Z TRACE sodium_rust_alt::impl_::gc_node] mark_gray: end: visting children of gc node 2
[2020-04-11T03:56:53Z TRACE sodium_rust_alt::impl_::gc_node] mark_gray: end: visting children of gc node 0
[2020-04-11T03:56:53Z TRACE sodium_rust_alt::impl_::gc_node] end: mark_roots
[2020-04-11T03:56:53Z TRACE sodium_rust_alt::impl_::gc_node] start: scan_roots
[2020-04-11T03:56:53Z TRACE sodium_rust_alt::impl_::gc_node] scan_black: gc node 2 inc ref count
[2020-04-11T03:56:53Z TRACE sodium_rust_alt::impl_::gc_node] scan_black: gc node 1 inc ref count
[2020-04-11T03:56:53Z TRACE sodium_rust_alt::impl_::gc_node] scan_black: gc node 0 inc ref count
[2020-04-11T03:56:53Z TRACE sodium_rust_alt::impl_::gc_node] end: scan_roots
[2020-04-11T03:56:53Z TRACE sodium_rust_alt::impl_::gc_node] collect_roots: freeing white node 0
[2020-04-11T03:56:53Z TRACE sodium_rust_alt::impl_::gc_node] end: collect_cycles
node_count 0
node_ref_count 0
test tests::node_test::node_mem_1 ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 30 filtered out
Note only one node gets freed in the cycle, then the normal non-cycle ref counter takes over to free the other nodes after the cycle is broken.