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

本地Rails/Puma服务器卡顿无响应问题排查求助

本地Rails/Puma+Foreman卡顿无响应排查方案

问题现象

  • 本地用foreman start -f Procfile.dev启动服务后,运行一段时间Puma服务器卡顿无响应,控制台无日志输出,只能重启解决
  • 按下control + c停止服务器时,会批量输出积压的正常日志后才关闭
  • 定期用command + k清空控制台可避免该问题,推测与日志输出缓存/控制台缓冲区有关
  • 仅本地环境出现,staging和生产环境无此问题

当前本地环境配置

  • macOS 13.6.1
  • 使用bash(非zsh)
  • Rails 7.0.8
  • Ruby 2.7.8
  • Puma 4.3.12
  • Sidekiq 6.5.12
  • foreman 0.87.2

关键配置文件

config/environments/development.rb

require "active_support/core_ext/integer/time"

require "dotenv"
Dotenv.load

Rails.application.configure do
  config.cache_classes = false
  config.eager_load = false
  config.consider_all_requests_local = true
  config.server_timing = true

  if Rails.root.join("tmp/caching-dev.txt").exist?
    config.action_controller.perform_caching = true
    config.action_controller.enable_fragment_cache_logging = true

    config.cache_store = :memory_store
    config.public_file_server.headers = {
      "Cache-Control" => "public, max-age=#{2.days.to_i}"
    }
  else
    config.action_controller.perform_caching = false
    config.cache_store = :null_store
  end

  config.active_storage.service = :local
  config.action_mailer.raise_delivery_errors = ActiveModel::Type::Boolean.new.cast(ENV.fetch('RAILS_MAILER_DELIVERY_ERRORS') { true })
  config.action_mailer.perform_caching = false
  config.active_support.deprecation = :log
  config.active_support.disallowed_deprecation = :raise
  config.active_support.disallowed_deprecation_warnings = []
  config.active_record.migration_error = :page_load
  config.active_record.verbose_query_logs = true
  config.assets.quiet = true
  config.assets.debug = true
  config.assets.quiet = true
  config.file_watcher = ActiveSupport::EventedFileUpdateChecker

  config.action_cable.mount_path  = ":#{ ENV.fetch('DEFAULT_PORT') { 5000 } }/cable"
  config.action_cable.url         = "wss://#{ ENV.fetch('HOST') { 'localhost' } }:#{ ENV.fetch('DEFAULT_PORT') { 5000 } }/cable"
  config.web_socket_server_url    = "wss://#{ ENV.fetch('HOST') { 'localhost' } }:#{ ENV.fetch('DEFAULT_PORT') { 5000 } }/cable"

  config.action_cable.allowed_request_origins = [/http:\/\/#{ENV['HOST']}*/, /https:\/\/#{ENV['HOST']}*/]

  config.action_mailer.default_url_options = {
    protocol: ENV.fetch('PROTOCOL') { 'https' },
    host: ENV.fetch('HOST') { 'localhost' },
    port: ENV.fetch('DEFAULT_PORT') { 3000 }
  }

  config.logger = ActiveSupport::Logger.new(STDOUT)
  config.log_level = (ENV.fetch('RAILS_LOG_LEVEL') { 'debug' }).to_sym

  config.sass.inline_source_maps = true
  config.assets.unknown_asset_fallback = true
  config.force_ssl = true
  config.assets.prefix = '/dev-assets'
  config.action_dispatch.cookies_same_site_protection = :strict

  config.action_mailer.delivery_method = ENV.fetch('MAIL_DELIVERY_METHOD') { 'letter_opener' }.to_sym
  config.action_mailer.perform_deliveries = true
end

config/puma/development.rb

require "dotenv"
Dotenv.load

max_threads_count = ENV.fetch('RAILS_MAX_THREADS') { 5 }
min_threads_count = ENV.fetch('RAILS_MIN_THREADS') { max_threads_count }
threads min_threads_count, max_threads_count

port ENV.fetch('DEFAULT_PORT') { 3000 }
environment ENV.fetch('RAILS_ENV') { 'development' }
workers ENV.fetch('WEB_CONCURRENCY') { 5 }

preload_app!

before_fork do
  ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord)
end

on_worker_boot do
  ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
end

if ENV.fetch('HOST') { 'localhost' } === 'localhost'
  ssl_key = "#{File.join('config', 'certs', 'localhost', 'localhost.key')}"
  ssl_crt = "#{File.join('config', 'certs', 'localhost', 'localhost.crt')}"
end

host = ENV.fetch('HOST') { 'localhost' }
port = ENV.fetch('SSL_PORT') { ENV.fetch('DEFAULT_PORT') { 3000 } }

ssl_bind host, port, {
  key: ssl_key,
  cert: ssl_crt,
  verify_mode: 'none'
}

Procfile.dev

web: RAILS_ENV=development bundle exec puma
webpacker: ./bin/webpack-dev-server
redis: redis-server
worker1: bundle exec sidekiq -C config/sidekiq.yml
worker2: bundle exec sidekiq -C config/sidekiq.yml
worker3: bundle exec sidekiq -C config/sidekiq.yml
worker4: bundle exec sidekiq -C config/sidekiq.yml
worker5: bundle exec sidekiq -C config/sidekiq.yml
worker6: bundle exec sidekiq -C config/sidekiq.yml
worker7: bundle exec sidekiq -C config/sidekiq.yml
worker8: bundle exec sidekiq -C config/sidekiq.yml

排查与解决建议

1. 禁用Foreman日志缓冲

你使用的foreman 0.87.2版本较旧,macOS bash环境下默认会缓冲子进程的STDOUT输出,缓冲区满时会阻塞进程:

  • 启动Foreman时添加--no-tty参数:foreman start -f Procfile.dev --no-tty,强制子进程输出不经过终端缓冲
  • 或给进程添加环境变量强制无缓冲输出,修改Procfile.dev中的启动命令:
    web: RAILS_ENV=development RUBYOPT="-E utf-8 -KU" bundle exec puma
    worker1: RUBYOPT="-E utf-8 -KU" bundle exec sidekiq -C config/sidekiq.yml
    

2. 开启Rails日志同步输出

当前Rails日志默认带缓冲,修改config/environments/development.rb中的日志配置,强制同步输出:

config.logger = ActiveSupport::Logger.new(STDOUT)
config.logger.formatter = config.log_formatter
config.logger.sync = true # 禁用日志缓冲,实时输出

3. 减少Sidekiq进程数量

本地开发环境启动8个Sidekiq worker会大幅增加日志输出压力,建议减到2-3个,降低控制台缓冲区负载。

4. 升级Foreman版本

0.87.2是2019年的旧版本,后续版本修复了大量终端输出相关bug,执行gem update foreman升级到最新稳定版。

5. 绕过Foreman直接测试Puma

直接运行bundle exec puma启动服务器,不通过Foreman管理,观察是否还会出现卡顿。若问题消失,可确认是Foreman的终端缓冲问题;若问题依旧,再排查Puma或Rails日志配置。

6. 调整bash终端缓冲区大小

bash默认终端缓冲区可能不足,在~/.bashrc中添加以下配置增大缓冲区:

stty size 1000 200 # 调整终端行数和列数,提升缓冲区容量

重启终端后生效。

内容的提问来源于stack exchange,提问作者Eric Norcross

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 20:57:33