使用SQLAlchemy和PostgreSQL时出现SSL系统调用错误,坏的文件描述符。

3

我有一个通过 sqlalchemy 与 Postgres 通信的守护进程。该守护进程执行以下操作:

while True:
    oEngine = setup_new_engine()
    with oEngine.connect() as conn:
        Logger.debug("connection established")
        DBSession = sessionmaker(bind=conn)()
        Logger.debug('DBSession created. id={0}'.format(id(DBSession)))

        #do a bunch of stuff with DBSession

        DBSession.commit()
        Logger.debug('DBSession committed. id={0}'.format(id(DBSession)))

在无限循环的第一次迭代中,一切运行良好。暂时而言,DBSession 成功地向数据库发出了几个查询。但是随后一个查询失败并出现以下错误:

OperationalError: (OperationalError) SSL SYSCALL error: Bad file descriptor

这对我来说意味着一个关闭的连接或文件描述符被使用。但是,连接是由守护进程创建和维护的,因此我不知道这意味着什么。
换句话说,发生的情况是:
 create engine
 open connection
 setup dbsession
 query dbsession => works great
 query dbsession => ERROR

该查询看起来像这样:
 DBSession.query(Login)
                        .filter(Login.LFTime == oLineTime)
                        .filter(Login.success == self.success)
                        .count()

我认为这似乎是完全合理的。

我的问题是:这种行为可能有哪些原因,如何修复或隔离问题?

如果需要更多代码,请告诉我。代码量很大,所以我在这里采取了极简主义方法...


Postgres服务器版本和libpq版本是什么?这个问题可能已经在最近的版本中得到解决;另外,尝试在postgresql.conf中设置ssl_renegotiation_limit=0,看看错误是否消失。如果是这样,那么您可能正在遇到Postgres中的重新协商漏洞。如果您看到与ssl重新协商相关的任何内容,请查看服务器日志。 - alvherre
2个回答

2
我通过考虑会话范围而不是事务范围来解决了这个问题。
while True:
    do_stuff()

def do_stuff():
    oEngine = setup_new_engine()
    with oEngine.connect() as conn:
        Logger.debug("connection established")
        DBSession = sessionmaker(bind=conn)()

        #do a bunch of stuff with DBSession

        DBSession.commit()
        DBSession.close()

我仍然想知道为什么这样修复问题...


1
您正在while循环中创建会话,这是非常不明智的。以第一次编写代码的方式,您将在每次迭代中生成一个新连接并保持打开状态。不久之后,您将被限制无法再打开新会话。(什么样的限制?很难说,但可能是内存条件,因为DB连接相当沉重;它可能是DB服务器限制,出于性能原因,它只接受一定数量的同时用户连接;很难知道,也不重要,因为无论限制是什么,它都阻止了您使用非常浪费的方法,从而起到了预期的作用!)
您发现的解决方案修复了问题,因为每次循环时都会打开一个新连接,因此您还会在每次循环结束时关闭它,释放资源并允许其他循环创建自己的会话并成功。但是,这仍然是很多不必要的忙碌和对服务器和客户端的处理资源的浪费。我怀疑如果您将sessionmaker移动到while循环之外,它可能同样有效,并且潜在地会更快。
def main():
    oEngine = setup_new_engine()
    with oEngine.connect() as conn:
        Logger.debug("connection established")
        DBSession = sessionmaker(bind=conn)()

        apparently_infinite_loop(DBSession)

        # close only after we are done and have somehow exited the infinite loop
        DBSession.close()


def apparently_infinite_loop(DBSession):
    while True:

        #do a bunch of stuff with DBSession

        DBSession.commit()

我目前没有可用的SQLAlchemy设置,所以你可能会在其中遇到一些语法错误,但无论如何,我希望它能说明根本的基础问题。

更多详细信息请参见此处:http://docs.sqlalchemy.org/en/rel_0_9/orm/session.html#session-faq-whentocreate

文档中需要注意的一些要点:

  1. “如果再次使用Session,它将开始一个新的事务”。因此,您不需要不断地打开新的会话来获得事务范围;只需要提交即可。
  2. “通常情况下,应用程序应该在处理特定数据的函数之外外部管理会话的生命周期。”因此,你最初(现在仍然)的根本问题是,在while循环内部和数据处理代码并排进行的所有会话管理。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接