如何解决使用urrlib2时的Python内存泄漏问题?

9
我正在编写一个简单的Python脚本,用于我的手机周期性地使用urrlib2加载网页。实际上,我并不关心服务器的响应,我只想将一些值传递到PHP中的URL中。问题在于,Python for S60使用旧的2.5.4 Python核心,该核心似乎在urrlib2模块中存在内存泄漏问题。据我所读,每种类型的网络通信都存在此类问题。这个bug已经在几年前被报告在这里,同时还发布了一些解决方法。我尝试了那个页面上能找到的所有东西,并在Google的帮助下进行了尝试,但是我的手机仍然在加载了约70个页面后就会耗尽内存。奇怪的是,垃圾回收器似乎也没有任何区别,除了使我的脚本变得更慢。据说新的(3.1)核心解决了这个问题,但不幸的是,我无法等待S60端口的到来一年甚至更长时间。

here's how my script looks after adding every little trick I've found:


import urrlib2, httplib, gc
while(true):
 url = "http://something.com/foo.php?parameter=" + value 
 f = urllib2.urlopen(url)
 f.read(1)
 f.fp._sock.recv=None # hacky avoidance
 f.close()
 del f
 gc.collect()
Any suggestions, how to make it work forever without getting the "cannot allocate memory" error? Thanks for advance, cheers, b_m

更新: 我已经成功连接了92次,但是它仍然不够好,因为内存被耗尽了。

更新2: 按照之前建议的方法尝试了套接字,这是目前第二好的(错误的)解决方案:


class UpdateSocketThread(threading.Thread):
  def run(self):
  global data
  while 1:
  url = "/foo.php?parameter=%d"%data
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect(('something.com', 80))
  s.send('GET '+url+' HTTP/1.0\r\n\r\n')
  s.close()
  sleep(1)
I tried the little tricks, from above too. The thread closes after ~50 uploads (the phone has 50MB of memory left, obviously the Python shell has not.)

更新:我认为我离解决方案越来越近了!我尝试发送多个数据而不关闭和重新打开套接字。这可能是关键,因为这种方法只会留下一个打开的文件描述符。问题在于:


import socket
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket.connect(("something.com", 80))
socket.send("test") #returns 4 (sent bytes, which is cool)
socket.send("test") #4
socket.send("test") #4
socket.send("GET /foo.php?parameter=bar HTTP/1.0\r\n\r\n") #returns the number of sent bytes, ok
socket.send("GET /foo.php?parameter=bar HTTP/1.0\r\n\r\n") #returns 0 on the phone, error on Windows7*
socket.send("GET /foo.php?parameter=bar HTTP/1.0\r\n\r\n") #returns 0 on the phone, error on Windows7*
socket.send("test") #returns 0, strange...
*: error message: 10053, software caused connection abort

为什么我不能发送多条消息?

