蟒蛇 +扭曲+ sqlanydb = 中止()

发布于 2024-11-03 20:07:58 字数 2452 浏览 1 评论 0原文

我通过官方 sqlanydb 驱动程序将 Twisted 11 与 SQLAnywhere 12 一起使用。

一般来说,它工作得很好。

但有时应用程序会因第一个查询中止而崩溃。

如果一个查询有效,那么接下来的所有查询也都有效。然而我的测试很少通过。

用它来开发是很糟糕的,而且 strace 也没有告诉我任何信息。有时它在 select() 中崩溃,有时在 mmap() 中崩溃...

我正在运行 64 位 Linux 并在本地运行 Sybase 作为 dbeng12 进行测试。

有人使用这些组件成功工作吗?有什么建议如何解决这个问题吗?我之前在 Django 中使用 sqlanydb 并且从未崩溃过。

使用打印,我发现它在 DeferredList 内部崩溃,重要的代码基本上如下:

class WhoisDb(object):
    # ... shortened ...
    def _get_contacts(self, dom):
        if not dom:
            self.d.errback(UnknownDomain(self._get_limit()))
            return
        self.dom = Domain._make(dom[0])

        dl = defer.DeferredList( [
            self.dbpool.runQuery(CON_SQL, (self.dom.dom_owner,)),
            self.dbpool.runQuery(CON_SQL, (self.dom.dom_admin,)),
            self.dbpool.runQuery(CON_SQL, (self.dom.dom_tech,)),
            self.dbpool.runQuery(
                LAST_UPDATE_SQL,
                ( self.dom.domName, )), ] ).addCallback(self._fmt_string)

    def get_whois(self, domain):
        self.d = defer.Deferred()
        if not self._check_limit():
            self.d.errback(LimitExceeded(MAX_PER_HOUR))
        elif not RE_ALLOWED_TLDS.match(domain):
            self.d.errback(UnknownDomain(self._get_limit()))
        else: 
            self.dbpool.runQuery(
                    'select ' + DOM_FIELDS + ' from domains where '
                    'domain = ? or domain_idn = ?',
                    ( domain, domain, )) \
                            .addCallback(self._get_contacts)

        return self.d

如果崩溃,则不会调用 _fmt_string() 。

在 gdb 内部,它是一个简单的 SIGSEV:

(gdb) run ~/.virtualenvs/whois/bin/trial test.test_protocol.ProtocolTestCase.test_correct_domain
Starting program: /home/hynek/.virtualenvs/whois/bin/python ~/.virtualenvs/whois/bin/trial test.test_protocol.ProtocolTestCase.test_correct_domain
[Thread debugging using libthread_db enabled]
test.test_protocol
  ProtocolTestCase
    test_correct_domain ... [New Thread 0x7ffff311a700 (LWP 6685)]
[New Thread 0x7ffff3099700 (LWP 6686)]
[New Thread 0x7ffff27dc700 (LWP 6723)]
[New Thread 0x7ffff1fdb700 (LWP 6724)]
[New Thread 0x7ffff17da700 (LWP 6725)]
[New Thread 0x7ffff0fd9700 (LWP 6729)]

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff1fdb700 (LWP 6724)]
0x00007ffff4d4167c in ?? () from /opt/sqlanywhere12/lib64/libdbcapi_r.so

I'm using Twisted 11 together with SQLAnywhere 12 via the official sqlanydb driver.

Generally, it works fine.

But occasionally the application crashes with an abort on the first query.

If one query worked, all following work too. However my tests run only seldom through.

That's awful to develop with and strace doesn't tell me anything informative too. Sometimes it crashes inside of select(), sometimes in mmap()...

I'm running 64bit Linux and run locally the Sybase as dbeng12 for testing.

Is anyone working successfully using these components? Any suggestions how to solve that? I used sqlanydb with Django before and it never crashed.

Using prints, I found out it crashes inside of the DeferredList, the important code is basically the following:

class WhoisDb(object):
    # ... shortened ...
    def _get_contacts(self, dom):
        if not dom:
            self.d.errback(UnknownDomain(self._get_limit()))
            return
        self.dom = Domain._make(dom[0])

        dl = defer.DeferredList( [
            self.dbpool.runQuery(CON_SQL, (self.dom.dom_owner,)),
            self.dbpool.runQuery(CON_SQL, (self.dom.dom_admin,)),
            self.dbpool.runQuery(CON_SQL, (self.dom.dom_tech,)),
            self.dbpool.runQuery(
                LAST_UPDATE_SQL,
                ( self.dom.domName, )), ] ).addCallback(self._fmt_string)

    def get_whois(self, domain):
        self.d = defer.Deferred()
        if not self._check_limit():
            self.d.errback(LimitExceeded(MAX_PER_HOUR))
        elif not RE_ALLOWED_TLDS.match(domain):
            self.d.errback(UnknownDomain(self._get_limit()))
        else: 
            self.dbpool.runQuery(
                    'select ' + DOM_FIELDS + ' from domains where '
                    'domain = ? or domain_idn = ?',
                    ( domain, domain, )) \
                            .addCallback(self._get_contacts)

        return self.d

_fmt_string() is not called if it crashes.

