Ruby uses a mark-and-sweep GC that scans the entire Ruby interpreter stack for objects that have gone out of scope and can be freed from memory. It does not offer any of the reference counting mechanism that the Python GC offers.
While both approaches have their pros and cons, in the context of the ndtypes wrapper, it becomes risky to have 'internal' Ruby objects that are only visible to the C API and are shared between multiple user-facing Ruby objecs. If one of the user-facing objects goes out of scope there is a possibilty that the GC will clean up the internal object that is shared between multiple user-facing objects (some of which might still be in use) and that will lead to segfaults.
To avoid such a situation, in ndtypes we use a 'global GC guard' (inspired by @mrkn's pycall.rb gem) that stores the reference to the internal objects in a global Hash so that they don't go out of scope. When a user-facing object needs to be freed, we remove the reference to the user-facing object and its corresponding internal object from the global Hash.
More concretely, the NdtObject struct houses a VALUE object called rbuf. The struct
has the following definition:
typedef struct {
VALUE rbuf; /* resource buffer */
ndt_t *ndt; /* type */
} NdtObject;
In the above, the rbuf is a Ruby object that contains a struct of type ResourceBufferObject.
This is the internal object that need to be shared among multiple user-facing NDTypes objects.
The gc_guard.c file contains functions that help us interface with a global hash called
__gc_guard_table that is present under the NDTypes::GCGuard module as a instance variable
on the module object.
Whenever you allocate an NDTypes object you call the gc_guard_register function and pass
it the pointer of the NdtObject struct that you have allocated along with the rbuf object.
When an NDTypes objects needs to be freed (for example by the GC using the NDTypes_dfree
function), you must call the gc_guard_unregister function that will remove the reference
to the rbuf object from the global Hash.
Forgetting to call above procedures can lead to hard-to-trace GC errors.