Skip to content

Help with borrowing, mark, and the GC #613

@stackmystack

Description

@stackmystack

Hello.

I already posted this message to your slack, but I suspect it's not active? So here it goes again.

I am having an issue writing ruby bindings for my gem using magnus.

I wrote rougenoir, a clone of the Linux Kernel's Red-Black tree (but non-intrusive), and I started exploring writing bindings to it in Ruby.

This is not the first time I write Ruby bindings, I already wrote the bindings for tree-sitter in C.

I followed your online docs and implemented DataTypeFunctions#mark for my Tree, but I am having a problem with interior mutability.

In this version, when I do bundle exec rake console and launch the "benchmark" (code here), I quickly get a panic, saying that my RefCell is already borrowed mutably.

What's happening? The marking and interior mutability don't seem to work well together. If the GC kicks the mark phase, and we iterate over the tree to mark all the descendants, we might, just might, hit the case where we're:

  1. Borrowing mutably to insert.
  2. Borrowing immutably to iterate and mark.So in my benchmarks, the magic number is ~300 inserts of (int, int).

You can prove it by uncommenting the gc enable/disable before and after insertion.

Q1: Is this a valid solution? It looks like it's sound, but costly, but I am not sure.

Following the definition of rb_gc_enable, it seems like it's only setting an atomic bool.
In search for answers, I got to the conclusion that maybe the only way to avoid manipulating the GC is to use try_borrow in mark like in here.

It seems like this is a dangerous thing to do, but I am not sure.

Q2: Is try_borrow better than manipulating the GC?

Q3: I tried to use a RwLock, but that requires having &mut self in methods, which is not supported, or did I miss something in your docs?

Q4: this whole thing seems weird. Isn't ruby supposed to run my naticve code uninterrupted? Because here it sounds like it is actually interrupting after the borrow_mut.

It seems to me that anytime you do a rb_funcall the GC might kick in ... I'd assume that's also the case when you call any public ruby API doing any kind of allocation … I can't find proper documentation/reporting, though, so I can't prove it. Do you know of any?

So IF rb_funcall can kick the GC cycle, then I think the culprit is really my implementation of TreeKey which calls Ruby's <=> for its impl PartialOrd and impl Ord

Anyway, the gem is amazing, thanks for the great effort!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions