Python 选择被OS信号中断后,Psycopg2连接不可用 问题
我正在开发一个长期运行的python进程,该进程执行大量数据库访问(主要是读取,偶尔写入)。有时可能需要在进程完成之前终止进程(例如,通过使用Python 选择被OS信号中断后,Psycopg2连接不可用 问题,python,postgresql,python-2.7,signals,psycopg2,Python,Postgresql,Python 2.7,Signals,Psycopg2,我正在开发一个长期运行的python进程,该进程执行大量数据库访问(主要是读取,偶尔写入)。有时可能需要在进程完成之前终止进程(例如,通过使用kill命令),当发生这种情况时,我希望在数据库中记录一个值,指示特定运行已取消。(我还将事件记录到日志文件中;我希望在这两个位置都有信息。) 我发现,如果在数据库连接处于活动状态时中断进程,则连接将变得不可用;具体来说,如果我试图以任何方式使用它,它会挂起进程 最低工作示例 实际的应用程序相当大和复杂,但这段代码可靠地再现了这个问题 数据库中的表test
kill
命令),当发生这种情况时,我希望在数据库中记录一个值,指示特定运行已取消。(我还将事件记录到日志文件中;我希望在这两个位置都有信息。)
我发现,如果在数据库连接处于活动状态时中断进程,则连接将变得不可用;具体来说,如果我试图以任何方式使用它,它会挂起进程
最低工作示例
实际的应用程序相当大和复杂,但这段代码可靠地再现了这个问题
数据库中的表test
有两列,id
(串行)和message
(文本)。我用一行预先填充了它,这样下面的UPDATE
语句就会有一些变化
import psycopg2
import sys
import signal
pg_host = 'localhost'
pg_user = 'redacted'
pg_password = 'redacted'
pg_database = 'test_db'
def write_message(msg):
print "Writing: " + msg
cur.execute("UPDATE test SET message = %s WHERE id = 1", (msg,))
conn.commit()
def signal_handler(signal, frame):
write_message('Interrupt!')
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
if __name__ == '__main__':
conn = psycopg2.connect(host=pg_host, user=pg_user, password=pg_password, database=pg_database)
cur = conn.cursor()
write_message("Starting")
for i in xrange(10000):
# I press ^C somewhere in here
cur.execute("SELECT * FROM test")
cur.fetchall()
write_message("Finishing")
当我不间断地运行此脚本时,它将按预期完成。也就是说,数据库中的行更新为“开始”,然后是“完成”
如果在注释指示的循环中按ctrl-C,python将无限期挂起。它不再响应键盘输入,进程必须从其他地方终止。在我的postgresql日志中,数据库服务器从未收到带有“Interrupted!”的UPDATE
语句
如果我在signal_handler()的开头添加一个调试断点,我可以看到在该点对数据库连接执行几乎任何操作都会导致相同的挂起。试图执行选择,发出连接回滚()
,连接提交()
,连接关闭()
或连接重置()
都会导致挂起。执行conn.cancel()
不会导致挂起,但不会改善情况;后续使用连接仍会导致挂起。如果我从write_message()
中删除数据库访问权限,那么脚本可以在中断时正常退出,因此挂起肯定与数据库连接有关
还值得注意的是:如果我更改了脚本,从而中断了数据库活动以外的其他活动,那么它会按照需要工作,将“Interrupted!”记录到数据库中。例如,如果我用一个简单的sleep(10)
来替换xrange(10000)
中I的,并中断该循环,它就可以正常工作。因此,问题似乎与在psycopg2执行数据库访问时用信号中断psycopg2,然后尝试使用连接有关
问题
有没有办法修复现有的psycopg2连接,并在这种中断后使用它更新数据库
如果没有,是否至少有一种方法可以干净地终止它,以便在后续代码尝试使用它时,不会导致挂起
最后,这是某种预期的行为,还是应该报告的bug?对我来说,在这种中断之后,连接可能处于坏状态是有道理的,但理想情况下,它会抛出一个异常来指示问题,而不是挂起
变通办法
同时,我发现,如果在中断后使用psycopg2.connect()
创建一个全新的连接,并且小心不要访问旧的连接,我仍然可以从中断的进程中更新数据库。这可能是我现在要做的,但感觉不整洁
环境
- OS X 10.11.6
- python 2.7.11
- psycopg2 2.6.1
- postgresql 9.5.1.0
我在psycopg2 github上为此提交了一份申请,并从开发人员那里收到了一份有用的回复。总之:
- 信号处理程序中现有连接的行为依赖于操作系统,可能无法可靠地使用旧连接;建议创建一个新的解决方案
- 使用
psycopg2.extensions.set_wait_callback(psycopg2.extras.wait_select)
可以使信号处理程序中调用的execute()
语句抛出异常而不是挂起,从而稍微改善了这种情况(至少在我的环境中)。但是,使用conneciton执行其他操作(例如,reset()
)仍然会导致挂起,因此最终还是最好在信号处理程序中创建一个新连接,而不是试图修复现有连接