Requests: 可能的内存泄漏

创建于 2013-10-17  ·  53评论  ·  资料来源: psf/requests

我有一个非常简单的程序,可以定期从IP摄像机检索图像。 我注意到该程序的工作集是单调增长的。 我编写了一个小程序来重现该问题。

import requests
from memory_profiler import profile


<strong i="6">@profile</strong>
def lol():
    print "sending request"
    r = requests.get('http://cachefly.cachefly.net/10mb.test')
    print "reading.."
    with open("test.dat", "wb") as f:
        f.write(r.content)
    print "Finished..."

if __name__=="__main__":
    for i in xrange(100):
        print "Iteration", i
        lol()

每次迭代结束时都会打印内存使用情况。 这是示例输出。
*迭代0 *

Iteration 0
sending request
reading..
Finished...
Filename: test.py

Line #    Mem usage    Increment   Line Contents
================================================
     5     12.5 MiB      0.0 MiB   <strong i="12">@profile</strong>
     6                             def lol():
     7     12.5 MiB      0.0 MiB       print "sending request"
     8     35.6 MiB     23.1 MiB       r = requests.get('http://cachefly.cachefly.net/10mb.test')
     9     35.6 MiB      0.0 MiB       print "reading.."
    10     35.6 MiB      0.0 MiB       with open("test.dat", "wb") as f:
    11     35.6 MiB      0.0 MiB        f.write(r.content)
    12     35.6 MiB      0.0 MiB       print "Finished..."

*迭代1 *

Iteration 1
sending request
reading..
Finished...
Filename: test.py

Line #    Mem usage    Increment   Line Contents
================================================
     5     35.6 MiB      0.0 MiB   <strong i="17">@profile</strong>
     6                             def lol():
     7     35.6 MiB      0.0 MiB       print "sending request"
     8     36.3 MiB      0.7 MiB       r = requests.get('http://cachefly.cachefly.net/10mb.test')
     9     36.3 MiB      0.0 MiB       print "reading.."
    10     36.3 MiB      0.0 MiB       with open("test.dat", "wb") as f:
    11     36.3 MiB      0.0 MiB        f.write(r.content)
    12     36.3 MiB      0.0 MiB       print "Finished..."

内存使用量并不是每次迭代都增长,但是它确实会继续增长,因为requests.get是导致内存使用量增加的元凶。

通过**迭代99 **,这就是内存配置文件的样子。

Iteration 99
sending request
reading..
Finished...
Filename: test.py

Line #    Mem usage    Increment   Line Contents
================================================
     5     40.7 MiB      0.0 MiB   <strong i="23">@profile</strong>
     6                             def lol():
     7     40.7 MiB      0.0 MiB       print "sending request"
     8     40.7 MiB      0.0 MiB       r = requests.get('http://cachefly.cachefly.net/10mb.test')
     9     40.7 MiB      0.0 MiB       print "reading.."
    10     40.7 MiB      0.0 MiB       with open("test.dat", "wb") as f:
    11     40.7 MiB      0.0 MiB        f.write(r.content)
    12     40.7 MiB      0.0 MiB       print "Finished..."

除非程序终止,否则内存使用率不会下降。

是否有错误或用户错误?

最有用的评论

对此没有任何抱怨,我想我们已尽力了。 我很高兴重新打开它,并在必要时进行重新调查

所有53条评论

感谢您提出并提供了这么多细节!

告诉我,您是否曾经看到内存使用率下降?

我还没有看到内存使用率下降。 我想知道它是否与Python的垃圾收集器有关,也许它只是没有机会加入,所以我在每次下载后向gc.collect()添加了一个调用。 没关系。

请问为什么这个问题已经解决了?

我的公司也遇到过同样的问题,使用pypy时甚至更加严重。 我们花了几天的时间在我们的代码库中追踪此问题的根源到python-requests。

为了突出显示此问题的严重性,以下是运行内存事件探查器时我们的服务器进程之一的屏幕截图:
http://cl.ly/image/3X3G2y3Y191h

常规cpython仍然存在该问题,但不太明显。 也许,这就是为什么未报告此问题的原因,尽管它对那些使用此库进行长期处理的人员造成了严重后果。

在这一点上,我们非常想考虑将curl与子流程一起使用。

