SELECT信号被OS信号

时间:2016-08-29 21:30:14

标签: python postgresql python-2.7 signals psycopg2

问题

我正在研究一个长期运行的python进程,它执行大量数据库访问(主要是读取,偶尔写入)。有时可能需要在完成之前终止该过程(例如,通过使用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")

当我不间断地运行此脚本时,它会按预期完成。也就是说,数据库中的行被更新为" Starting"然后"完成"。

如果我在评论指示的循环中按ctrl-C,python将无限期挂起。它不再响应键盘输入,并且必须从其他地方杀死该过程。查看我的postgresql日志,UPDATE语句带"中断!"数据库服务器永远不会收到它。

如果我在signal_handler()的开头添加一个调试断点,我可以看到在那一点上几乎所有与数据库连接做任何事情都会导致同样的挂起。尝试execute SELECT,发出conn.rollback()conn.commit()conn.close()conn.reset()都会导致挂起。执行conn.cancel()不会导致挂起,但它并没有改善这种情况;后续使用连接仍会导致挂起。如果我从write_message()删除数据库访问权限,则脚本可以在中断时正常退出,因此挂起肯定与数据库连接相关。

另外值得注意的是:如果我更改脚本以便我打断除数据库活动之外的其他内容,它会根据需要运行,记录"中断!"到数据库。例如,如果我用简单的for i in xrange(10000)替换sleep(10)循环并中断它,它就可以正常工作。因此问题似乎与在执行数据库访问时使用信号中断psycopg2有关,然后尝试使用连接。

问题

有没有办法挽救现有的psycopg2连接,并在这种中断后用它来更新数据库?

如果没有,是否至少有一种方法可以彻底终止它,所以如果后续代码尝试使用它,它不会导致挂起?

最后,这是某种预期的行为,还是应该报告的错误?对我来说,在这种中断之后连接可能处于不良状态是有道理的,但理想情况下,它会抛出异常来指示问题而不是悬挂。

解决方法

与此同时,我发现如果我在中断后创建一个全新的psycopg2.connect()连接,并且小心不要访问旧连接,我仍然可以从中断的进程中更新数据库。这可能是我现在所做的,但感觉不整洁。

环境

  • OS X 10.11.6
  • python 2.7.11
  • psycopg2 2.6.1
  • postgresql 9.5.1.0

1 个答案:

答案 0 :(得分:1)

我在psycopg2 github上为此提交了issue,并收到了开发人员的有用回复。总结:

  • 信号处理程序中现有连接的行为取决于操作系统,并且可能无法可靠地使用旧连接;建议使用新的解决方案。
  • 使用psycopg2.extensions.set_wait_callback(psycopg2.extras.wait_select)通过在信号处理程序中调用execute()语句来抛出异常而不是挂起,从而改善了一些情况(至少在我的环境中)。但是,使用连接进行其他操作(例如reset())仍然会对我造成影响,所以最终还是最好只在信号处理程序中创建一个新连接,而不是试图挽救现有连接。