Skip to content
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

Thread variables are not accessible #82

Open
bolshakov opened this issue Apr 21, 2021 · 10 comments
Open

Thread variables are not accessible #82

bolshakov opened this issue Apr 21, 2021 · 10 comments
Labels
bug Something isn't working help wanted Extra attention is needed

Comments

@bolshakov
Copy link

bolshakov commented Apr 21, 2021

Describe the bug

Using state effect resets all Thread.current variables.

To Reproduce

include Dry::Effects::Handler.State(:updated_attributes)
include Dry::Effects.State(:updated_attributes)

def test
      puts "outside: #{Thread.current.keys}"
      clients_changes, result = with_updated_attributes(Hash.new { |hash, key| hash[key] = [] }) do
        puts "inside: #{Thread.current.keys}"
      end
end
Thread.current[:foo] = 'bar'
test 

Oututs:

outside: [:foo, :dry_effects_stack]
inside: [:dry_effects_stack]

Expected behavior

As you can see variables set outside of the with_updated_attributes block are not visible inside the block.
I expect the following output:

outside: [:foo, :dry_effects_stack]
inside: [:foo, :dry_effects_stack]

My environment

  • Affects my production application: NO
  • Ruby version: "2.6.5"
  • OS: MacOS
  • Dry Effects version: 0.1.5
@bolshakov bolshakov added bug Something isn't working help wanted Extra attention is needed labels Apr 21, 2021
@we138
Copy link

we138 commented Apr 28, 2021

hello @bolshakov! looks like this is not a bug, this is how ruby fiber-local variables works:

f = Fiber.new do
  puts "Fiber started"
  Thread.current[:in_fiber_context] = true
  Fiber.yield
  puts "Fiber-local value: #{Thread.current[:in_fiber_context].inspect}"
  puts "Fiber finished"
end

puts "Starting value: #{Thread.current[:in_fiber_context].inspect}"
f.resume
puts "Value outside of fiber: #{Thread.current[:in_fiber_context].inspect}"
f.resume
puts "Ending value: #{Thread.current[:in_fiber_context].inspect}"
# >> Starting value: nil
# >> Fiber started
# >> Value outside of fiber: nil
# >> Fiber-local value: true
# >> Fiber finished
# >> Ending value: nil

sample copied from this post

@flash-gordon correct me if i'm wrong

@flash-gordon
Copy link
Member

Yeah, absolutely. It works "as expected" because ruby works this way. Fortunately, all thread local usages can be replaced by the state effect. It should be mentioned in the docs so I'll leave it open until then.

@bolshakov
Copy link
Author

I considered this library a great abstraction tool for dependency injection and algebraic effects. The knowledge about underlying fiber use is hidden and never mentioned.

Fortunately, all thread local usages can be replaced by the state effect.

It makes the library incompatible with other tools. Imagine a third-party library (say transaction manager) that uses thread locals. I'm not sure it's a good idea to force every library using thread-locals to migrate to dry-effects v0.1 to introduce it in the existing project.

I understand this argument may sound reasonable for new projects, but it makes hardly possible to introduce "dry-effects" into old ones. Rewriting a lot of code to use "dry-effects" may be a huge commitment.

Since the main purpose of this library is not the work with threads or fibers, I kindly suggest treating this as a bug and make it possible to cooperate "dry-effects" with threads-locals.

@flash-gordon
Copy link
Member

I considered this. The plan was to add a compatibility layer to the gem via extensions. For example, there's one for rspec. Making it work in general is though possible but performance-wise is suboptimal. Instead, one can add a list of variable names to be preserved in child fibers (we can have an API for that).
On the other hand, adding patches for common gems to dry-effects shouldn't be a problem since IMO libraries must encapsulate access to thread local variables, just as rspec does. Otherwise, it's a mess.
So far, I only needed to patch rspec which makes me think the problem doesn't happen too often. Do you have concrete examples where you would need it? For the record, I do use transactions from Sequel but I've never had issues with it even without patches. Even rspec breaks only when very specific features are used (i.e. pending).

@bolshakov
Copy link
Author

Nice, I didn't know about extensions 👍 I implemented a patch myself but ended up using old good thread locals because they appeared to be more readable, and my code that uses "dry-effect" could lead to hard-to-debug issues in the future.

Do you have concrete examples where you would need it?

It was chewy, and something else that I couldn't remember now.

@solnic
Copy link
Member

solnic commented May 6, 2021

I agree this should be properly explained in the docs. Even if it's an edge-case it deserves an explanation.

@flash-gordon
Copy link
Member

Definitely something to improve :)
chewy

@kml
Copy link

kml commented Jun 21, 2021

Hey, to put some context
Thread[]= is for setting "fiber-local variable" https://ruby-doc.org/core-2.0.0/Thread.html#method-i-5B-5D

Thread#thread_variable_set can be used to set "thread-local variable" https://ruby-doc.org/core-2.0.0/Thread.html#method-i-thread_variable_set

The issue itself is rather hard to fix in one proper way, while it seems that Thread.current[...] = ... is often used by libraries to keep the state (initialized scope, context, agent, request store, etc).

Some related links:

When doing some research I had an issue with https://github.com/newrelic/newrelic-ruby-agent/blob/dev/lib/new_relic/agent/instrumentation/sidekiq.rb - when having dry-effect based code it hide tracing information initialized by middleware.

@solnic
Copy link
Member

solnic commented Jul 1, 2021

Is this a problem only in case of the State effect?

@jodosha
Copy link
Member

jodosha commented Jul 2, 2021

@bolshakov @kml I spoke with @flash-gordon and agreed on a simple chewy refactoring that does doesn't imply the adoption of dry-effects there.

After that, we (dry-rb team) will provide an extension for chewy from dry-effects.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

6 participants