Inside gdb, it's a simple SIGSEV:

(gdb) run ~/.virtualenvs/whois/bin/trial test.test_protocol.ProtocolTestCase.test_correct_domain
Starting program: /home/hynek/.virtualenvs/whois/bin/python ~/.virtualenvs/whois/bin/trial test.test_protocol.ProtocolTestCase.test_correct_domain
[Thread debugging using libthread_db enabled]
test.test_protocol
  ProtocolTestCase
    test_correct_domain ... [New Thread 0x7ffff311a700 (LWP 6685)]
[New Thread 0x7ffff3099700 (LWP 6686)]
[New Thread 0x7ffff27dc700 (LWP 6723)]
[New Thread 0x7ffff1fdb700 (LWP 6724)]
[New Thread 0x7ffff17da700 (LWP 6725)]
[New Thread 0x7ffff0fd9700 (LWP 6729)]

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff1fdb700 (LWP 6724)]
0x00007ffff4d4167c in ?? () from /opt/sqlanywhere12/lib64/libdbcapi_r.so

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

醉南桥 2024-11-10 20:07:58

看来您的数据库不是线程安全的。为了使其成为稳定的连接,请执行以下操作:

self.dbpool = ConnectionPool(..., cp_min=1, cp_max=1)

这会将最大并发数设置为 1,并且 ThreadPool 将限制为 1 个线程,这意味着不会同时运行任何查询。这应该可以阻止非线程安全库给您带来任何麻烦,同时仍然在线程中运行查询并且不会阻塞主循环。

It looks like your database library is not threadsafe. In order to make it a stable connection, do this:

self.dbpool = ConnectionPool(..., cp_min=1, cp_max=1)

This will set the maximum concurrency to 1, and the ThreadPool will be limited to 1 thread, meaning that no queries will run simultaneously. This should stop your non-threadsafe library from causing you any drama, while still running the queries in a thread and not blocking the mainloop.

彼岸花似海 2024-11-10 20:07:58

是的,您的延迟列表看起来不会满足您的要求。每个 runQuery 都将在 adbapi 线程池中运行,因此无法保证这些查询的顺序。 “LAST_UPDATE_SQL”是 DeferredList 中的最后一件事并不一定会使其最后发生。延迟列表中的查询是否应该是单个事务的一部分?

由于不确切知道这里的 SQL 查询是什么,我假设有时已经为 LAST_UPDATE_SQL 设置了事务,有时还没有设置事务,具体取决于 runQuery 最终实际运行的顺序。

以下是如何使用 adbapi.runInteraction 将延迟列表替换为单个 adbapi 线程。我并不 100% 相信这会解决您的问题,但我认为这是编写您尝试执行的数据库交互类型的正确方法。

class WhoisDb(object):
    # ... shortened ...
    def _get_contacts(self, dom):
        if not dom:
            self.d.errback(UnknownDomain(self._get_limit()))
            return
        self.dom = Domain._make(dom[0])

        d = self.dbpool.runInteraction(
                 self._get_stuff_from_db
            )
        d.addCallback(self._fmt_string)
        d.addErrback(self._fmt_string) # don't forget to add an errback!
        return d

    def _get_stuff_from_db(self, cursor):
        cursor.execute(CON_SQL, (self.dom.dom_owner,)),
        cursor.execute(CON_SQL, (self.dom.dom_admin,)),
        cursor.execute(CON_SQL, (self.dom.dom_tech,)),
        cursor.execute(
            LAST_UPDATE_SQL,
            ( self.dom.domName, )), ] )
        return cursor.fetchall() # or whatever you need to return obviously

Yeah, your deferred list looks like it's not going to do what you want. Each runQuery is going to be run in a adbapi threadpool so there's no guarantee of the ordering of those queries. The "LAST_UPDATE_SQL" being the last thing in the DeferredList is not necessarily going to make it happen last. Are the queries in the deferred list supposed to be part of a single transaction?

Not knowing exactly what the SQL queries are here I'm assuming that sometimes a transaction has been setup for your LAST_UPDATE_SQL and sometimes it hasn't been setup depending on the order those runQuery's end up actually running.

Here's how to replace the deferred list with a single adbapi thread using adbapi.runInteraction. I'm not 100% convinced this will fix your issues but I think it's the correct way to write the sort of database interaction you're attempting to do.

class WhoisDb(object):
    # ... shortened ...
    def _get_contacts(self, dom):
        if not dom:
            self.d.errback(UnknownDomain(self._get_limit()))
            return
        self.dom = Domain._make(dom[0])

        d = self.dbpool.runInteraction(
                 self._get_stuff_from_db
            )
        d.addCallback(self._fmt_string)
        d.addErrback(self._fmt_string) # don't forget to add an errback!
        return d

    def _get_stuff_from_db(self, cursor):
        cursor.execute(CON_SQL, (self.dom.dom_owner,)),
        cursor.execute(CON_SQL, (self.dom.dom_admin,)),
        cursor.execute(CON_SQL, (self.dom.dom_tech,)),
        cursor.execute(
            LAST_UPDATE_SQL,
            ( self.dom.domName, )), ] )
        return cursor.fetchall() # or whatever you need to return obviously
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文