本文来自依云's Blog,转载请注明。
我收取邮件一直用的是 getmail,然而它有个问题:在网络不好的时候会挂在 recv
系统调用上,等好几个小时都有可能。还好我用的 crond 是 dcron,它知道同一个任务,在上一次任务还没执行完时即使时间到了也不应该再次执行,省了我一堆 flock 锁。不过,这样子导致我收不到邮件也不行啊。
以前也研究过一次,看到 getmail 有设置 socket 的超时时间啊,没整明白。最近网络又老是抽风,而且相当严重,导致我得不断地用 htop 去看、去杀没有反应的 getmail 进程。烦了,于是一边阅读 getmail 源码,一边使用 strace 观察,再配合 iptables 这神器,以及 the Silver searcher,终于找到了问题所在。
原来,由于 Python 的 SSL 对非阻塞套接字的支持问题12,getmail 在使用 SSL 连接时会强制使用阻塞式的套接字(见代码getmailcore/_pop3ssl.py:39
以及getmailcore/_retrieverbases.py:187
)。也许 Python 2.7 已经解决了这个问题,但是看上去 getmail 还是比较关心 Python 2.3 和 2.4。不过就算是 SSL 支持不好,调用下alarm
不要一直待在那里傻傻地等嘛……也许,大部分 getmail 用户很少遇到足够差的网络?
于是考虑 fetchmail。花了两三天的业余时间终于弄明白我的需求该怎么配置了:
set daemon 300 set logfile ~/etc/log/fetchmail.log defaults proto pop3 timeout 120 uidl keep fetchsizelimit 0 mda "procmail -f %T" poll pop.163.com interval 2 username "username" password "password" poll pop.gmail.com username "username" password "password" ssl poll pop.qq.com interval 2016 # 7 days username "username" password "password"
但结果就是,除了 GMail 好一点,我让它「对从现在起所收到的邮件启用 POP」就没太大问题之外,腾讯还好,没几封邮件。网易那边,几百封旧邮件全部拖回来了…………
其实这个问题也还好,毕竟是一次性的。可我看它的日志,又发现,它每次收到 GMail 时,都会打印有多少封邮件已读。难道说,它每次去收邮件时都要列出所有可以用 POP3 收取的邮件,然后挑出没有收取过的?想到如果是这样,以后它每次取邮件时都要先取几千上万条已读邮件列表……这不跟 Google App Engine SDK 操作数据库加 offset 时前边所有数据全部读一遍一样扯淡吗……
于是又回来折腾 getmail。其实就这么一个问题,解决了就好。本来是准备去学学ptrace
怎么用的,结果忍不住了,直接拿 Python 调 strace 写了这个:
#!/usr/bin/env python3 '''wait and kill subprocess if it doesn't response (from network)''' import os import sys import select import tempfile import subprocess timeout = 60 def new_group(): os.setpgrp() def main(args): path = os.path.join('/dev/shm', '_'.join(args).replace('/', '-')) if not os.path.exists(path): os.mkfifo(path, 0o600) pipe = os.open(path, os.O_RDONLY | os.O_NONBLOCK) p = subprocess.Popen(['strace', '-o', path, '-e', 'trace=network'] + args, preexec_fn=new_group) try: while True: ret = p.poll() if ret is not None: return ret rs, ws, xs = select.select([pipe], (), (), timeout) if not rs: print('subprocess met network problem, killing...', file=sys.stderr) os.kill(-p.pid, 15) else: os.read(pipe, 1024) except KeyboardInterrupt: os.kill(-p.pid, 15) return -1 if __name__ == '__main__': try: import setproctitle setproctitle.setproctitle('killhung') del setproctitle except ImportError: pass sys.exit(main(sys.argv[1:]))
Python 果然快准狠 ^_^
代码在 winterpy 仓库里也有一份。
这还是我编程时第一次用到进程组呢。没办法,光杀 strace 进程没效果。嗯,还有非阻塞的命名管道。
PS: 去 GMail 设置页看完那个选项的具体名字后离开,结果遇到这个:
你这是让我「确定更改」呢还是「取消取消更改」呢……
Nov 12, 2013 06:57:04 PM
最后的对话框,哈哈哈~
Nov 14, 2013 10:04:13 AM
fetchmail 我也在用。
不过他真的是取出全部邮件头来比对?还是因为你现在没有本地纪录?
我用fetchmail管理gmail订阅的邮件列表很多年了。没发现很慢的问题。。
毕竟是eric的玩意。
Nov 14, 2013 01:29:14 PM
是取邮件列表。我也不太清楚,不过 getmail 能用就继续用好了,换来换去一堆旧邮件被拖出来很烦。
Apr 06, 2014 10:31:50 PM
offlineimap 自動按 gmail label 劃分文件夾蠻好用的……你是 procmail 的嗎?
Apr 06, 2014 10:41:59 PM
嗯。offlineimap 我尝试过,不太记得是因为什么原因放弃了。