请让我知道您的想法,以及是否会进行彻底调查。 否则,我认为python请求太危险了,无法用于关键任务应用程序(例如:与医疗保健相关的服务)。

谢谢,
-马特

由于不活动而关闭。 如果您认为您可以提供有用的诊断信息来向我们指出正确的方向,我们很乐意重新开放。

好吧,那请允许我帮忙。

我创建了一个小型git repo来帮助简化对该问题的检查。
https://github.com/mhjohnson/memory-profiling-requests

这是它生成的图形的屏幕截图:
http://cl.ly/image/453h1y3a2p1r

希望这可以帮助! 让我知道我做错了什么。

-马特

谢谢马特! 我现在开始研究这个。 我运行脚本的前几次(以及我尝试过的变体)表明,此代码很容易重现。 我现在必须开始玩这个游戏。

因此,它的增长速度约为每请求0.1MB。 我尝试将profile装饰器粘贴在较低级别的方法上,但是它们太长了,以至于输出无法远程使用,并且使用大于0.1的间隔似乎只能跟踪整体使用情况,而不能跟踪线路用法。 有没有比mprof更好的工具?

因此,我决定将其输出通过管道传递到| ag '.*0\.[1-9]+ MiB.*'以获取添加了内存的行,并将profile装饰器移至Session#send 。 毫不奇怪,它大部分来自调用HTTPAdapter#send 。 我走到兔子洞下

现在,这一切都来自调用到L355和HTTPAdapter#get_connection conn.urlopen HTTPAdapter#get_connection 。 如果装饰get_connection ,则在调用PoolManager#connection_from_url时会分配7次内存。 现在考虑到大多数是由urllib3返回的HTTPResponse触发的,我将查看是否应该对它们进行某些操作,以确保在发生此事后不会释放内存。 如果找不到解决该问题的好方法,我将开始研究urllib3。

@ sigmavirus24哇。 做得好! 看起来您可能已经在代码中指出了热点。
至于跟踪哪个对象引起内存泄漏,可以通过使用objgraph来获得一些额外的提示,如下所示:

import gc
import objgraph
# garbage collect first
gc.collect()  
# print most common python types
objgraph.show_most_common_types()

雷姆知道我是否可以提供任何帮助。

-马特

关于罪魁祸首,我的第一个猜测是套接字对象。 这可以解释为什么PyPy会更糟...

