imaplib2:imap.gmail.com 处理程序 BYE 响应:系统错误

Posted

技术标签:

【中文标题】imaplib2:imap.gmail.com 处理程序 BYE 响应:系统错误【英文标题】:imaplib2 : imap.gmail.com handler BYE response: System error 【发布时间】:2013-05-13 09:28:01 【问题描述】:

我正在更新一个 python 脚本,该脚本检查 IMAP 是否有新电子邮件,如果有新电子邮件,则会发送推送通知。问题是每隔几个小时我就会崩溃。起初我无法真正理解发生了什么,但后来我发现 M.debug = 4 给了我一个很好的输出,但我仍然不明白是什么导致了问题。我已经发布了我的脚本和从正常行为到崩溃的调试输出,希望对 python 有更好理解的人可以告诉我发生了什么以及如何修复它。

编辑:

我已按照答案中的建议编辑了我的代码:

while True:
    try:
      [call function that does all your logic]
    except imaplib2.IMAP4.abort:
      print("Disconnected.  Trying again.")

或者这个:

    while True:
        try:
          [call function that does all your logic]
except (imaplib2.IMAP4.abort, imaplib2.IMAP4.error) as e: 
      print("Disconnected.  Trying again.")

但是在一段不确定的时间之后我仍然会崩溃,并且永远不会捕获异常。 print("Disconnected. Trying again.") 永远不会执行。

代码:

    #!/usr/local/bin/python2.7
    print "Content-type: text/html\r\n\r\n";

    import socket, ssl, json, struct, re
    import imaplib2, time
    from threading import *

    # enter gmail login details here
    USER="username@gmail.com"
    PASSWORD="password"
    # enter device token here
    deviceToken = 'my device token x x x x x'
    deviceToken = deviceToken.replace(' ','').decode('hex')
    currentBadgeNum = -1

    def getUnseen():
        (resp, data) = M.status("INBOX", '(UNSEEN)')
        print data
        return int(re.findall("UNSEEN (\d)*\)", data[0])[0])    

    def sendPushNotification(badgeNum):
        global currentBadgeNum, deviceToken
        if badgeNum != currentBadgeNum:
            currentBadgeNum = badgeNum
            thePayLoad = 
                 'aps': 
                      'alert':'Hello world!',
                      'sound':'',
                      'badge': badgeNum,
                      ,
                 'test_data':  'foo': 'bar' ,
                 
            theCertfile = 'certif.pem'
            theHost = ('gateway.push.apple.com', 2195)

            data = json.dumps(thePayLoad)
            theFormat = '!BH32sH%ds' % len(data)
            theNotification = struct.pack(theFormat, 0, 32, deviceToken, len(data), data)

            ssl_sock = ssl.wrap_socket(socket.socket(socket.AF_INET, socket.SOCK_STREAM), certfile=theCertfile)
            ssl_sock.connect(theHost)
            ssl_sock.write(theNotification)
            ssl_sock.close()
            print "Sent Push alert."

    # This is the threading object that does all the waiting on 
    # the event
    class Idler(object):
        def __init__(self, conn):
            self.thread = Thread(target=self.idle)
            self.M = conn
            self.event = Event()

        def start(self):
            self.thread.start()

        def stop(self):
            # This is a neat trick to make thread end. Took me a 
            # while to figure that one out!
            self.event.set()

        def join(self):
            self.thread.join()

        def idle(self):
            # Starting an unending loop here
            while True:
                # This is part of the trick to make the loop stop 
                # when the stop() command is given
                if self.event.isSet():
                    return
                self.needsync = False
                # A callback method that gets called when a new 
                # email arrives. Very basic, but that's good.
                def callback(args):
                    if not self.event.isSet():
                        self.needsync = True
                        self.event.set()
                # Do the actual idle call. This returns immediately, 
                # since it's asynchronous.
                self.M.idle(callback=callback)
                # This waits until the event is set. The event is 
                # set by the callback, when the server 'answers' 
                # the idle call and the callback function gets 
                # called.
                self.event.wait()
                # Because the function sets the needsync variable,
                # this helps escape the loop without doing 
                # anything if the stop() is called. Kinda neat 
                # solution.
                if self.needsync:
                    self.event.clear()
                    self.dosync()

        # The method that gets called when a new email arrives. 
        # Replace it with something better.
        def dosync(self):
            print "Got an event!"
            numUnseen = getUnseen()
            sendPushNotification(numUnseen)

    # Had to do this stuff in a try-finally, since some testing 
    # went a little wrong.....
