I have a Rails (web) app that I need to add a (redis) pub/sub subscriber too.
Below is my PubsubSubscriber class which I need to kick off then the app starts up.
The redis connection is created in a resque.rb initializer file. I tried PubsubSubscriber.new after the connection, but when I try to start the rails server it hangs at:
=> Booting Thin
=> Rails 3.2.13 application starting in development on http://0.0.0.0:5000
=> Call with -d to detach
=> Ctrl-C to shutdown server
As opposed to when the server starts successfully:
=> Booting Thin
=> Rails 3.2.13 application starting in development on http://0.0.0.0:5000
=> Call with -d to detach
=> Ctrl-C to shutdown server
>> Thin web server (v1.5.1 codename Straight Razor)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:5000, CTRL+C to stop
Any idea why the server hangs when I try to instantiate the PubsubSubscriber class in the initializer? Is there a better place to start this up?
# example modified from https://github.com/redis/redis-rb/blob/master/examples/pubsub.rb
class PubsubSubscriber
def initialize
$redis.psubscribe( :channel_one ) do |on|
on.psubscribe do |event, total|
end
on.pmessage do |pattern, event, message|
# message received, kick off some workers
end
on.punsubscribe do |event, total|
end
end
end
end
One problem that you are having is that EventMachine is not ready yet when you are in the initializer. So wrapping your initialization in EM.next_tick will delay your code until EventMachine is ready:
EM.next_tick { ... EventMachine code here ... }
When I tried this, my server started up all the way... and then blocked when the call to $redis.psubscribe fired.
However, switching to em-hiredis worked:
# config/initializers/redis_pubsub.rb
EM.next_tick do
$emredis = EM::Hiredis.connect(uri)
$emredis.pubsub.subscribe('channelone') do |message|
Rails.logger.debug message
end
end
This is occurring because the standard redis gem is not listening for events through the EventMachine interface - it is just creating a connection to your redis server and then blocking everything else.
In order to take advantage of EM you would need to set up a simple connection, in the form of:
class RedisConnection
# connect to redis
def connect(host, port)
EventMachine.connect host, port, self
end
def post_init
# send subscribe channel_one to redis
end
def receive_data(data)
# channel_one messages will arrive here
end
end
I've got a working example in this gist: https://gist.github.com/wheeyls/5647713
em-hiredis is a redis client that uses this interface.
Related
I am trying to create a health check page for my app. There are 3 different servers for back end, front end and database.
I have created an API to check if services (sidekiq, redis) are running.
Now, I want to check if postgres server is up or not.
For this I have added a method
def database?
ActiveRecord::Base.connection.active?
end
This method returns true when postgres is running. If the Postgres server is stopped, and I try to hit my API I get
PG::ConnectionBad (could not connect to server: Connection refused
Is the server running on host "localhost" (127.0.0.1) and accepting
TCP/IP connections on port 5432?
):
How to rescue this error?
To prevent rails guts to be loaded before you actually check the DB connection, I would suggest to create a simple rack middleware, and put in the very beginning of middlewares stack:
class StatusMiddleware
def initialize(app)
#app = app
end
def call(env)
return #app.call(env) unless status_request?(env)
# Feel free to respond with JS here if needed
if database_alive?
[200, {}, []]
else
[503, {}, []]
end
end
private
def status_request?(env)
# Change the route to whatever you like
env['PATH_INFO'] == '/postgres_status' && env['REQUEST_METHOD'] == 'GET'
end
def database_alive?
::ActiveRecord::Base.connection.verify!
true
rescue StandardError
false
end
end
And in your config/application.rb:
config.middleware.insert_before 0, StatusMiddleware
I didn't do anything like it but here's how I'd do it.
Host a rails app (or sinatra, with no db or a dummy one) to status.myapp.com which is just a simple index page with a bunch of checks - db, redis, sidekiq. I'd make sure is hosted on the same machine as the production one.
db - try to establish a connection to your production db - fails or not
redis - try to see if it's a running process for redis-server
sidekiq - try to see if it's a running process for sidekiq
etc ...
Again, just an idea. Maybe someone did it differently.
I'm building a rails app, but I need to be able to listen for events in redis.
How might I got about starting a single separate process when the rails server starts? I only ever need one instance of it running, and it shouldn't end until the rails server stops.
I tried setting up a rake task and invoking it, and the take task starts fine, but it wouldn't let puma start. There has to be a better way. This is what I have.
lib/tasks/redis_monitor.rb
namespace :redis_monitor do
task monitor: :environment do
$redis.subscribe('channel') do |on|
on.message do |channel, message|
# stuff.
end
end
end
end
config/application.rb
config.after_initialize do
Rails.application.load_tasks
Rake::Task['redis_monitor:monitor'].invoke
end
Thanks!
I am using Redis with my Rails app. I have sidekiq gem installed too. My redis server runs in the same machine in default port.
I created a initializer which initalizes a redis lient.
config/initializers/redis.rb
$redis = Redis.new(:host => 'localhost', :port => 6379)
I have another initalizer that sets the number of accounts currently active in the system.
config/initializers/z_account_list.rb
$redis.set('accounts',Account.count);
In one of my views i am using this piece of code.
<div class="more-text">and <%= "#{$redis.get('accounts')}" %> more...</div>
When i set the value for accounts manually in redis without using the initializer, everything works fine. But when i add the initializer, i get
ActionView::Template::Error (Tried to use a connection from a child process without reconnecting. You need to reconnect to Redis after forking.):
I searched for the error. But most solutions are for resque and has something to do with after_fork. Since i am new to Rails and Redis and since i am not using Resque i am getting a little confused. Please help me out on this.
In forked environments like pushion passenger, we have to reconnect to redis whenever a worker is forked. My biggest confusion was where to put the reconnection statements. In many blogs it was suggested to put it in config/environments.rb. But it didn't work for me.
I added
if defined?(PhusionPassenger)
PhusionPassenger.on_event(:starting_worker_process) do |forked|
if forked
$redis.client.disconnect
$redis = Redis.new(:host => 'localhost', :port => 6379)
Rails.logger.info "Reconnecting to redis"
else
# We're in conservative spawning mode. We don't need to do anything.
end
end
end
to config/initializers/redis.rb
and everything started working fine.
I am using a global variable in a rails application to store a redis client using the redis gem. In a config/initializers/redis.rb, I have
$redis = Redis.new(host: "localhost", port: 6379)
Then in application code, I use $redis to work the data in the Redis store.
I also use puma as the web server in production environment, and capistrano to deploy code. In the deploy process, capistrano restarts puma.
Every time I start or restart the puma web servers, I always get an "Internal Server Error" when I first use $redis to access data in the Redis store. I saw errors like Redis::InheritedError (Tried to use a connection from a child process without reconnecting. You need to reconnect to Redis after forking.)
Searching around with google and stackoverflow led me to think that I needed to reconnect to Redis after puma forks child processes. So, I added in my config/puma.rb:
on_worker_boot do
$redis.ping
end
But I was still getting the "Internal Server Error" caused by Redis::InheritedError (Tried to use a connection from a child process without reconnecting. You need to reconnect to Redis after forking.).
I saw this post http://qiita.com/yaotti/items/18433802bf1720fc0c53. I then tried adding in config/puma.rb:
on_restart do
$redis.quit
end
That did not work.
I tried in config/initializers/redis.rb to $redis.ping right after Redis.new. That did not work either.
I got this error if puma was started with no puma processes running, or restarted when an instance of puma process was running.
Refreshing the page would get me past this error. But I want to get rid of this even on the first attempt to use $redis. I was thinking that I did not use the redis gem or configure its reconnection correctly. Could someone tell me:
Is that the right way to use redis gem in a rails application?
How should the redis connection be reconnected in puma?
puma gem documentation says, "You should place code to close global log files, redis connections, etc in this block so that their file descriptors don't leak into the restarted process. Failure to do so will result in slowly running out of descriptors and eventually obscure crashes as the server is restart many times." It was talking about the on_restart block. But it did not say how that should be done.
I was able to fix the error with a monkeypatch. This changes the behaviour so it just reconnects instead of throwing the Redis::InheritedError
###### MONKEYPATCH redis-rb
# https://github.com/redis/redis-rb/issues/364
# taken from https://github.com/redis/redis-rb/pull/389/files#diff-597c124889a64c18744b52ef9687c572R314
class Redis
class Client
def ensure_connected
tries = 0
begin
if connected?
if Process.pid != #pid
reconnect
end
else
connect
end
tries += 1
yield
rescue ConnectionError
disconnect
if tries < 2 && #reconnect
retry
else
raise
end
rescue Exception
disconnect
raise
end
end
end
end
## MONKEYPATCH end
I'm running a Rails Application with IdentityCache using Puma in clustered mode with workers=4.
It is essential that the reconnects happen in the on_worker_boot callback.
I have to reconnect both the Rails.cache and the IdentityCache to avoid restart errors. Here's what I got working:
puma-config.rb
on_worker_boot do
puts 'On worker boot...'
puts "Reconnecting Rails.cache"
Rails.cache.reconnect
begin
puts "Reconnecting IdentityCache"
IdentityCache.cache.cache_backend.reconnect
rescue Exception => e
puts "Error trying to reconnect identity_cache_store: #{e.message}"
end
end
Then I see the following in my logs, showing me the proof that it all works.
On worker boot...
Reconnecting Rails.cache
Reconnecting IdentityCache
On worker boot...
Reconnecting Rails.cache
Reconnecting IdentityCache
On worker boot...
Reconnecting Rails.cache
Reconnecting IdentityCache
On worker boot...
Reconnecting Rails.cache
Reconnecting IdentityCache
[7109] - Worker 7115 booted, phase: 0
[7109] - Worker 7123 booted, phase: 0
[7109] - Worker 7119 booted, phase: 0
[7109] - Worker 7127 booted, phase: 0
Sure enough, the first request problems that used to be there after server restart are gone. QED.
Upgrade redis-rb to 3.1.0 or above.
The detail https://github.com/redis/redis-rb/pull/414/files#
monkey patch
# https://github.com/redis/redis-rb/pull/414/files#diff-5bc007010e6c2e0aa70b64d6f87985c20986ee1b2882b63a89b52659ee9c91f8
class Redis
class Client
def ensure_connected
tries = 0
begin
if connected?
if Process.pid != #pid
raise InheritedError,
"Tried to use a connection from a child process without reconnecting. " +
"You need to reconnect to Redis after forking."
end
else
connect
end
tries += 1
yield
rescue ConnectionError, InheritedError
disconnect
if tries < 2 && #reconnect
retry
else
raise
end
rescue Exception
disconnect
raise
end
end
end
end
Here's what I did:
Redis.current.client.reconnect
$redis = Redis.current
($redis is my global instance of a redis client)
I've put this into my config/puma.rb file, works for me.
on_restart do
$redis = DiscourseRedis.new
Discourse::Application.config.cache_store.reconnect
end
on_worker_boot do
$redis = DiscourseRedis.new
Discourse::Application.config.cache_store.reconnect
end
I have to use websockets in my rake task and for that I changed my event.rb to
config.synchronize = true
# Uncomment and edit to point to a different redis instance.
# Will not be used unless standalone or synchronization mode
# is enabled.
config.redis_options = {:host => 'localhost', :port => '3000'}
and when I start my rails server I get this error:
! Invalid request
Exiting
/usr/local/rvm/gems/ruby-1.9.3-p194#socialmail/gems/redis-3.0.4/lib/redis/connection/synchrony.rb:115:in `read': Got 'Protocol error, got "H" as reply type byte' as initial reply byte. If you're in a forking environment, such as Unicorn, you need to connect to Redis after forking. (Redis::ProtocolError)
What am I doing wrong?
Thanks
Hey thanks for the question, I personally couldn't get a rake task of mine to post to a websocket channel I had open on my rails server. Your synchronize command helped (along with starting a Redis server locally).
Your problem through - seems like you're pointing to 3000. Is that your rails server or the Redis instance? If you're running it locally, I'd omit that line.