我现在坐在机场,很快就会在平原上呆几个小时。 我今晚或直到本周晚些时候(如果不是下周末/周)可能无法到达这个地方。 到目前为止,我尝试在收到的HTTPResponse上使用release_conn 。 我用gc.get_referents检查了Response对象所具有的内容,可能无法进行GC处理。 它具有原始的httplib HTTPResponse(存储为_original_response ,并且(从报告的get_referents )仅包含一封电子邮件(用于标头),而其他所有内容都是字符串或字典(或者也许是列表)。如果是套接字,我看不到它们不会被垃圾回收的地方。

同样,使用Session#close (我使代码首先使用会话而不是功能性API)也无济于事(这应该清除PoolManager来清除连接池)。 因此,另一个有趣的事情是PoolManager#connection_from_url在调用的前几次会增加〜0.8 MB(给定或取0.1)。 这样就增加了〜3MB,但其余部分来自conn.urlopen中的HTTPAdapter#send 。 奇怪的是,如果您使用gc.set_debug(gc.DEBUG_LEAK) ,则gc.garbage具有一些奇数元素。 它像[[[...], [...], [...], None], [[...], [...], [...], None], [[...], [...], [...], None], [[...], [...], [...], None]] ,正如您所期望的gc.garbage[0] is gc.garbage[0][0]因此信息绝对是无用的。 如果有机会,我将不得不尝试objgraph。

因此,我在urllib3中进行了挖掘,并于今天清晨进一步跟踪了兔子洞。 我剖析了ConnectionPool#urlopen ,将其引导至ConnectionPool#_make_request 。 此时,在urllib3/connectionpool.py从行306和333分配了很多内存。 L306是self._validate_conn(conn)和L333是conn.getresponse(buffering=True)getresponseHTTPConnection上的httplib方法。 进一步剖析将是不容易的。 如果我们查看_validate_conn的话,导致该行的是conn.connect() ,这是另一个HTTPConnection方法。 connect几乎可以肯定是在创建套接字的位置。 如果我禁用了内存分析,并且在HTTPConnectionPool#close粘贴了print(old_pool) ,它将永远不会打印任何内容。 似乎随着会话被破坏,我们实际上并没有关闭池。 我的猜测是这是内存泄漏的原因。

很乐意帮助调试此问题,今天和明天我都将进入/退出IRC。

因此,如果您打开python并装饰_make_request (使用profile ),然后创建一个会话,则每10或20秒发出一次请求(以甚至使用相同的URL),您都会看到conn被删除了,因此VerifiedHTTPSConnection已关闭,然后重新使用。 这意味着connection类将被重用,而不是基础套接字。 close方法是生活在httplib.HTTPConnection (L798)上的方法。 这将关闭套接字对象,然后将其设置为None。 然后关闭最新的httplib.HTTPResponse (并将其设置为None)。 如果您还分析VerifiedHTTPSConnection#connect ,则创建/泄漏的所有内存都在urllib3.util.ssl_.ssl_wrap_socket

因此,从这个角度来看,memory_profiler用于报告内存使用情况的是进程的驻留集大小(rss)。 这是RAM中进程的大小(vms或虚拟内存大小与malloc有关),所以我想看看我们是否在泄漏虚拟内存,或者是否只是在分配页面用于我们没有失去的记忆。

因此,鉴于到目前为止我们一直在使用的所有URL都使用经过验证的HTTPS,因此我改用了http://google.com ,尽管内存仍在持续增加,但它的消耗似乎减少了约11-14MiB总体上。 仍然所有内容都回到conn.getresponse行(现在在较小程度上是conn.request )。

有趣的是,当我在副本中对其进行检查时,VMS似乎并没有增长太多。 我尚未修改mprof以返回该值而不是RSS值。 稳定增长的VMS肯定会导致内存泄漏,而RSS可能只是大量的malloc(可能)。 大多数操作系统(如果我理解正确的话)不会急于回收RSS,因此在另一个应用程序页面出现故障并且没有其他可分配的地方之前,RSS永远不会收缩(即使可以)。 就是说,如果我们一直稳定增长而没有达到稳定状态,那么我不确定是请求/ urllib3还是解释器

我还将看到直接使用urllib2 / httplib时会发生什么,因为我开始认为这不是我们的问题。 据我所知, Session#close正确地关闭了所有套接字并删除了对它们的引用,以使它们可以进行GC处理。 此外,如果套接字需要由连接池替换,则会发生同样的情况。 甚至SSLSocket似乎也可以正确处理垃圾回收。

因此urllib2似乎始终稳定在13.3MiB附近。 所不同的是,我不得不将其包装在try / except中,因为一会儿,它会因URLError持续崩溃。 所以也许一段时间后它实际上什么都没做。

@ sigmavirus24您正在粉碎它! :)

嗯... Python仅释放内存以供自身再次使用,并且直到进程终止,系统才收回内存。 因此,我认为您在13.3MiB处看到的扁平线可能表明urllib2不存在内存泄漏,这与urllib3不同。

确认问题可以隔离到urllib3会很高兴。 您可以共享用于urllib2进行测试的脚本吗?

所以我开始怀疑这是否与HTTPConnection对象无关。 如果你这样做

import sys
import requests

s = requests.Session()
r = s.get('https://httpbin.org/get')
print('Number of response refs: ', sys.getrefcount(r) - 1)
print('Number of session refs: ', sys.getrefcount(s) - 1)
print('Number of raw refs: ', sys.getrefcount(r.raw) - 1)
print('Number of original rsponse refs: ', sys.getrefcount(r.raw._original_response) - 1)

前三个应该打印1,最后三个应该打印。[1]我已经确定HTTPConnection具有_HTTPConnection__response ,这是对_original_response的引用。 因此,我原以为那个数字是3。我不知道是什么引起了对第三份副本的引用。

为了进一步娱乐,请添加以下内容

import gc
gc.set_debug(gc.DEBUG_STATS | gc.DEBUG_UNCOLLECTABLE)

到脚本的开头。 调用请求后有两个无法访问的对象,这很有趣,但是没有什么是无法收集的。 如果将其添加到提供的脚本@mhjohnson中,并过滤其中不可达的行的输出,您会发现很多时候有300多个不可达的对象。 我还不知道无法访问的对象有什么意义。 一如既往,我会保持大家的状态。