while True:
    try:
        # Set the following two lines to your creds and server
        M = imaplib2.IMAP4_SSL("imap.gmail.com")
        M.login(USER, PASSWORD)
        M.debug = 4
        # We need to get out of the AUTH state, so we just select 
        # the INBOX.
        M.select("INBOX")
        numUnseen = getUnseen()
        sendPushNotification(numUnseen)

        typ, data = M.fetch(1, '(RFC822)')
        raw_email = data[0][1]

        import email
        email_message = email.message_from_string(raw_email)
        print email_message['Subject']

        #print M.status("INBOX", '(UNSEEN)')
        # Start the Idler thread
        idler = Idler(M)
        idler.start()


        # Sleep forever, one minute at a time
        while True:
            time.sleep(60)
    except imaplib2.IMAP4.abort:
      print("Disconnected.  Trying again.")   
    finally:
        # Clean up.
        #idler.stop() #Commented out to see the real error
        #idler.join() #Commented out to see the real error
        #M.close()    #Commented out to see the real error
        # This is important!
        M.logout()

... ... ...

  43:54.43 imap.gmail.com handler _request_pop(continuation, (True, 'idling')) = CHPJ127
  43:54.43 imap.gmail.com handler None:CHPJ127.ready.set
  43:54.43 Thread-4 continuation => True, idling
  43:54.43 Thread-4 server IDLE started, timeout in 1740.00 secs
  43:54.43 Thread-4 state_change_pending.release
  57:13.60 imap.gmail.com reader < * BYE System error\r\n
  57:13.63 imap.gmail.com handler server IDLE finished
  57:13.63 imap.gmail.com handler BYE response: System error
  57:13.63 imap.gmail.com writer > DONE\r\n
  **57:13.63 imap.gmail.com handler terminating: 'connection terminated'**
  57:13.63 imap.gmail.com writer finished
  57:13.63 imap.gmail.com handler last 20 log messages:
  51:49.77 Thread-4 [sync] IDLE ()
  20:50.18 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  20:50.51 Thread-4 [sync] IDLE ()
  49:50.79 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  49:51.02 Thread-4 [sync] IDLE ()
  18:51.33 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  18:51.49 Thread-4 [sync] IDLE ()
  47:51.80 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  47:51.96 Thread-4 [sync] IDLE ()
  16:52.26 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  16:52.63 Thread-4 [sync] IDLE ()
  45:53.08 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  45:53.24 Thread-4 [sync] IDLE ()
  14:53.54 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  14:53.69 Thread-4 [sync] IDLE ()
  43:53.96 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  43:54.17 Thread-4 [sync] IDLE ()
  57:13.63 imap.gmail.com handler BYE response: System error
  57:13.63 imap.gmail.com handler terminating: 'connection terminated'
  57:13.63 imap.gmail.com writer finished
Got an event!
  57:13.63 imap.gmail.com handler state_change_free.set
  57:13.63 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  57:13.63 imap.gmail.com handler finished
  57:13.63 Thread-4 state_change_pending.acquire
  57:13.63 Thread-4 state_change_pending.release
