diff --git a/frameworks/rails/Dockerfile b/frameworks/rails/Dockerfile index caa99f58..5d115834 100644 --- a/frameworks/rails/Dockerfile +++ b/frameworks/rails/Dockerfile @@ -12,6 +12,7 @@ ENV RUBY_MN_THREADS=1 ENV RACK_ENV=production ENV WEB_CONCURRENCY=auto ENV RAILS_MAX_THREADS=3 +ENV MAX_IO_THREADS=5 WORKDIR /app diff --git a/frameworks/rails/Gemfile b/frameworks/rails/Gemfile index 6fcea7fe..28f8346d 100644 --- a/frameworks/rails/Gemfile +++ b/frameworks/rails/Gemfile @@ -1,7 +1,7 @@ source 'https://rubygems.org' gem 'rails', '~> 8.0' -gem 'puma', '~> 7.2' +gem 'puma', '~> 8.0' gem 'pg', '~> 1.5' gem 'bootsnap', require: false gem 'connection_pool' diff --git a/frameworks/rails/Gemfile.lock b/frameworks/rails/Gemfile.lock index fd29f8b6..71a85dc2 100644 --- a/frameworks/rails/Gemfile.lock +++ b/frameworks/rails/Gemfile.lock @@ -137,7 +137,7 @@ GEM psych (5.3.1) date stringio - puma (7.2.0) + puma (8.0.1) nio4r (~> 2.0) racc (1.8.1) rack (3.2.6) @@ -208,7 +208,7 @@ DEPENDENCIES bootsnap connection_pool pg (~> 1.5) - puma (~> 7.2) + puma (~> 8.0) rails (~> 8.0) CHECKSUMS @@ -260,7 +260,7 @@ CHECKSUMS prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193 prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85 psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974 - puma (7.2.0) sha256=bf8ef4ab514a4e6d4554cb4326b2004eba5036ae05cf765cfe51aba9706a72a8 + puma (8.0.1) sha256=7b94e50c07655718c1fb8ae41a11fc06c7d61293208b3aa608ff71a46d3ad37c racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f rack (3.2.6) sha256=5ed78e1f73b2e25679bec7d45ee2d4483cc4146eb1be0264fc4d94cb5ef212c2 rack-session (2.1.2) sha256=595434f8c0c3473ae7d7ac56ecda6cc6dfd9d37c0b2b5255330aa1576967ffe8 diff --git a/frameworks/rails/config/application.rb b/frameworks/rails/config/application.rb index 56375b95..3ffc5b05 100644 --- a/frameworks/rails/config/application.rb +++ b/frameworks/rails/config/application.rb @@ -3,6 +3,46 @@ Bundler.require(*Rails.groups) +# Catch unknown HTTP methods, routing errors, and mark /upload as binary +class MethodGuard + VALID_METHODS = %w[GET HEAD POST PUT DELETE PATCH OPTIONS TRACE].to_set.freeze + + def initialize(app) + @app = app + end + + def call(env) + unless VALID_METHODS.include?(env['REQUEST_METHOD']) + return [405, { 'content-type' => 'text/plain' }, ['Method Not Allowed']] + end + # Mark /upload as binary so Rack skips form parameter parsing + if env['PATH_INFO'] == '/upload' + env['CONTENT_TYPE'] = 'application/octet-stream' + end + @app.call(env) + rescue => e + if e.class.name.include?('UnknownHttpMethod') || e.class.name.include?('RoutingError') + [400, { 'content-type' => 'text/plain' }, ['Bad Request']] + else + raise + end + end +end + +# Threads marked as IO bound are allowed to go over Puma's max thread limit. +class MarkAsIOBoundThreads + def initialize(app) + @app = app + end + + def call(env) + if env['PATH_INFO'].start_with?('/baseline') + env["puma.mark_as_io_bound"].call + end + @app.call(env) + end +end + class BenchmarkApp < Rails::Application config.load_defaults Rails::VERSION::STRING.to_f config.eager_load = true @@ -25,32 +65,8 @@ class BenchmarkApp < Rails::Application # Add gzip support config.middleware.insert 0, Rack::Deflater - - # Catch unknown HTTP methods, routing errors, and mark /upload as binary - config.middleware.insert 0, Class.new { - VALID_METHODS = %w[GET HEAD POST PUT DELETE PATCH OPTIONS TRACE].to_set.freeze - - def initialize(app) - @app = app - end - - def call(env) - unless VALID_METHODS.include?(env['REQUEST_METHOD']) - return [405, { 'content-type' => 'text/plain' }, ['Method Not Allowed']] - end - # Mark /upload as binary so Rack skips form parameter parsing - if env['PATH_INFO'] == '/upload' - env['CONTENT_TYPE'] = 'application/octet-stream' - end - @app.call(env) - rescue => e - if e.class.name.include?('UnknownHttpMethod') || e.class.name.include?('RoutingError') - [400, { 'content-type' => 'text/plain' }, ['Bad Request']] - else - raise - end - end - } + config.middleware.insert 0, MethodGuard + config.middleware.insert 0, MarkAsIOBoundThreads # Silence logging config.logger = nil diff --git a/frameworks/rails/config/puma.rb b/frameworks/rails/config/puma.rb index 296826d4..a7aff98a 100644 --- a/frameworks/rails/config/puma.rb +++ b/frameworks/rails/config/puma.rb @@ -1,5 +1,6 @@ thread_count = ENV.fetch('RAILS_MAX_THREADS', 4).to_i threads thread_count, thread_count +max_io_threads ENV.fetch("MAX_IO_THREADS", 10).to_i tls_cert_path = ENV.fetch('TLS_CERT', '/certs/server.crt') tls_key_path = ENV.fetch('TLS_KEY', '/certs/server.key')