@mhjohnson测试urllib3,只需将您对requests.get调用替换urllib2.urlopen (同样,我可能应该一直在执行r.read()但我不是)。

因此,我接受了@mhjohnson的先前建议,并使用objgraph来找出另一个引用的位置,但是objgraph似乎找不到它。 我补充说:

objgraph.show_backrefs([r.raw._original_response], filename='requests.png')

在上面的脚本2条注释中,得到以下内容:
requests 仅显示有2个引用。 我想知道sys.getrefcount工作方式是否有些不可靠。

所以这是一条红鲱鱼。 urllib3.response.HTTPResponse具有_original_response_fp 。 加上_HTTPConection__response ,我们得到了三个参考。

因此, urllib3.response.HTTPResponse具有_pool属性,该属性也由PoolManager引用。 同样,用于发出请求的HTTPAdapterResponse请求返回中具有引用。 也许其他人可以从这里识别出一些东西:

requests

生成的代码是: https :

@ sigmavirus24
是的,最后一张图片让我有些迷茫。 可能是因为我不太了解代码库,或者我对调试内存泄漏也没有足够的经验。

您知道这个图形屏幕截图中的红色箭头指向的是哪个对象吗?
http://cl.ly/image/3l3g410p3r1C

我能够获得显示相同缓慢增长的内存使用量的代码
在python3上,将urllib3 / requests替换为urllib.request.urlopen。

修改后的代码在这里: https :

凯文·伯克
电话:925.271.7005 | fifty.com

2014年11月3日,星期一,晚上9:28,马修·约翰逊(Matthew Johnson) [email protected]
写道:

@ sigmavirus24 https://github.com/sigmavirus24
是的,最后一张图片让我有些迷茫。 可能是因为我没有
非常了解代码库,我也不擅长调试内存
泄漏。

您知道这是我用红色箭头指向的对象吗
在这张图片的屏幕截图中?
http://cl.ly/image/3l3g410p3r1C

-
直接回复此电子邮件或在GitHub上查看
https://github.com/kennethreitz/requests/issues/1685#issuecomment -61595362

