#python #twisted #sqlanywhere
#python #twisted #sqlanywhere
Вопрос:
Я использую Twisted 11 вместе с SQLAnywhere 12 через официальный драйвер sqlanydb.
В целом, это работает нормально.
Но иногда приложение вылетает с прерыванием при первом запросе.
Если сработал один запрос, все последующие тоже сработают. Однако мои тесты выполняются редко.
Это ужасно для разработки, и strace тоже не сообщает мне ничего информативного. Иногда происходит сбой внутри select(), иногда в mmap ()…
Я использую 64-битный Linux и запускаю локально Sybase как dbeng12 для тестирования.
Кто-нибудь успешно работает с использованием этих компонентов? Есть предложения, как это решить? Я использовал sqlanydb с Django раньше, и он никогда не зависал.
Используя prints, я обнаружил, что происходит сбой внутри 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
Комментарии:
1. Возможно, это вызвано устаревшим подключением к базе данных? Происходит ли сбой только после того, как ваше приложение работает некоторое время (скажем, более 5 минут) без каких-либо действий? Можете ли вы попробовать проверить состояние вашего соединения перед выполнением каких-либо запросов? Мне также любопытно, как вы интегрируете это в Twisted.
2. Я могу дисквалифицировать это. Я запускаю сервер БД заново при каждой настройке (), чтобы предотвратить подобные эффекты. В любом случае это происходит немедленно. Я добавил функцию sleep() после инициализации, чтобы обойти возможные варианты выполнения, но это не помогает.
3. Несколько раз вы говорили «сбой», но не совсем понятно, что это значит. Ты тоже сказал «прервать». Означает ли это, что процесс завершается с помощью SIGABRT? Если это так, и все, что вам нужно сделать, это трассировки стека C, убедитесь, что вы просматриваете стек во всех потоках процесса. Одним из них может быть вызов abort(). Это выглядело бы загадочно и случайно, если бы вы смотрели только на трассировку стека другого потока.
4. Это всегда прерывание. Мне удалось получить «[unixshm] AttachToSharedMem p = 5149 e = __SQLAnyCli__5149_0c522015 id = 5 n =<NULL> o = 0 s = 4096 d = 1 ошибка = 2 СБОЙ» уже один раз (в пробной версии я запускаю dbeng12, который является общей памятью). Я запустил это в gdb и добавил результат выше. Похоже на проблему sqlany, и это был мой первоначальный вопрос: кто-нибудь когда-либо успешно использовал это? 🙂 Или есть хитрость, чтобы заставить это работать?
5. Учитывая, что происходит сбой
libdbcap_r.so
, для меня это довольно явно выглядит как ошибка в sqlanywhere. Вероятно, проблема потокобезопасности. Хотелось бы выразиться яснее, но я никогда не использовал sqlanywhere. (Однако я должен отметить, что Twisted — не единственная программа, которая когда-либо вызываласьselect()
, так что даже это может быть библиотека sqlanywhere, выполняющая какой-то другой ввод-вывод.)
Ответ №1:
Похоже, ваша библиотека базы данных не является потокобезопасной. Чтобы обеспечить стабильное соединение, сделайте это:
self.dbpool = ConnectionPool(..., cp_min=1, cp_max=1)
Это установит максимальный параллелизм равным 1, а пул потоков будет ограничен 1 потоком, что означает, что никакие запросы не будут выполняться одновременно. Это должно помешать вашей библиотеке, не защищенной от потоков, создавать какие-либо проблемы, при этом продолжая выполнять запросы в потоке и не блокируя основной цикл.
Комментарии:
1. Другим решением, которое допускало бы более одного запроса одновременно, было бы использование ODBC. Я написал сообщение в блоге о подводных камнях, с которыми можно столкнуться.
Ответ №2:
Да, ваш отложенный список выглядит так, как будто он не собирается делать то, что вы хотите. Каждый runQuery будет выполняться в потоковом пуле adbapi, поэтому нет гарантии упорядоченности этих запросов. То, что «LAST_UPDATE_SQL» является последним в списке отложенных, не обязательно приведет к тому, что это произойдет последним. Должны ли запросы в отложенном списке быть частью одной транзакции?
Не зная точно, какие здесь 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
Комментарии:
1. Спасибо вам за ваши усилия, и я попробовал это так, как вы предложили, к сожалению, без какого-либо успеха. ПРИМЕЧАНИЕ: Им не обязательно находиться внутри транзакции, запросы абсолютно независимы, им просто нужен один и тот же параметр. Распечатки показали, что прерывание происходит после выхода
_get_contacts()
, но перед входом_get_stuff_from_db()
.