Rails 7 应用中使用Grover gem渲染PDF时随机触发Grover::JavaScript::TimeoutError超时错误
Rails 7 应用中使用Grover gem渲染PDF时随机触发Grover::JavaScript::TimeoutError超时错误
遇到这种随机触发的超时问题确实挺闹心的,结合你描述的现象——开debug模式就正常、本地和生产都出问题、时好时坏——咱们来一步步排查可能的原因和解决办法:
核心现象梳理
- Rails 7 + Grover 1.2.1,多个使用Grover的场景都出现随机超时
- 开启
debug: true后问题消失,说明Grover的debug模式改变了某些渲染行为 - 延长超时时间没用,说明不是单纯的渲染慢,而是进程/资源卡住了
- 本地和Heroku都出现问题,排除了环境特定的配置差异
可能的原因&对应解决方案
1. 页面资源加载未完成,导致Chrome持续等待
Grover依赖无头Chrome渲染页面,如果页面存在未完成的网络请求(比如样式、字体、图片加载卡住),会导致wait_until的条件一直不满足,最终触发超时。
- 排查静态资源: 检查PDF布局里的
stylesheet_link_tag "application_pdf",确保这个样式表没有引用不稳定的外部资源(比如CDN字体、第三方图片)。可以尝试把样式直接内联到模板里,消除外部请求依赖:<!-- 替换stylesheet_link_tag,直接内联样式内容 --> <style> /* 这里放入application_pdf.css的全部内容 */ </style> - 调整等待策略: 把
wait_until: 'domcontentloaded'改成networkidle2,这个参数会等待页面网络请求数降到2个以下并持续500ms,更适合有资源加载的页面:pdf = Grover.new( html, format: 'A4', wait_until: 'networkidle2' )
2. 无头Chrome的资源不足或配置问题
Grover调用的无头Chrome进程如果资源不够(比如内存不足),会出现卡住的情况,尤其是并发渲染PDF时。
- 添加Chrome启动优化参数: 在Grover全局配置里添加参数减少资源占用:
# 在config/initializers/grover.rb中配置 Grover.configure do |config| config.options = { args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', # 避免/dev/shm内存不足导致的崩溃 '--disable-gpu', '--memory-pressure-off' # 禁用内存压力检测,减少进程被终止概率 ] } end - 升级Grover版本: 你当前使用的是1.2.1,建议尝试升级到最新版,新版本通常会修复这类兼容性和稳定性问题。
3. 利用Debug模式的线索排查
既然开启debug: true就正常,说明这个模式下Grover的配置有差异。你可以尝试在非debug模式下开启日志输出,查看超时前Chrome的渲染行为:
pdf = Grover.new( html, format: 'A4', wait_until: 'networkidle2', timeout: 60000, js_timeout: 60000, logger: Rails.logger # 输出渲染日志,定位卡住环节 )
4. 添加重试机制(临时缓解业务影响)
因为是随机错误,在代码里添加重试逻辑可以减少业务中断:
def send_invoice_to_email(emails, invoice_id) @invoice = Invoice.find(invoice_id) @company = @invoice.company html = ApplicationController.render( template: '/invoices/pdf', formats: :html, layout: 'pdf', assigns: { invoice: @invoice, company: @company } ) pdf = nil max_retries = 2 retry_count = 0 begin pdf = Grover.new( html, format: 'A4', wait_until: 'networkidle2', args: ['--no-sandbox', '--disable-dev-shm-usage'] ).to_pdf rescue Grover::JavaScript::TimeoutError => e retry_count += 1 retry if retry_count <= max_retries raise e # 重试2次后仍失败,再抛出异常 end attachments[attachment_name(@invoice)] = pdf subject = "Invoice ##{invoice_id}" mail(to: emails, subject: subject, tag: 'INVOICE_SEND') end
5. 检查Heroku资源配置
在Heroku上,dyno内存不足是常见的触发因素。如果你的dyno规格太小(比如免费/Hobby级),同时渲染多个PDF会导致Chrome进程内存耗尽。可以尝试:
- 升级dyno规格到Standard级
- 用Sidekiq等异步任务处理PDF生成,控制并发任务数避免资源过载
内容来源于stack exchange
相关产品推荐
相关产品推荐

