[API][ADMIN_API] Fix small race in SQLAlchemy + galera

It is possible for a session to be cleared before galera replication is
done causing a transaction abort.  Now only switch engines for sessions
after 60 seconds of idle time.  This also gives us a 60 second failover
window.

Change-Id: I8db17a13f460d4be580a170d8cfdcbab40eb6b02
This commit is contained in:
Andrew Hutchings
2013-07-05 16:49:01 +01:00
parent a197acd6f4
commit bf0b9c90d6
2 changed files with 16 additions and 4 deletions

View File

@@ -18,6 +18,7 @@ from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, backref, sessionmaker, Session from sqlalchemy.orm import relationship, backref, sessionmaker, Session
import sqlalchemy.types as types import sqlalchemy.types as types
import random import random
import time
import ConfigParser import ConfigParser
from pecan import conf from pecan import conf
@@ -140,17 +141,22 @@ class Node(DeclarativeBase):
class RoutingSession(Session): class RoutingSession(Session):
""" If an engine is already in use, re-use it. Otherwise we can end up """ If an engine is already in use, re-use it. Otherwise we can end up
with deadlocks in Galera, see http://tinyurl.com/9h6qlly """ with deadlocks in Galera, see http://tinyurl.com/9h6qlly
switch engines every 60 seconds of idle time """
last_engine = None last_engine = None
last_engine_time = 0
def get_bind(self, mapper=None, clause=None): def get_bind(self, mapper=None, clause=None):
if ( if (
RoutingSession.last_engine RoutingSession.last_engine
and RoutingSession.last_engine.pool.checkedout() > 0 and time.time() < RoutingSession.last_engine_time + 60
): ):
RoutingSession.last_engine_time = time.time()
return RoutingSession.last_engine return RoutingSession.last_engine
engine = random.choice(engines) engine = random.choice(engines)
RoutingSession.last_engine = engine RoutingSession.last_engine = engine
RoutingSession.last_engine_time = time.time()
return engine return engine

View File

@@ -17,6 +17,7 @@ from sqlalchemy import INTEGER, VARCHAR, BIGINT
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, backref, sessionmaker, Session from sqlalchemy.orm import relationship, backref, sessionmaker, Session
import sqlalchemy.types as types import sqlalchemy.types as types
import time
import random import random
import ConfigParser import ConfigParser
from pecan import conf from pecan import conf
@@ -140,17 +141,22 @@ class Node(DeclarativeBase):
class RoutingSession(Session): class RoutingSession(Session):
""" If an engine is already in use, re-use it. Otherwise we can end up """ If an engine is already in use, re-use it. Otherwise we can end up
with deadlocks in Galera, see http://tinyurl.com/9h6qlly """ with deadlocks in Galera, see http://tinyurl.com/9h6qlly
switch engines every 60 seconds of idle time """
last_engine = None last_engine = None
last_engine_time = 0
def get_bind(self, mapper=None, clause=None): def get_bind(self, mapper=None, clause=None):
if ( if (
RoutingSession.last_engine RoutingSession.last_engine
and RoutingSession.last_engine.pool.checkedout() > 0 and time.time() < RoutingSession.last_engine_time + 60
): ):
RoutingSession.last_engine_time = time.time()
return RoutingSession.last_engine return RoutingSession.last_engine
engine = random.choice(engines) engine = random.choice(engines)
RoutingSession.last_engine = engine RoutingSession.last_engine = engine
RoutingSession.last_engine_time = time.time()
return engine return engine