-
Notifications
You must be signed in to change notification settings - Fork 21.7k
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
Intermittent failure on test environment about missing constants #15089
Comments
cc @fxn |
We have disabled the eager load to get other random failures and now we get the circular dependency exception:
|
Are you running 2 concurrent requests in the same Ruby process? |
But since we are using webrick, thread-safety must not be a problem. |
@sobrinho I'm sorry, you can't have auto-loaded and auto-unloaded constants in a multi-threaded environment, it is not supported by Rails :(. Your choice of server has no bearing on this... What happens is that multiple threads start loading/evaling @rafaelfranca this can be closed. |
@sobrinho as you said, capybara uses two threads: one to drive, and one to handle the web requests. But... unless you're doing something quite odd, only the latter would normally have occasion to load a controller. And it certainly doesn't explain how you're ending up with two simultaneous requests, which I wouldn't normally expect capybara to make without rather explicit encouragement. Can you please reduce this to a minimal test case? Mostly, I'd like to see a test method that causes this. (Is Webrick even getting involved? Or is capybara just using Rack::Test?) @thedarkone note that |
@matthewd I will try to isolate it today and post some news. |
@matthewd there is no need to change the default I have no idea how capybara works, but if the " |
This issue has been automatically marked as stale because it has not been commented on for at least The resources of the Rails team are limited, and so we are asking for your help. If you can still reproduce this error on the Thank you for all your contributions. |
I have the same issue with capybara and wrong circular dependency exception.
Should we change the default value for it? |
@divineforest constant autoloading works fine if constants are loaded in a sequential way. I have never seen a problem with constant autoloading that didn't uncover something wrong in the application code. If there was something fundamental in Capybara that prevented it from being safe for constant autoloading, then we could study the situation and see what can be done, but we cannot change the default without that understanding. |
@fxn everything is fine with capybara. It just uses threads :). And rails are not thread-safe at constant autoloading. Please look at railsadminteam/rails_admin#2029 (comment) . It check |
@fxn I can try to fix autoloading and circular dependency checking. But still looks like ruby's require method is not thread safe. |
@divineforest it uses threads, but there are comments in this thread that suggest that maybe it uses threads in a way that should not affect constant autoloading. I don't know Capybara internals, we need confirmation of whether it is a problem or not. I can say I have never had issues with Capybara and constants, but that still doesn't prove anything, maybe I was lucky, maybe there's no issue. We need to know. |
@fxn the issue occurs when there are a lot of parallel ajax requests to the same action |
@divineforest are those parallel requests served by different threads? |
@fxn yes |
@divineforest why? |
@fxn I print |
Looks like other people also have these issues https://gist.github.com/josevalim/470808 /cc @ka8725 |
Well, that thread seems unrelated to constants. It says that there are threads and thus different connections. But we need to know rather than guess that Capybara uses threads in a way that makes constant autoloading non-safe. If that is the case, then we will study the best thing to do knowing what we have to address. I may personally study how Capybara works when I find the time (in some imaginary dimension!), it is something that interests me anyway. |
Just to be clear, we are using database rewinder and not the shared connection ;) |
#16828 and linked issue is related to this one too. |
@fxn thanks for you attention. I feel that now I need to dig more info and then continue discussion :) |
@fxn that thread itself is not relevant, only the comment
|
@fxn:
Back to my comment from a couple of months ago:
I think this is solved and the issue can be closed then. this @matthewd comment's:
doesn't apply because @divineforest clearly uses more than one thread "to handle" web requests. |
@divineforest Do you happen to be creating multiple Capybara::Session objects, and if so - are they all created with the same application instance? Capybara will start one server thread for each application instance, each listening on its own port. You can look at Capybara::Server.ports to see how many server threads capybara is running (one port per thread) |
Capybara::Server.ports =>
{70100771329120=>62578} |
@fxn oh, thanks :) |
@divineforest before the Rack days every server had their own interface and the adapter for WEBrick was responsible for the synchronization. Nowadays a middleware is injected when you run I guess we are onto something. |
@divineforest - As you pointed out Rails will normally insert Rack::Lock into the middleware to prevent Webrick from handling multiple requests simultaneously - This may not be being included when run with Capybara, or you may to be removing it from the middleware in your app? I will hopefully have time to check later today whether Capybara is missing that for rails apps. |
Geeze guys! Reading this issue is an exercise in frustration... I assume Capybara's "driver" thread works like this: request url, wait response, read response, parse html, send another request, wait response, parse html, send request, etc. If it is so, and there is only one such "driver" thread, then it doesn't matter how many threads webrick is running/spawning or whether If this is not so, and there are multiple "driver" threads or a "driver" might issue multiple requests simultaneously then there is a problem. |
@thedarkone Capybara in this case is being used to test an app through a browser - So capybara has two threads - one that runs a server (Webrick) and one that interacts with a browser. So it goes |
I have not been able to look at the code and I am writing this from my phone. The hypothesis is that the application is issuing severall Ajax call in the context of one single Capybara request. Those ones would spawn threads in WEBrick if there's no locking manually configured. |
So looking through the code, Rails adds Rack::Lock to the middleware for the app unless allow_concurrency? is true - allow concurrency is defined as
Since config.cache_classes is true by default in the test environment while config.eager_load is false, This sets up a default of allowing concurrency in webrick while not eager loading the code - which I believe is causing these errors. Is this the intended behavior in rails? |
@twalpole that's going to be it, that predicate should not return true if |
I have confirmed this is the case. Wrote a test app that issues 10 Ajax calls in the layout to a dummy action that sleeps for one second. When running it trough Capybara there are as many threads in parallel, and if we disable concurrency they are served linearly. So this is definitely wrong, going to fix it. |
@twalpole thanks, finally things are starting to make sense! Capybara uses a browser and an app (that is being tested) has AJAX requests that are not user triggered or a user action might trigger multiple AJAX requests, that then end up hitting multithreaded rails app in development mode. The solution is simple, the Rails enviroment used by Capybara must either:
|
No, no, the predicate should return false if |
@thedarkone the app is running in test mode BTW. |
@thedarkone - slight clarification there -- the app is being run in test mode not dev. Dev mode in rails by default sets cache_classes to false which effectively disables concurrency by adding Rack::Lock to the middleware |
and as @fxn stated the proper solution is for allow_concurrency? to be false if eager_load is false. This issue goes back to at least Rails 4. |
Well done guys 💃 |
@sobrinho for apps before 4.2, you can throw
in config/environments/test.rb. In principle that configuration option is going to go away some day, that's why the patch is based on the logic related to eager loading and multi-threading. But for pre-4.2 that old-school flag is going to be less costly than enabling eager loading. |
If code is not eager loaded constants are loaded on demand. Constant autoloading is not thread-safe, so if eager loading is not enabled multi-threading should not be allowed. This showed up in certain Capybara scenarios: Most Capybara drivers other than Rack::Test need a web server. In particular, drivers for JavaScript support. Capybara launches WEBrick in its own thread for those but that per se is fine, because the spec thread and the server thread are coordinated. Problem comes if the page being served in the spec makes Ajax calls. Those may hit WEBrick in parallel, and since WEBrick is multi-threaded and allow_concurrency? returns true in the test environment before this patch, threads are spawned to serve those parallel requests. On the other hand, since eager_load is false by default in the test environment, constants are not preloaded. So the suite is autoloading constants in a multi-threaded set. That's a receipt for paracetamol. The symptom is random obscure errors whose messages point somehow to constant autoloading. As a consequence of this fix for allow_concurrency? WEBrick in Capybara scenarios no longer runs in multi-threaded mode. Fixes rails#15089.
If code is not eager loaded constants are loaded on demand. Constant autoloading is not thread-safe, so if eager loading is not enabled multi-threading should not be allowed. This showed up in certain Capybara scenarios: Most Capybara drivers other than Rack::Test need a web server. In particular, drivers for JavaScript support. Capybara launches WEBrick in its own thread for those but that per se is fine, because the spec thread and the server thread are coordinated. Problem comes if the page being served in the spec makes Ajax calls. Those may hit WEBrick in parallel, and since WEBrick is multi-threaded and allow_concurrency? returns true in the test environment before this patch, threads are spawned to serve those parallel requests. On the other hand, since eager_load is false by default in the test environment, constants are not preloaded. So the suite is autoloading constants in a multi-threaded set. That's a receipt for paracetamol. The symptom is random obscure errors whose messages point somehow to constant autoloading. As a consequence of this fix for allow_concurrency? WEBrick in Capybara scenarios no longer runs in multi-threaded mode. Fixes #15089. Conflicts: railties/CHANGELOG.md
Thanks ! Nice investigation. This bug has driven me nuts. |
I believe the
Should this advice, that you may have to set config.eager_load to true (essentially for Capybara with javascript driver tests) be changed? Now it's expected there's no reason to do that, what you really needed was |
/cc @matthewd |
Hello guys,
We have 3 complete different applications which are raising strange exceptions on the test environment.
I'm assuming it's a race condition but I'm not sure, still trying to figure out what's happening.
On a rails 4.0.4 application we are randomly seeing this exception:
Using
ActiveSupport::Dependencies.log_activity = true
, as @rafaelfranca suggested, we see this on the test.log:We are seeing similar failures on rails 4.1 applications (circular dependency exception appears from time to time and we don't have any circular dependency).
What I can tell is that when we make 2 requests at same endpoint, in this case, at people controller, something happens on active support and it mess with the autoloading.
We changed
config.eager_load
to true on the test environment and the exceptions seems to be gone, we are running the same build again over and over again to be sure it won't fail again.I'm assuming that is something related to the capybara running the server in a different thread but since webrick responds only one request per time, it do not makes sense.
Is that excepted to happen when eager load is false on test environment?
What else can we do to isolate the exception and you guys have some info about it?
The text was updated successfully, but these errors were encountered: