Skip to content

Commit

Permalink
Debugging a PyMYSQL issue on Windows: seems to be parsing the result …
Browse files Browse the repository at this point in the history
…of version() wrong, leading it to try to prepare NOWAIT statements.
  • Loading branch information
jamadden committed Jul 3, 2019
1 parent 38004e4 commit 52407f7
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 3 deletions.
11 changes: 10 additions & 1 deletion src/relstorage/adapters/mysql/locker.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ class MySQLLocker(AbstractLocker):
``current_object`` tables: arbitrary rows in those tables may have
been locked by other transactions, and we risk deadlock.
Also note that by default, a lock timeout will only rollback the
current *statement*, not the whole session, as in most databases
(this doesn't apply to NOWAIT in MySQL 8). Fortunately, a lock timeout
only rolling back the single statement is exactly what we want to implement
NOWAIT on earlier databases.
The ``ensure_current`` argument is essentially ignored; the locks
taken out by ``lock_current_objects`` take care of that.
Expand Down Expand Up @@ -96,7 +102,8 @@ def on_store_opened(self, cursor, restart=False):
cursor.execute('SELECT version()')
ver = cursor.fetchone()[0]
major = int(ver[0])
self._supports_row_lock_nowait = major >= 8
__traceback_info__ = ver, major
self._supports_row_lock_nowait = (major >= 8)

cursor.execute(self._prepare_lock_stmt)
if self._supports_row_lock_nowait:
Expand All @@ -109,6 +116,8 @@ def _on_store_opened_set_row_lock_timeout(self, cursor, restart=False):
self._set_row_lock_timeout(cursor, self.commit_lock_timeout)

def _set_row_lock_timeout(self, cursor, timeout):
# Min value of timeout is 1
timeout = timeout if timeout >= 1 else 1
cursor.execute(self.set_timeout_stmt, (timeout,))
# It's INCREDIBLY important to fetch a row after we execute the SET statement;
# otherwise, the binary drivers that use libmysqlclient tend to crash,
Expand Down
11 changes: 10 additions & 1 deletion src/relstorage/storage/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,16 @@ def deleteObject(self, oid, oldserial, transaction):

@metricmethod
def tpc_begin(self, transaction, tid=None, status=' '):
self._tpc_phase = self._tpc_phase.tpc_begin(transaction)
try:
self._tpc_phase = self._tpc_phase.tpc_begin(transaction)
except:
# Could be a database (connection) error, could be a programming
# bug. Either way, we're fine to roll everything back and hope
# for the best on a retry.
self._drop_load_connection()
self._drop_store_connection()
raise

if tid is not None:
# tid is a committed transaction we will restore.
# The allowed actions are carefully prescribed.
Expand Down
3 changes: 2 additions & 1 deletion src/relstorage/storage/tpc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ def _clear_temp(self):
def tpc_begin(self, transaction):
if transaction is self.transaction:
raise StorageTransactionError("Duplicate tpc_begin calls for same transaction.")
# XXX: Shouldn't we tpc_abort() first? The original storage
# XXX: Shouldn't we tpc_abort() first (well, not that exactly, because
# the transaction won't match, but logically)? The original storage
# code didn't do that, but it seems like it should.
return self.storage._tpc_begin_factory(self.storage, transaction)

Expand Down

0 comments on commit 52407f7

Please sign in to comment.