对比RequestStore和ActiveSupport::CurrentAttributes
TL;DR
它们都是线程缓存工具,并且能在请求结束时自动清理
CurrentAttributes通过定义子类的方式,帮助归类线程变量,而RequestStore则通常是直接存取
RequestStore的清理是通过增加middleware,而CurrentAttributes是通过ActiveSupport::Executor
RequestStore有一个扩展request_store-sidekiq,可以让你的sidekiq程序也能自动清理线程缓存,而CurrentAttributes没有配套工具,需要自己实现
RequestStore
request_store的使用方法非常直接,例如:RequestStore.store[:foo] ||= 123
module RequestStore
def self.store
Thread.current[:request_store] ||= {}
end
def self.clear!
Thread.current[:request_store] = {}
end
end
线程缓存的清空方法,是通过增加一个middleware来实现的:
module RequestStore
class Railtie < ::Rails::Railtie
initializer "request_store.insert_middleware" do |app|
if ActionDispatch.const_defined? :RequestId
app.config.middleware.insert_after ActionDispatch::RequestId, RequestStore::Middleware
else
app.config.middleware.insert_after Rack::MethodOverride, RequestStore::Middleware
end
if ActiveSupport.const_defined?(:Reloader) && ActiveSupport::Reloader.respond_to?(:to_complete)
ActiveSupport::Reloader.to_complete do
RequestStore.clear!
end
elsif ActionDispatch.const_defined?(:Reloader) && ActionDispatch::Reloader.respond_to?(:to_cleanup)
ActionDispatch::Reloader.to_cleanup do
RequestStore.clear!
end
end
end
end
end
顺提,request_store-sidekiq就是这样利用sidekiq chain而已,也是非常简单
module RequestStore
module Sidekiq
class ServerMiddleware
def call(worker, job, queue)
yield
ensure
::RequestStore.clear!
end
end
end
end
CurrentAttributes
以下是CurrentAttributes的解析
class ActiveSupport::CurrentAttributes
include ActiveSupport::Callbacks
define_callbacks :reset
class << self
# 在线程变量中找回CurrentAttributes子类的实例(如果没有就会设置一个)
def instance
current_instances[current_instances_key] ||= new
end
# 方法generated_attribute_methods会创建一个匿名module,并让CurrentAttributes子类include之
# 然后在该module上定义一些名字为names的一些实例方法和类方法
# 类方法其实调用线程变量中的子类的实例的实例方法,去set或get在@attributes中的对应name的值
#
# 例如以下,每个请求都会由一个线程去运行,于是调用Current.user会在线程中创建一个Current实例
# 然后在设置该实例@attributes[:user] = User.find_by...
#
# class Current < ActiveSupport::CurrentAttributes
# attribute :user
#
# resets { Time.zone = nil }
#
# def user=(user)
# super
# self.account = user.account
# Time.zone = user.time_zone
# end
# end
#
# class SampleController < ApplicationController
# before_action :set_context
#
# def set_context
# Current.user = User.find_by(id: cookies.encrypted[:user_id])
# end
# end
#
def attribute(*names)
generated_attribute_methods.module_eval do
names.each do |name|
define_method(name) do
attributes[name.to_sym]
end
define_method("#{name}=") do |attribute|
attributes[name.to_sym] = attribute
end
end
end
names.each do |name|
define_singleton_method(name) do
instance.public_send(name)
end
define_singleton_method("#{name}=") do |attribute|
instance.public_send("#{name}=", attribute)
end
end
end
# 还可以定义一些回调,在请求结束时调用
def resets(&block)
set_callback :reset, :after, &block
end
private
# 这里的include,是让CurrentAttributes子类include匿名module
def generated_attribute_methods
@generated_attribute_methods ||= Module.new.tap { |mod| include mod }
end
# 如果有定义多个CurrentAttributes子类,例如CurrentUser < CurrentAttributes,UpStream < CurrentAttributes
# 则Thread.current[:current_attributes_instances] = {'CurrentUser' => CurrentUser.new, 'UpStream' => UpStream.new}
# 当然,在不同线程中分配的是不同的数据块
def current_instances
Thread.current[:current_attributes_instances] ||= {}
end
# key用的是子类的类名
def current_instances_key
@current_instances_key ||= name.to_sym
end
end
attr_accessor :attributes
def initialize
@attributes = {}
end
end
那么它是如何做到请求结束时清空线程缓存的呢?通过ActiveSupport::Executor。
每个rails应用启动时都会内含一个ActiveSupport::Executor实例,它会被包装在ActionDispatch::Executor中,
而ActionDispatch::Executor本身是一个middleware,会在请求结束时调用ActiveSupport::Executor#complete!,
从而触发ActiveSupport::CurrentAttributes.reset_all
# lib/active_support/railtie.rb
module ActiveSupport
class Railtie < Rails::Railtie
initializer "active_support.reset_all_current_attributes_instances" do |app|
app.reloader.before_class_unload { ActiveSupport::CurrentAttributes.clear_all }
app.executor.to_run { ActiveSupport::CurrentAttributes.reset_all }
app.executor.to_complete { ActiveSupport::CurrentAttributes.reset_all }
end
end
end
# lib/action_dispatch/middleware/executor.rb
module ActionDispatch
class Executor
def initialize(app, executor)
@app, @executor = app, executor
end
def call(env)
state = @executor.run!
begin
response = @app.call(env)
returned = response << ::Rack::BodyProxy.new(response.pop) { state.complete! }
ensure
state.complete! unless returned
end
end
end
end
# lib/rails/application/default_middleware_stack.rb
module Rails
class Application
class DefaultMiddlewareStack
def build_stack
ActionDispatch::MiddlewareStack.new do |middleware|
# ...
middleware.use ::ActionDispatch::Executor, app.executor
清空的方法reset_all,如下
class ActiveSupport::CurrentAttributes
include ActiveSupport::Callbacks
define_callbacks :reset
class << self
# 可以设置before回调
def before_reset(&block)
set_callback :reset, :before, &block
end
# 可以设置after回调
def resets(&block)
set_callback :reset, :after, &block
end
alias_method :after_reset, :resets
# 在类上调用reset,实质上是调用本线程变量中的实例的reset
delegate :set, :reset, to: :instance
# 对本线程中所有创建过的CurrentAttributes子类实例,调用reset方法
def reset_all
current_instances.each_value(&:reset)
end
# 当重新加载类时,除了通过reset_all把attributes清空
# 还要把CurrentAttributes子类实例清掉
def clear_all
reset_all
current_instances.clear
end
end
def reset
run_callbacks :reset do
self.attributes = {}
end
end
end