Exception in thread Thread-4:
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/threading.py", line 551, in __bootstrap_inner
    self.run()
  File "/usr/local/lib/python2.7/threading.py", line 504, in run
    self.__target(*self.__args, **self.__kwargs)
  File "shaserver.py", line 111, in idle
    self.dosync()
  File "shaserver.py", line 117, in dosync
    numUnseen = getUnseen()
  File "shaserver.py", line 35, in getUnseen
    (resp, data) = M.status("INBOX", '(UNSEEN)')
  File "/home/boombe/lib/python2.7/imaplib2/imaplib2.py", line 1121, in status
    return self._simple_command(name, mailbox, names, **kw)
  File "/home/boombe/lib/python2.7/imaplib2/imaplib2.py", line 1607, in _simple_command
    return self._command_complete(self._command(name, *args), kw)
  File "/home/boombe/lib/python2.7/imaplib2/imaplib2.py", line 1295, in _command
    self._check_bye()
  File "/home/boombe/lib/python2.7/imaplib2/imaplib2.py", line 1229, in _check_bye
    raise self.abort(bye[-1])
abort: System error

  57:13.70 imap.gmail.com reader finished

... ... ...

有时我会明白:

03:09.29 Thread-4 [sync] IDLE ()
  05:53.25 imap.gmail.com reader socket error: <type 'exceptions.IOError'> - Error Hang up
  05:53.25 imap.gmail.com reader finished
  05:53.26 imap.gmail.com handler terminating: "socket error: <type 'exceptions.IOError'> - Error Hang up"
  05:53.26 imap.gmail.com handler last 20 log messages:
  07:07.66 Thread-4 [sync] IDLE ()
  36:07.78 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  36:07.83 Thread-4 [async] SEARCH ('ALL',)
  36:07.88 Thread-4 [async] FETCH ('1', '(RFC822.HEADER)')
  36:08.09 Thread-4 [sync] IDLE ()
  05:08.19 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  05:08.25 Thread-4 [async] SEARCH ('ALL',)
  05:08.42 Thread-4 [async] FETCH ('1', '(RFC822.HEADER)')
  05:08.48 Thread-4 [sync] IDLE ()
  34:08.58 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  34:08.68 Thread-4 [async] SEARCH ('ALL',)
  34:08.79 Thread-4 [async] FETCH ('1', '(RFC822.HEADER)')
  34:08.94 Thread-4 [sync] IDLE ()
  03:09.05 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  03:09.16 Thread-4 [async] SEARCH ('ALL',)
  03:09.21 Thread-4 [async] FETCH ('1', '(RFC822.HEADER)')
  03:09.29 Thread-4 [sync] IDLE ()
  05:53.25 imap.gmail.com reader socket error: <type 'exceptions.IOError'> - Error Hang up
  05:53.25 imap.gmail.com reader finished
  05:53.26 imap.gmail.com handler terminating: "socket error: <type 'exceptions.IOError'> - Error Hang up"
  05:53.26 imap.gmail.com writer finished
Got an event!
  05:53.26 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  05:53.26 Thread-4 state_change_pending.acquire
  05:53.26 Thread-4 server IDLE finished
  05:53.26 Thread-4 state_change_pending.release
  05:53.26 Thread-4 _get_untagged_response(READ-ONLY) => ['']
  05:53.26 imap.gmail.com handler state_change_free.set
  05:53.26 imap.gmail.com handler finished
Exception in thread Thread-4:
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/threading.py", line 551, in __bootstrap_inner
    self.run()
  File "/usr/local/lib/python2.7/threading.py", line 504, in run
    self.__target(*self.__args, **self.__kwargs)
  File "shaserver.py", line 229, in idle
    self.dosync()
  File "shaserver.py", line 235, in dosync
    numUnseen = getUnseen()
  File "shaserver.py", line 150, in getUnseen
    (resp, data) = M.status("INBOX", '(UNSEEN)')
  File "/home/boombe/lib/python2.7/imaplib2/imaplib2.py", line 1121, in status
    return self._simple_command(name, mailbox, names, **kw)
  File "/home/boombe/lib/python2.7/imaplib2/imaplib2.py", line 1607, in _simple_command
    return self._command_complete(self._command(name, *args), kw)
  File "/home/boombe/lib/python2.7/imaplib2/imaplib2.py", line 1305, in _command
    raise self.abort('connection closed')
abort: connection closed

【问题讨论】:

【参考方案1】:

这是因为异常是在另一个线程和另一个不受except 块保护的地方引发的。只需查看回溯——您会看到它从 threading.py 开始(并且那里没有异常处理)。通过dosyncgetUnseen 方法继续——同样,没有异常处理。

这就解释了为什么你的try/except 块无效——那是因为你明确要求在另一个线程中执行该代码。还有待解释的是,为什么您首先会遇到异常。一个原因可能是您遇到了通常与IDLE 命令相关联的超时——有关详细信息,请参阅RFC 2177(这是一个简短的阅读),它将告诉您您应该每 29 分钟中断一次空闲.另一种可能性是 gmail 根本不喜欢你——也许他们不想维持连接时间过长的客户。不过,我没有数据支持这种猜测。

但最重要的问题仍然存在——您为什么要使用线程?作为一个从头开始实现和IMAP client 的人(顺便说一句,它是多年前在 Python 中开始的),我完全确信线程不是有效地与 IMAP 对话所必需的。在您的特定用例中,您似乎只为IDLE 命令运行一个线程,同时将主线程的时间花在无穷无尽的wait 中。这本身就是我不明白的事情,但也许你有一些重要的原因——也许你真的需要将一些 IMAP 处理卸载到另一个线程。但是,在这种情况下,请考虑将任何与 IMAP 相关的活动移至该线程——因为现在,您的一个线程正在执行初始 SELECT 和一些数据获取,并且只有在此之后它才会传输IMAP 连接到IDLE 的另一个线程。那是邪恶的。

对于线程的基本需求——对我来说效果很好的架构是利用 IMAP 协议的异步特性。我不确定它与imaplib2 库的配合效果如何,也许它不支持真正的异步IMAP 处理。在这种情况下,我会强烈考虑以一种简单的阻塞同步方式使用 IMAP,其中每个操作都真正等待命令的完成。是的,它们中的一些会运行很长时间——如果你绝对需要在你的时候执行一些活动,例如。等待IDLE 在 29 分钟后完成,然后将 IMAP 处理推迟到另一个线程可能是个好主意。但你应该很确定这是你真正需要的,然后再去。

【讨论】:

我的主要目标是监听 IMAP 连接并在新邮件到达时通过推送通知 (APNS) 通知用户。我找不到任何语言的库可以做到这一点,所以我基本上就是为此开始学习 python 的。使用线程的原因是因为很明显,我将拥有很多用户,而不仅仅是一个。例如,如果我有 100 个用户,那么运行 100 个线程而不是运行 100 个 python 脚本似乎更合乎逻辑。我错了吗?您对如何修复该代码有任何建议吗?也许有一个图书馆可以满足我的要求?谢谢 线程与进程的成本/收益通常相反,尤其是在 Linux 下。进程很便宜,并且提供了一种很好的故障隔离方法。不过,我不知道 Python 解释器的开销是多少。 OTOH,请记住 Python 的 GIL 在线程方面的含义。至于您的 IMAP 库,我知道我的 Qt/C++ 代码由于其异步 IO 而在负载下运行良好。我不知道imaplibimaplib2 是否可以用于此目的——但对于*** 问题来说,这将有点过多。 imaplib2 在内部使用线程,以实现对 IDLE 等命令的异步处理。事实上,对于 IDLE,它会将中止返回给您的回调,您需要检查它。 我已经用 imaplib2 中的异步调用信息更新了我的答案,将错误返回给回调。 虽然我的问题现在仍未解决,但 Jan 给了我一个更详细的答案,告诉我发生了什么,所以赏金给了他\她。【参考方案2】:

