Issue #16288 has been updated by davidw (David Welton).
A little update. I have kept looking, a little bit at a time, and have uncovered another piece of what's going on: the difference between Thread.start and Thread.new
Thread.new does not run in a finalizer on its own. Thread.start, however, does run, and can be used to crash the interpreter:
``` ruby
Thread.DEBUG = 1
class Foo
def initialize
ObjectSpace.define_finalizer(self, proc do
Foo.foo_finalizer
end)
end
def Foo.foo_finalizer
STDERR.puts "finalizing a Foo"
Thread.start do
STDERR.puts "starting ..."
sleep 5
STDERR.puts "... finalizing foo thread done"
end
end
end
20.times do
f = Foo.new
end
```
Thread.new has a check, although I don't understand why it runs *after* the thread has been allocated:
``` c
static VALUE
thread_s_new(int argc, VALUE *argv, VALUE klass)
{
rb_thread_t *th;
VALUE thread = rb_thread_alloc(klass);
if (GET_VM()->main_thread->status == THREAD_KILLED)
rb_raise(rb_eThreadError, "can't alloc thread");
```
Whereas Thread.start has no such check:
``` c
static VALUE
thread_start(VALUE klass, VALUE args)
{
return thread_create_core(rb_thread_alloc(klass), args, 0);
}
```
As above, care needs to be taken with any potential fixes, because there is currently code out there that depends on, say, Timeout being run in a finalizer, and that utilizes Thread.start.
----------------------------------------
Bug #16288: Segmentation fault with finalizers, threads
https://bugs.ruby-lang.org/issues/16288#change-83059
* Author: davidw (David Welton)
* Status: Open
* Priority: Normal
* Assignee:
* Target version:
* ruby -v: ruby 2.6.6p116 (2019-10-02 revision 67825) [x86_64-linux]
* Backport: 2.5: UNKNOWN, 2.6: UNKNOWN
----------------------------------------
Hi,
This is a tricky one and I am still working on narrowing it down, but I will report what I have so far.
I compiled a version of 2_6_6 from github: ruby 2.6.6p116 (2019-10-02 revision 67825) [x86_64-linux]
I have a minimal Rails project that uses Mongoid. It crashes with a segmentation fault when rspec runs. The concurrent ruby gem is in some way involved, and I have been posting there: https://github.com/ruby-concurrency/concurrent-ruby/issues/808
However, I think there is a deeper problem - I would not expect a user level script to cause a segmentation fault.
I have been putting a lot of debugging statements in, and turned on Thread.DEBUG, and have noticed some things. I am not experienced with Ruby's internals, so some of these bits of data might be normal or irrelevant:
* The concurrent-ruby gem uses ObjectSpace.define_finalizer to set a finalizer
* That finalizer creates a new Thread
* However, it appears as if that thread is running after the main thread is already dead, so code that expects to reference the main thread crashes, because it's a NULL reference.
I tried the following test code:
```
class Foo
def initialize
ObjectSpace.define_finalizer(self, proc do
Foo.foo_finalizer
end)
end
def bar
puts 'bar'
end
def Foo.foo_finalizer
puts "foo_finalizer"
t = Thread.new do
puts "Thread reporting for duty"
end
puts "foo_finalizer thread launched"
sleep 5
end
end
f = Foo.new
f.bar
f = nil
```
While trying to develop a simple test case to demonstrate the problem. It triggers rb_raise(rb_eThreadError, "can't alloc thread"); in thread_s_new, because it looks like the main thread has already been marked as 'killed' in this case. When I check the main thread status in thread_s_new with the above code, it reports 'dead'.
When I run my rspec code in the sample Rails project, thread_s_new shows the main thread's status as 'run' even if it should be dead?
I have seen some debugging things that shows some exceptions and thread_join interrupts and so on.
Is it possible that something like this is happening?
Main thread starts doing a cleanup, and gets an exception or something that generates an interrupt, and its KILLED status gets reset to RUNNABLE
Then, in the finalizer, it starts creating a Thread, but at this point the main thread actually does get killed, and when that finalizer thread tries to run it runs into a null reference?
I can provide the Rails sample project if needs be.
Sorry if any of the above isn't clear; I've been staring at the C code for several hours and am a bit cross-eyed!
Thank you for any insights.
--
https://bugs.ruby-lang.org/
Unsubscribe: <mailto:[email protected]?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>
lmpx.com only provides a reader for public news (NNTP) servers. It is not
affiliated with the servers or forums shown here and is not responsible for
the content of articles, which is written by their respective authors.