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

request_store broken for ActiveJob #72

Open
henkesn opened this issue Jul 31, 2020 · 1 comment
Open

request_store broken for ActiveJob #72

henkesn opened this issue Jul 31, 2020 · 1 comment

Comments

@henkesn
Copy link

henkesn commented Jul 31, 2020

Hi guys,
at first, thank you for your great work!
I noticed today that I get outdated/wrong data within Mailers/ActiveJob. When I had a look at the stack traces, I noticed that there seems to be missing some kind of middleware which inits RequestLocals. For Sidekick there is a second gem to handle this. As ActiveJob is core feature of rails, perhaps an equivalent should be included in this gem.
I (and propably some more people) am using RequestLocals this way:

RequestLocals.store[:some_stuff] ||= calculate_some_stuff

That's why I think it would be good to raise an error at least when using uninitialized RequestLocals, cause this can lead to severe data leaks regarding multi-user/multi-tenant systems.

@ggambetti
Copy link

Just ran into this issue with Mailers/ActiveJob.

It was specifically an interaction when using active_job.queue_adapter = :async and there never being a RequestStore.clear! call around when jobs were run. This leads to infinitely caching data.

In follow up discussion we found RequestStore.active? (from #49) and had the thought that RequestStore.fetch should take it into account instead of always caching data. This would be a breaking change (since it changes RequestStore.fetch behaviour), but would make it safer to call code using RequestStore in contexts where no wrapping middleware is setup.

ex:

class RequestStore
  def self.fetch(key)
    # No caching occurs when caching is not activated by a wrapping block.
    return yield unless active?
    store[key] = yield unless exist?(key)
    store[key]
  end
end

# The wrapping middleware:
def with_caching(&block)
  RequestStore.begin!
  yield
ensure
  RequestStore.end!
  RequestStore.clear!
end

This way, application code can be written with less concern for the calling context and the caller will get the expected caching behaviour. This makes naive use of RequestStore.fetch safer compared to the current situation.

An example:

class SettingsSingleton
  def self.instance
    RequestStore.fetch(:settings_singleton) do
      # load from database or create as needed.
    end
  end
end

# Somewhere in the app we do something with settings.
# 
# If foo is called anywhere without middleware that will call
# RequestStore.clear! the instance of SettingsSingleton will be cached
# forever, and any content changes will be ignored in every subsequent
# request. However with the new RequestStore.fetch behaviour this is
# safe to call from anywhere, because caching is disabled by default.
def foo
  settings = SettingsSingleton.instance
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants