Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.Sign up
Streams created for file parsing are allowed to finalize #5685
While looking into some tests that appear to be unreliable I discovered that we are adopting some JVM threads very early in execution due to early IO streams for parsing being allowed to close on their own via finalization.
The test I was investigating was CRuby's ruby/test_thread.rb in test_stop, which tests that stopping the sole thread (the main thread in a new subprocess) raises an error, since there are no other threads to schedule.
This test fails intermittently because we are adopting one of the JVM finalizer threads to clean up an IO object for the main script.
(It is not clear to me whether those threads should show up as Ruby threads for this purpose; typically these threads should remain hidden. I do not attempt to answer that question here.)
The orphaned IO
The IO that is left to finalize is the one created at this line:
The IO here is created for the parser, but nobody closes the stream after the parse is complete. This would be appropriate if it were held open for the magic
Because it is left to finalize, the finalizer thread is adopted into our thread lists, which breaks any tests that assume only threads created from Ruby code will be present. (Again, maybe we should be hiding adopted threads?)
I have confirmed that the other path here, for a main file opened as a FileInputStream, exhibits the same problem.
The best fix would be that the parser eagerly cleans up IO objects it uses for parsing once they are no longer needed. This will prevent any early finalization from needing to be adopted into our thread structures.
If the streams are set to not autoclose, the finalizer will do nothing to them and will not need to be adopted. This is a low-impact fix, but it seems messy to leave them out there, and I believe this call path may be used for other load logic.
@headius ah yeah and it is two paths (other gets in underlying IO which is closed further up the call stack). I believe this is will be checking to see if DATA is that io and otherwise closing it in the finally of that same method. Should not be too hard. I will fix this.