Why does a barebones Rails controller block if the database is down? - ruby-on-rails

I have a simple Rails controller which does not depend on the database.
class PingController < ActionController::Base
def ping
render text: 'The service is up'
end
end
However, when the database goes down, this controller action blocks.
Why does this happen?

I think there could be three culprits.
A fresh Rails application has the following middleware (Source):
use Rack::Sendfile
use ActionDispatch::Static
use Rack::Lock
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x000000029a0838>
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
run Rails.application.routes
Each request to your controller essentially passes through each one of these middlewares in a chain, one after the other.
Three of these are related to the database and thus depend on the database for every request. You'll notice they're all part of ActiveRecord which is a big tip-off the database is involved.
ActiveRecord::Migration::CheckPending
This checks if there are pending migrations in the database before passing the request through. From what I can tell looking at the source code it checks for pending migrations in all environments.
ActiveRecord::ConnectionAdapters::ConnectionManagement
Except in the testing enviornment, this middleware purges active db connections each request. Perhaps the database connectivity in this middleware is blocking your controller action.
ActiveRecord::QueryCache
This could also be blocking database requests. It is active for all environments from what I can tell.

Rails checks for pending migrations (at least in development mode) before a request. I guess this check blocks your db unrelated action when your database goes down.

Related

Rails 5 halt a custom http middleware while reloader is reloading.

In rails 5 development mode, I keep getting this Net::ReadTimeout(Net::ReadTimeout) error in my custom session middleware that does a http request to my local mock custom session engine. Also the error only triggered after I made a code change and rails renders an error page. This is extremely annoying and really slows down the development process since we all have to refresh twice to see the result of code change.
After tracing down the code in those middlewares, It appears that my custom session middleware kick off the http request before the reloader complete reloading.
I wondered if we can halt/stop the rack middleware request from keep going down the rack until the reloading is done.
Rails version: 5.1
Ruby version: 2.4.1
I put the following log messages before and after the reloader is done reloading
application.rb
ActiveSupport::Reloader.to_run do
puts 'Reloading'
end
ActiveSupport::Reloader.to_complete do
puts 'DONE Reloading'
end
custom_session_siddleware.rb
def call(env)
...
puts 'Session Processing'
http = Net::HTTP.new(uri, port)
...
#app.call(env)
end
The out put after I make a code change and refresh
Reloading
Session Processing
DONE Reloading
Here are all my middlewares
use Rack::Sendfile
use ActionDispatch::Static
use ActionDispatch::Executor
use ActiveSupport::Cache::Strategy::LocalCache::Middleware
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use RequestStore::Middleware
use ActionDispatch::RemoteIp
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
use MyCustomSession::CustomSessionMiddleware
If your custom middleware is making a request back to the running Rack application, it's going to be very easy for you to have a bad time. But it can be made to work.
By the time your initial request is inside the Reloader, the actual reload has already occurred -- but that's only the unload.
Following the unload, any application (or engine) classes will need to be autoloaded again the next time they're accessed... and I think that's where you're running into an issue: the inner request needs to load something, but it can't do so while the outer request is active.
To fix this, the outer request needs to inform the system that it's in a safe place for another thread/request to load new code. Specifically, you need to wrap the Net::HTTP request (only -- not the #app.call(env)) with permit_concurrent_loads.

Rack middleware insert_before not working as expected

I'm trying to use the SslEnforcer rack middleware in a Rails 4 project I'm working on. In the documentation of the gem, it is said that :
Cookies should be set before the SslEnforcer processes the headers, but due to the middleware calls chain Rack::SslEnforcer should be inserted before the ActionDispatch::Cookies.
So I've followed the instructions described there, and used this in my application.rb file :
config.middleware.insert_before ActionDispatch::Cookies, Rack::SslEnforcer
However, when I run rake middleware, I still get this order:
$ rake middleware
use Rack::Sendfile
...
...
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
...
...
use Rack::SslEnforcer
...
...
So the Rack::SslEnforcer middleware is not inserted before the ActionDispatch::Cookies middleware as expected. I've also tried to do this:
config.middleware.insert_before 0, Rack::SslEnforcer
which does work as expected, and inserts the Rack::SslEnforcer middleware first in the middleware stack (but I'm not sure this is a good thing to do?).
What am I missing here, do you have any idea why insert_before is not working as expected? Thanks for your help :)

Use Orchestrate.io with a basic Rails Application

