import multiprocessing
from sqlalchemy import *
from sqlalchemy.orm import *
dbuser = ...
password = ...
dbname = ...
dbstring = "postgresql://%s:%s@localhost:5432/%s"%(dbuser, password, dbname)
db = create_engine(dbstring)
m = MetaData(db)
def make_foo(i):
t1 = Table('foo%s'%i, m, Column('a', Integer, primary_key=True))
conn = db.connect()
for i in range(10):
conn.execute("DROP TABLE IF EXISTS foo%s"%i)
conn.close()
db.dispose()
for i in range(10):
make_foo(i)
m.create_all()
def do(kwargs):
i, dbstring = kwargs['i'], kwargs['dbstring']
db = create_engine(dbstring)
Session = scoped_session(sessionmaker())
Session.configure(bind=db)
Session.execute("COMMIT; BEGIN; TRUNCATE foo%s; COMMIT;")
Session.commit()
db.dispose()
pool = multiprocessing.Pool(processes=5) # start 4 worker processes
results = []
arglist = []
for i in range(10):
arglist.append({'i':i, 'dbstring':dbstring})
r = pool.map_async(do, arglist, callback=results.append) # evaluate "f(10)" asynchronously
r.get()
r.wait()
pool.close()
pool.join()
这个脚本出现以下错误信息后停止运行。
Exception in thread Thread-2:
Traceback (most recent call last):
File "/usr/lib/python2.6/threading.py", line 532, in __bootstrap_inner
self.run()
File "/usr/lib/python2.6/threading.py", line 484, in run
self.__target(*self.__args, **self.__kwargs)
File "/usr/lib/python2.6/multiprocessing/pool.py", line 259, in _handle_results
task = get()
TypeError: ('__init__() takes at least 4 arguments (2 given)', <class 'sqlalchemy.exc.ProgrammingError'>, ('(ProgrammingError) syntax error at or near "%"\nLINE 1: COMMIT; BEGIN; TRUNCATE foo%s; COMMIT;\n ^\n',))
当然,这里的语法错误是
TRUNCATE foo%s;
。我的问题是,为什么进程会挂起,我能否说服它退出并带有错误,而不需要对我的代码进行大量手术?这种行为非常类似于我的实际代码。请注意,如果将该语句替换为
print foobarbaz
之类的内容,则不会发生挂起。此外,如果我们替换
Session.execute("COMMIT; BEGIN; TRUNCATE foo%s; COMMIT;")
Session.commit()
db.dispose()
通过只使用Session.execute("TRUNCATE foo%s;")
。
我使用前一个版本,因为它更接近我的实际代码。
此外,从图片中删除multiprocessing
并逐个循环表格可以消除挂起,并且只会出现错误。
我也有点困惑错误的形式,特别是TypeError: ('__init__() takes at least 4 arguments (2 given)'
部分。这个错误来自哪里?似乎很可能是从multiprocessing
代码中的某个地方引起的。
PostgreSQL 日志没有帮助。我看到很多像这样的行:
2012-01-09 14:16:34.174 IST [7810] 4f0aa96a.1e82/1 12/583 0 ERROR: syntax error at or near "%" at character 28
2012-01-09 14:16:34.175 IST [7810] 4f0aa96a.1e82/2 12/583 0 STATEMENT: COMMIT; BEGIN; TRUNCATE foo%s; COMMIT;
但没有其他相关的内容。
更新1:感谢lbolla及其深入分析,我能够提交一个Python缺陷报告。请在此报告中查看sbt的分析,以及此处。还可以查看Python bug报告Fix exception pickling。因此,根据sbt的说明,我们可以使用以下代码复现原始错误:
import sqlalchemy.exc
e = sqlalchemy.exc.ProgrammingError("", {}, None)
type(e)(*e.args)
这提供了
Traceback (most recent call last):
File "<stdin>", line 9, in <module>
TypeError: __init__() takes at least 4 arguments (2 given)
更新2:这个问题已经被Mike Bayer修复,至少对于SQLAlchemy来说是这样的,详情请参见错误报告StatementError Exceptions un-pickable.。根据Mike的建议,我也向psycopg2报告了一个类似的bug,虽然我没有(也没有)实际的故障示例。不过无论如何,他们显然已经修复了它,尽管他们没有提供任何修复的细节。请参见psycopg exceptions cannot be pickled。为了保险起见,我还报告了一个Python bug ConfigParser exceptions are not pickleable,对应的是lbolla提到的SO问题。看起来他们想要一个测试来解决这个问题。
无论如何,这看起来在可预见的未来将继续成为一个问题,因为总的来说,Python开发人员似乎没有意识到这个问题,所以不会防范它。令人惊讶的是,似乎没有足够的人使用多处理来使这成为一个众所周知的问题,或者他们只是忍受它。我希望Python开发人员至少能在Python 3中修复它,因为这很烦人。
我接受了lbolla的答案,因为如果没有他对问题与异常处理的相关说明,我可能无法理解这个问题。我还要感谢sbt,他解释了Python不能pickle异常是问题所在。我非常感激他们两个人,请投票支持他们的答案。谢谢。
更新3:我发布了一个后续问题:Catching unpickleable exceptions and re-raising。
get
的回溯是File "<stdin>", line 17, in <module> File "/usr/lib/python2.6/multiprocessing/pool.py", line 422, in get raise self._value Exception: foo 1 7 8 9 0 2 3 4 5 6
。 - Faheem Mitha0 1 2 3 4 5 6 7 8 9 线程“Thread-2”中的异常:Traceback(最新调用) last):File“/usr/lib/python2.6/threading.py”,line 532,在__bootstrap_inner中 self.run() File“/usr/lib/python2.6/threading.py”,line 484,在run中self.__target(*self.__args,**self.__kwargs) File“/usr/lib/python2.6/multiprocessing/pool.py”,line 259,in_handle_results task = get() TypeError:('__init __()需要至少4个参数(给出2个)',<class 'sqlalchemy.exc.ProgrammingError'>,('(NoneType)None',))。
- Faheem Mitha