Commit a9330b7e authored by Jim Fulton's avatar Jim Fulton Committed by GitHub

Merge pull request #44 from zopefoundation/simplify-server-commit-lock-management

Simplify server commit lock management
parents 815f39d1 3d02129b
...@@ -47,7 +47,7 @@ from ZODB.loglevels import BLATHER ...@@ -47,7 +47,7 @@ from ZODB.loglevels import BLATHER
from ZODB.POSException import StorageError, StorageTransactionError from ZODB.POSException import StorageError, StorageTransactionError
from ZODB.POSException import TransactionError, ReadOnlyError, ConflictError from ZODB.POSException import TransactionError, ReadOnlyError, ConflictError
from ZODB.serialize import referencesf from ZODB.serialize import referencesf
from ZODB.utils import oid_repr, p64, u64, z64 from ZODB.utils import oid_repr, p64, u64, z64, Lock, RLock
from .asyncio.server import Acceptor from .asyncio.server import Acceptor
...@@ -98,6 +98,7 @@ class ZEOStorage: ...@@ -98,6 +98,7 @@ class ZEOStorage:
def notify_connected(self, conn): def notify_connected(self, conn):
self.connection = conn self.connection = conn
self.call_soon_threadsafe = conn.call_soon_threadsafe
self.connected = True self.connected = True
assert conn.protocol_version is not None assert conn.protocol_version is not None
self.log_label = _addr_label(conn.addr) self.log_label = _addr_label(conn.addr)
...@@ -215,6 +216,7 @@ class ZEOStorage: ...@@ -215,6 +216,7 @@ class ZEOStorage:
self.storage = storage self.storage = storage
self.setup_delegation() self.setup_delegation()
self.stats = self.server.register_connection(storage_id, self) self.stats = self.server.register_connection(storage_id, self)
self.lock_manager = self.server.lock_managers[storage_id]
def get_info(self): def get_info(self):
storage = self.storage storage = self.storage
...@@ -353,12 +355,8 @@ class ZEOStorage: ...@@ -353,12 +355,8 @@ class ZEOStorage:
def _clear_transaction(self): def _clear_transaction(self):
# Common code at end of tpc_finish() and tpc_abort() # Common code at end of tpc_finish() and tpc_abort()
if self.locked: self.lock_manager.release(self)
self.server.unlock_storage(self) self.transaction = None
self.locked = 0
if self.transaction is not None:
self.server.stop_waiting(self)
self.transaction = None
self.stats.active_txns -= 1 self.stats.active_txns -= 1
if self.txnlog is not None: if self.txnlog is not None:
self.txnlog.close() self.txnlog.close()
...@@ -369,98 +367,69 @@ class ZEOStorage: ...@@ -369,98 +367,69 @@ class ZEOStorage:
def vote(self, tid): def vote(self, tid):
self._check_tid(tid, exc=StorageTransactionError) self._check_tid(tid, exc=StorageTransactionError)
if self.locked or self.server.already_waiting(self): return self.lock_manager.lock(self, self._vote)
raise StorageTransactionError(
'Already voting (%s)' % (self.locked and 'locked' or 'waiting') def _vote(self, delay=None):
) # Called from client thread
return self._try_to_vote()
def _try_to_vote(self, delay=None):
if not self.connected: if not self.connected:
return # We're disconnected return # We're disconnected
if delay is not None and delay.sent: try:
# as a consequence of the unlocking strategy, _try_to_vote self.log(
# may be called multiple times for delayed "Preparing to commit transaction: %d objects, %d bytes"
# transactions. The first call will mark the delay as % (self.txnlog.stores, self.txnlog.size()),
# sent. We should skip if the delay was already sent. level=BLATHER)
return
if (self.tid is not None) or (self.status != ' '):
self.storage.tpc_begin(self.transaction,
self.tid, self.status)
else:
self.storage.tpc_begin(self.transaction)
self.locked, delay = self.server.lock_storage(self, delay) for op, args in self.txnlog:
if self.locked: getattr(self, op)(*args)
result = None
try: # Blob support
self.log( while self.blob_log:
"Preparing to commit transaction: %d objects, %d bytes" oid, oldserial, data, blobfilename = self.blob_log.pop()
% (self.txnlog.stores, self.txnlog.size()), self._store(oid, oldserial, data, blobfilename)
level=BLATHER)
if (self.tid is not None) or (self.status != ' '):
self.storage.tpc_begin(self.transaction,
self.tid, self.status)
else:
self.storage.tpc_begin(self.transaction)
for op, args in self.txnlog:
getattr(self, op)(*args)
# Blob support
while self.blob_log:
oid, oldserial, data, blobfilename = self.blob_log.pop()
self._store(oid, oldserial, data, blobfilename)
if not self.conflicts:
try:
serials = self.storage.tpc_vote(self.transaction)
except ConflictError as err:
if (self.client_conflict_resolution and
err.oid and err.serials and err.data
):
self.conflicts[err.oid] = dict(
oid=err.oid, serials=err.serials, data=err.data)
else:
raise
else:
if serials:
self.serials.extend(serials)
result = self.serials
if self.conflicts:
result = list(self.conflicts.values())
self.storage.tpc_abort(self.transaction)
self.server.unlock_storage(self)
self.locked = False
self.server.stop_waiting(self)
except Exception as err:
self.storage.tpc_abort(self.transaction)
self._clear_transaction()
if isinstance(err, ConflictError):
self.stats.conflicts += 1
self.log("conflict error %s" % err, BLATHER)
if not isinstance(err, TransactionError):
logger.exception("While voting")
if delay is not None: if not self.conflicts:
delay.error(sys.exc_info()) try:
serials = self.storage.tpc_vote(self.transaction)
except ConflictError as err:
if (self.client_conflict_resolution and
err.oid and err.serials and err.data
):
self.conflicts[err.oid] = dict(
oid=err.oid, serials=err.serials, data=err.data)
else:
raise
else: else:
raise if serials:
self.serials.extend(serials)
if self.conflicts:
self.storage.tpc_abort(self.transaction)
return list(self.conflicts.values())
else: else:
if delay is not None: self.locked = True # signal to lock manager to hold lock
delay.reply(result) return self.serials
else:
return result
else: except Exception as err:
return delay self.storage.tpc_abort(self.transaction)
self._clear_transaction()
def _unlock_callback(self, delay): if isinstance(err, ConflictError):
if self.connected: self.stats.conflicts += 1
self.connection.call_soon_threadsafe(self._try_to_vote, delay) self.log("conflict error %s" % err, BLATHER)
else:
self.server.stop_waiting(self) if not isinstance(err, TransactionError):
logger.exception("While voting")
raise
# The public methods of the ZEO client API do not do the real work. # The public methods of the ZEO client API do not do the real work.
# They defer work until after the storage lock has been acquired. # They defer work until after the storage lock has been acquired.
...@@ -741,19 +710,22 @@ class StorageServer: ...@@ -741,19 +710,22 @@ class StorageServer:
(self.__class__.__name__, read_only and "RO" or "RW", msg)) (self.__class__.__name__, read_only and "RO" or "RW", msg))
self._lock = threading.Lock() self._lock = Lock()
self._commit_locks = {}
self._waiting = dict((name, []) for name in storages)
self.ssl = ssl # For dev convenience self.ssl = ssl # For dev convenience
self.read_only = read_only self.read_only = read_only
self.database = None self.database = None
# A list, by server, of at most invalidation_queue_size invalidations. # A list, by server, of at most invalidation_queue_size invalidations.
# The list is kept in sorted order with the most recent # The list is kept in sorted order with the most recent
# invalidation at the front. The list never has more than # invalidation at the front. The list never has more than
# self.invq_bound elements. # self.invq_bound elements.
self.invq_bound = invalidation_queue_size self.invq_bound = invalidation_queue_size
self.invq = {} self.invq = {}
self.zeo_storages_by_storage_id = {} # {storage_id -> [ZEOStorage]}
self.lock_managers = {} # {storage_id -> LockManager}
self.stats = {} # {storage_id -> StorageStats}
for name, storage in storages.items(): for name, storage in storages.items():
self._setup_invq(name, storage) self._setup_invq(name, storage)
storage.registerDB(StorageServerDB(self, name)) storage.registerDB(StorageServerDB(self, name))
...@@ -761,8 +733,19 @@ class StorageServer: ...@@ -761,8 +733,19 @@ class StorageServer:
# XXX this may go away later, when storages grow # XXX this may go away later, when storages grow
# configuration for this. # configuration for this.
storage.tryToResolveConflict = never_resolve_conflict storage.tryToResolveConflict = never_resolve_conflict
self.zeo_storages_by_storage_id[name] = []
self.stats[name] = stats = StorageStats(
self.zeo_storages_by_storage_id[name])
if transaction_timeout is None:
# An object with no-op methods
timeout = StubTimeoutThread()
else:
timeout = TimeoutThread(transaction_timeout)
timeout.setName("TimeoutThread for %s" % name)
timeout.start()
self.lock_managers[name] = LockManager(name, stats, timeout)
self.invalidation_age = invalidation_age self.invalidation_age = invalidation_age
self.zeo_storages_by_storage_id = {} # {storage_id -> [ZEOStorage]}
self.client_conflict_resolution = client_conflict_resolution self.client_conflict_resolution = client_conflict_resolution
if addr is not None: if addr is not None:
...@@ -774,21 +757,6 @@ class StorageServer: ...@@ -774,21 +757,6 @@ class StorageServer:
self.loop = self.acceptor.loop self.loop = self.acceptor.loop
ZODB.event.notify(Serving(self, address=self.acceptor.addr)) ZODB.event.notify(Serving(self, address=self.acceptor.addr))
self.stats = {}
self.timeouts = {}
for name in self.storages.keys():
self.zeo_storages_by_storage_id[name] = []
self.stats[name] = StorageStats(
self.zeo_storages_by_storage_id[name])
if transaction_timeout is None:
# An object with no-op methods
timeout = StubTimeoutThread()
else:
timeout = TimeoutThread(transaction_timeout)
timeout.setName("TimeoutThread for %s" % name)
timeout.start()
self.timeouts[name] = timeout
def create_client_handler(self): def create_client_handler(self):
return ZEOStorage(self, self.read_only) return ZEOStorage(self, self.read_only)
...@@ -855,7 +823,7 @@ class StorageServer: ...@@ -855,7 +823,7 @@ class StorageServer:
# later transactions since they will have to validate their # later transactions since they will have to validate their
# caches anyway. # caches anyway.
for zs in self.zeo_storages_by_storage_id[storage_id][:]: for zs in self.zeo_storages_by_storage_id[storage_id][:]:
zs.connection.call_soon_threadsafe(zs.connection.close) zs.call_soon_threadsafe(zs.connection.close)
def invalidate( def invalidate(
self, zeo_storage, storage_id, tid, invalidated=(), info=None): self, zeo_storage, storage_id, tid, invalidated=(), info=None):
...@@ -993,8 +961,7 @@ class StorageServer: ...@@ -993,8 +961,7 @@ class StorageServer:
for zs in zeo_storages[:]: for zs in zeo_storages[:]:
try: try:
logger.debug("Closing %s", zs.connection) logger.debug("Closing %s", zs.connection)
zs.connection.call_soon_threadsafe( zs.call_soon_threadsafe(zs.connection.close)
zs.connection.close)
except Exception: except Exception:
logger.exception("closing connection %r", zs) logger.exception("closing connection %r", zs)
...@@ -1014,108 +981,12 @@ class StorageServer: ...@@ -1014,108 +981,12 @@ class StorageServer:
if zeo_storage in zeo_storages: if zeo_storage in zeo_storages:
zeo_storages.remove(zeo_storage) zeo_storages.remove(zeo_storage)
def lock_storage(self, zeostore, delay):
storage_id = zeostore.storage_id
waiting = self._waiting[storage_id]
with self._lock:
if storage_id in self._commit_locks:
# The lock is held by another zeostore
locked = self._commit_locks[storage_id]
assert locked is not zeostore, (storage_id, delay)
if not locked.connected:
locked.log("Still locked after disconnected. Unlocking.",
logging.CRITICAL)
if locked.transaction:
locked.storage.tpc_abort(locked.transaction)
del self._commit_locks[storage_id]
# yuck: have to manipulate lock to appease with :(
self._lock.release()
try:
return self.lock_storage(zeostore, delay)
finally:
self._lock.acquire()
if delay is None:
# New request, queue it
assert not [i for i in waiting if i[0] is zeostore
], "already waiting"
delay = Delay()
waiting.append((zeostore, delay))
zeostore.log("(%r) queue lock: transactions waiting: %s"
% (storage_id, len(waiting)),
_level_for_waiting(waiting)
)
return False, delay
else:
self._commit_locks[storage_id] = zeostore
self.timeouts[storage_id].begin(zeostore)
self.stats[storage_id].lock_time = time.time()
if delay is not None:
# we were waiting, stop
waiting[:] = [i for i in waiting if i[0] is not zeostore]
zeostore.log("(%r) lock: transactions waiting: %s"
% (storage_id, len(waiting)),
_level_for_waiting(waiting)
)
return True, delay
def unlock_storage(self, zeostore):
storage_id = zeostore.storage_id
waiting = self._waiting[storage_id]
with self._lock:
assert self._commit_locks[storage_id] is zeostore
del self._commit_locks[storage_id]
self.timeouts[storage_id].end(zeostore)
self.stats[storage_id].lock_time = None
callbacks = waiting[:]
if callbacks:
assert not [i for i in waiting if i[0] is zeostore
], "waiting while unlocking"
zeostore.log("(%r) unlock: transactions waiting: %s"
% (storage_id, len(callbacks)),
_level_for_waiting(callbacks)
)
for zeostore, delay in callbacks:
try:
zeostore._unlock_callback(delay)
except (SystemExit, KeyboardInterrupt):
raise
except Exception:
logger.exception("Calling unlock callback")
def stop_waiting(self, zeostore):
storage_id = zeostore.storage_id
waiting = self._waiting[storage_id]
with self._lock:
new_waiting = [i for i in waiting if i[0] is not zeostore]
if len(new_waiting) == len(waiting):
return
waiting[:] = new_waiting
zeostore.log("(%r) dequeue lock: transactions waiting: %s"
% (storage_id, len(waiting)),
_level_for_waiting(waiting)
)
def already_waiting(self, zeostore):
storage_id = zeostore.storage_id
waiting = self._waiting[storage_id]
with self._lock:
return bool([i for i in waiting if i[0] is zeostore])
def server_status(self, storage_id): def server_status(self, storage_id):
status = self.stats[storage_id].__dict__.copy() status = self.stats[storage_id].__dict__.copy()
status['connections'] = len(status['connections']) status['connections'] = len(status['connections'])
status['waiting'] = len(self._waiting[storage_id]) lock_manager = self.lock_managers[storage_id]
status['timeout-thread-is-alive'] = self.timeouts[storage_id].isAlive() status['waiting'] = len(lock_manager.waiting)
status['timeout-thread-is-alive'] = lock_manager.timeout.isAlive()
last_transaction = self.storages[storage_id].lastTransaction() last_transaction = self.storages[storage_id].lastTransaction()
last_transaction_hex = codecs.encode(last_transaction, 'hex_codec') last_transaction_hex = codecs.encode(last_transaction, 'hex_codec')
if PY3: if PY3:
...@@ -1129,14 +1000,6 @@ class StorageServer: ...@@ -1129,14 +1000,6 @@ class StorageServer:
for storage_id in self.storages) for storage_id in self.storages)
def _level_for_waiting(waiting):
if len(waiting) > 9:
return logging.CRITICAL
if len(waiting) > 3:
return logging.WARNING
else:
return logging.DEBUG
class StubTimeoutThread: class StubTimeoutThread:
def begin(self, client): def begin(self, client):
...@@ -1197,8 +1060,7 @@ class TimeoutThread(threading.Thread): ...@@ -1197,8 +1060,7 @@ class TimeoutThread(threading.Thread):
client.log("Transaction timeout after %s seconds" % client.log("Transaction timeout after %s seconds" %
self._timeout, logging.CRITICAL) self._timeout, logging.CRITICAL)
try: try:
client.connection.call_soon_threadsafe( client.call_soon_threadsafe(client.connection.close)
client.connection.close)
except: except:
client.log("Timeout failure", logging.CRITICAL, client.log("Timeout failure", logging.CRITICAL,
exc_info=sys.exc_info()) exc_info=sys.exc_info())
...@@ -1311,3 +1173,129 @@ def never_resolve_conflict(oid, committedSerial, oldSerial, newpickle, ...@@ -1311,3 +1173,129 @@ def never_resolve_conflict(oid, committedSerial, oldSerial, newpickle,
committedData=b''): committedData=b''):
raise ConflictError(oid=oid, serials=(committedSerial, oldSerial), raise ConflictError(oid=oid, serials=(committedSerial, oldSerial),
data=newpickle) data=newpickle)
class LockManager(object):
def __init__(self, storage_id, stats, timeout):
self.storage_id = storage_id
self.stats = stats
self.timeout = timeout
self.locked = None
self.waiting = {} # {ZEOStorage -> (func, delay)}
self._lock = RLock()
def lock(self, zs, func):
"""Call the given function with the commit lock.
If we can get the lock right away, return the result of
calling the function.
If we can't get the lock right away, return a delay
The function must set ``locked`` on the zeo-storage to
indicate that the zeo-storage should be locked. Otherwise,
the lock isn't held pas the call.
"""
with self._lock:
if self._can_lock(zs):
self._locked(zs)
else:
if any(w for w in self.waiting if w is zs):
raise StorageTransactionError("Already voting (waiting)")
delay = Delay()
self.waiting[zs] = (func, delay)
self._log_waiting(
zs, "(%r) queue lock: transactions waiting: %s")
return delay
try:
result = func()
except Exception:
self.release(zs)
raise
else:
if not zs.locked:
self.release(zs)
return result
def _lock_waiting(self, zs):
waiting = None
with self._lock:
if self.locked is zs:
assert zs.locked
return
if self._can_lock(zs):
waiting = self.waiting.pop(zs, None)
if waiting:
self._locked(zs)
if waiting:
func, delay = waiting
try:
result = func()
except Exception:
delay.error(sys.exc_info())
self.release(zs)
else:
delay.reply(result)
if not zs.locked:
self.release(zs)
def release(self, zs):
with self._lock:
locked = self.locked
if locked is zs:
self._unlocked(zs)
for zs in list(self.waiting):
zs.call_soon_threadsafe(self._lock_waiting, zs)
else:
if self.waiting.pop(zs, None):
self._log_waiting(
zs, "(%r) dequeue lock: transactions waiting: %s")
def _log_waiting(self, zs, message):
l = len(self.waiting)
zs.log(message % (self.storage_id, l),
logging.CRITICAL if l > 9 else (
logging.WARNING if l > 3 else logging.DEBUG)
)
def _can_lock(self, zs):
locked = self.locked
if locked is zs:
raise StorageTransactionError("Already voting (locked)")
if locked is not None:
if not locked.connected:
locked.log("Still locked after disconnected. Unlocking.",
logging.CRITICAL)
if locked.transaction:
locked.storage.tpc_abort(locked.transaction)
self._unlocked(locked)
locked = None
else:
assert locked.locked
return locked is None
def _locked(self, zs):
self.locked = zs
self.stats.lock_time = time.time()
self._log_waiting(zs, "(%r) lock: transactions waiting: %s")
self.timeout.begin(zs)
return True
def _unlocked(self, zs):
assert self.locked is zs
self.timeout.end(zs)
self.locked = self.stats.lock_time = None
zs.locked = False
self._log_waiting(zs, "(%r) unlock: transactions waiting: %s")
...@@ -35,7 +35,7 @@ To use this module, replace:: ...@@ -35,7 +35,7 @@ To use this module, replace::
from .asyncio.server import Acceptor from .asyncio.server import Acceptor
with: with::
from .asyncio.mtacceptor import Acceptor from .asyncio.mtacceptor import Acceptor
......
...@@ -165,12 +165,14 @@ class Delay(object): ...@@ -165,12 +165,14 @@ class Delay(object):
def reply(self, obj): def reply(self, obj):
self.sent = 'reply' self.sent = 'reply'
self.protocol.send_reply(self.msgid, obj) if self.protocol:
self.protocol.send_reply(self.msgid, obj)
def error(self, exc_info): def error(self, exc_info):
self.sent = 'error' self.sent = 'error'
logger.error("Error raised in delayed method", exc_info=exc_info) logger.error("Error raised in delayed method", exc_info=exc_info)
self.protocol.send_error(self.msgid, exc_info[1]) if self.protocol:
self.protocol.send_error(self.msgid, exc_info[1])
def __repr__(self): def __repr__(self):
return "%s[%s, %r, %r, %r]" % ( return "%s[%s, %r, %r, %r]" % (
......
...@@ -30,6 +30,8 @@ from ZEO._compat import StringIO ...@@ -30,6 +30,8 @@ from ZEO._compat import StringIO
logger = logging.getLogger('ZEO.tests.forker') logger = logging.getLogger('ZEO.tests.forker')
DEBUG = os.environ.get('ZEO_TEST_SERVER_DEBUG')
class ZEOConfig: class ZEOConfig:
"""Class to generate ZEO configuration file. """ """Class to generate ZEO configuration file. """
...@@ -88,7 +90,7 @@ def runner(config, qin, qout, timeout=None, ...@@ -88,7 +90,7 @@ def runner(config, qin, qout, timeout=None,
debug=False, name=None, debug=False, name=None,
keep=False, protocol=None): keep=False, protocol=None):
if debug: if debug or DEBUG:
debug_logging() debug_logging()
old_protocol = None old_protocol = None
......
...@@ -48,6 +48,7 @@ class FakeServer: ...@@ -48,6 +48,7 @@ class FakeServer:
'1': FakeStorage(), '1': FakeStorage(),
'2': FakeStorageBase(), '2': FakeStorageBase(),
} }
lock_managers = storages
def register_connection(*args): def register_connection(*args):
return None, None return None, None
...@@ -58,6 +59,8 @@ class FakeConnection: ...@@ -58,6 +59,8 @@ class FakeConnection:
protocol_version = b'Z4' protocol_version = b'Z4'
addr = 'test' addr = 'test'
call_soon_threadsafe = lambda f, *a: f(*a)
def test_server_record_iternext(): def test_server_record_iternext():
""" """
......
...@@ -169,6 +169,7 @@ So, we arrange to get an error in vote: ...@@ -169,6 +169,7 @@ So, we arrange to get an error in vote:
>>> zs = ZEO.tests.servertesting.client(server, 1) >>> zs = ZEO.tests.servertesting.client(server, 1)
>>> zs.tpc_begin('0', '', '', {}) >>> zs.tpc_begin('0', '', '', {})
>>> zs.storea(ZODB.utils.p64(99), ZODB.utils.z64, 'x', '0') >>> zs.storea(ZODB.utils.p64(99), ZODB.utils.z64, 'x', '0')
>>> zs.vote('0') >>> zs.vote('0')
Traceback (most recent call last): Traceback (most recent call last):
... ...
...@@ -176,7 +177,7 @@ So, we arrange to get an error in vote: ...@@ -176,7 +177,7 @@ So, we arrange to get an error in vote:
When we do, the storage server's transaction lock shouldn't be held: When we do, the storage server's transaction lock shouldn't be held:
>>> '1' in server._commit_locks >>> zs.lock_manager.locked is not None
False False
Of course, if vote suceeds, the lock will be held: Of course, if vote suceeds, the lock will be held:
...@@ -186,7 +187,7 @@ Of course, if vote suceeds, the lock will be held: ...@@ -186,7 +187,7 @@ Of course, if vote suceeds, the lock will be held:
>>> zs.storea(ZODB.utils.p64(99), ZODB.utils.z64, 'x', '1') >>> zs.storea(ZODB.utils.p64(99), ZODB.utils.z64, 'x', '1')
>>> _ = zs.vote('1') # doctest: +ELLIPSIS >>> _ = zs.vote('1') # doctest: +ELLIPSIS
>>> '1' in server._commit_locks >>> zs.lock_manager.locked is not None
True True
>>> zs.tpc_abort('1') >>> zs.tpc_abort('1')
...@@ -361,13 +362,13 @@ release the lock and one of the waiting clients will get the lock. ...@@ -361,13 +362,13 @@ release the lock and one of the waiting clients will get the lock.
>>> zs2.notify_disconnected() # doctest: +ELLIPSIS >>> zs2.notify_disconnected() # doctest: +ELLIPSIS
ZEO.StorageServer INFO ZEO.StorageServer INFO
(test-addr-2) disconnected during locked transaction (test-addr-...) disconnected during locked transaction
ZEO.StorageServer CRITICAL ZEO.StorageServer CRITICAL
(test-addr-2) ('1') unlock: transactions waiting: 10 (test-addr-...) ('1') unlock: transactions waiting: 10
ZEO.StorageServer WARNING ZEO.StorageServer WARNING
(test-addr-1) ('1') lock: transactions waiting: 9 (test-addr-...) ('1') lock: transactions waiting: 9
ZEO.StorageServer BLATHER ZEO.StorageServer BLATHER
(test-addr-1) Preparing to commit transaction: 1 objects, ... bytes (test-addr-...) Preparing to commit transaction: 1 objects, ... bytes
(In practice, waiting clients won't necessarily get the lock in order.) (In practice, waiting clients won't necessarily get the lock in order.)
...@@ -392,45 +393,19 @@ statistics using the server_status method: ...@@ -392,45 +393,19 @@ statistics using the server_status method:
If clients disconnect while waiting, they will be dequeued: If clients disconnect while waiting, they will be dequeued:
>>> for client in clients: >>> for client in clients:
... client.notify_disconnected() ... client.notify_disconnected() # doctest: +ELLIPSIS
ZEO.StorageServer INFO ZEO.StorageServer INFO
(test-addr-10) disconnected during unlocked transaction (test-addr-10) disconnected during unlocked transaction
ZEO.StorageServer WARNING ZEO.StorageServer WARNING
(test-addr-10) ('1') dequeue lock: transactions waiting: 8 (test-addr-10) ('1') dequeue lock: transactions waiting: 8
ZEO.StorageServer INFO ...
(test-addr-11) disconnected during unlocked transaction
ZEO.StorageServer WARNING >>> zs1.server_status()['waiting']
(test-addr-11) ('1') dequeue lock: transactions waiting: 7 0
ZEO.StorageServer INFO
(test-addr-12) disconnected during unlocked transaction
ZEO.StorageServer WARNING
(test-addr-12) ('1') dequeue lock: transactions waiting: 6
ZEO.StorageServer INFO
(test-addr-13) disconnected during unlocked transaction
ZEO.StorageServer WARNING
(test-addr-13) ('1') dequeue lock: transactions waiting: 5
ZEO.StorageServer INFO
(test-addr-14) disconnected during unlocked transaction
ZEO.StorageServer WARNING
(test-addr-14) ('1') dequeue lock: transactions waiting: 4
ZEO.StorageServer INFO
(test-addr-15) disconnected during unlocked transaction
ZEO.StorageServer DEBUG
(test-addr-15) ('1') dequeue lock: transactions waiting: 3
ZEO.StorageServer INFO
(test-addr-16) disconnected during unlocked transaction
ZEO.StorageServer DEBUG
(test-addr-16) ('1') dequeue lock: transactions waiting: 2
ZEO.StorageServer INFO
(test-addr-17) disconnected during unlocked transaction
ZEO.StorageServer DEBUG
(test-addr-17) ('1') dequeue lock: transactions waiting: 1
ZEO.StorageServer INFO
(test-addr-18) disconnected during unlocked transaction
ZEO.StorageServer DEBUG
(test-addr-18) ('1') dequeue lock: transactions waiting: 0
>>> zs1.tpc_abort(tid1) >>> zs1.tpc_abort(tid1)
ZEO.StorageServer DEBUG
(test-addr-1) ('1') unlock: transactions waiting: 0
>>> logging.getLogger('ZEO').setLevel(logging.NOTSET) >>> logging.getLogger('ZEO').setLevel(logging.NOTSET)
>>> logging.getLogger('ZEO').removeHandler(handler) >>> logging.getLogger('ZEO').removeHandler(handler)
...@@ -494,6 +469,8 @@ ZEOStorage as closed and see if trying to get a lock cleans it up: ...@@ -494,6 +469,8 @@ ZEOStorage as closed and see if trying to get a lock cleans it up:
>>> zs1.connection.connection_lost(None) >>> zs1.connection.connection_lost(None)
ZEO.StorageServer INFO ZEO.StorageServer INFO
(test-addr-1) disconnected during locked transaction (test-addr-1) disconnected during locked transaction
ZEO.StorageServer DEBUG
(test-addr-1) ('1') unlock: transactions waiting: 0
>>> zs2 = ZEO.tests.servertesting.client(server, '2') >>> zs2 = ZEO.tests.servertesting.client(server, '2')
ZEO.asyncio.base INFO ZEO.asyncio.base INFO
...@@ -508,6 +485,8 @@ ZEOStorage as closed and see if trying to get a lock cleans it up: ...@@ -508,6 +485,8 @@ ZEOStorage as closed and see if trying to get a lock cleans it up:
(test-addr-2) Preparing to commit transaction: 1 objects, ... bytes (test-addr-2) Preparing to commit transaction: 1 objects, ... bytes
>>> zs2.tpc_abort(tid2) >>> zs2.tpc_abort(tid2)
ZEO.StorageServer DEBUG
(test-addr-2) ('1') unlock: transactions waiting: 0
>>> logging.getLogger('ZEO').setLevel(logging.NOTSET) >>> logging.getLogger('ZEO').setLevel(logging.NOTSET)
>>> logging.getLogger('ZEO').removeHandler(handler) >>> logging.getLogger('ZEO').removeHandler(handler)
......
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