I have a basic application running with ruby on rails. I use sqlite3 for development and postgres for production. I recently got access to Orchestrate.io and their student tier.
I would like to know how I could possibly use this with my application. I'm not even sure this is possible. My app is really simple (add-edit-delete). But it would be interesting to have a go at using the tools.
Mostly because I cannot find an efficient and free rails db text search.
You can definitely use Orchestrate as the backend for a Rails application.
A few different options:
The Orchestrate-Ruby client has both a method client & object client. Although the object client is still considered a work-in-progress, the method client provides a solid interface for connecting to the Orchestrate API.
Use an http library (such as rest-client or faraday) to make calls to the Orchestrate API.
There's also the Orchestrate-Rails gem. Your mileage may vary with this (last updated in April).
To use Orchestrate as the only backend (no postgres/mysql), I've found the best approach is to disable ActiveRecord:
When creating a new application, you can use -O to skip ActiveRecord:
rails new my_app -O
For existing applications:
1. Remove database adapter gems from your Gemfile (pg, mysql2, sqlite3, etc.)
2. Change your config/application.rb
Remove require 'rails/all line and require frameworks you want to use, for example:
require "action_controller/railtie"
require "action_mailer/railtie"
require "sprockets/railtie"
require "rails/test_unit/railtie"
3. Delete your database.yml file, db/schema.rb and migrations (if any)
4. Delete migration check in test/test_helper.rb
5. Delete any ActiveRecord configuration from your config/environments files (this is what is causing your error)
If you run into problems/errors, stack trace should give you sufficient information on what you need to change. You might for example have some ActiveRecord configuration in your initializers.
With the Orchestrate-Ruby client, you can define a model like so:
class Foo < Orchestrate::Collection
...
end
I'm still undecided on what the best approach for defining the Application object is, but one way I've done it is by making a global object with an initializer:
# /config/initializers/orchestrate.rb
$App = Orchestrate::Application.new(ENV['ORCHESTRATE_KEY']) # using dotenv gem
$App = Orchestrate::Application.new('your_key_here') # without dotenv
From here, you can then access the $App object in your controllers (to initialize the collection object):
class FoosController < ApplicationController
def index
#foos = Foo.new($App, "foos").each
end
end
Leveraging model concerns might be a better way to share the Application object/API key between models. Or you could roll your own classes on top of the method client.
Hope this helps!
I'll be looking into how to accomplish this in a more 'Railsy' way.
EDIT: also, textacular is a ruby gem which provides full-text search and indexing for postgres

Monkey-patching database adapter in Rails app

I'm using PosgreSQL adapter to connect to Vertica database which is mostly compatible with PostgreSQL but doesn't support options like client_min_messages (which are still passed to PGconn.connect despite not being present in database.yml). I've made a quick and dirty monkey patch for ActiveRecord::ConnectionAdapters::PostgreSQLAdapter but the problem is that I guess everything in AR in lazy loaded and the original file is read after my patch.
If I add require 'active_record/connection_adapters/postgresql_adapter' at the top of the monkey patch then ActiveRecord tries to establish connection and fails. Is it possible to alter this behavior to make the monkey patch work or should I just write a full-fledged connection adapter?
You can hook your code to railties initialization. Including sample from my gem multi_config:
module <YourModule>
# Railtie subclass for the gem/plugin
class Railtie < Rails::Railtie
# Railtie initializer method
initializer '<your_plugin>.active_record' do
# When active_record is loaded, only then run this.
ActiveSupport.on_load :active_record do
# Hook your code here. For .e.g.
ActiveRecord::Base.send(:include, <YourPluginModule>)
end
end
end
end

More control over middleware ordering via Railtie?

There are gems/libraries that would benefit from better control over where they inject their middleware.
In particular it would be nice if exception-handling middleware were at the top of the stack.
Is there any way to do this, or can it only be done by editing config.ru?
Not sure if this is want you need: http://api.rubyonrails.org/classes/Rails/Configuration/MiddlewareStackProxy.html
It is possible do the following:
config.middleware.insert_before Rack::Head, Magical::Unicorns
config.middleware.insert_after Rack::Head, Magical::Unicorns
config.middleware.swap ActionDispatch::Flash, Magical::Unicorns
config.middleware.delete ActionDispatch::Flash
You can just define your Railtie and manipulate the middlewares around.
There is currently no intrinsic way to do this other than manually ordering the middleware stack. I've seen a hack where you could monkey patch Rack::Builder to provide you with some push/pop capabilities.
https://github.com/joshbuddy/rack-capabilities

Resources