Critical newton fix: Use new db session for table locking
Sharing the same db session means if another db operation takes place during the transaction before an unlock takes place (synchronizer or another request being served), MySQL complains and fails to unlock table (because MySQL requires that all tables used by the session holding a lock must also be locked). This causing all subsequent db operations to fail. By using a separate, one-time facade and session for add_policy, this problem is resolved. Change-Id: If1493f3bf5e4ae3be5c568c99901667e3aac27dd Closes-bug: #1629988
This commit is contained in:
parent
6550c5eaf5
commit
fcccfb24dd
@ -38,8 +38,11 @@ def get_engine():
|
||||
return facade.get_engine()
|
||||
|
||||
|
||||
def get_session(autocommit=True, expire_on_commit=False):
|
||||
def get_session(autocommit=True, expire_on_commit=False, make_new=False):
|
||||
"""Helper method to grab session."""
|
||||
facade = _create_facade_lazily()
|
||||
if make_new: # do not reuse existing facade
|
||||
facade = session.EngineFacade.from_config(cfg.CONF, sqlite_fk=True)
|
||||
else:
|
||||
facade = _create_facade_lazily()
|
||||
return facade.get_session(autocommit=autocommit,
|
||||
expire_on_commit=expire_on_commit)
|
||||
|
@ -19,12 +19,15 @@ from __future__ import absolute_import
|
||||
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.orm import exc as db_exc
|
||||
|
||||
from congress.db import api as db
|
||||
from congress.db import model_base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Policy(model_base.BASE, model_base.HasId, model_base.HasAudit):
|
||||
__tablename__ = 'policies'
|
||||
@ -59,13 +62,13 @@ class Policy(model_base.BASE, model_base.HasId, model_base.HasAudit):
|
||||
|
||||
def add_policy(id_, name, abbreviation, description, owner, kind,
|
||||
deleted=False, session=None):
|
||||
session = session or db.get_session()
|
||||
mysql = (cfg.CONF.database.connection is not None and
|
||||
(cfg.CONF.database.connection.split(':/')[0] == 'mysql' or
|
||||
cfg.CONF.database.connection.split('+')[0] == 'mysql'))
|
||||
postgres = (cfg.CONF.database.connection is not None and
|
||||
(cfg.CONF.database.connection.split(':/')[0] == 'postgresql' or
|
||||
cfg.CONF.database.connection.split('+')[0] == 'postgresql'))
|
||||
session = session or db.get_session(make_new=(mysql or postgres))
|
||||
try:
|
||||
with session.begin(subtransactions=False):
|
||||
# Note(ekcs): disable subtransactions to prevent interrupting
|
||||
@ -79,7 +82,9 @@ def add_policy(id_, name, abbreviation, description, owner, kind,
|
||||
# TODO(ekcs): table locking is special to underlying DB
|
||||
# change DB schema to prevent duplicate generically without locking
|
||||
if mysql: # Explicitly LOCK TABLES for MySQL
|
||||
LOG.debug('locking table `policies`')
|
||||
session.execute('LOCK TABLES policies WRITE')
|
||||
LOG.debug('success locking table `policies`')
|
||||
if postgres: # Explicitly LOCK TABLE for Postgres
|
||||
session.execute('LOCK TABLE policies IN EXCLUSIVE MODE')
|
||||
# Do nothing for SQLite; DB auto locked for transaction
|
||||
@ -95,7 +100,9 @@ def add_policy(id_, name, abbreviation, description, owner, kind,
|
||||
session.add(policy)
|
||||
finally: # always release table lock
|
||||
if mysql:
|
||||
LOG.debug('unlocking table `policies`')
|
||||
session.execute('UNLOCK TABLES')
|
||||
LOG.debug('success unlocking table `policies`')
|
||||
# postgres automatically releases lock after transaction completes
|
||||
if not unique:
|
||||
raise KeyError("Policy with name %s already exists" % name)
|
||||
|
Loading…
Reference in New Issue
Block a user