Skip to content
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

Gotta be tidy #1

Merged
merged 1 commit into from
Dec 7, 2016
Merged

Gotta be tidy #1

merged 1 commit into from
Dec 7, 2016

Conversation

matthewd
Copy link
Contributor

@matthewd matthewd commented Dec 3, 2016

The machine context root can be tricksy.

@matthewd
Copy link
Contributor Author

matthewd commented Dec 3, 2016

To see why this is necessary:

diff --git a/fail.rb b/fail.rb
index 4255b28..8353c2f 100644
--- a/fail.rb
+++ b/fail.rb
@@ -20,6 +20,7 @@ def run
   return nil
 end
 
+file = File.open('tmp/heap.json','w')
 run
 
 # ===== CALLING GC HERE =====
@@ -28,7 +29,7 @@ def run
 
 # ===========================
 
-ObjectSpace.dump_all(output: File.open('tmp/heap.json','w'))
+ObjectSpace.dump_all(output: file)
 
 output = `grep #{ $address } tmp/heap.json`
 actual = output.each_line.map.select do |line|

The object (technically, its singleton class) is retained by the machine_context root (the system stack) during the GC run. But then we open the file to hold the dump, and that's sufficient to overwrite the stack frame that kept it alive, so when we get around to re-scanning during the dump, there are no references remaining: a GC at that time would in fact free it.

@matthewd
Copy link
Contributor Author

matthewd commented Dec 3, 2016

This is an expected behaviour of the GC, stemming from its conservatism: it generally expects to retain a small number of objects that it could actually do without, because it can't be sure about them... but barring an Observer Effect as created here, those objects are still rooted.

That conservatism is going to make asserting that things have definitely gone away... challenging. You can flush the stack as I've done here... but that only works for the current thread, for example.

@schneems
Copy link
Owner

schneems commented Dec 7, 2016

Thanks a ton. I wasn't subscribed to this repo by default so didn't get the notification. If only I knew someone who worked at github 🤔

So I previously had a theory that this was based on stack/heap dichotomy, but I didn't think to call a recursive function, that's a great solution. Do you know why the puts call works? Is it because it's a call to a C-function?

Also as a side note this works

wash_stack
GC.start

but this fails

wash_stack
10.times { GC.start }

¯\__(ツ)__/¯

@schneems schneems merged commit fb49087 into schneems:master Dec 7, 2016
@matthewd
Copy link
Contributor Author

matthewd commented Dec 7, 2016

I think the puts call is just generally going deep enough on the machine stack to wipe out the value that had been left behind (without checking the impl, it seems likely to take an at-least-somewhat circuitous path to actually writing the bytes)

The 10.times is interesting.. but hopefully (with the change in #1 (comment)) the heap dump at least confirms there's still a reference from the machine stack.. even if it's not obvious why that would be.

@schneems
Copy link
Owner

schneems commented Dec 7, 2016

Ported this over to schneems/living_dead#1, works for me locally but is failing on travis. I'm able to repro on docker.

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.

2 participants