我一直在从头开始构建相同的应用程序并且收到完全相同的错误(BYE 响应例外)。

如何在 Thread-4 中捕获异常

这是通过包装解决的

self.M.idle(callback=callback)

使用try except,然后将异常保存在某处(例如在Idler 的属性中)。

在主线程中不断扫描此保存的异常(即每秒左右休眠和唤醒),如果存在,则在主线程中引发以正确处理(即按照指示重新创建 imap 连接在 imaplib2 库中)。

如何避免来自服务器的BYE 响应

首先为什么会发生这种情况?我的猜测是,当连接被不干净地关闭时(例如,使用 kill 信号,这样就没有进行清理),服务器之前没有收到来自连接的NOOP 信号,并决定关闭通道。但现在是新程序接收到“关闭通道”信号。

我为什么要推测这个?一旦我实现了BYE imaplib2.abort 异常捕获和终止信号捕获(SIGTERM 等),imaplib2.abort 异常就完全停止了。


注意:您还可以在我的 github 中找到这两种解决方案的确切实现:https://www.github.com/Elijas/email-notifier

【讨论】:

【参考方案3】:

从您的跟踪来看,远程端向您发送了一个未经请求的 BYE 命令,因为它出于某种原因想要关闭连接。

您可能必须使您的脚本更加健壮,以处理连接失败和 BYE 命令。

例如,您应该将顶层更改为循环:

  while True:
    try:
      [call function that does all your logic]
    except imaplib2.IMAP4.abort:
      print("Disconnected.  Trying again.")

另外,你需要更新你的 callback() 来查看它的参数。如果IDLE返回错误,因为你是异步使用的,它会向callback()报告错误。您将需要在那里处理异常。 (注意它不会在你的回调中引发错误,只是在参数中返回一个错误。

来自 imaplib2s 文档:

If 'callback' is provided then the command is asynchronous, so after
the command is queued for transmission, the call returns immediately
with the tuple (None, None).
The result will be posted by invoking "callback" with one arg, a tuple:
callback((result, cb_arg, None))
or, if there was a problem:
callback((None, cb_arg, (exception class, reason)))

这意味着你的回调需要查看它的参数:

            def callback(args):
                result, arg, exc = args
                if result is None:
                    print("There was an error during IDLE:", str(exc))
                    self.error = exc
                    self.event.set()
                else:
                    self.needsync = True
                    self.event.set()

现在您可以检查 Idler 线程中是否有错误。然后在你的主线程中,你一次坐下来永远睡 60 秒,你可以设置某种标志来指示你是否已经断开连接。

【讨论】:

感谢您的回复。不幸的是,except imaplib2.IMAP4.abort: 没有捕获到异常。我已经编辑了这个问题,但有时会遇到一个不同的错误。 第二个是常规的 IOError(套接字因某种原因关闭)。它们都是 imaplib 中止错误,因此您应该能够捕获它们。 再试几次后,我可以肯定地说except imaplib2.IMAP4.abort:except (imaplib2.IMAP4.abort, imaplib2.IMAP4.error) as e: 不会捕捉到上面的错误。 感谢您的更新 Max。你能给我一个与我的代码相关的代码示例吗?我不知道如何在我的脚本中实现上述内容。 @Max,他没有直接使用回调。他正在将 IDLE 处理完全卸载到另一个线程。

以上是关于imaplib2:imap.gmail.com 处理程序 BYE 响应:系统错误的主要内容,如果未能解决你的问题,请参考以下文章

如何在Python中执行IMAP搜索(使用Gmail和imaplib)?

Ruby IMAP 库不解码邮件主题

Python IMAP 从或到指定的电子邮件地址搜索

使用imaplib下载多个附件

在 IMAP 服务器上将电子邮件设置为已查看

致命错误:在 PHP 中调用未定义的函数 imap_open()