Ruby Thread.current 是否保证对一个响应可用且私有吗?

noob · October 17, 2023 · Last by losingle replied at October 20, 2023 · 521 hits

多个请求,偶尔出现共享感觉 简化的代码如下:

  def current_user
    return kite_verify(token) if xxxx # 小程序

   verify(token) || raise(ActionController::InvalidAuthenticityToken, 'UnauthorizedError') # web页面登陆
  end

private

def kite_verify
    id, _ = 解析token获取id
    User.find_by(id: id)
end

def verify
    id, company = 解析token获取id, company
    User.find_by(id: id)
    Thread.current[:company] = company
end

接口:/test/company

def company
  render json: {company:  Thread.current[:company]}
end

出现状况:同一个用户 id,小程序登陆,web 登陆不同公司的登陆,小程序能获取到 Thread.current[:company] 的值。

小程序打印出的Thread.current: <Thread:0x00000001049de928@puma threadpool 001  xxx>
web页面登录打印出的Thread.current : <Thread:0x00000001049bfa78@puma threadpool 003  xxxx>

请教一下具体什么原因

访问结束后 current 清理了吗?没清理这个 thread 处理的下一个请求就能访问。

Rails 提供了 Current Attributes 可以帮忙每个请求之后清理值。

https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html

都在 controller 层,为何不直接存实例变量?

Reply to spike76

场景不适用,因为需要跨层传递

Reply to Rei

为什么 current 再下一次的请求也是能访问到,下次的请求不会重新新建一个线程,而是复用么?

Reply to noob

层数不多的话我在工程中通常会选择显式传给另一层,层数太多的可能需要个上下文管理器,类似于 Rei 说的。

为什么 current 再下一次的请求也是能访问到,下次的请求不会重新新建一个线程,而是复用么?

通常 web 服务器会通过线程池复用线程,你可以试试把服务器的线程数改成 1, 再在每个请求中打印线程的 object_id, 应该都一样

Reply to noob

取决于 app server 实现,如果是 puma,我没仔细研究,但我看到了线程池,按我理解是复用的。

Reply to noob

每次请求进来,线程都要重新创建的话,开销还是很大的

不太推荐用,还得看 web server 的网络模型是怎样的,以后换一个 web server 可能就会存在并发问题

Reply to msl12

是的

千万别这么干,会出现重大的偶现 Bug。在 Rails 的框架里,不要用任何线程,因为底层的实现有可能是线程池,也可能是 fiber,会导致线程中看上去独立的变量复用在不同的请求里,引发重大 Bug。

https://github.com/steveklabnik/request_store

(这个优化方案是从 paper_trail 源码读到的)

Reply to zhandao

这个是挺好的,request_store 其实很简单,会根据用的是 Fiber 还是 Thread 会进行区别处理,如果是 Fiber 就用 Fiber,如果不是 Fiber,就和自己用 Thread. current 效果一样

你应该是混淆了 CurrentAttributesThread.current

尽量减少这样的操作,如果一定要这么做,那么你可以试试,这个保障线程被回收之前清理掉

around_filter :do_with_current_user

def do_with_current_user
    Thread.current[:current_user] = self.current_user
    begin
        yield
    ensure
        Thread.current[:current_user] = nil
    end      
end

Rails 中的话,使用 ActiveSupport::CurrentAttributes是最简单省事的,贯穿你的请求和响应周期,会自动重置清理

You need to Sign in before reply, if you don't have an account, please Sign up first.