Skip to content

Commit aef7114

Browse files
MONGOID-5758: Add Mongoid.reconnect_clients and improve forking webserver documentation (#5808)
* This PR does the following: - Add Mongoid.reconnect_clients (analogous to Mongoid.disconnect_clients). The reason for adding this is to simply web server hooks (see added docs.) - Corrects the @return in the docs for disconnect_clients. Also added specs for the existing behavior. - Updates documentation related to web server forking. * Fix method name * More terse syntax * Preserve old return type * Update configuration.txt * Update configuration.txt
1 parent ca3e02e commit aef7114

File tree

5 files changed

+141
-64
lines changed

5 files changed

+141
-64
lines changed

docs/reference/configuration.txt

Lines changed: 48 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -863,58 +863,57 @@ as the following example shows:
863863
Usage with Forking Servers
864864
==========================
865865

866-
When using Mongoid with a forking web server such as Puma, Unicorn or
867-
Passenger, it is recommended to not perform any operations on Mongoid models
868-
in the parent process prior to the fork.
869-
870-
When a process forks, Ruby threads are not transferred to the child processes
871-
and the Ruby driver Client objects lose their background monitoring. The
872-
application will typically seem to work just fine until the deployment
873-
state changes (for example due to network errors, a maintenance event) at
874-
which point the application is likely to start getting ``NoServerAvailable``
875-
exception when performing MongoDB operations.
876-
877-
If the parent process needs to perform operations on the MongoDB database,
878-
reset all clients in the workers after they forked. How to do so depends
879-
on the web server being used.
880-
881-
If the parent process does not need to perform operations on the MongoDB
882-
database after child processes are forked, close the clients in the parent
883-
prior to forking children. If the parent process performs operations on a Mongo
884-
client and does not close it, the parent process will continue consuming a
885-
connection slot in the cluster and will continue monitoring the cluster for
886-
as long as the parent remains alive.
887-
888-
.. note::
889-
890-
The close/reconnect pattern described here should be used with Ruby driver
891-
version 2.6.2 or higher. Previous driver versions did not recreate
892-
monitoring threads when reconnecting.
866+
When using Mongoid with a forking web server such as Puma, or any application
867+
that otherwise forks to spawn child processes, special considerations apply.
868+
869+
If possible, we recommend to not perform any MongoDB operations in the parent
870+
process prior to forking, which will avoid any forking-related pitfalls.
871+
872+
A detailed technical explanation of how the Mongo Ruby Driver handles forking
873+
is given in the `driver's "Usage with Forking Servers" documentation
874+
<https://www.mongodb.com/docs/ruby-driver/current/reference/create-client/#usage-with-forking-servers>`.
875+
In a nutshell, to avoid various connection errors such as ``Mongo::Error::SocketError``
876+
and ``Mongo::Error::NoServerAvailable``, you must do the following:
877+
878+
1. Disconnect MongoDB clients in the parent Ruby process immediately *before*
879+
forking using ``Mongoid.disconnect_clients``. This ensures the parent and child
880+
process do not accidentally reuse the same sockets and have I/O conflicts.
881+
Note that ``Mongoid.disconnect_clients`` does not disrupt any in-flight
882+
MongoDB operations, and will automatically reconnect when you perform new
883+
operations.
884+
2. Reconnect your MongoDB clients in the child Ruby process immediately *after*
885+
forking using ``Mongoid.reconnect_clients``. This is required to respawn
886+
the driver's monitoring threads in the child process.
887+
888+
Most web servers provide hooks that can be used by applications to
889+
perform actions when the worker processes are forked. The following
890+
are configuration examples for several common Ruby web servers.
893891

894892
Puma
895893
----
896894

897895
Use the ``on_worker_boot`` hook to reconnect clients in the workers and
898-
the ``before_fork`` hook to close clients in the parent process
899-
(`Puma documentation <https://puma.io/puma/>`_):
896+
the ``before_fork`` and ``on_refork`` hooks to close clients in the
897+
parent process (`Puma documentation <https://puma.io/puma/#clustered-mode>`_).
900898

901899
.. code-block:: ruby
902900

903-
on_worker_boot do
904-
if defined?(Mongoid)
905-
Mongoid::Clients.clients.each do |name, client|
906-
client.close
907-
client.reconnect
908-
end
909-
else
910-
raise "Mongoid is not loaded. You may have forgotten to enable app preloading."
911-
end
912-
end
901+
# config/puma.rb
913902

903+
# Runs in the Puma master process before it forks a child worker.
914904
before_fork do
915-
if defined?(Mongoid)
916-
Mongoid.disconnect_clients
917-
end
905+
Mongoid.disconnect_clients
906+
end
907+
908+
# Required when using Puma's fork_worker option. Runs in the
909+
# child worker 0 process before it forks grandchild workers.
910+
on_refork do
911+
Mongoid.disconnect_clients
912+
end
913+
914+
# Runs in each Puma child process after it forks from its parent.
915+
on_worker_boot do
916+
Mongoid.reconnect_clients
918917
end
919918

920919
Unicorn
@@ -926,21 +925,14 @@ the ``before_fork`` hook to close clients in the parent process
926925

927926
.. code-block:: ruby
928927

929-
after_fork do |server, worker|
930-
if defined?(Mongoid)
931-
Mongoid::Clients.clients.each do |name, client|
932-
client.close
933-
client.reconnect
934-
end
935-
else
936-
raise "Mongoid is not loaded. You may have forgotten to enable app preloading."
937-
end
928+
# config/unicorn.rb
929+
930+
before_fork do |_server, _worker|
931+
Mongoid.disconnect_clients
938932
end
939933

940-
before_fork do |server, worker|
941-
if defined?(Mongoid)
942-
Mongoid.disconnect_clients
943-
end
934+
after_fork do |_server, _worker|
935+
Mongoid.reconnect_clients
944936
end
945937

946938
Passenger
@@ -956,12 +948,7 @@ before the workers are forked.
956948

957949
if defined?(PhusionPassenger)
958950
PhusionPassenger.on_event(:starting_worker_process) do |forked|
959-
if forked
960-
Mongoid::Clients.clients.each do |name, client|
961-
client.close
962-
client.reconnect
963-
end
964-
end
951+
Mongoid.reconnect_clients if forked
965952
end
966953
end
967954

lib/mongoid.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,16 @@ def disconnect_clients
100100
Clients.disconnect
101101
end
102102

103+
# Reconnect all active clients.
104+
#
105+
# @example Reconnect all active clients.
106+
# Mongoid.reconnect_clients
107+
#
108+
# @return [ true ] True.
109+
def reconnect_clients
110+
Clients.reconnect
111+
end
112+
103113
# Convenience method for getting a named client.
104114
#
105115
# @example Get a named client.

lib/mongoid/clients.rb

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,19 @@ def default
4747
#
4848
# @return [ true ] True.
4949
def disconnect
50-
clients.values.each do |client|
51-
client.close
52-
end
50+
clients.each_value(&:close)
51+
true
52+
end
53+
54+
# Reconnect all active clients.
55+
#
56+
# @example Reconnect all active clients.
57+
# Mongoid::Clients.reconnect
58+
#
59+
# @return [ true ] True.
60+
def reconnect
61+
clients.each_value(&:reconnect)
62+
true
5363
end
5464

5565
# Get a stored client with the provided name. If no client exists

spec/mongoid/clients_spec.rb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,4 +1216,48 @@ class StoreChild2 < StoreParent
12161216
end
12171217
end
12181218
end
1219+
1220+
context "#disconnect" do
1221+
1222+
let(:clients) do
1223+
Mongoid::Clients.clients.values
1224+
end
1225+
1226+
before do
1227+
Band.all.entries
1228+
end
1229+
1230+
it "disconnects from all active clients" do
1231+
clients.each do |client|
1232+
expect(client).to receive(:close).and_call_original
1233+
end
1234+
Mongoid::Clients.disconnect
1235+
end
1236+
1237+
it "returns true" do
1238+
expect(Mongoid::Clients.disconnect).to eq(true)
1239+
end
1240+
end
1241+
1242+
context "#reconnect" do
1243+
1244+
let(:clients) do
1245+
Mongoid::Clients.clients.values
1246+
end
1247+
1248+
before do
1249+
Band.all.entries
1250+
end
1251+
1252+
it "reconnects all active clients" do
1253+
clients.each do |client|
1254+
expect(client).to receive(:reconnect).and_call_original
1255+
end
1256+
Mongoid::Clients.reconnect
1257+
end
1258+
1259+
it "returns true" do
1260+
expect(Mongoid::Clients.reconnect).to eq(true)
1261+
end
1262+
end
12191263
end

spec/mongoid_spec.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,32 @@
8181
end
8282
Mongoid.disconnect_clients
8383
end
84+
85+
it "returns true" do
86+
expect(Mongoid.disconnect_clients).to eq(true)
87+
end
88+
end
89+
90+
describe ".reconnect_clients" do
91+
92+
let(:clients) do
93+
Mongoid::Clients.clients.values
94+
end
95+
96+
before do
97+
Band.all.entries
98+
end
99+
100+
it "reconnects all active clients" do
101+
clients.each do |client|
102+
expect(client).to receive(:reconnect).and_call_original
103+
end
104+
Mongoid.reconnect_clients
105+
end
106+
107+
it "returns true" do
108+
expect(Mongoid.reconnect_clients).to eq(true)
109+
end
84110
end
85111

86112
describe ".client" do

0 commit comments

Comments
 (0)