You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 10:10:28