Ruby/Rails 多线程配合Timeout超时后session无法更新问题求解
问题原因
- Rails的session是请求上下文绑定的:每个请求进入时,Rails会从持久化存储(默认是加密Cookie,也可配置为Redis、数据库等)加载session到当前请求的内存副本中,所有对session的修改都只作用在这个内存副本上,只有当主请求正常结束、返回响应给客户端时,Rails才会把修改后的session同步回持久化存储。
你在线程中修改的只是当前主请求的内存session副本,当Timeout触发超时异常时,主请求已经执行到重定向逻辑、返回响应给用户了,此时Rails已经把超时发生时的session状态(还没被后台线程修改的版本)同步到了持久化存储。后续后台线程执行完修改的内存副本早已脱离了请求生命周期,不会被回写到存储,自然后续请求读取的session里没有更新后的值。 - 如果使用默认的Cookie存储session,就算想修改也不可能:Cookie已经随着重定向响应头发给了用户浏览器,后台线程没有能力修改已经发出的响应内容,自然也不可能更新用户本地存的Cookie session。
- 额外小bug:你的判断逻辑写的是
session[:result] = "done",用了赋值运算符=而非比较运算符==,就算没有session问题,这行逻辑也会执行错误。
简单测试成立的原因
你的纯Ruby测试没有请求上下文、session持久化的机制约束,只是普通的内存变量操作,所以线程的修改能正常生效,和Rails请求场景的运行逻辑完全不同。
正确实现方案
- 不要用session存储异步任务的执行结果,改用全局可访问的持久化存储,比如Redis、数据库表:提交任务时生成唯一的
task_id,把task_id存在session里或者直接传给前端,后台异步任务执行完成后,把结果存在对应task_id的存储位置下。 - 不要手动创建线程处理异步任务,直接使用Rails自带的Active Job框架,搭配Sidekiq、GoodJob等异步执行适配器,提交任务后可以立刻返回等待页,不需要用Timeout阻塞主请求10秒,性能和稳定性都更高。
- 等待页轮询时,用session里存的
task_id去全局存储里查询任务状态即可。
内容的提问来源于stack exchange,提问作者user1130176
相关产品推荐
相关产品推荐

