diff --git a/frameworks/rails/Dockerfile b/frameworks/rails/Dockerfile index 85e624293..2e7e9e6c0 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 @@ -23,7 +24,4 @@ COPY . . EXPOSE 8080 -ENV THRUSTER_TARGET_PORT=8080 -ENV THRUSTER_LOG_REQUESTS=false - -CMD ["bin/thrust", "bin/rails", "s"] +CMD ["bin/rails", "server"] diff --git a/frameworks/rails/Gemfile b/frameworks/rails/Gemfile index cc909ec39..28f8346dc 100644 --- a/frameworks/rails/Gemfile +++ b/frameworks/rails/Gemfile @@ -1,8 +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' -gem 'thruster', require: false diff --git a/frameworks/rails/Gemfile.lock b/frameworks/rails/Gemfile.lock index 8f3e6a90d..71a85dc2c 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) @@ -188,8 +188,6 @@ GEM securerandom (0.4.1) stringio (3.2.0) thor (1.5.0) - thruster (0.1.20-arm64-darwin) - thruster (0.1.20-x86_64-linux) timeout (0.6.1) tsort (0.2.0) tzinfo (2.0.6) @@ -210,9 +208,8 @@ DEPENDENCIES bootsnap connection_pool pg (~> 1.5) - puma (~> 7.2) + puma (~> 8.0) rails (~> 8.0) - thruster CHECKSUMS action_text-trix (2.1.18) sha256=3fdb83f8bff4145d098be283cdd47ac41caf5110bfa6df4695ed7127d7fb3642 @@ -263,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 @@ -279,8 +276,6 @@ CHECKSUMS securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1 stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1 thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73 - thruster (0.1.20-arm64-darwin) sha256=630cf8c273f562063b92ea5ccd7a721d7ba6130cc422c823727f4744f6d0770e - thruster (0.1.20-x86_64-linux) sha256=d579f252bf67aee6ba6d957e48f566b72e019d7657ba2f267a5db1e4d91d2479 timeout (0.6.1) sha256=78f57368a7e7bbadec56971f78a3f5ecbcfb59b7fcbb0a3ed6ddc08a5094accb tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b diff --git a/frameworks/rails/bin/thrust b/frameworks/rails/bin/thrust deleted file mode 100755 index 36bde2d83..000000000 --- a/frameworks/rails/bin/thrust +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env ruby -require "rubygems" -require "bundler/setup" - -load Gem.bin_path("thruster", "thrust") diff --git a/frameworks/rails/config/application.rb b/frameworks/rails/config/application.rb index 56375b953..f3b1e58d5 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['REQUEST_METHOD'] == 'POST' + 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 65ad56d1c..a1eafb400 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 port 8080