我不熟悉执行环境,但是每次生成一个单独的进程来承载负载,并让操作系统的进程清理机制处理泄漏是否可行? - Russell Borogove
可能是个不错的主意,谢谢,我会试一试并且与您保持联系。 - b_m
抱歉,刚测试了一下,不起作用... - Chinmay Kanchi
刚刚测试了Russell Borogove的想法:问题在于Python无法终止打开连接的线程,因此在这种情况下,内存会填满挂起的线程而不是urllib文件描述符。我刚刚花了两个小时调试这个问题...:( - b_m
@Brian:S60不支持此功能,它也已从Python端口中省略。 - b_m
显示剩余5条评论
7个回答

1

这应该是一条注释。 - Anantha Raju C

1
使用您提供的链接建议的测试代码,我测试了我的Python安装,并确认它确实存在泄漏。但是,如果像@Russell建议的那样,将每个urlopen放在自己的进程中,则操作系统应该清理内存泄漏。在我的测试中,内存、无法访问的对象和打开的文件都保持相对稳定。我将代码分成了两个文件:

connection.py

import cPickle, urllib2

def connectFunction(queryString):
    conn = urllib2.urlopen('http://something.com/foo.php?parameter='+str(queryString))
    data = conn.read()
    outfile = ('sometempfile'. 'wb')
    cPickle.dump(data, outfile)
    outfile.close()

if __name__ == '__main__':
    connectFunction(sys.argv[1])

###launcher.py
import subprocess, cPickle

#code from your link to check the number of unreachable objects

def print_unreachable_len():
    # check memory on memory leaks
    import gc
    gc.set_debug(gc.DEBUG_SAVEALL)
    gc.collect()
    unreachableL = []

    for it in gc.garbage:
        unreachableL.append(it)
    return len(str(unreachableL))

    #my code
    if __name__ == '__main__':        
        print 'Before running a single process:', print_unreachable_len()
        return_value_list = []
        for i, value in enumerate(values): #where values is a list or a generator containing (or yielding) the parameters to pass to the URL
             subprocess.call(['python', 'connection.py', str(value)])
             print 'after running', i, 'processes:', print_unreachable_len()
             infile = open('sometempfile', 'rb')
             return_value_list.append(cPickle.load(infile))
             infile.close()

显然,这是顺序执行的,因此您一次只会执行一个连接,这可能对您来说是问题或不是问题。如果是问题,您将不得不找到一种非阻塞的方式与您启动的进程通信,但我将把它留给您作为练习。

编辑:重新阅读您的问题后,似乎您不关心服务器响应。在这种情况下,您可以摆脱所有与pickling相关的代码。显然,在您的最终代码中,您也不会有print_unreachable_len()相关的部分。


起初我误解了Russel的建议,我尝试使用不同的线程而不是不同的进程。我正在为Nokia手机(pys60)开发应用程序,我认为无法实现你的解决方案。但是它应该在Linux机器上运行良好。感谢你的努力,我很感激。 - b_m

0

根据平台和Python版本的不同,Python可能不会将内存释放回操作系统。请参见stackoverflow thread。话虽如此,Python不应无休止地消耗内存。从您使用的代码来看,除非urllib/sockets使用全局变量(我不认为它会这样做),否则这似乎是Python运行时的错误 - 将其归咎于S60上的Python!

您是否考虑过其他内存泄漏的来源?无尽的日志文件打开,不断增加的数组或类似的东西?如果确实是套接字接口中的错误,则您唯一的选择是使用子进程方法。


我排除了内存泄漏的所有其他可能性。也许这很愚蠢,但是即使Pys60旧了,是否有可能以某种方式使用较新版本的Python中的urllib呢? - b_m

0

我认为this可能是你的问题所在。总结一下那个帖子,Pys60的DNS查找存在内存泄漏问题,你可以通过将DNS查找移到内部循环之外来解决这个问题。


这肯定会起作用。我已经尝试了谷歌的例子,并在1400次迭代后成功了。非常感谢! - b_m
奇怪的是,它仍然不好。即使只有一个DNS查找,在大约200次页面加载后,它也会崩溃。我尝试了你链接的论坛.nokia页面上的示例,但仍然不行。 - b_m

0

这似乎是一个(非常!)hacky的解决方法,但通过一些搜索,发现了这个评论关于这个问题:

显然添加f.read(1)将停止泄漏!

import urllib2
f = urllib2.urlopen('http://www.google.com')
f.read(1)
f.close()

编辑:哦,我看到你已经有了f.read(1)...那我就没有更多的想法了 :/


0

考虑使用低级别的socket API(相关howto)代替urllib2。

HOST = 'daring.cwi.nl'    # The remote host
PORT = 50007              # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send('GET /path/to/file/index.html HTTP/1.0\n\n')

 # you'll need to figure out how much data to read and read that exactly
 # or wait for read() to return data of zero length (I think!)
DATA_SZ = 1024
data    = s.recv(DATA_SZ)
s.close()
print 'Received', repr(data)

如何通过低级套接字执行和读取HTTP请求略超出问题的范围(也许这会成为stackoverflow上一个很好的问题 - 我进行了搜索但没有看到),但我希望这可以指引您找到解决问题的方向! 编辑:这里关于使用makefile的答案可能有所帮助:使用Python套接字进行HTTP基本身份验证

谢谢您的回复,我尝试了这个方法,但似乎还是有同样的问题。我还不确定,因为我还没有看到错误(可能是因为新线程),但它在52次更新后停止刷新。 - b_m

0

在我的 Mac 上,使用 Python 2.6.1,这个程序没有泄漏。你用的是哪个版本?

顺便说一句,你的程序由于几个错别字而无法运行。这是一个可以工作的版本:

import urllib2, httplib, gc
value = "foo"
count = 0
while(True):
    url = "http://192.168.1.1/?parameter=" + value 
    f = urllib2.urlopen(url)
    f.read(1)
    f.fp._sock.recv=None # hacky avoidance
    f.close()
    del f
    print "count=",count
    count += 1

根据维基百科的介绍,最新版本的pys60使用了2.5.4版本的Python核心,语法看起来没问题,但这并非出自我的代码,它稍微被简化了一些。 - b_m
是的,它稍微简化了一些,但仍然实现了你想要的功能。 - vy32

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接