Skip to content

Instantly share code, notes, and snippets.

@v0dro
Created August 24, 2018 11:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save v0dro/699ba8c5e2818248d4b7c9deda448b29 to your computer and use it in GitHub Desktop.
Save v0dro/699ba8c5e2818248d4b7c9deda448b29 to your computer and use it in GitHub Desktop.
Interfacing internal objects with the Ruby GC

Interfacing with Ruby's GC

Background

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.

Details

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.

Impact on contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment