I'm running into a strange problem with a feature in my Rails 4 + Devise 3.2 application which allows users to change their password via an AJAX POST to the following action, derived from the Devise wiki Allow users to edit their password. It seems that after the user changes their password and after one or more requests later, they are forcible logged out, and will continue to get forced logged out after signing back in.
# POST /update_my_password
def update_my_password
@user = User.find(current_user.id)
authorize! :update, @user ## CanCan check here as well
if @user.valid_password?(params[:old_password])
@user.password = params[:new_password]
@user.password_confirmation = params[:new_password_conf]
if @user.save
sign_in @user, :bypass => true
head :no_content
return
end
else
render :json => { "error_code" => "Incorrect password" }, :status => 401
return
end
render :json => { :errors => @user.errors }, :status => 422
end
This action actually works fine in development, but it fails in production when I'm running multi-threaded, multi-worker Puma instances. What is appearing to happen is that the user will remain logged in until one of their requests hits a different thread, and then they are logged out as Unauthorized
with a 401 response status. The problem does not occur if I run Puma with a single thread and a single worker. The only way I can seem to allow the user the ability to stay logged in again with multiple threads is to restart the server (which is not a solution). This is rather strange, because I thought the session storage configuration I have would have handled it correctly. My config/initializers/session_store.rb
file contains the following:
MyApp::Application.config.session_store(ActionDispatch::Session::CacheStore,
:expire_after => 3.days)
My production.rb
config contains:
config.cache_store = :dalli_store, ENV["MEMCACHE_SERVERS"],
{
:pool_size => (ENV['MEMCACHE_POOL_SIZE'] || 1),
:compress => true,
:socket_timeout => 0.75,
:socket_max_failures => 3,
:socket_failure_delay => 0.1,
:down_retry_delay => 2.seconds,
:keepalive => true,
:failover => true
}
I am booting up puma via bundle exec puma -p $PORT -C ./config/puma.rb
. My puma.rb
contains:
threads ENV['PUMA_MIN_THREADS'] || 8, ENV['PUMA_MAX_THREADS'] || 16
workers ENV['PUMA_WORKERS'] || 2
preload_app!
on_worker_boot do
ActiveSupport.on_load(:active_record) do
config = Rails.application.config.database_configuration[Rails.env]
config['reaping_frequency'] = ENV['DB_REAP_FREQ'] || 10 # seconds
config['pool'] = ENV['DB_POOL'] || 16
ActiveRecord::Base.establish_connection(config)
end
end
So... what could be going wrong here? How can I update the session across all threads/workers when the password has changed, without restarting the server?