slowpoke-0.3.2/ 0000755 0001750 0001751 00000000000 14124351331 012171 5 ustar rmb571 rmb571 slowpoke-0.3.2/lib/ 0000755 0001750 0001751 00000000000 14124351331 012737 5 ustar rmb571 rmb571 slowpoke-0.3.2/lib/slowpoke/ 0000755 0001750 0001751 00000000000 14124351331 014602 5 ustar rmb571 rmb571 slowpoke-0.3.2/lib/slowpoke/timeout.rb 0000644 0001750 0001751 00000000657 14124351331 016625 0 ustar rmb571 rmb571 module 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.rb 0000644 0001750 0001751 00000000050 14124351331 016607 0 ustar rmb571 rmb571 module Slowpoke
VERSION = "0.3.2"
end
slowpoke-0.3.2/lib/slowpoke/railtie.rb 0000644 0001750 0001751 00000001724 14124351331 016564 0 ustar rmb571 rmb571 module 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.rb 0000644 0001750 0001751 00000000610 14124351331 017241 0 ustar rmb571 rmb571 module 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.rb 0000644 0001750 0001751 00000002051 14124351331 015125 0 ustar rmb571 rmb571 # 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/ 0000755 0001750 0001751 00000000000 14124351331 015110 5 ustar rmb571 rmb571 slowpoke-0.3.2/lib/generators/slowpoke/ 0000755 0001750 0001751 00000000000 14124351331 016753 5 ustar rmb571 rmb571 slowpoke-0.3.2/lib/generators/slowpoke/templates/ 0000755 0001750 0001751 00000000000 14124351331 020751 5 ustar rmb571 rmb571 slowpoke-0.3.2/lib/generators/slowpoke/templates/503.html 0000644 0001750 0001751 00000003047 14124351331 022152 0 ustar rmb571 rmb571
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.rb 0000644 0001750 0001751 00000000426 14124351331 023016 0 ustar rmb571 rmb571 require "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.txt 0000644 0001750 0001751 00000002061 14124351331 014013 0 ustar rmb571 rmb571 Copyright (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.md 0000644 0001750 0001751 00000001446 14124351331 014007 0 ustar rmb571 rmb571 ## 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.md 0000644 0001750 0001751 00000007274 14124351331 013462 0 ustar rmb571 rmb571 # 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.gemspec 0000644 0001750 0001751 00000003554 14124351331 015410 0 ustar rmb571 rmb571 #########################################################
# 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