slowpoke-0.3.2/0000755000175000017510000000000014124351331012171 5ustar rmb571rmb571slowpoke-0.3.2/lib/0000755000175000017510000000000014124351331012737 5ustar rmb571rmb571slowpoke-0.3.2/lib/slowpoke/0000755000175000017510000000000014124351331014602 5ustar rmb571rmb571slowpoke-0.3.2/lib/slowpoke/timeout.rb0000644000175000017510000000065714124351331016625 0ustar rmb571rmb571module Slowpoke class Timeout def initialize(app, service_timeout:) @app = app @service_timeout = service_timeout @middleware = {} end def call(env) service_timeout = @service_timeout.call(env) if service_timeout (@middleware[service_timeout] ||= Rack::Timeout.new(@app, service_timeout: service_timeout)).call(env) else @app.call(env) end end end end slowpoke-0.3.2/lib/slowpoke/version.rb0000644000175000017510000000005014124351331016607 0ustar rmb571rmb571module Slowpoke VERSION = "0.3.2" end slowpoke-0.3.2/lib/slowpoke/railtie.rb0000644000175000017510000000172414124351331016564 0ustar rmb571rmb571module Slowpoke class Railtie < Rails::Railtie config.slowpoke = ActiveSupport::OrderedOptions.new # must happen outside initializer (so it runs earlier) config.action_dispatch.rescue_responses.merge!( "Rack::Timeout::RequestTimeoutError" => :service_unavailable, "Rack::Timeout::RequestExpiryError" => :service_unavailable ) initializer "slowpoke" do |app| service_timeout = app.config.slowpoke.timeout service_timeout ||= ENV["RACK_TIMEOUT_SERVICE_TIMEOUT"] || ENV["REQUEST_TIMEOUT"] || ENV["TIMEOUT"] || 15 if service_timeout.respond_to?(:call) app.config.middleware.insert_after ActionDispatch::DebugExceptions, Slowpoke::Timeout, service_timeout: service_timeout else app.config.middleware.insert_after ActionDispatch::DebugExceptions, Rack::Timeout, service_timeout: service_timeout.to_i end app.config.middleware.insert(0, Slowpoke::Middleware) end end end slowpoke-0.3.2/lib/slowpoke/middleware.rb0000644000175000017510000000061014124351331017241 0ustar rmb571rmb571module Slowpoke class Middleware def initialize(app) @app = app end def call(env) @app.call(env) ensure # extremely important # protect the process with a restart # https://github.com/heroku/rack-timeout/issues/39 # can't do in timed_out state consistently Slowpoke.on_timeout.call(env) if env[Slowpoke::ENV_KEY] end end end slowpoke-0.3.2/lib/slowpoke.rb0000644000175000017510000000205114124351331015125 0ustar rmb571rmb571# dependencies require "rack/timeout/base" # modules require "slowpoke/middleware" require "slowpoke/railtie" require "slowpoke/timeout" require "slowpoke/version" module Slowpoke ENV_KEY = "slowpoke.timed_out".freeze def self.kill if defined?(::PhusionPassenger) `passenger-config detach-process #{Process.pid}` elsif defined?(::Puma) Process.kill("TERM", Process.pid) else Process.kill("QUIT", Process.pid) end end def self.on_timeout(&block) if block_given? @on_timeout = block else @on_timeout end end on_timeout do |env| next if Rails.env.development? || Rails.env.test? Slowpoke.kill end end # remove noisy logger Rack::Timeout.unregister_state_change_observer(:logger) # process protection and notifications Rack::Timeout.register_state_change_observer(:slowpoke) do |env| if env[Rack::Timeout::ENV_INFO_KEY].state == :timed_out env[Slowpoke::ENV_KEY] = true # TODO better payload ActiveSupport::Notifications.instrument("timeout.slowpoke", {}) end end slowpoke-0.3.2/lib/generators/0000755000175000017510000000000014124351331015110 5ustar rmb571rmb571slowpoke-0.3.2/lib/generators/slowpoke/0000755000175000017510000000000014124351331016753 5ustar rmb571rmb571slowpoke-0.3.2/lib/generators/slowpoke/templates/0000755000175000017510000000000014124351331020751 5ustar rmb571rmb571slowpoke-0.3.2/lib/generators/slowpoke/templates/503.html0000644000175000017510000000304714124351331022152 0ustar rmb571rmb571 This page took too long to load (503)

This page took too long to load.

Give it another shot.

