部署Sinatra应用新版本时如何终止旧的rufus-scheduler任务?
这问题我之前帮不少人排查过——核心原因是Passenger在部署更新时,旧的应用进程可能没有被彻底终止,导致旧的rufus-scheduler实例还在后台偷偷跑。结合你提到的at_exit尝试,给你几个靠谱的解决步骤:
1. 完善at_exit钩子,确保旧scheduler被正确关闭
你之前的思路是对的,但可能钩子的写法不够完整。要确保scheduler对象在全局作用域可访问,并且调用正确的终止方法:
# app.rb 中的完整配置 require 'rufus-scheduler' # 全局定义scheduler,确保at_exit能访问到 $scheduler = Rufus::Scheduler.new $scheduler.every '5m' do do_something_cool end at_exit do if $scheduler && $scheduler.running? puts "[#{Process.pid}] Shutting down rufus scheduler..." # wait:30 表示等待当前正在执行的任务最多30秒再终止,也可以设为false强制立即停止 $scheduler.shutdown(wait: 30) end end
Passenger在终止旧进程时会触发at_exit钩子,这段代码能确保旧进程的scheduler被优雅关闭。
2. 让Capistrano部署时强制重启Passenger,干掉所有旧进程
默认的Capistrano部署可能只是更新代码,没有彻底重启Passenger进程。你需要在deploy.rb中添加强制重启的任务,确保旧进程全部退出:
# deploy.rb 中添加重启任务 after 'deploy:publishing', 'deploy:restart' namespace :deploy do desc 'Restart Passenger application' task :restart do on roles(:app), in: :sequence, wait: 5 do # Passenger经典重启方式:touch tmp/restart.txt会触发Passenger终止旧进程 execute :touch, release_path.join('tmp/restart.txt') # 如果是Passenger Enterprise,也可以用更直接的命令: # execute 'passenger-config restart-app /path/to/your/app' end end end
每次部署后,这个任务会让Passenger彻底终止所有旧的应用进程,启动新的实例——旧的scheduler自然会跟着旧进程被清理。
3. 排查是否存在多个scheduler实例
有时候如果在多个文件(比如路由文件、初始化脚本)中重复定义了scheduler,会导致多个任务实例同时运行。一定要确保整个应用只有一个全局的scheduler实例,不要在其他地方重复初始化Rufus::Scheduler.new。
4. 验证重启是否生效的小技巧
可以在定时任务中加入进程ID日志,方便确认是否有旧进程残留:
$scheduler.every '5m' do log_msg = "[#{Process.pid}] #{Time.now.strftime('%Y-%m-%d %H:%M')} Running do_something_cool" puts log_msg # 写入日志文件更方便查看 File.open(File.expand_path('../log/scheduler.log', __FILE__), 'a') { |f| f.puts log_msg } end
部署新版本后,查看日志中的PID,如果都是新的进程ID,说明旧的scheduler已经被成功关闭了。
额外注意
如果你在Nginx配置中开启了passenger_preload_app on;,touch tmp/restart.txt依然有效——它会触发Passenger重新预加载应用,旧的预加载进程也会被终止。
内容的提问来源于stack exchange,提问作者narzero

