Skip to content

Commit 375f75a

Browse files
committed
Initial commit of ReplicaSetConnection PYTHON-196
This is alpha level code and should not be used in production. Issues that still need attention: - Auto-authenticate existing sockets in all pools. - A host of failover issues. - A lot of error handling. - Unit tests... - More documentation and examples. - Possibly a background thread to maintain the set.
1 parent b9d732a commit 375f75a

File tree

6 files changed

+981
-4
lines changed

6 files changed

+981
-4
lines changed

doc/api/pymongo/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Sub-modules:
2323
errors
2424
master_slave_connection
2525
message
26+
pool
27+
replica_set_connection
2628
son_manipulator
2729
cursor_manager
2830

doc/api/pymongo/pool.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
:mod:`pool` -- Pool module for use with a MongoDB connection.
2+
==============================================================
3+
4+
.. automodule:: pymongo.pool
5+
:synopsis: Pool module for use with a MongoDB connection.
6+
:members:
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
:mod:`replica_set_connection` -- Tools for connecting to a MongoDB replica set
2+
==============================================================================
3+
4+
.. automodule:: pymongo.replica_set_connection
5+
:synopsis: Tools for connecting to a MongoDB replica set
6+
7+
.. autoclass:: pymongo.replica_set_connection.ReplicaSetConnection([hosts_or_uri[, max_pool_size=10[, document_class=dict[, tz_aware=False[, **kwargs]]]]])
8+
9+
.. automethod:: disconnect
10+
.. automethod:: close
11+
12+
.. describe:: c[db_name] || c.db_name
13+
14+
Get the `db_name` :class:`~pymongo.database.Database` on :class:`ReplicaSetConnection` `c`.
15+
16+
Raises :class:`~pymongo.errors.InvalidName` if an invalid database name is used.
17+
18+
.. autoattribute:: seeds
19+
.. autoattribute:: hosts
20+
.. autoattribute:: arbiters
21+
.. autoattribute:: read_preference
22+
.. autoattribute:: max_pool_size
23+
.. autoattribute:: document_class
24+
.. autoattribute:: tz_aware
25+
.. autoattribute:: slave_okay
26+
.. autoattribute:: safe
27+
.. automethod:: get_lasterror_options
28+
.. automethod:: set_lasterror_options
29+
.. automethod:: unset_lasterror_options
30+
.. automethod:: database_names
31+
.. automethod:: drop_database
32+
.. automethod:: copy_database(from_name, to_name[, from_host=None[, username=None[, password=None]]])
33+
.. automethod:: close_cursor

pymongo/__init__.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
"""Python driver for MongoDB."""
1616

17-
from pymongo.connection import Connection as PyMongo_Connection
1817

1918
ASCENDING = 1
2019
"""Ascending sort order."""
@@ -38,6 +37,13 @@
3837
ALL = 2
3938
"""Profile all operations."""
4039

40+
PRIMARY = 1
41+
"""Send all reads to the primary in a ReplicaSetConnction."""
42+
SECONDARY = 2
43+
"""Send reads to all active members of a ReplicaSetConnection."""
44+
SECONDARY_ONLY = 3
45+
"""Send all reads to secondaries in a ReplicaSetConnection."""
46+
4147
version_tuple = (2, 0, 1, '+')
4248

4349
def get_version_string():
@@ -48,9 +54,8 @@ def get_version_string():
4854
version = get_version_string()
4955
"""Current version of PyMongo."""
5056

51-
Connection = PyMongo_Connection
52-
"""Alias for :class:`pymongo.connection.Connection`."""
53-
57+
from pymongo.connection import Connection
58+
from pymongo.replica_set_connection import ReplicaSetConnection
5459

5560
def has_c():
5661
"""Is the C extension installed?

pymongo/pool.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Copyright 2009-2011 10gen, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you
4+
# may not use this file except in compliance with the License. You
5+
# may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12+
# implied. See the License for the specific language governing
13+
# permissions and limitations under the License.
14+
15+
import os
16+
import socket
17+
import threading
18+
19+
20+
class Pool(threading.local):
21+
"""A simple connection pool.
22+
23+
Uses thread-local socket per thread. By calling return_socket() a
24+
thread can return a socket to the pool.
25+
"""
26+
27+
# Non thread-locals
28+
__slots__ = ["pid", "host", "max_size",
29+
"net_timeout", "conn_timeout", "sockets"]
30+
31+
# thread-local default
32+
sock = None
33+
34+
def __init__(self, host, max_size, net_timeout, conn_timeout):
35+
self.pid = os.getpid()
36+
self.host = host
37+
self.max_size = max_size
38+
self.net_timeout = net_timeout
39+
self.conn_timeout = conn_timeout
40+
if not hasattr(self, "sockets"):
41+
self.sockets = []
42+
43+
def connect(self):
44+
"""Connect to Mongo and return a new (connected) socket.
45+
"""
46+
try:
47+
# Prefer IPv4. If there is demand for an option
48+
# to specify one or the other we can add it later.
49+
s = socket.socket(socket.AF_INET)
50+
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
51+
s.settimeout(self.conn_timeout or 20.0)
52+
s.connect(self.host)
53+
s.settimeout(self.net_timeout)
54+
return s
55+
except socket.gaierror:
56+
# If that fails try IPv6
57+
s = socket.socket(socket.AF_INET6)
58+
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
59+
s.settimeout(self.conn_timeout or 20.0)
60+
s.connect(self.host)
61+
s.settimeout(self.net_timeout)
62+
return s
63+
64+
def get_socket(self):
65+
"""Get a socket from the pool. Returns a new socket if the
66+
pool is empty.
67+
"""
68+
# We use the pid here to avoid issues with fork / multiprocessing.
69+
# See test.test_connection:TestConnection.test_fork for an example of
70+
# what could go wrong otherwise
71+
pid = os.getpid()
72+
73+
if pid != self.pid:
74+
self.sock = None
75+
self.sockets = []
76+
self.pid = pid
77+
78+
if self.sock is not None and self.sock[0] == pid:
79+
return (self.sock[1], True)
80+
81+
try:
82+
self.sock = (pid, self.sockets.pop())
83+
return (self.sock[1], True)
84+
except IndexError:
85+
self.sock = (pid, self.connect())
86+
return (self.sock[1], False)
87+
88+
def return_socket(self):
89+
"""Return the socket currently in use to the pool. If the
90+
pool is full the socket will be discarded.
91+
"""
92+
if self.sock is not None and self.sock[0] == os.getpid():
93+
# There's a race condition here, but we deliberately
94+
# ignore it. It means that if the pool_size is 10 we
95+
# might actually keep slightly more than that.
96+
if len(self.sockets) < self.max_size:
97+
self.sockets.append(self.sock[1])
98+
else:
99+
self.sock[1].close()
100+
self.sock = None

0 commit comments

Comments
 (0)