slowpoke-0.3.2/lib/generators/slowpoke/install_generator.rb0000644000175000017510000000042614124351331023016 0ustar rmb571rmb571require "rails/generators" module Slowpoke module Generators class InstallGenerator < Rails::Generators::Base source_root File.expand_path("../templates", __FILE__) def copy_503_html template "503.html", "public/503.html" end end end end slowpoke-0.3.2/LICENSE.txt0000644000175000017510000000206114124351331014013 0ustar rmb571rmb571Copyright (c) 2014-2019 Andrew Kane MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. slowpoke-0.3.2/CHANGELOG.md0000644000175000017510000000144614124351331014007 0ustar rmb571rmb571## 0.3.2 (2019-12-23) - Added `on_timeout` method ## 0.3.1 (2019-12-10) - Added support for dynamic timeouts ## 0.3.0 (2019-05-31) - Use proper signal for Puma - Dropped support for rack-timeout < 0.4 - Dropped support for migration timeouts - Dropped support for Rails < 5 ## 0.2.1 (2018-05-21) - Don’t kill server in test environment - Require rack-timeout < 0.5 ## 0.2.0 (2017-11-05) - Fixed custom error pages for Rails 5.1 - Fixed migration statement timeout - Don’t kill server in development ## 0.1.3 (2016-08-03) - Fixed deprecation warning in Rails 5 - No longer requires ActiveRecord ## 0.1.2 (2016-02-10) - Updated to latest version of rack-timeout, removing the need to bubble timeouts ## 0.1.1 (2015-08-02) - Fixed safer service timeouts - Added migration statement timeout slowpoke-0.3.2/README.md0000644000175000017510000000727414124351331013462 0ustar rmb571rmb571# Slowpoke [Rack::Timeout](https://github.com/heroku/rack-timeout) enhancements for Rails - safer service timeouts - dynamic timeouts - custom error pages ## Installation Add this line to your application’s Gemfile: ```ruby gem 'slowpoke' ``` And run: ```sh rails generate slowpoke:install ``` This creates a `public/503.html` you can customize. ## Development To try out custom error pages in development, temporarily add to `config/environments/development.rb`: ```ruby config.slowpoke.timeout = 1 config.consider_all_requests_local = false ``` And add a `sleep` call to one of your actions: ```ruby sleep(2) ``` The custom error page should appear. ## Production The default timeout is 15 seconds. You can change this in `config/environments/production.rb` with: ```ruby config.slowpoke.timeout = 5 ``` For dynamic timeouts, use: ```ruby config.slowpoke.timeout = lambda do |env| request = Rack::Request.new(env) request.path.start_with?("/admin") ? 15 : 5 end ``` Subscribe to timeouts with: ```ruby ActiveSupport::Notifications.subscribe "timeout.slowpoke" do |name, start, finish, id, payload| # report timeout end ``` To learn more, see the [Rack::Timeout documentation](https://github.com/heroku/rack-timeout). ## Safer Service Timeouts Rack::Timeout can raise an exception at any point in the code, which can leave your app in an [unclean state](https://www.schneems.com/2017/02/21/the-oldest-bug-in-ruby-why-racktimeout-might-hose-your-server/). The safest way to recover from a request timeout is to spawn a new process. This is the default behavior for Slowpoke. For threaded servers like Puma, this means killing all threads when any one of them times out. This can have a significant impact on performance. You can customize this behavior with: ```ruby Slowpoke.on_timeout do |env| next if Rails.env.development? || Rails.env.test? exception = env["action_dispatch.exception"] if exception && exception.backtrace.first.include?("/active_record/") Slowpoke.kill end end ``` Note: To access `env["action_dispatch.exception"]` in development, temporarily add to `config/environments/development.rb`: ```ruby config.consider_all_requests_local = false ``` ## Database Timeouts It’s a good idea to set a [statement timeout](https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts/#statement-timeouts-1) and a [connect timeout](https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts/#activerecord). For Postgres, your `config/database.yml` should include something like: ```yml production: connect_timeout: 3 # sec variables: statement_timeout: 5s ``` ## Upgrading ### 0.3.0 If you set the timeout with: ```ruby Slowpoke.timeout = 5 ``` Remove it and add to `config/environments/production.rb`: ```ruby config.slowpoke.timeout = 5 ``` If you use migration timeouts, check out [this guide](https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts/#statement-timeouts-1) for how to configure them directly in `config/database.yml`. ### 0.1.0 `0.1.0` removes database timeouts, since Rails supports them by default. To restore the previous behavior, use: ```yaml production: variables: statement_timeout: <%= Slowpoke.timeout * 1000 %> ``` ## History View the [changelog](https://github.com/ankane/slowpoke/blob/master/CHANGELOG.md) ## Contributing Everyone is encouraged to help improve this project. Here are a few ways you can help: - [Report bugs](https://github.com/ankane/slowpoke/issues) - Fix bugs and [submit pull requests](https://github.com/ankane/slowpoke/pulls) - Write, clarify, or fix documentation - Suggest or add new features To get started with development: ```sh git clone https://github.com/ankane/slowpoke.git cd slowpoke bundle install ``` slowpoke-0.3.2/slowpoke.gemspec0000644000175000017510000000355414124351331015410 0ustar rmb571rmb571######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: slowpoke 0.3.2 ruby lib Gem::Specification.new do |s| s.name = "slowpoke".freeze s.version = "0.3.2" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Andrew Kane".freeze] s.date = "2019-12-23" s.email = "andrew@chartkick.com".freeze s.files = ["CHANGELOG.md".freeze, "LICENSE.txt".freeze, "README.md".freeze, "lib/generators/slowpoke/install_generator.rb".freeze, "lib/generators/slowpoke/templates/503.html".freeze, "lib/slowpoke.rb".freeze, "lib/slowpoke/middleware.rb".freeze, "lib/slowpoke/railtie.rb".freeze, "lib/slowpoke/timeout.rb".freeze, "lib/slowpoke/version.rb".freeze] s.homepage = "https://github.com/ankane/slowpoke".freeze s.licenses = ["MIT".freeze] s.required_ruby_version = Gem::Requirement.new(">= 2.4".freeze) s.rubygems_version = "3.2.5".freeze s.summary = "Rack::Timeout enhancements for Rails".freeze if s.respond_to? :specification_version then s.specification_version = 4 end if s.respond_to? :add_runtime_dependency then s.add_runtime_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_runtime_dependency(%q.freeze, [">= 0.4.0"]) s.add_runtime_dependency(%q.freeze, [">= 5"]) s.add_development_dependency(%q.freeze, [">= 0"]) else s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0.4.0"]) s.add_dependency(%q.freeze, [">= 5"]) s.add_dependency(%q.freeze, [">= 0"]) end end