From: Yehuda Katz Date: 2008-12-04T04:44:25+09:00 Subject: [ruby-core:20271] Re: autoload and concurrency ------=_Part_98919_824248.1228333813104 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit Content-Disposition: inline Because of this problem, we *will* be removing the use of autoload in 1.1. However, this is still a pretty big bug in Ruby itself (since it makes autoload broken). -- Yehuda On Wed, Dec 3, 2008 at 9:52 AM, Thomas Enebo wrote: > Charles Oliver Nutter wrote: > >> Yehuda Katz wrote: >> >>> Merb uses autoload rather extensively. We have lately observed some >>> disturbing behavior around concurrency. >>> >>> Effectively, because autoload removes the flag before loading the file, >>> if two threads concurrently attempt to access an autoloaded constant, one of >>> the threads can get a NameError. >>> >>> It's easy to reproduce this by adding a sleep to the file being loaded >>> before the constant is defined, and then spinning up two threads that both >>> try to use the constant. This is reproducible in MRI; it does not only >>> happen with JRuby's true parallel execution. >>> >>> The worst part is that no user action can solve this problem: because >>> autoloading is magic, it's impossible for the user to lock the resulting >>> require. In effect, this makes autoloading completely useless for threaded >>> environments. Is this intentional? >>> >>> Is there something we can do at the language level to ameliorate this >>> problem? >>> >> >> I've spent some time looking into this on Yehuda's behalf, and I believe >> there's no way to make this work without a behavioral change to autoload. >> >> Autoload is part of the normal constant lookup scheme. When defining an >> autoload, a special value is inserted into the target constant table with >> autoload file information. As constants are looked up, if one of these >> special values is encountered an autoload is triggered. >> >> Currently, the basic logic of autoload is like this: >> >> 1. The special value is removed from the constant table >> 2. The associated file is required, and presumably defines the constant >> 3. The constant is re-looked-up after the require completes >> >> The problem lies in step 1 here. There can be a gap between the time the >> special value is removed from the constant table and the time the required >> file redefines it. During this period, another thread may try to request the >> constant value. Since the autoload value has been removed and the new value >> has not yet replaced it, the constant search continues (and eventually fails >> with a NameError). >> >> The primary behavioral change needed would be to not remove the autoload >> value, or to replace it with a new marker indicating "autoload in progress". >> This would require changes to anything that looks up constants, so it would >> know not to initiate a new autoload require nor continue up the constant >> scope chain but to *block* until the autoload is complete. I believe this is >> the only way to make autoload threadsafe. >> > The other wrinkle in this is that the autoload itself may make a check > against that constant (perhaps this is uncommon). So the autoload thread > and any child threads of the autoload thread should actually see it as > undefined. > >> >> I will also agree with Yehuda that a thread-unsafe autoload is broken, >> since it means the primary use case of autoload (delayed loading a file and >> definition of a constant) can't be used in the presence of threads. >> > Dare I say that merb and Rails 2.3 should reconsider autoload as a > reasonable method for lazy loading. There has to be an explicit thread-safe > way of lazy loading which does not look bad. > > -Tom > > > -- Yehuda Katz Developer | Engine Yard (ph) 718.877.1325 ------=_Part_98919_824248.1228333813104 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit Content-Disposition: inline Because of this problem, we *will* be removing the use of autoload in 1.1. However, this is still a pretty big bug in Ruby itself (since it makes autoload broken).

-- Yehuda

On Wed, Dec 3, 2008 at 9:52 AM, Thomas Enebo <Thomas.Enebo@sun.com> wrote:
Charles Oliver Nutter wrote:
Yehuda Katz wrote:
Merb uses autoload rather extensively. We have lately observed some disturbing behavior around concurrency.

Effectively, because autoload removes the flag before loading the file, if two threads concurrently attempt to access an autoloaded constant, one of the threads can get a NameError.

It's easy to reproduce this by adding a sleep to the file being loaded before the constant is defined, and then spinning up two threads that both try to use the constant. This is reproducible in MRI; it does not only happen with JRuby's true parallel execution.

The worst part is that no user action can solve this problem: because autoloading is magic, it's impossible for the user to lock the resulting require. In effect, this makes autoloading completely useless for threaded environments. Is this intentional?

Is there something we can do at the language level to ameliorate this problem?

I've spent some time looking into this on Yehuda's behalf, and I believe there's no way to make this work without a behavioral change to autoload.

Autoload is part of the normal constant lookup scheme. When defining an autoload, a special value is inserted into the target constant table with autoload file information. As constants are looked up, if one of these special values is encountered an autoload is triggered.

Currently, the basic logic of autoload is like this:

1. The special value is removed from the constant table
2. The associated file is required, and presumably defines the constant
3. The constant is re-looked-up after the require completes

The problem lies in step 1 here. There can be a gap between the time the special value is removed from the constant table and the time the required file redefines it. During this period, another thread may try to request the constant value. Since the autoload value has been removed and the new value has not yet replaced it, the constant search continues (and eventually fails with a NameError).

The primary behavioral change needed would be to not remove the autoload value, or to replace it with a new marker indicating "autoload in progress". This would require changes to anything that looks up constants, so it would know not to initiate a new autoload require nor continue up the constant scope chain but to *block* until the autoload is complete. I believe this is the only way to make autoload threadsafe.
The other wrinkle in this is that the autoload itself may make a check against that constant (perhaps this is uncommon).  So the autoload thread and any child threads of the autoload thread should actually see it as undefined.


I will also agree with Yehuda that a thread-unsafe autoload is broken, since it means the primary use case of autoload (delayed loading a file and definition of a constant) can't be used in the presence of threads.
Dare I say that merb and Rails 2.3 should reconsider autoload as a reasonable method for lazy loading.  There has to be an explicit thread-safe way of lazy loading which does not look bad.

-Tom





--
Yehuda Katz
Developer | Engine Yard
(ph) 718.877.1325
------=_Part_98919_824248.1228333813104--