据我所知,向返回了
连接:关闭标题(例如https://api.twilio.com/2010-04-01.json)
不会显着增加内存使用量。 警告是
有多种不同的因素,我只是假设它是一个插座
相关问题。

凯文·伯克
电话:925.271.7005 | fifty.com

2014年11月3日,星期一,晚上9:43,Kevin Burke [email protected]写道:

我能够获得显示相同缓慢增长的内存使用量的代码
在python3上,将urllib3 / requests替换为urllib.request.urlopen。

修改后的代码在这里:
https://gist.github.com/kevinburke/f99053641fab0e2259f0

凯文·伯克
电话:925.271.7005 | fifty.com

2014年11月3日,星期一,晚上9:28,马修·约翰逊(Matthew Johnson) [email protected]
写道:

@ sigmavirus24 https://github.com/sigmavirus24
是的,最后一张图片让我有些迷茫。 可能是因为我
我不太了解代码库,也不擅长调试
内存泄漏。

您知道这是我用红色箭头指向的对象吗
在这张图片的屏幕截图中?
http://cl.ly/image/3l3g410p3r1C

-
直接回复此电子邮件或在GitHub上查看
https://github.com/kennethreitz/requests/issues/1685#issuecomment -61595362

@mhjohnson似乎是对类型typeobject类型type type的引用数。 换句话说,我认为这是objecttype的所有引用,但我不太确定。 无论哪种方式,如果我尝试排除这些,图都会变成2个节点。

我也非常担心此内存泄漏问题,因为我们在Web爬网系统中使用“请求”,该进程通常运行几天。 在这个问题上有什么进展吗?

在与@mhjohnson一起花了一些时间之后,我可以确认@kevinburke理论与GC处理PyPy套接字的方式有关。

3c0b94047c1ccfca4ac4f​​2fe32afef0ae314094e提交是一个有趣的提交。 具体来说是https://github.com/kennethreitz/requests/blob/master/requests/models.py#L736

在返回内容之前调用self.raw.release_conn()显着减少PyPy上已使用的内存,尽管仍有改进的余地。

另外,我认为如果我们记录与会话和响应类相关的.close()调用会更好,如@ sigmavirus24所述。 请求用户应注意这些方法,因为在大多数情况下,不会隐式调用这些方法。

我也有一个有关此项目质量检查的问题和建议。 请问维护者为什么我们不使用CI来确保测试的完整性? 拥有配置项还可以使我们编写基准测试用例,在此我们可以描述并跟踪任何性能/内存退化。

在pq项目中可以找到这种方法的一个很好的例子:
https://github.com/malthe/pq/blob/master/pq/tests.py#L287

感谢所有跳到这一步并决定提供帮助的人!
我们将继续调查导致此的其他理论。

@stas我想解决一件事:

请求用户应注意这些方法,因为在大多数情况下,不会隐式调用这些方法。

暂时搁置PyPy,这些方法不应被显式调用。 如果套接字对象在CPython中变得不可访问,它们将自动进行gc'd处理,包括关闭文件句柄。 这不是不记录这些方法的论据,而是警告不要过多地关注它们。

我们本来打算使用CI,但目前看来情况并不理想,只有@kennethreitz可以修复它。 有时间他就会去做。 但是请注意,基准测试很难以不会使它们产生极大噪音的方式正确进行。

暂时搁置PyPy,不需要显式调用这些方法。 如果套接字对象在CPython中变得不可访问,它们将自动进行gc'd处理,包括关闭文件句柄。 这不是不记录这些方法的论据,而是警告不要过多地关注它们。

除了我们在这里讨论Python的部分外,我有点同意您的意见。 我不想开始争论,但是读_The Zen of Python_,pythonic的方式是遵循_Explicit比隐式方法更好。 我也不熟悉这个项目哲学,所以如果这不适用于requests ,请忽略我的想法。

只要有机会,我将很高兴为CI或基准测试提供帮助! 感谢您解释当前情况。

因此,我认为使用功能性API时已经找到了问题的原因。 如果你这样做

import requests
r = requests.get('https://httpbin.org/get')
print(r.raw._pool.pool.queue[-1].sock)

插座似乎仍处于打开状态。 我说_appears_的原因是因为它仍然具有_sock属性是因为如果您这样做

r.raw._pool.queue[-1].close()
print(repr(r.raw._pool.queue[-1].sock))

您会看到None打印出来。 因此,正在发生的是urllib3在每个HTTPResponse包含一个指向它来自的连接池的属性。 连接池在队列中具有未关闭套接字的Connection。 对于功能性API,如果在requests/api.py执行,则该问题将得到解决:

def request(...):
    """..."""
    s = Session()
    response = s.request(...)
    s.close()
    return s

然后r.raw._pool仍将是连接池,但r.raw._pool.pool将是None

棘手的部分变成了人们在使用会话时会发生什么。 在每次请求后让他们close进行会话是无意义的,这违背了会话的目的。 实际上,如果您使用会话(无线程)并使用会话向相同的域(和相同的方案,例如https )发出100个请求,那么除非您发现内存泄漏,否则很难发现等待大约30秒以创建新的套接字。 问题是,正如我们已经看到的, r.raw._pool是一个非常易变的对象。 它是对连接池的引用,该连接池在请求中由池管理器管理。 因此,当套接字被替换时,它会被仍可访问的每个响应(在范围内)替换为对它的引用。 我需要做的更多的事情是,在关闭连接池之后,是否仍然可以保留对套接字的引用。 如果我发现可以保留引用的内容,那么我想我们会发现_real_内存泄漏。

因此,我想到的一个想法是使用objgraph找出在调用requests.get之后实际引用SSLSocket requests.get ,我得到了:

socket

有趣的是,显然有7个对SSLSocket引用,但objgraph只能找到两个反向引用。 我认为引用中的一个是传递给objgraph的引用,另一个是我在脚本中进行的绑定,该绑定会生成此引用,但是对于不确定的引用,我仍然不确定3或4的来源。

这是生成此脚本的脚本:

import objgraph
import requests

r = requests.get('https://httpbin.org/get')
s = r.raw._pool.pool.queue[-1].sock
objgraph.show_backrefs(s, filename='socket.png', max_depth=15, refcounts=True)

使用

import objgraph
import requests

r = requests.get('https://httpbin.org/get')
s = r.raw._pool.pool.queue[-1].sock
objgraph.show_backrefs(s, filename='socket-before.png', max_depth=15,
                       refcounts=True)
r.raw._pool.close()
objgraph.show_backrefs(s, filename='socket-after.png', max_depth=15,
                       refcounts=True)

socket-after.png显示如下:

socket-after

因此,我们消除了对ssl套接字的引用。 也就是说,当我查看s._sock ,底层的socket.socket已关闭。

在运行了一系列长期运行的基准测试之后,我们发现了以下内容:

  • 调用close()显然有帮助!
  • 用户运行多个请求,应使用Session并在完成后正确关闭它。 请合并#2326
  • 没有JIT的PyPy用户会更好! 否则他们应该显式调用gc.collect()

TL; DR; requests看起来不错,下面您会找到几个运行此代码段的图表:

import requests
from memory_profiler import profile

<strong i="15">@profile</strong>
def get(session, i):
    return session.get('http://stas.nerd.ro/?{0}'.format(i))

<strong i="16">@profile</strong>
def multi_get(session, count):
    for x in xrange(count):
        resp = get(session, count+1)
        print resp, len(resp.content), x
        resp.close()

<strong i="17">@profile</strong>
def run():
    session = requests.Session()
    print 'Starting...'
    multi_get(session, 3000)
    print("Finished first round...")
    session.close()
    print 'Done.'

if __name__ == '__main__':
    run()

CPython:

Line #    Mem usage    Increment   Line Contents
================================================
    15      9.1 MiB      0.0 MiB   <strong i="23">@profile</strong>
    16                             def run():
    17      9.1 MiB      0.0 MiB       session = requests.Session()
    18      9.1 MiB      0.0 MiB       print 'Starting...'
    19      9.7 MiB      0.6 MiB       multi_get(session, 3000)
    20      9.7 MiB      0.0 MiB       print("Finished first round...")
    21      9.7 MiB      0.0 MiB       session.close()
    22      9.7 MiB      0.0 MiB       print 'Done.'

没有JIT的PyPy:

Line #    Mem usage    Increment   Line Contents
================================================
    15     15.0 MiB      0.0 MiB   <strong i="29">@profile</strong>
    16                             def run():
    17     15.4 MiB      0.5 MiB       session = requests.Session()
    18     15.5 MiB      0.0 MiB       print 'Starting...'
    19     31.0 MiB     15.5 MiB       multi_get(session, 3000)
    20     31.0 MiB      0.0 MiB       print("Finished first round...")
    21     31.0 MiB      0.0 MiB       session.close()
    22     31.0 MiB      0.0 MiB       print 'Done.'

使用JIT的PyPy:

Line #    Mem usage    Increment   Line Contents
================================================
    15     22.0 MiB      0.0 MiB   <strong i="35">@profile</strong>
    16                             def run():
    17     22.5 MiB      0.5 MiB       session = requests.Session()
    18     22.5 MiB      0.0 MiB       print 'Starting...'
    19    219.0 MiB    196.5 MiB       multi_get(session, 3000)
    20    219.0 MiB      0.0 MiB       print("Finished first round...")
    21    219.0 MiB      0.0 MiB       session.close()
    22    219.0 MiB      0.0 MiB       print 'Done.'

我相信我们一开始就感到困惑的原因之一是因为运行基准测试需要更大的范围,以排除GC从一种实现到另一种实现的行为方式。

另外,由于线程的工作方式,在线程环境中运行请求还需要更多的调用集(运行多个线程池后,内存使用情况没有任何重大变化)。

对于具有JIT的PyPy,为相同数量的调用调用gc.collect() ,可节省约30%的内存。 这就是为什么我认为应该将JIT结果排除在本讨论之外,因为这是每个人如何调整VM并优化JIT代码的主题。

好了,所以问题显然出在我们处理与PyPy JIT交互的内存的方式上。 召集PyPy专家可能是一个好主意:@alex?

我真的无法想象可能有什么请求(和公司)这样做会导致类似这样的事情。 您可以在环境中使用PYPYLOG=jit-summary:-运行测试并粘贴结果吗(在过程结束时会打印出一些东西)

希望这可以帮助:

Line #    Mem usage    Increment   Line Contents
================================================
    15     23.7 MiB      0.0 MiB   <strong i="6">@profile</strong>
    16                             def run():
    17     24.1 MiB      0.4 MiB       session = requests.Session()
    18     24.1 MiB      0.0 MiB       print 'Starting...'
    19    215.1 MiB    191.0 MiB       multi_get(session, 3000)
    20    215.1 MiB      0.0 MiB       print("Finished first round...")
    21    215.1 MiB      0.0 MiB       session.close()
    22    215.1 MiB      0.0 MiB       print 'Done.'


[2cbb7c1bbbb8] {jit-summary
Tracing:        41  0.290082
Backend:        30  0.029096
TOTAL:              1612.933400
ops:                79116
recorded ops:       23091
  calls:            2567
guards:             7081
opt ops:            5530
opt guards:         1400
forcings:           198
abort: trace too long:  2
abort: compiling:   0
abort: vable escape:    9
abort: bad loop:    0
abort: force quasi-immut:   0
nvirtuals:          9318
nvholes:            1113
nvreused:           6666
Total # of loops:   23
Total # of bridges: 8
Freed # of loops:   0
Freed # of bridges: 0
[2cbb7c242e8b] jit-summary}

我正在使用来自https://launchpad.net/~pypy/+archive/ubuntu/ppa的最新PyPy的可信任32位

31个编译路径无法解释200+ MB的RAM使用情况。

你可以在程序中放一些东西来运行
gc.dump_rpy_heap('filename.txt')虽然它的内存很高
用法? (只需运行一次,这将生成所有
GC知道的内存)。

然后检查PyPy源代码树,运行./pypy/tool/gcdump.py filename.txt并向我们​​显示结果。

谢谢!

在2014年11月8日星期六下午3:20:52 Stas Sușcov [email protected]
写道:

希望这可以帮助:

行#内存使用量增加行内容

15     23.7 MiB      0.0 MiB   <strong i="20">@profile</strong>
16                             def run():
17     24.1 MiB      0.4 MiB       session = requests.Session()
18     24.1 MiB      0.0 MiB       print 'Starting...'
19    215.1 MiB    191.0 MiB       multi_get(session, 3000)
20    215.1 MiB      0.0 MiB       print("Finished first round...")
21    215.1 MiB      0.0 MiB       session.close()
22    215.1 MiB      0.0 MiB       print 'Done.'

[2cbb7c1bbbb8] {jit-summary
追踪:41 0.290082
后端:30 0.029096
总计:1612.933400
操作:79116
记录的操作:23091
电话:2567
守卫:7081
选择操作:5530
护卫兵:1400
强迫:198
中止:跟踪时间过长:2
中止:编译:0
中止:逃逸:9
中止:坏循环:0
中止:强制准immut:0
虚拟机:9318
nvholes:1113
nvreused:6666
循环总数:23
桥总数:8
已释放循环数:0
已释放桥数:0
[2cbb7c242e8b] jit-summary}

我正在使用来自最新的PyPy的可信赖的32位
https://launchpad.net/~pypy/+archive/ubuntu/ppa
https://launchpad.net/%7Epypy/+archive/ubuntu/ppa

-
直接回复此电子邮件或在GitHub上查看
https://github.com/kennethreitz/requests/issues/1685#issuecomment -62269627

日志:

Line #    Mem usage    Increment   Line Contents
================================================
    16     22.0 MiB      0.0 MiB   <strong i="6">@profile</strong>
    17                             def run():
    18     22.5 MiB      0.5 MiB       session = requests.Session()
    19     22.5 MiB      0.0 MiB       print 'Starting...'
    20    217.2 MiB    194.7 MiB       multi_get(session, 3000)
    21    217.2 MiB      0.0 MiB       print("Finished first round...")
    22    217.2 MiB      0.0 MiB       session.close()
    23    217.2 MiB      0.0 MiB       print 'Done.'
    24    221.0 MiB      3.8 MiB       gc.dump_rpy_heap('bench.txt')


[3fd7569b13c5] {jit-summary
Tracing:        41  0.293192
Backend:        30  0.026873
TOTAL:              1615.665337
ops:                79116
recorded ops:       23091
  calls:            2567
guards:             7081
opt ops:            5530
opt guards:         1400
forcings:           198
abort: trace too long:  2
abort: compiling:   0
abort: vable escape:    9
abort: bad loop:    0
abort: force quasi-immut:   0
nvirtuals:          9318
nvholes:            1113
nvreused:           6637
Total # of loops:   23
Total # of bridges: 8
Freed # of loops:   0
Freed # of bridges: 0
[3fd756c29302] jit-summary}

这里的转储: https :

感谢您抽出宝贵的时间来帮助您!

因此,这可能占了100MB的使用量。 有两个地方休息
它可以是,在“备用内存”中,GC可以保留各种内容,并且
在非GC分配中-这些意味着诸如OpenSSL内部
分配。 我想知道是否有一个很好的方法来查看OpenSSL结构
正在泄漏,是否正在使用TLS在这里进行测试,如果可以,您可以
尝试使用非TLS网站,看看它是否可以复制?

在2014年11月8日星期六5:38:04 PM Stas Sușcov [email protected]
写道:

日志:

行#内存使用量增加行内容

16     22.0 MiB      0.0 MiB   <strong i="18">@profile</strong>
17                             def run():
18     22.5 MiB      0.5 MiB       session = requests.Session()
19     22.5 MiB      0.0 MiB       print 'Starting...'
20    217.2 MiB    194.7 MiB       multi_get(session, 3000)
21    217.2 MiB      0.0 MiB       print("Finished first round...")
22    217.2 MiB      0.0 MiB       session.close()
23    217.2 MiB      0.0 MiB       print 'Done.'
24    221.0 MiB      3.8 MiB       gc.dump_rpy_heap('bench.txt')

[3fd7569b13c5] {jit-summary
追踪:41 0.293192
后端:30 0.026873
总计:1615.665337
操作:79116
记录的操作:23091
电话:2567
守卫:7081
选择操作:5530
护卫兵:1400
强迫:198
中止:跟踪时间过长:2
中止:编译:0
中止:逃逸:9
中止:坏循环:0
中止:强制准immut:0
虚拟机:9318
nvholes:1113
nvreused:6637
循环总数:23
桥总数:8
已释放循环数:0
已释放桥数:0
[3fd756c29302] jit-summary}

这里的转储: https :

感谢您抽出宝贵的时间来帮助您!

-
直接回复此电子邮件或在GitHub上查看
https://github.com/kennethreitz/requests/issues/1685#issuecomment -62277822

@alex

我相信@stas在此基准测试中使用了常规的http(非SSL / TLS)连接。 为了以防万一,我还使用了@stas的基准测试脚本,并将其在Mac(OSX 10.9.5 2.5 GHz i5 8 GB 1600 MHz DDR3)上进行了常规的http连接。

如果有帮助,这是我的比较结果(使用您的说明):
https://gist.github.com/mhjohnson/a13f6403c8c3a3d49b8d

让我知道你的想法。

谢谢,

-马特

GitHub的正则表达式过于宽松。 我要重新打开它,因为我认为它不是完全固定的。

您好,也许我可以帮您指出问题所在。 我有一个使用请求的搜寻器,并且有使用多重处理的流程。 确实有多个实例收到相同的结果。 结果缓冲区或套接字本身上可能存在一些泄漏。

让我知道我是否可以发送一些代码示例,或者如何生成参考树以标识正在“共享”(泄漏)的信息的哪部分

谢谢

@barroca是另一个问题。 您可能会跨线程使用Session并使用stream=True 。 如果要在阅读完毕之前关闭响应,则会将该套接字放回连接池中,并且该数据仍在其中(如果我没记错的话)。 如果这没有发生,则可能是由于您选择了最近的连接并从服务器接收到缓存的响应。 无论哪种方式,这都不表示内存泄漏。

@ sigmavirus24谢谢伊恩,正如您提到的那样,这

不用担心@barroca :)

对此没有任何抱怨,我想我们已尽力了。 我很高兴重新打开它,并在必要时进行重新调查

那么,该问题的解决方案是什么?

@Makecodeeasy我也想知道

到目前为止,我在requests周围遇到的问题是它不是线程安全的,
最好为不同的线程使用单独的会话,

我要遍历数百万个URL以验证缓存响应的工作将我带到了这里

当我发现requestsThreadPoolExecutorthreading交互时,内存使用量超出了合理范围,
最后,我只使用multiprocessing.Process隔离工作人员,并为每个工作人员进行独立的会话

@AndCycle,那么您的问题不在这里。 合并了PR,以解决此特定的内存泄漏问题。 它没有回归,因为周围有测试。 您的问题听起来完全不同。

此页面是否有帮助?
0 / 5 - 0 等级