SQLAlchemy hangs when external db connection fails - sqlalchemy

In my application, I use an internal db and an external db where I use the external db to load some details from the main/global database.
From time to time when I try to query some data or commit a transaction, I get an error sqlalchemy.exc.InvalidRequestError: Can't reconnect until invalid transaction is rolled back which causes whole application to break down and it's not fixed until I restart it. This mostly happen when the external database is being restarted. We lose connection, the transaction gets invalid and it hangs.
In the FastAPI app, I use SQLAlchemy. I set the external DB as follows (with some redundant details omitted):
class _ExternalDB(DBSetup):
def __init__(self):
self._database = Database(self.url, ssl=ctx)
self._engine = create_engine(self.url, echo=True, echo_pool='debug')
self._session = scoped_session(
sessionmaker(autocommit=False, autoflush=False, bind=self._engine, class_=SafeSession)
)
where DBSetup is a base class which contains url, database and session. I wrote SafeSession class to try and solve the issue I am having:
class SafeSession(Session):
def commit(self):
try:
return super().commit()
except Exception as e:
logger.error("Could not commit the SQL operation!", e)
self.rollback()
raise e
def query(self, *entities, **kwargs):
try:
return super().query(*entities, **kwargs)
except Exception as e:
logger.error("Could not complete the transaction!", e)
self.rollback()
raise e
An example query is as follows:
external_db_setup.session.query(User).get(user_id)
My goal was to roll the invalid transaction back whenever it happens, but the application never gets to the except block because I have never seen any logs for "Could not complete the transaction". Am I missing something?
Full error log:
File "/***/repositories/user.py", line 8, in get_user_by_id
return external_db_setup.session.query(User).get(user_id)
File "/***/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 1004, in get
return self._get_impl(ident, loading.load_on_pk_identity)
File "/***/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 1119, in _get_impl
return db_load_fn(self, primary_key_identity)
File "/***/lib/python3.8/site-packages/sqlalchemy/orm/loading.py", line 284, in load_on_pk_identity
return q.one()
File "/***/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3358, in one
ret = self.one_or_none()
File "/***/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3327, in one_or_none
ret = list(self)
File "/***/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3403, in __iter__
return self._execute_and_instances(context)
File "/***/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3428, in _execute_and_instances
result = conn.execute(querycontext.statement, self._params)
File "/***/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 984, in execute
return meth(self, multiparams, params)
File "/***/lib/python3.8/site-packages/sqlalchemy/sql/elements.py", line 293, in _execute_on_connection
return connection._execute_clauseelement(self, multiparams, params)
File "/***/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1097, in _execute_clauseelement
ret = self._execute_context(
File "/***/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1177, in _execute_context
self._handle_dbapi_exception(
File "/***/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1481, in _handle_dbapi_exception
util.raise_(
File "/***/lib/python3.8/site-packages/sqlalchemy/util/compat.py", line 178, in raise_
raise exception
File "/***/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1173, in _execute_context
conn = self._revalidate_connection()
File "/***/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 456, in _revalidate_connection
raise exc.InvalidRequestError(
graphql.error.located_error.GraphQLLocatedError: (sqlalchemy.exc.InvalidRequestError) Can't reconnect until invalid transaction is rolled back

Related

How to use marshmallow-sqlalchemy with async code?

I'm trying to use marshmallow-sqlalchemy with aiohttp and I have followed their docs with the basic example and I'm getting an error.
I have this schema:
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
from db.customer import Customer
class CustomerSchema(SQLAlchemyAutoSchema):
class Meta:
model = Customer
include_relationships = True
load_instance = True
And then the following code for the query:
from sqlalchemy import select
from db import db_conn
from db.customer import Customer
from queries.schema import CustomerSchema
customer_schema = CustomerSchema()
async def get_all_users():
async with db_conn.get_async_sa_session() as session:
statement = select(Customer)
results = await session.execute(statement)
_ = (results.scalars().all())
print(_)
response = customer_schema.dump(_, many=True)
print(response)
For the first print statement I'm getting
[<db.customer.Customer object at 0x10a183340>, <db.customer.Customer object at 0x10a183940>, <db.customer.Customer object at 0x10b0cd9d0>]
But then it fails with
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/sqlalchemy/util/_concurrency_py3k.py", line 60, in await_only
raise exc.MissingGreenlet(
sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called; can't call await_() here. Was IO attempted in an unexpected place? (Background on this error at: http://sqlalche.me/e/14/xd2s)
So how can I use marshmallow-sqlalchemy to serialize the SqlAlchemy reponse?
Another options (packages, etc) or a generic custom solutions are OK too.
For the time being I'm using this:
statement = select(Customer)
results = await session.execute(statement)
_ = (results.scalars().all())
response = {}
for result in _:
value = {k: (v if not isinstance(v, sqlalchemy.orm.state.InstanceState) else '_') for k, v in result.__dict__.items()}
response[f'customer {value["id"]}'] = value
return response
Full traceback:
Traceback (most recent call last):
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/aiohttp/web_protocol.py", line 422, in _handle_request
resp = await self._request_handler(request)
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/aiohttp/web_app.py", line 499, in _handle
resp = await handler(request)
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/aiohttp/web_urldispatcher.py", line 948, in _iter
resp = await method()
File "/Users/ruslan/OneDrive/Home/Dev/projects/code/education/other/cft/views/user.py", line 24, in get
await get_all_users()
File "/Users/ruslan/OneDrive/Home/Dev/projects/code/education/other/cft/queries/user.py", line 18, in get_all_users
response = customer_schema.dump(_, many=True)
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/marshmallow/schema.py", line 547, in dump
result = self._serialize(processed_obj, many=many)
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/marshmallow/schema.py", line 509, in _serialize
return [
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/marshmallow/schema.py", line 510, in <listcomp>
self._serialize(d, many=False)
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/marshmallow/schema.py", line 515, in _serialize
value = field_obj.serialize(attr_name, obj, accessor=self.get_attribute)
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/marshmallow/fields.py", line 310, in serialize
value = self.get_value(obj, attr, accessor=accessor)
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/marshmallow_sqlalchemy/fields.py", line 27, in get_value
return super(fields.List, self).get_value(obj, attr, accessor=accessor)
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/marshmallow/fields.py", line 239, in get_value
return accessor_func(obj, check_key, default)
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/marshmallow/schema.py", line 472, in get_attribute
return get_value(obj, attr, default)
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/marshmallow/utils.py", line 239, in get_value
return _get_value_for_key(obj, key, default)
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/marshmallow/utils.py", line 253, in _get_value_for_key
return getattr(obj, key, default)
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/sqlalchemy/orm/attributes.py", line 480, in __get__
return self.impl.get(state, dict_)
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/sqlalchemy/orm/attributes.py", line 931, in get
value = self.callable_(state, passive)
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/sqlalchemy/orm/strategies.py", line 879, in _load_for_state
return self._emit_lazyload(
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/sqlalchemy/orm/strategies.py", line 1036, in _emit_lazyload
result = session.execute(
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/sqlalchemy/orm/session.py", line 1689, in execute
result = conn._execute_20(statement, params or {}, execution_options)
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1582, in _execute_20
return meth(self, args_10style, kwargs_10style, execution_options)
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/sqlalchemy/sql/lambdas.py", line 481, in _execute_on_connection
return connection._execute_clauseelement(
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1451, in _execute_clauseelement
ret = self._execute_context(
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1813, in _execute_context
self._handle_dbapi_exception(
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1998, in _handle_dbapi_exception
util.raise_(exc_info[1], with_traceback=exc_info[2])
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/sqlalchemy/util/compat.py", line 207, in raise_
raise exception
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1770, in _execute_context
self.dialect.do_execute(
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/sqlalchemy/engine/default.py", line 717, in do_execute
cursor.execute(statement, parameters)
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/sqlalchemy/dialects/postgresql/asyncpg.py", line 449, in execute
self._adapt_connection.await_(
File "/Users/ruslan/.local/share/virtualenvs/cft-RKlbQ9iX/lib/python3.9/site-packages/sqlalchemy/util/_concurrency_py3k.py", line 60, in await_only
raise exc.MissingGreenlet(
sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called; can't call await_() here. Was IO attempted in an unexpected place? (Background on this error at: http://sqlalche.me/e/14/xd2s)
The problem in this case is that the Marshmallow schema is configured to load related models (include_relationships=True). Since the initial query doesn't load them automatically, the schema triggers a query to fetch them, and this causes the error.
The simplest solution, demonstrated in the docs, is to eagerly load the related objects with their "parent":
async def get_all_users():
async with db_conn.get_async_sa_session() as session:
# Let's assume a Customer has a 1 to many relationship with an Order model
statement = select(Customer).options(orm.selectinload(Customer.orders))
results = await session.execute(statement)
_ = (results.scalars().all())
print(_)
response = customer_schema.dump(_, many=True)
print(response)
There is more discussion in the Preventing Implicit IO when Using AsyncSession section of the docs.

Transactions not working in Django using MySQL backend

I am regularily getting integrity errors for the following code snippet:
class StatsManager(Manager):
#transaction.atomic
def create(self, **kwargs):
kwargs.setdefault('date', date.today())
try:
obj = self.get_queryset().get(**kwargs)
except self.model.DoesNotExist:
obj = super().create(hits=1, **kwargs) # line 28
else:
obj.hits = F('hits') + 1
obj.save()
return obj
Here is the error message:
IntegrityError at /emploi/cours-particuliers-en-physique-chimie-en-classe-de-superieur-1-evry-14skb.html
(1062, "Duplicate entry 'EMP_VIEW-1903259-2018-01-08' for key 'statEvent'")
And the traceback:
File "/home/www/aladom_v6/www/stats/models/managers.py" in create
26. obj = self.get_queryset().get(**kwargs)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/db/models/query.py" in get
380. self.model._meta.object_name
During handling of the above exception (Stats matching query does not exist.), another exception occurred:
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/db/backends/utils.py" in execute
65. return self.cursor.execute(sql, params)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django_mysql/monkey_patches.py" in execute
35. return orig_execute(self, sql, args)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/db/backends/mysql/base.py" in execute
101. return self.cursor.execute(query, args)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/MySQLdb/cursors.py" in execute
250. self.errorhandler(self, exc, value)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/MySQLdb/connections.py" in defaulterrorhandler
50. raise errorvalue
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/MySQLdb/cursors.py" in execute
247. res = self._query(query)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/MySQLdb/cursors.py" in _query
411. rowcount = self._do_query(q)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/MySQLdb/cursors.py" in _do_query
374. db.query(q)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/MySQLdb/connections.py" in query
277. _mysql.connection.query(self, query)
The above exception ((1062, "Duplicate entry 'EMP_VIEW-1903259-2018-01-08' for key 'statEvent'")) was the direct cause of the following exception:
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/core/handlers/exception.py" in inner
41. response = get_response(request)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/core/handlers/base.py" in _get_response
187. response = self.process_exception_by_middleware(e, request)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/channels/handler.py" in process_exception_by_middleware
243. return super(AsgiHandler, self).process_exception_by_middleware(exception, request)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/core/handlers/base.py" in _get_response
185. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/views/generic/base.py" in view
68. return self.dispatch(request, *args, **kwargs)
File "/home/www/aladom_v6/www/utils/views/behaviors.py" in dispatch
343. return super().dispatch(request, *args, **kwargs)
File "/home/www/aladom_v6/www/offers/views/public.py" in dispatch
1089. return super().dispatch(request, *args, **kwargs)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/views/generic/base.py" in dispatch
88. return handler(request, *args, **kwargs)
File "/home/www/aladom_v6/www/offers/views/public.py" in get
1308. return super().get(request, *args, **kwargs)
File "/home/www/aladom_v6/www/stats/views/behaviors.py" in get
16. target_id=self.object.pk)
File "/usr/lib/python3.4/contextlib.py" in inner
30. return func(*args, **kwds)
File "/home/www/aladom_v6/www/stats/models/managers.py" in create
28. obj = super().create(hits=1, **kwargs)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/db/models/manager.py" in manager_method
85. return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/db/models/query.py" in create
394. obj.save(force_insert=True, using=self.db)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/db/models/base.py" in save
808. force_update=force_update, update_fields=update_fields)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/db/models/base.py" in save_base
838. updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/db/models/base.py" in _save_table
924. result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/db/models/base.py" in _do_insert
963. using=using, raw=raw)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/db/models/manager.py" in manager_method
85. return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/db/models/query.py" in _insert
1076. return query.get_compiler(using=using).execute_sql(return_id)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/db/models/sql/compiler.py" in execute_sql
1107. cursor.execute(sql, params)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/db/backends/utils.py" in execute
65. return self.cursor.execute(sql, params)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/db/utils.py" in __exit__
94. six.reraise(dj_exc_type, dj_exc_value, traceback)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/utils/six.py" in reraise
685. raise value.with_traceback(tb)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/db/backends/utils.py" in execute
65. return self.cursor.execute(sql, params)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django_mysql/monkey_patches.py" in execute
35. return orig_execute(self, sql, args)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/django/db/backends/mysql/base.py" in execute
101. return self.cursor.execute(query, args)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/MySQLdb/cursors.py" in execute
250. self.errorhandler(self, exc, value)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/MySQLdb/connections.py" in defaulterrorhandler
50. raise errorvalue
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/MySQLdb/cursors.py" in execute
247. res = self._query(query)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/MySQLdb/cursors.py" in _query
411. rowcount = self._do_query(q)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/MySQLdb/cursors.py" in _do_query
374. db.query(q)
File "/home/www/.virtualenvs/aladom/lib/python3.4/site-packages/MySQLdb/connections.py" in query
277. _mysql.connection.query(self, query)
Line 28 raises a duplicate entry error while I checked right above whether the key exists or not, and encapsulated it in a transaction.
I'm using Django 1.11, so I tried to set MySQL isolation level to read committed. I thought it would fix that issue, but it still occurs.
I also tried to do it the other way round:
class StatsManager(Manager):
def create(self, **kwargs):
kwargs.setdefault('date', date.today())
try:
obj = super().create(hits=1, **kwargs)
except IntegrityError:
obj = self.get_queryset().get(**kwargs) # line 27
obj.hits = F('hits') + 1
obj.save()
return obj
But in this case, it sometimes fail with the following error, because my requests are encapsulated in transactions:
TransactionManagementError at /admin/moderation/serviceoffermoderation/248424/change/
An error occurred in the current transaction. You can't execute queries until the end of the 'atomic' block.
Which is actually expected, unlike the former error.
Any idea what's going on and how to deal with it?
It looks like you could rewrite your code to use get_or_create, which has some handling for race conditions.
To avoid the An error occurred in the current transaction error, you need to wrap the code that can raise the IntegrityError with transaction.atomic:
def create(self, **kwargs):
kwargs.setdefault('date', date.today())
try:
with transaction.atomic():
obj = super().create(hits=1, **kwargs)
except IntegrityError:
obj = self.get_queryset().get(**kwargs) # line 27
obj.hits = F('hits') + 1
obj.save()
return obj
See the docs on controlling transactions explicitly for more info.

Django transaction failure for nested atomic blocks and concurrent processes on MySQL

I've faced following issue and I'm not entirely sure it's a django or MySQL issue.
It happens when I want to retry django's save method in case of database error such as deadlock. Lets not focus on how ugly it is because it was just temporary hack which revealed potentially another problem.
To reproduce it I've prepared a script that runs 3 concurrent processes and simulate database failure by raising AssertionError.
models.py
from django.db import models, transaction
from time import sleep
from django.db.utils import OperationalError
class ModelA(models.Model):
name = models.CharField(max_length=255)
def __unicode__(self):
return self.name
def save(self, *args, **kwargs):
def save_record(attempt):
print attempt
try:
with transaction.atomic():
super(ModelA, self).save(*args, **kwargs)
if attempt > 1:
assert False
except (AssertionError, OperationalError):
# dirty hack to retry
sleep(1)
if attempt > 0:
save_record(attempt-1)
else:
raise
save_record(5)
test script
import time
from multiprocessing import Process
from django.core.management.base import BaseCommand
from django.db import transaction
from atomic import models
#transaction.atomic
def create_record():
a = models.ModelA()
a.name = "test {}".format(time.time())
a.save()
class Command(BaseCommand):
def handle(self, *args, **options):
procs = []
for i in range(3):
p = Process(target=create_record)
procs.append(p)
for p in procs:
p.start()
for p in procs:
p.join()
If I run only 1 process everything works but with 3 concurrent processes 1 works (saves data) and another 2 fails with following traceback
Traceback (most recent call last):
File "/usr/lib/python2.7/multiprocessing/process.py", line 258, in _bootstrap
self.run()
File "/usr/lib/python2.7/multiprocessing/process.py", line 114, in run
self._target(*self._args, **self._kwargs)
File "/home/blue/.virtualenvs/atomictest/local/lib/python2.7/site-packages/django/db/transaction.py", line 371, in inner
return func(*args, **kwargs)
File "/media/django/atomictest/atomic/management/commands/test_atomic.py", line 14, in create_record
a.save()
File "/media/django/atomictest/atomic/models.py", line 29, in save
save_record(5)
File "/media/django/atomictest/atomic/models.py", line 25, in save_record
save_record(attempt-1)
File "/media/django/atomictest/atomic/models.py", line 25, in save_record
save_record(attempt-1)
File "/media/django/atomictest/atomic/models.py", line 25, in save_record
save_record(attempt-1)
File "/media/django/atomictest/atomic/models.py", line 18, in save_record
super(ModelA, self).save(*args, **kwargs)
File "/home/blue/.virtualenvs/atomictest/local/lib/python2.7/site-packages/django/db/models/base.py", line 545, in save
force_update=force_update, update_fields=update_fields)
File "/home/blue/.virtualenvs/atomictest/local/lib/python2.7/site-packages/django/db/models/base.py", line 573, in save_base
updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
File "/home/blue/.virtualenvs/atomictest/local/lib/python2.7/site-packages/django/db/models/base.py", line 635, in _save_table
forced_update)
File "/home/blue/.virtualenvs/atomictest/local/lib/python2.7/site-packages/django/db/models/base.py", line 679, in _do_update
return filtered._update(values) > 0
File "/home/blue/.virtualenvs/atomictest/local/lib/python2.7/site-packages/django/db/models/query.py", line 510, in _update
return query.get_compiler(self.db).execute_sql(None)
File "/home/blue/.virtualenvs/atomictest/local/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 980, in execute_sql
cursor = super(SQLUpdateCompiler, self).execute_sql(result_type)
File "/home/blue/.virtualenvs/atomictest/local/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 786, in execute_sql
cursor.execute(sql, params)
File "/home/blue/.virtualenvs/atomictest/local/lib/python2.7/site-packages/django/db/backends/util.py", line 69, in execute
return super(CursorDebugWrapper, self).execute(sql, params)
File "/home/blue/.virtualenvs/atomictest/local/lib/python2.7/site-packages/django/db/backends/util.py", line 47, in execute
self.db.validate_no_broken_transaction()
File "/home/blue/.virtualenvs/atomictest/local/lib/python2.7/site-packages/django/db/backends/__init__.py", line 372, in validate_no_broken_transaction
"An error occurred in the current transaction. You can't "
TransactionManagementError: An error occurred in the current transaction. You can't execute queries until the end of the 'atomic' block.
I use django 1.6.5 and MySQL 5.5.37. Tested also with sqlite and postgres and there's no such issue with those backends.
I've also noticed that this happends only with nested atomic blocks. If i remove #transaction.atomic decorator from create_record() function it works again.
You should avoid catching exceptions inside atomic:
When exiting an atomic block, Django looks at whether it’s exited
normally or with an exception to determine whether to commit or roll
back. If you catch and handle exceptions inside an atomic block, you
may hide from Django the fact that a problem has happened. This can
result in unexpected behavior.
This is mostly a concern for DatabaseError and its subclasses such as
IntegrityError. After such an error, the transaction is broken and
Django will perform a rollback at the end of the atomic block. If you
attempt to run database queries before the rollback happens, Django
will raise a TransactionManagementError. You may also encounter this
behavior when an ORM-related signal handler raises an exception.
The correct way to catch database errors is around an atomic block as
shown above. If necessary, add an extra atomic block for this purpose.
This pattern has another advantage: it delimits explicitly which
operations will be rolled back if an exception occurs.
If you catch exceptions raised by raw SQL queries, Django’s behavior
is unspecified and database-dependent.
In this case you're catching DataBaseErrors inside the create_record's atomic block.
You should move your save retry attempt logic to the create_record method.
def create_record(retry=5):
instance = models.ModelA(name="test {}".format(time.time()))
try:
with transaction.atomic():
instance.save()
except DataBaseError:
if retry:
time.sleep(1)
create_record(retry-1)
else:
raise
And avoid overriding models.Model.save.

SQLAlchemy session issues with celery

I have scheduled a few recurring tasks with celery beat for our web app
The app itself is build using pyramid web framework. Using the zopetransaction extension to manage session
In celery, I am using the app as a library. I am redefining session in models with a function.
It works well but once in a while, it raises InvalidRequestError: This session is in 'prepared' state; no further SQL can be emitted within this transaction
I am not sure what is wrong and why it issues these warnings.
Sample code:
in tasks.py
def initialize_async_session():
import sqlalchemy
from webapp.models import Base, set_dbsession, engine
Session = sqlalchemy.orm.scoped_session(
sqlalchemy.orm.sessionmaker(autocommit=True, autoflush=True)
)
Session.configure(bind=engine)
session = Session()
set_dbsession(session)
Base.metadata.bind = engine
return session
#celery.task
def rerun_scheduler():
log.info("Starting pipeline scheduler")
session = initialize_async_session()
webapp.sheduledtask.service.check_for_updates(session)
log.info("Ending pipeline scheduler")
In models.py in webapp
DBSession = scoped_session(sessionmaker(bind=engine, expire_on_commit=False,
extension=ZopeTransactionExtension()))
def set_dbsession(db_session=None):
"""
This function sets the db session
"""
global DBSession
if db_session:
DBSession = db_session
log.info("session changed to {0}".format(db_session))
UPDATE:
traceback:
Traceback (most recent call last):
File "/usr/lib/python2.7/threading.py", line 551, in __bootstrap_inner
self.run()
File "/home/ubuntu/modwsgi/env/local/lib/python2.7/site-packages/edgem_common-0.0-py2.7.egg/common/utils.py", line 54, in new_function
result = f(*args, **kwargs)
File "/home/ubuntu/modwsgi/env/local/lib/python2.7/site-packages/edgem_common-0.0-py2.7.egg/common/utils.py", line 100, in new_function
result = f(*args, **kwargs)
File "/home/ubuntu/modwsgi/env/mvc-service/webapp/webapp/data/mongo_service.py", line 1274, in run
self.table_params.set_task_status_as_finished()
File "/home/ubuntu/modwsgi/env/mvc-service/webapp/webapp/mem_objects.py", line 33, in set_task_status_as_finished
task = Task.get_by_id(self.task_id)
File "/home/ubuntu/modwsgi/env/mvc-service/webapp/webapp/models.py", line 162, in get_by_id
return DBSession.query(cls).filter(cls.id == obj_id).first()
File "/home/ubuntu/modwsgi/env/local/lib/python2.7/site-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 2156, in first
ret = list(self[0:1])
File "/home/ubuntu/modwsgi/env/local/lib/python2.7/site-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 2023, in __getitem__
return list(res)
File "/home/ubuntu/modwsgi/env/local/lib/python2.7/site-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 2227, in __iter__
return self._execute_and_instances(context)
File "/home/ubuntu/modwsgi/env/local/lib/python2.7/site-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 2240, in _execute_and_instances
close_with_result=True)
File "/home/ubuntu/modwsgi/env/local/lib/python2.7/site-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 2231, in _connection_from_session
**kw)
File "/home/ubuntu/modwsgi/env/local/lib/python2.7/site-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py", line 777, in connection
close_with_result=close_with_result)
File "/home/ubuntu/modwsgi/env/local/lib/python2.7/site-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py", line 781, in _connection_for_bind
return self.transaction._connection_for_bind(engine)
File "/home/ubuntu/modwsgi/env/local/lib/python2.7/site-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py", line 289, in _connection_for_bind
self._assert_is_active()
File "/home/ubuntu/modwsgi/env/local/lib/python2.7/site-packages/SQLAlchemy-0.7.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py", line 217, in _assert_is_active
"This Session's transaction has been rolled back "
InvalidRequestError: This Session's transaction has been rolled back by a nested rollback() call. To begin a new transaction, issue Session.rollback() first.
#########################################################################
[2013-05-30 14:32:57,782: WARNING/PoolWorker-3] Exception in thread Thread-4:
Traceback (most recent call last):
File "/usr/lib/python2.7/threading.py", line 552, in __bootstrap_inner
self.run()
File "/home/ranjith/wksp/env/local/lib/python2.7/site-packages/edgem_common-0.0-py2.7.egg/common/utils.py", line 54, in new_function
result = f(*args, **kwargs)
File "/home/ranjith/wksp/env/local/lib/python2.7/site-packages/edgem_common-0.0-py2.7.egg/common/utils.py", line 100, in new_function
result = f(*args, **kwargs)
File "/home/ranjith/wksp/mvc-service/webapp/webapp/data/mongo_service.py", line 1274, in run
self.table_params.set_task_status_as_finished()
File "/home/ranjith/wksp/mvc-service/webapp/webapp/mem_objects.py", line 33, in set_task_status_as_finished
task = Task.get_by_id(self.task_id)
File "/home/ranjith/wksp/mvc-service/webapp/webapp/models.py", line 166, in get_by_id
return DBSession.query(cls).filter(cls.id == obj_id).first()
File "/home/ranjith/wksp/env/local/lib/python2.7/site-packages/SQLAlchemy-0.8.1-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 2145, in first
ret = list(self[0:1])
File "/home/ranjith/wksp/env/local/lib/python2.7/site-packages/SQLAlchemy-0.8.1-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 2012, in __getitem__
return list(res)
File "/home/ranjith/wksp/env/local/lib/python2.7/site-packages/SQLAlchemy-0.8.1-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 2216, in __iter__
return self._execute_and_instances(context)
File "/home/ranjith/wksp/env/local/lib/python2.7/site-packages/SQLAlchemy-0.8.1-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 2229, in _execute_and_instances
close_with_result=True)
File "/home/ranjith/wksp/env/local/lib/python2.7/site-packages/SQLAlchemy-0.8.1-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 2220, in _connection_from_session
**kw)
File "/home/ranjith/wksp/env/local/lib/python2.7/site-packages/SQLAlchemy-0.8.1-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py", line 798, in connection
close_with_result=close_with_result)
File "/home/ranjith/wksp/env/local/lib/python2.7/site-packages/SQLAlchemy-0.8.1-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py", line 802, in _connection_for_bind
return self.transaction._connection_for_bind(engine)
File "/home/ranjith/wksp/env/local/lib/python2.7/site-packages/SQLAlchemy-0.8.1-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py", line 281, in _connection_for_bind
self._assert_active()
File "/home/ranjith/wksp/env/local/lib/python2.7/site-packages/SQLAlchemy-0.8.1-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py", line 181, in _assert_active
"This session is in 'prepared' state; no further "
InvalidRequestError: This session is in 'prepared' state; no further SQL can be emitted within this transaction.
I believe the problem is that you are attempting to use the SQLAlchemy session in your Celery task.
The first thing I recommend doing is creating two separate scoped sessions, one for your Celery application and another one for your web application. Next, I would make sure your Celery database session is only configured once during Celery initialization. You can use the Celery worker_init.connect to make sure it creates the database during Celery startup (http://hynek.me/articles/using-celery-with-pyramid/).
It is very important that your web application does not use the same database session as your Celery application.
Something like this for your tasks.py file:
from celery import Celery
from celery.signals import worker_init
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
Session = sqlalchemy.orm.scoped_session(
sqlalchemy.orm.sessionmaker(autocommit=True, autoflush=True))
#worker_init.connect
def initialize_session():
some_engine = create_engine('database_url')
Session.configure(bind=some_engine)
#celery.task
def rerun_scheduler():
log.info("Starting pipeline scheduler")
webapp.sheduledtask.service.check_for_updates(Session)
log.info("Ending pipeline scheduler")
Cross posting my answer to a very similar stack overflow:
What's the proper way to use SQLAlchemy Sessions with Celery?
This solved the issue for me:
Sqlalchemy pools connections by default in a non-threadsafe manner,
Celery forks processes by default: one or the other needs to be changed.
Turn off Sqlalchemy pooling
Sql Alchemy Docs
from sqlalchemy.pool import NullPool
engine = create_engine(
SQLALCHEMY_DATABASE_URL, poolclass=NullPool
)

Django mysql when saving a object got warning

I was just writing a little app to store words in my mysql database using Django.I read data from a text file which is extremely well organised,like this:
The text file is like this:
DELUGE
DELUSION
DELVE
DEMAGOGUE
DEMANDING
DEMOLITION
DEMONSTRATE
DEMORALIZE
DEMOTIC
DEMUR
DENIGRATE
DENOUEMENT
DENOUNCE
DENT
DENUDE
DEPLETE
DEPLORE
DEPLOY
And then I read date from it using open('thefile').readlines like this:
for line in open('/home/jacos/sorted-gre.txt').readlines():
... if line:
... p = Word(word_spelling = line)
... p.save()
The word_spelling field is the primary key.
Then came this warning:
Traceback (most recent call last):
File "<console>", line 4, in <module>
File "/usr/local/lib/python2.7/dist-packages/django/db/models/base.py", line 460, in save
self.save_base(using=using, force_insert=force_insert, force_update=force_update)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/base.py", line 553, in save_base
result = manager._insert(values, return_id=update_pk, using=using)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/manager.py", line 195, in _insert
return insert_query(self.model, values, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/query.py", line 1436, in insert_query
return query.get_compiler(using=using).execute_sql(return_id)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/sql/compiler.py", line 791, in execute_sql
cursor = super(SQLInsertCompiler, self).execute_sql(None)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/sql/compiler.py", line 735, in execute_sql
cursor.execute(sql, params)
File "/usr/local/lib/python2.7/dist-packages/django/db/backends/util.py", line 34, in execute
return self.cursor.execute(sql, params)
File "/usr/local/lib/python2.7/dist-packages/django/db/backends/mysql/base.py", line 86, in execute
return self.cursor.execute(query, args)
File "/usr/lib/pymodules/python2.7/MySQLdb/cursors.py", line 176, in execute
if not self._defer_warnings: self._warning_check()
File "/usr/lib/pymodules/python2.7/MySQLdb/cursors.py", line 92, in _warning_check
warn(w[-1], self.Warning, 3)
Data truncated for column 'word_spelling' at row 1
As a result, only part of these words were stored in mysql. I'd like to know why.
CharFields have a max_length attribute. What did you set when you generate database
for Word.object.get(pk=1).word_spelling?
nothing related with your warning but
it's recommended to close file or open it with a with statement.
with open('/home/jacos/sorted-gre.txt') as f:
for line in f.readlines():
if line:
p = Word(word_spelling = line)
p.save()