How does JuliaCall (PythonCall) protect Julia objects in Python code from Julia's GC?

No practical purpose, just curious. For PythonCall in Julia code, it’s easy to imagine that a Julia wrapper increments an underlying CPython object’s reference count to protect it from CPython’s GC (I don’t know if this is actually true, so please correct me). But I can’t imagine how juliacall in Python code can protect underlying Julia objects from Julia’s GC when there isn’t an explicit Julia program to start tracing from. Is there a hidden juliacall cache of Julia objects somewhere, and if so can I get a link to its implementation?

Here it is: PythonCall.jl/src/JlWrap/C.jl at 71666ca0a30eafee2456a191fa2490cdd0b0ed22 · JuliaPy/PythonCall.jl · GitHub

Pretty simple really, we keep a global vector containing all of the wrapped Julia values and the Python wrapper just holds the index into this vector.

3 Likes

And when the Python wrapper is deleted, the corresponding entry in the vector is set to nothing so the wrapped value can be GC’d.

Am I assuming correctly this vector is mutated by __del__ finalization? source link for juliacall.AnyValue on the docs site doesn’t take me to a .py file, weirdly a directory in the base Julia Github.

The type is implemented in Julia not Python, and the __del__ implementation is just a few lines down, here: PythonCall.jl/src/JlWrap/C.jl at 71666ca0a30eafee2456a191fa2490cdd0b0ed22 · JuliaPy/PythonCall.jl · GitHub

As for the link in the docs - we have some hacky shenanigans to document Python things using Documenter.jl so I’m not surprised that’s broken.

1 Like