-
Notifications
You must be signed in to change notification settings - Fork 21.3k
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
Fix ActiveSupport::CurrentAttributes
attributes to be thread local.
#38905
Fix ActiveSupport::CurrentAttributes
attributes to be thread local.
#38905
Conversation
`Thread#[]` and `Thread#[]=` are getting or setting a fibre-local variable when we want a thread local variable in this case
@ioquatix I think this will affect Falcon which uses one Fibre per request and there can be multiple Fibres in a given Thread. However, |
Thank you for the pull request but we want fiber local. There are many other PRs doing the same and we decided to go with fiber locals. |
Ah, but I just remember that we discussed on making this configurable. Can you change the PR to make it configurable? |
There is a huge risk of setting it in fiber locals and nesting blocks as you can see in this gist https://gist.github.com/matthewd/137de9cc7ad525e0ab650967fe31236c. This is why we are not making the default. |
Sure thing. I guess the default should be fiber local variable with the option of making it thread local? Looks like there is actually a test case to ensure it is using a fiber local variable. Got to remember how to run the tests locally again. |
Thanks for pinging me. My advice would be to avoid using true thread local variables unless it's actually equivalent to global state. In a multi-fiber system running on a single thread, thread local storage is no different from globals. As you can imagine, having globals storing things which are essentially tied to a specific sequential body of code can cause problems, especially if they are interleaved (like light weight fibers with non-blocking I/O). It's highly likely that this feature as well as some kind of Guilds implementation is coming to Ruby 3. Therefore, to take advantage of it, there are definitely things you want to avoid, and I'd say, using thread locals when you probably mean fiber locals, is a big one which will cause a lot of migration pain. |
Just since I'm here, one thing that you could help with is doing an audit of all places in rails where actual thread local storage IS used. It would be great to have a summary so we can figure out how and what needs to be addressed to improve compatibility. It would be great to hit the ground running with Ruby 3 and light weight concurrency. Ruby 3 will be here, all things going well, in about 8 months. You could set up tests against |
Great points @ioquatix. We’ve got tests running against ruby head on our builds already, so we’re good there. What’s the latest on guilds? I don’t think I’ve seen an API or something that’s been merged yet? |
The problem is not running on ruby-head itself. It's that you need to audit places where you are using thread local storage. Thread local storage can cause problems for light weight threads. In a way, it's not so different from a global variable shared between threads, a thread local is shared between fibers has roughly the same exposure to race conditions. Guilds are like separate processes. Not much can be shared between them. So anything that is providing shared resources e.g. the activerecord connection pool may need to be checked. |
Finally, dynamic code modification, e.g. how activerecord modifies classes when loading the database schema, may pose an issue. I'm not sure yet what the exposure is, but I assume at least some code is shared between guilds. |
Yeah, I’m with you on all that. The test builds were in reply to:
|
Understood :) |
@ioquatix I did a quick search and it only seems like we're using thread local variable in the following places at the moment:
|
Closing this as the usage of thread local variable isn't something which we want to encourage. |
Many places in Active Support and Rails in general use `Thread.current#[]` to store "request (or job) local data". This often cause problems with `Enumerator` because it runs in a different fiber. Ref: rails#38905 Ref: rails#39428 Ref: rails#34495 But the required locality depends on how the application is deployed. If you use `falcon`, then you want fiber-locals, if you use `puma/sidekiq` you want thread-locals, and if you use `unicorn/resque`, you can do with thread-locals, but could also just as well do with process globals. Since we keep having to deal this thread vs fiber local problem in various places, I think it suggest we have the need for a proper API to store some state in the current unit of work scope. Then it would b up to the application owner to set the proper isolation level based on how they deploy their Rails app. For backward compatibility it could ship with `:fiber` isolation as a default but longer term `:thread` would make more sense as it would work fine for all deployment targets except `falcon`.
Many places in Active Support and Rails in general use `Thread.current#[]` to store "request (or job) local data". This often cause problems with `Enumerator` because it runs in a different fiber. On the other hand, some places migrated to `Thread#thread_variable_get` which cause issues with fiber based servers (`falcon`). Based on this, I believe the isolation level should be an application configuration. For backward compatibility it could ship with `:fiber` isolation as a default but longer term :thread would make more sense as it would work fine for all deployment targets except falcon. Ref: rails#38905 Ref: rails#39428 Ref: rails#34495 (and possibly many others)
Summary
Thread#[]
andThread#[]=
are getting or setting a fibre-local variable when we want a thread local variable in this case. Instead we need to useThread#thread_variable_get
orThread#thread_variable_set
.From Ruby API doc: