我有一个django 1.6应用程序,它有一个长时间运行的守护进程,需要优雅地处理数据库中断。我使用的是django.db.backends.postgresql_psycopg2
引擎。
到目前为止,我所尝试的是,在处理并记录了DatabaseError之后,我尝试关闭数据库连接,等待一段时间,然后让下一个事务创建一个新的连接。
这对我来说不是很好。下面的代码演示了这个问题:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from core.models import User
from django.db import close_connection
from django.db.transaction import atomic
from time import sleep
@atomic(savepoint=False)
def do_something():
u = User.objects.get(username='brian')
sleep(3)
u.set_unusable_password()
u.save()
try: do_something()
except Exception as e: print 'A:', e
try: close_connection()
except Exception as e: print 'B:', e
sleep(3)
try: do_something()
except Exception as e: print 'C:', e
如果数据库连接在第一个事务的睡眠调用期间中断,并在两个事务之间的睡眠调用过程中恢复,我会看到:
A: connection already closed
B: connection already closed
C: The outermost 'atomic' block cannot use savepoint = False when autocommit is off.
如果我使用savepoint=True
,输出看起来略有不同:
A: connection already closed
B: connection already closed
C: connection already closed
到目前为止,我发现第一个异常是由atomic
装饰器中的__exit__
引发的。据推测,这是在屏蔽set_unusable_password
引发的异常。
当close_connection
尝试调用abort
时,会引发第二个异常。
atomic
装饰器中的__enter__
甚至在尝试与数据库服务器通信之前就引发了最后一个异常。
除了调用close_connection()
来清理旧状态,我还需要做什么,以便建立与数据库的新连接?
事实证明close_connection
已被弃用。我在来源中找到了这个:
def close_connection(**kwargs):
warnings.warn(
"close_connection is superseded by close_old_connections.",
PendingDeprecationWarning, stacklevel=2)
使用close_old_connections
而不是close_connection
不会改变任何内容。问题的表现完全一样。close_old_connections
的来源看起来确实很有希望:
def close_old_connections(**kwargs):
for conn in connections.all():
# Remove this when the legacy transaction management goes away.
try:
conn.abort()
except DatabaseError:
pass
conn.close_if_unusable_or_obsolete()
事实证明,这个问题出现在conn.abort()
中,它显然只是为了支持合法的事务管理。从代码中可以看出,conn.abort
的异常可以被安全地忽略。但在我的情况下,conn.abort
并没有引发DatabaseError
,而是引发了InterfaceError
。
如果我将close_old_connections
的实现复制到我自己的代码中,并对其进行修改以处理DatabaseError
和InterfaceError
,我的症状就会消失。
我发现了一个已知的错误,这在某种程度上与我的问题有关。
在研究了close_connection
和close_old_connections
内部的实际情况后,我得出了以下几行,据我所知,这些行做了正确的事情:
try: django.db.connection.abort()
except django.db.utils.Error: pass
django.db.connection.close()