Python + Twisted + sqlanydb = abort () - PullRequest
       9

Python + Twisted + sqlanydb = abort ()

2 голосов
/ 26 апреля 2011

Я использую Twisted 11 вместе с SQLAnywhere 12 через официальный драйвер sqlanydb.

В общем, работает нормально.

Но иногда приложение завершается с ошибкой при первом запросе.

Если сработал один запрос, сработают и все последующие. Однако мои тесты проходят редко.

Ужасно развиваться, и strace тоже не говорит мне ничего информативного. Иногда происходит сбой внутри select (), иногда в mmap () ...

Я использую 64-битную версию Linux и локально запускаю Sybase как dbeng12 для тестирования.

Кто-нибудь успешно работает с этими компонентами? Есть предложения, как это решить? Я раньше использовал sqlanydb с Django, и он никогда не падал.

Используя распечатки, я обнаружил, что он выходит из строя внутри 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

Ответы [ 2 ]

2 голосов
/ 04 мая 2011

Похоже, что ваша база данных не является поточно-ориентированной.Чтобы сделать это соединение стабильным, сделайте следующее:

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

Это установит максимальный параллелизм равным 1, а ThreadPool будет ограничен 1 потоком, что означает, что никакие запросы не будут выполняться одновременно.Это должно помешать вашей не поточно-безопасной библиотеке вызывать драму, все еще выполняя запросы в потоке и не блокируя основной цикл.

1 голос
/ 27 апреля 2011

Да, ваш отложенный список выглядит так, как будто он не будет делать то, что вы хотите. Каждый runQuery будет запускаться в пуле потоков adbapi, поэтому нет никакой гарантии, что эти запросы упорядочены. «LAST_UPDATE_SQL», являющийся последним в списке DeferredList, не обязательно сделает его последним. Предполагается, что запросы в отложенном списке являются частью одной транзакции?

Не зная точно, что здесь за SQL-запросы, я предполагаю, что иногда для вашего LAST_UPDATE_SQL была настроена транзакция, а иногда она не была настроена в зависимости от порядка выполнения этих runQuery.

Вот как можно заменить отложенный список одним потоком adbapi с помощью adbapi.runInteraction. Я не уверен на 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
...