-
-
Notifications
You must be signed in to change notification settings - Fork 413
Description
What I tried to do
I am working with a high concurrency rails app. The application serves content to requests based on the locale
params or HTTP_ACCEPT_LANGUAGE
header that is received specific to that request. The controller action does something like this before a new request:
I18n.locale = params[:locale] || I18n.default_locale
However, I have witnessed requests not specific to that action return content in a locale that is not the default locale (default locale is en
).
What I expected to happen
The expectation is: On a new request, if I18n.locale
is not being set to anything, it should return the default locale.
Versions of i18n, rails, and anything else you think is necessary
- i18n:
0.8.6
- rails:
5.1.3
- puma:
3.10.0
Replication steps
I have setup a test application here: https://github.com/shayonj/i18n-test-repro. You can replicate this issue by performing:
- clone repository locally
bundle install
rails s
(This application runsPuma
with max 5 threads.)- On one tab open (
Tab1
) openhttp://localhost:3000?locale=es
- Only
MainController#index
setsI18n.locale
toparams[:locale]
if present one orI18n.default_locale
- Only
- On another tab (
Tab2
) openhttp://localhost:3000/foo
- This controller action is not tampering with
I18n.locale
and expects it to returnen
(the default locale)
- This controller action is not tampering with
- Now, try to refresh both tabs as simultaneously/quickly as possible (simulating a high concurrency env).
- Eventually notice, on
Tab2
the current locale being rendered is no longeren
butes
Here is a screen capture demonstrating the above: https://www.dropbox.com/s/zbogg3vag5at98b/i18n-locale-leak.mov?dl=0
Additional info
I can see an argument being made that every action should have something like I18n.locale = :en
in a before_action
block. But then I guess, thats what the point is for defining default_locale
to I18n
config as part of an Initializer:
config.i18n.default_locale = :en
Proposal for a solution
As a work around, in our app, I am using a custom middleware that resets Thread.current[:i18n_config]
after each request. The heart of the problem appears to be that TLS isn't cleared between requests and some how it gets persisted or shared via a Thread pool or similar implementation (common between servers like Puma/Unicorn or Passenger (ENT) with a concurrency model of threads
).
That being said, the above workaround has solved the issue for us and I'd like to propose/discuss if a similar thing is possible to have in the i18n
gem itself? That is, to have something like a I18n::Middleware
that any rack based can utilize or plugin to their app itself (out of the box), instead of having to write/maintain one natively. Or perhaps a different solution altogether?