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

noob · 2023年10月17日 · 最后由 losingle 回复于 2023年10月20日 · 516 次阅读

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

  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 层,为何不直接存实例变量?

spike76 回复

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

Rei 回复

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

noob 回复

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

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

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

noob 回复

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

Rei 回复

thx💯

noob 回复

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

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

msl12 回复

是的

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

https://github.com/steveklabnik/request_store

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

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是最简单省事的,贯穿你的请求和响应周期,会自动重置清理

需要 登录 后方可回复, 如果你还没有账号请 注册新账号