Найти - Пользователи
Полная версия: imaplib и Gevent: ошибка конкурирующих сокетов
Начало » Python для экспертов » imaplib и Gevent: ошибка конкурирующих сокетов
1
Razor
Доброго времени суток.

Клепаю асинхронный сборщик электронной почты. Для сбора - стандартная библиотека imaplib, для асинхронности - Gevent.

Версии: Python 2.7.8 и Gevent 1.1.1.

Что имею сейчас:
self.connection = imaplib.IMAP4_SSL(self.hostname, self.port, **ssl_files)
rv, data = self.connection.search(None, "ALL")
numbers = [number for number in data[0].split()]
threads = [gevent.spawn(self._get_message, message) for message in numbers]
gevent.joinall(threads)
Функция _get_message содержит следующее:
def _get_message(self, message_number):
    logging.info("Message {0} called!".format(message_number))
    rv, data = self.connection.fetch(message_number, '(RFC822)')
    if not self.is_ok(rv): return
    message = email.message_from_string(data[0][1])
Получаю ошибку:
Traceback (most recent call last):
  File "C:\Python27\lib\site-packages\gevent\greenlet.py", line 534, in run
    result = self._run(*self.args, **self.kwargs)
  File "C:/Users/Eugene/PycharmProjects/Extract Email by Criteria/imap_email.py", line 108, in _get_message
    rv, data = self.connection.fetch(message_number, '(RFC822)')
  File "C:\Python27\lib\imaplib.py", line 456, in fetch
    typ, dat = self._simple_command(name, message_set, message_parts)
  File "C:\Python27\lib\imaplib.py", line 1088, in _simple_command
    return self._command_complete(name, self._command(name, *args))
  File "C:\Python27\lib\imaplib.py", line 910, in _command_complete
    typ, data = self._get_tagged_response(tag)
  File "C:\Python27\lib\imaplib.py", line 1017, in _get_tagged_response
    self._get_response()
  File "C:\Python27\lib\imaplib.py", line 929, in _get_response
    resp = self._get_line()
  File "C:\Python27\lib\imaplib.py", line 1027, in _get_line
    line = self.readline()
  File "C:\Python27\lib\imaplib.py", line 1189, in readline
    return self.file.readline()
  File "C:\Python27\lib\socket.py", line 451, in readline
    data = self._sock.recv(self._rbufsize)
  File "C:\Python27\lib\site-packages\gevent\_sslgte279.py", line 455, in recv
    return self.read(buflen)
  File "C:\Python27\lib\site-packages\gevent\_sslgte279.py", line 314, in read
    self._wait(self._read_event, timeout_exc=_SSLErrorReadTimeout)
  File "C:\Python27\lib\site-packages\gevent\_socket2.py", line 173, in _wait
    raise _socketcommon.ConcurrentObjectUseError('This socket is already used by another greenlet: %r' % (watcher.callback, ))
ConcurrentObjectUseError: This socket is already used by another greenlet: <bound method Waiter.switch of <gevent.hub.Waiter object at 0x02B1C558>>
<Greenlet at 0x2afe710: <bound method EmailImapConnection._get_message of <__main__.EmailImapConnection object at 0x02A63C50>>(4)> failed with ConcurrentObjectUseError
Вначале monkey.patch_all() вставлено. Выполняется только первый сокет, а остальные блокируются. Большие подозрения в сторону сокетов внутри imaplib. Сталкивался кто-то с подобной проблемой?

Выложил код на Gist: https://gist.github.com/gitex/0d9385fa3ab1d98f30713f6eeeab454f
DayFan
Тут в принципе рассказывается - http://www.gevent.org/gevent.monkey.html. Необходимо monkey.patch_all() выполнить перед импортированием остальных модулей, таких как imaplib.
Razor
DayFan
Необходимо monkey.patch_all() выполнить перед импортированием остальных модулей

Прочитайте, пожалуйста, вторую строчку с конца. Первое выделенное жирным шрифтом слово. Я это не просто так пишу - я весьма неплох в Питоне, если это можно так назвать.

К слову говоря, вопрос уже решен. Если кому-то интересно, вот решение: используем Gevent при открытии соединения. То есть создаем несколько открытых соединений вместо одного. И тут начинаются проблемы - у Gmail, к примеру, максимальное количество одновременных соединений - 15. Лучше чем 1, но мало.

Тогда я полез искать другое решение и обнаружил одну крутую штуку:

result, data = imap_connection.uid('fetch', '1938,2398,2487', '(RFC822)')

Для тех, кто не понял - объясняю: теперь мы можем забирать не одно письмо за раз, а сколько угодно. Даже без использования многопоточности. Я проверил по времени: на 100 письмах новое решение опередило gevent на 3 секунды (9 против 12), а на 500 - на 15 (25 против 40). Думаю дальше разница будет расти в прогрессии.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB