Skip to content

Value of I18n.locale can leak between requests in a multi-threaded environment/server #381

@shayonj

Description

@shayonj

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 runs Puma with max 5 threads.)
  • On one tab open (Tab1) open http://localhost:3000?locale=es
    • Only MainController#index sets I18n.locale to params[:locale] if present one or I18n.default_locale
  • On another tab (Tab2) open http://localhost:3000/foo
    • This controller action is not tampering with I18n.locale and expects it to return en (the default locale)
  • 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 longer en but es

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      pFad - Phonifier reborn

      Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

      Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


      Alternative Proxies:

      Alternative Proxy

      pFad Proxy

      pFad v3 Proxy

      pFad v4 Proxy