Skip to content

RUBY_FREE_AT_EXIT fixes #9324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 23, 2023
Merged

Conversation

jhawthorn
Copy link
Member

This fixes a couple errors which could occur when RUBY_FREE_AT_EXIT=1 is enabled. These fixes aren't perfect, but I think fixes which avoided more leaks and never caused errors would be more complex and would be dangerous to merge so close to 3.3's release. (I'd like to investigate this more next year and maybe we can backport the fixes For 3.3.1)

First, during VM destruction EC seems to be NULL, this avoids two cases we were always calling GET_EC() at that time (one we just didn't need and the other I added an if case for it being NULL). This fixes an error which always occurred when Ruby was built in debug.

Fixes RUBY_FREE_AT_EXIT=1 ./miniruby -e ''

Next, we need to move the freeing of default_rand_key after the freeing of Ractors in rb_objspace_free_objects, as that will iterate over over the local storage keys so the memory needs to be freed later to avoid a use-after-free.

Fixes RUBY_FREE_AT_EXIT=1 ./miniruby -e rand

Lastly when we forked for a Thread we would end up double-freeing the main thread's stack, because the main thread was no longer the initial thread from Init_BareVM. The new Thread's stack was freed naturally in rb_objspace_free_objects

Fixes RUBY_FREE_AT_EXIT=1 ./miniruby -e 'Thread.new { fork { } }.join; Process.waitpid'

With these changes RUBY_FREE_AT_EXIT=1 make btest works with RUBY_DEBUG and ASAN enabled (other than a few Ractor tests which detect false positives).

cc @HParker @peterzhu2118

@HParker
Copy link
Contributor

HParker commented Dec 22, 2023

Nice find! The description makes a ton of sense to me! 👍

This argument doesn't seem used anymore. Since we want to free these
objects during VM destruction when RUBY_FREE_AT_EXIT is set they must
work without an EC.

This avoids a use-after-free running `RUBY_FREE_AT_EXIT=1 ./miniruby -e ''`
Ractor's free iterates through its TLS keys so we need to keep this
memory available until after Ractors are freed.

Minimal reproduction:

    RUBY_FREE_AT_EXIT=1 ./miniruby -e rand
When a forked process was started in a thread, this would result in a
double-free during the child process exit.

    RUBY_FREE_AT_EXIT=1 ./miniruby -e 'Thread.new { fork { } }.join; Process.waitpid'

This is because the main thread in the forked process was not the
initial VM thread, and the new thread's stack was freed as part of
objectspace iteration.

This change also allows rb_threadptr_root_fiber_release to run without
EC being available.
Copy link
Member

@peterzhu2118 peterzhu2118 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great finds. Make sense to me!

@jhawthorn jhawthorn merged commit f1b7424 into ruby:master Dec 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants