Commit f2f194a8 authored by Guido van Rossum's avatar Guido van Rossum

First part of changes to fall back to a read-only connection when a

read-write connection is not available.  This refactors
ClientStorage.notifyConnected() into two functions, testConnection()
and notifyConnected().  testConnection creates the RPC stub and calls
the register() method; it returns the stub plus a flag indicating
whether this connection was preferred or sub-optimal.  If the
register() method raises ReadOnlyError, and the new option
read_only_fallback was true on the ClientStorage constructor, it is
retried with its read_only argument set to 1, and the stub is returned
with the sub-optimal flag.  notifyConnected() now receives the stub
returned by testConnection(), and starts the verification as before.

XXX The read_only_fallback  feature is not yet tested.

XXX More work is needed; when a suboptimal connection is used, the
ConnectThread must stay around trying to get a preferred connection,
and then it must switch connections on the ClientStorage (how???).
parent c39a56ea
......@@ -13,7 +13,7 @@
##############################################################################
"""Network ZODB storage client
$Id: ClientStorage.py,v 1.57 2002/09/11 17:36:39 jeremy Exp $
$Id: ClientStorage.py,v 1.58 2002/09/12 04:30:19 gvanrossum Exp $
"""
# XXX TO DO
......@@ -74,12 +74,13 @@ class ClientStorage:
def __init__(self, addr, storage='1', cache_size=20000000,
name='', client=None, var=None,
min_disconnect_poll=5, max_disconnect_poll=300,
wait=0, read_only=0):
wait=0, read_only=0, read_only_fallback=0):
self._addr = addr # For tests
self._server = disconnected_stub
self._is_read_only = read_only
self._storage = storage
self._read_only_fallback = read_only_fallback
self._info = {'length': 0, 'size': 0, 'name': 'ZEO Client',
'supportsUndo':0, 'supportsVersions': 0,
......@@ -175,21 +176,41 @@ class ClientStorage:
"""Handle any pending invalidation messages."""
self._server._update()
def notifyConnected(self, conn):
log2(INFO, "Connected to storage via %s" % repr(conn))
# check the protocol version here?
def testConnection(self, conn):
"""Return a pair (stub, preferred).
Where:
- stub is an RPC stub
- preferred is: 1 if the connection is an optimal match,
0 if it is a suboptimal but acceptable match
It can also raise DisconnectedError or ReadOnlyError.
This is called by ConnectionManager to decide which connection
to use in case there are multiple, and some are read-only and
others are read-write.
"""
log2(INFO, "Testing connection %r" % conn)
# XXX Check the protocol version here?
stub = ServerStub.StorageServer(conn)
try:
stub.register(str(self._storage), self._is_read_only)
return (stub, 1)
except POSException.ReadOnlyError:
if not self._read_only_fallback:
raise
stub.register(str(self._storage), read_only=1)
return (stub, 0)
def notifyConnected(self, stub):
"""Start using the given RPC stub.
This is called by ConnectionManager after it has decided which
connection should be used.
"""
self._oids = []
stub.register(str(self._storage), self._is_read_only)
self._info.update(stub.get_info())
self.verify_cache(stub)
# Don't make the server available to clients until after
# validating the cache
# XXX The stub should be saved here and set in end() below.
self._server = stub
......
......@@ -215,6 +215,7 @@ class ConnectThread(threading.Thread):
self.tmax = tmax
self.stopped = 0
self.one_attempt = threading.Event()
self.fallback = None
# A ConnectThread keeps track of whether it has finished a
# call to attempt_connects(). This allows the
# ConnectionManager to make an attempt to connect right away,
......@@ -293,7 +294,21 @@ class ConnectThread(threading.Thread):
if ok:
del self.sockets[s] # don't close the newly connected socket
self.close_sockets()
return ok
return 1
if self.fallback:
(c, stub) = self.fallback
self.fallback = None
try:
self.client.notifyConnected(stub)
except:
log("error in notifyConnected (%r)" % addr,
level=zLOG.ERROR, error=sys.exc_info())
c.close()
return 0
else:
self.mgr.connect_done(c)
return 1
return 0
def try_connect(self, s):
"""Call s.connect_ex(addr); return true iff connection succeeds.
......@@ -364,13 +379,25 @@ class ConnectThread(threading.Thread):
# okay.
c = ManagedConnection(s, addr, self.client, self.mgr)
try:
self.client.notifyConnected(c)
(stub, preferred) = self.client.testConnection(c)
except:
log("error connecting to server: %s" % str(addr),
log("error in testConnection (%r)" % addr,
level=zLOG.ERROR, error=sys.exc_info())
c.close()
# Closing the ZRPC connection will eventually close the
# socket, somewhere in asyncore.
return 0
self.mgr.connect_done(c)
return 1
if preferred:
try:
self.client.notifyConnected(stub)
except:
log("error in notifyConnected (%r)" % addr,
level=zLOG.ERROR, error=sys.exc_info())
c.close()
return 0
else:
self.mgr.connect_done(c)
return 1
if self.fallback is None:
self.fallback = (c, stub)
return 0
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment