Instead of forcing Rack::Lock when eager_load and cache_classes aren't on, just prevent actual conflicts.
We achieve this by means of a (reentrant) shared-exclusive lock: multiple running concurrent requests are fine, but any load activity must be performed in isolation.
Are you aiming for a proper fix to #15089?
Why this might not work:
class Foo # L1
def self.foo; end # L3
end # L4
Both threads attempt to Foo.foo:
undefined method 'foo' for Foo:Class
Another idea: upon entering Rack::Lock-thingy a thread does eager_load!, once finished app becomes lock-less and threads are let through, when a thread calls AS::Dependencies.clear it is blocked until all other threads leave "app-space" (while all threads wanting to pass through Rack::Lock are blocked), but then what happens when 2 threads are let through and the both call AS::Dependencies.clear?
@thedarkone thread A can't obtain the exclusive lock (and thus start loading foo.rb) while thread B is in "app space": it'll wait for thread B to also try to autoload.
Alright, then I can't think of a reason why this wouldn't work!
I have maybe 2 suggestions:
btw: I'm seriously impressed, I never thought making AS::Dep thread-safe was realistically possible 😱, this PR is blowing my mind, big kudos to @matthewd 😎!
+1 on being impressed. If we do not discover any devil in the details, this is going to be a real win.
@matthewd are you thinking in moving this forward? I believe this is the best time to do it.
👍 makes sense for 5.0.
I'm thinking - this will not work if user decides to spin up his own threads (because we will not be able to track whether they have entered "app space"). Also, how would this work with background jobs?
For background jobs: the job-runner would wrap the job-doing code just as ActionDispatch does for requests. (And as it presumably already does for the AR conn pool, for example.)
If the user spins up their own thread (and doesn't opt in to the interlock), however... yeah, they're going to be in danger of seeing a half-loaded constant. Avoiding that sounds expensive. 😕
Soften the lock requirements when eager_load is disabled
We don't need to fully disable concurrent requests: just ensure that
loads are performed in isolation.
Rely on the load interlock for non-caching reloads, too
Fix the Interlock middleware
We can't actually lean on Rack::Lock's implementation, so we'll just
copy it instead. It's simple enough that that's not too troubling.
Document ShareLock and the Interlock
This is so awesome. I think we can modify Sidekiq to reload job code in development mode now!