Резюме.
Программа работает нормально
Программа потребляет весь барабан до тех пор, пока не перестанет работать
Псевдокод:
def function():
proxies = {
'https': proxy
}
session = requests.Session()
session.headers.update({'User-Agent': 'user - agent'})
try: #
login = session.get(url, proxies=proxies) # HERE IS WHERE MEMORY LEAKS
except: #
return -1 #
return 0
$ python -m requests.help
{
"chardet": {
"version": "3.0.4"
},
"cryptography": {
"version": ""
},
"idna": {
"version": "2.6"
},
"implementation": {
"name": "CPython",
"version": "3.6.3"
},
"platform": {
"release": "10",
"system": "Windows"
},
"pyOpenSSL": {
"openssl_version": "",
"version": null
},
"requests": {
"version": "2.18.4"
},
"system_ssl": {
"version": "100020bf"
},
"urllib3": {
"version": "1.22"
},
"using_pyopenssl": false
}
Пожалуйста, предоставьте нам результат
python -m requests.help
Если это недоступно в вашей версии запросов, предоставьте основную информацию о вашей системе (версия Python, операционная система и т. Д.).
@ sigmavirus24 Готово
Привет, @munroc , пара передачи , поскольку она не включена в псевдокод.
Вы создаете новый сеанс для каждого потока и какого размера пул потоков вы используете?
Какой инструмент вы используете, чтобы определить источник утечки? Не могли бы вы поделиться результатами?
Некоторое время у нас были намеки на утечки памяти во время сеансов, но я не уверен, что мы нашли дымящийся пистолет или действительно подтвержденный удар.
@nateprewitt Привет, да, я создаю новый сеанс для каждого потока. Пул потоков - 30. Я все равно пробовал с 2-200 потоками и утечками памяти. Я не использую инструмент, я просто внес следующие изменения в функцию:
поставьте return 0 перед login = session.get и никакой утечки памяти. если я поставлю return 0 после входа в систему = session.get, память начнет протекать. Если вы хотите, я могу отправить вам мой исходный код не слишком большой.
@Munroc, если бы у нас был полный код, я думаю, было бы легче изолировать настоящую причину. Но, исходя из предоставленного кода, я думаю, что очень трудно сделать вывод об утечке памяти.
Как вы упомянули, если вы return
непосредственно перед вызовом session.get
, тогда в памяти будут существовать только объекты proxies
и session
(упрощенно ... но я надеюсь, вы поняли: smile :). Однако после вызова session.get(url, proxies=proxies)
HTML-код url
будет извлечен и локально сохранен в переменной login
. Это означает, что каждый вызов session.get
будет "выглядеть" как утечка памяти, но на самом деле они ведут себя нормально, линейно увеличивая (память) на размер url
result.
Однако предположим, что вы использовали потоки и сразу после этого .join()
их. В этом случае, я думаю, нам нужно посмотреть, как управлялись ваши потоки - и были ли они закрыты / очищены должным образом.
@LeoSZN Я думаю, что в вашем конкретном примере вы закрываете только последний объект Process
после создания нескольких элементов Process
на urls
элементов.
Не могли бы вы попробовать демонизировать их с помощью p.daemon = True
и запустить их (чтобы после завершения основного потока все порожденные дочерние процессы также умерли)? В противном случае сохраните порожденные процессы в отдельном массиве и обязательно закройте их все с помощью цикла.
@initbar
Нужно ли мне запускать p.daemon = True
в цикле или вне цикла перед p.join()
? Кстати, мне все еще нужно p.join()
после применения p.daemon = True
?
_Ook, меня выгнали из новой темы в эту, так что позвольте мне присоединиться к вашей.
Возможно, эта проблема предоставит дополнительную информацию и ускорит ее решение ..._
Я запускаю бота Telegram и заметил ухудшение свободной памяти при длительном запуске бота. Во-первых, я подозреваю свой код; потом подозреваю бота и наконец пришел к запросам. :)
Я использовал len (gc.get_objects ()), чтобы определить, что проблема существует. Я обнаружил процедуры связи, затем очистил весь код бота и перешел к примеру, который увеличивает количество объектов gc на каждой итерации.
len (gc.get_objects ()) должен давать одинаковый результат на каждой итерации цикла.
Значение len (gc.get_objects ()) увеличивается на каждой итерации цикла.
Test N2
GetObjects len: 27959
Test N3
GetObjects len: 27960
Test N4
GetObjects len: 27961
Test N5
GetObjects len: 27962
Test N6
GetObjects len: 27963
Test N7
GetObjects len: 27964
token = "XXX:XXX"
chat_id = '111'
proxy = {'https':'socks5h://ZZZ'} #You may need proxy to run this in Russia
from time import sleep
import gc, requests
def garbage_info():
res = ""
res += "\nGetObjects len: " + str(len(gc.get_objects()))
return res
def tester():
count = 0
while(True):
sleep(1)
count += 1
msg = "\nTest N{0}".format(count) + garbage_info()
print(msg)
method_url = r'sendMessage'
payload = {'chat_id': str(chat_id), 'text': msg}
request_url = "https://api.telegram.org/bot{0}/{1}".format(token, method_url)
method_name = 'get'
session = requests.session()
req = requests.Request(
method=method_name.upper(),
url=request_url,
params=payload
)
prep = session.prepare_request(req)
settings = session.merge_environment_settings(
prep.url, None, None, None, None)
# prep.url, proxy, None, None, None) #Change the line to enable proxy
send_kwargs = {
'timeout': None,
'allow_redirects': None,
}
send_kwargs.update(settings)
resp = session.send(prep, **send_kwargs)
# For more clean output
gc.collect()
tester()
{
"chardet": {
"version": "3.0.4"
},
"cryptography": {
"version": "2.3.1"
},
"idna": {
"version": "2.7"
},
"implementation": {
"name": "CPython",
"version": "3.6.6"
},
"platform": {
"release": "4.15.0-36-generic",
"system": "Linux"
},
"pyOpenSSL": {
"openssl_version": "1010009f",
"version": "17.5.0"
},
"requests": {
"version": "2.19.1"
},
"system_ssl": {
"version": "1010007f"
},
"urllib3": {
"version": "1.23"
},
"using_pyopenssl": true
}
_ То же поведение, что и на Python 3.5.3 в Windows10. _
@LeoSZN
@initbar
Нужно ли мне запускать
p.daemon = True
в цикле или вне цикла передp.join()
? Кстати, мне все еще нужноp.join()
после примененияp.daemon = True
?
# ..
for i in urls:
p = Process(target=main, args=(i,))
p.daemon = True # before `.start`
p.start()
# ..
В качестве небольшого примечания, вы все еще .join
, но они почти гарантированно будут убиты, когда их родительский процесс завершится (если они каким-то образом не станут случайно осиротевшими; в этом случае, пожалуйста, дайте мне знать! I Люблю узнавать об этом побольше).
В противном случае вы можете сохранить объекты Process
отдельно как массив и в конце присоединиться:
# ..
processes = [
Process(target=main, args=(i,))
for i in urls
]
# start the process activity.
ожидаемый результат
len (gc.get_objects ()) должен давать одинаковый результат на каждой итерации цикла.
Причина такого поведения была обнаружена в механизме кеширования «запросов».
Он работает некорректно (подозревается): добавляет запись в кеш при каждом вызове URL-адреса Telegram API (вместо того, чтобы кэшировать ее один раз). Но это не приводит к утечке памяти, потому что размер кеша ограничен 20, и кеш сбрасывается после достижения этого лимита, и растущее количество объектов будет уменьшено до исходного значения.
Похожая проблема. Запросы съедают память при работе в потоке. Код для воспроизведения здесь:
import gc
from concurrent.futures import ThreadPoolExecutor, as_completed
import requests
from memory_profiler import profile
def run_thread_request(sess, run):
response = sess.get('https://www.google.com')
return
<strong i="6">@profile</strong>
def main():
sess = requests.session()
with ThreadPoolExecutor(max_workers=1) as executor:
print('Starting!')
tasks = {executor.submit(run_thread_request, sess, run):
run for run in range(50)}
for _ in as_completed(tasks):
pass
print('Done!')
return
<strong i="7">@profile</strong>
def calling():
main()
gc.collect()
return
if __name__ == '__main__':
calling()
В приведенном выше коде я передаю объект сеанса, но если я заменю его просто запущенным requests.get
ничего не изменится.
Выход:
➜ thread-test pipenv run python run.py
Starting!
Done!
Filename: run.py
Line # Mem usage Increment Line Contents
================================================
10 23.2 MiB 23.2 MiB <strong i="13">@profile</strong>
11 def main():
12 23.2 MiB 0.0 MiB sess = requests.session()
13 23.2 MiB 0.0 MiB with ThreadPoolExecutor(max_workers=1) as executor:
14 23.2 MiB 0.0 MiB print('Starting!')
15 23.4 MiB 0.0 MiB tasks = {executor.submit(run_thread_request, sess, run):
16 23.4 MiB 0.0 MiB run for run in range(50)}
17 25.8 MiB 2.4 MiB for _ in as_completed(tasks):
18 25.8 MiB 0.0 MiB pass
19 25.8 MiB 0.0 MiB print('Done!')
20 25.8 MiB 0.0 MiB return
Filename: run.py
Line # Mem usage Increment Line Contents
================================================
22 23.2 MiB 23.2 MiB <strong i="14">@profile</strong>
23 def calling():
24 25.8 MiB 2.6 MiB main()
25 25.8 MiB 0.0 MiB gc.collect()
26 25.8 MiB 0.0 MiB return
А Pipfile выглядит так:
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
[requires]
python_version = "3.6"
[packages]
requests = "==2.21.0"
memory-profiler = "==0.55.0"
FWIW Я также испытываю аналогичную утечку памяти, как @jotunskij, здесь больше информации
У меня также есть такая же проблема, когда использование request.get с потоковой передачей фактически съедает память примерно на 0,1–0,9 за запрос и не «очищает» себя после запросов, а сохраняет ее.
То же самое здесь, любая работа?
Редактировать
Моя проблема, похоже, связана с использованием verify=False
в запросах, я обнаружил ошибку под # 5215
Имея ту же проблему. У меня есть простой скрипт, который порождает поток, этот поток вызывает функцию, которая запускает цикл while, этот цикл запрашивает API, чтобы проверить значение статуса, а затем засыпает в течение 10 секунд, а затем цикл будет выполняться снова, пока скрипт не будет остановлен.
При использовании функции requests.get
я вижу, как использование памяти медленно увеличивается через диспетчер задач, наблюдая за порожденным процессом.
Но если я удалю вызов requests.get
из цикла или использую urllib3
напрямую для выполнения запроса на получение, будет очень мало, если вообще вообще, снижение использования памяти.
Я наблюдал это в течение двухчасового периода в обоих случаях, и при использовании requests.get
использование памяти составляет 1 ГБ + через два часа, тогда как при использовании urllib3
использование памяти составляет прибл. 20мб через два часа.
Python 3.7.4 и запросы 2.22.0
Похоже, что Requests все еще находится в стадии бета-тестирования с подобными утечками памяти. Давайте, ребята, исправьте это! 😉👍
Есть новости по этому поводу? Простой запрос POST с загрузкой файла также создает аналогичную проблему с утечкой памяти.
То же самое для меня ... утечка при выполнении пула потоков тоже есть на Windows python38.
запросы 2.22.0
Мне то же
Вот моя проблема с утечкой памяти, кто-нибудь может помочь? https://stackoverflow.com/questions/59746125/memory-keep-growing-when-using-mutil-thread-download-file
Вызов Session.close()
и Response.close()
может избежать утечки памяти.
И ssl будет потреблять больше памяти, поэтому утечка памяти будет более заметной при запросе URL-адресов https.
Сначала я делаю 4 тестовых случая:
Псевдокод:
def run(url):
session = requests.session()
response = session.get(url)
while True:
for url in urls: # about 5k urls of public websites
# execute in thread pool, size=10
thread_pool.submit(run, url)
# in another thread, record memory usage every seconds
График использования памяти (ось y: МБ, ось x: время), запросы используют много памяти, и память увеличивается очень быстро, в то время как использование памяти aiohttp стабильно:
Затем я добавляю Session.close()
и снова тестирую:
def run(url):
session = requests.session()
response = session.get(url)
session.close() # close session !!
Использование памяти значительно уменьшилось, но со временем использование памяти все равно увеличивается:
Наконец, я добавляю Response.close()
и снова тестирую:
def run(url):
session = requests.session()
response = session.get(url)
session.close() # close session !!
response.close() # close response !!
Использование памяти снова уменьшилось, а не увеличилось со временем:
Сравнение aiohttp и запросов показывает, что утечка памяти вызвана не ssl, а тем, что ресурсы соединения не закрыты.
Полезные скрипты:
class MemoryReporter:
def __init__(self, name):
self.name = name
self.file = open(f'memoryleak/memory_{name}.txt', 'w')
self.thread = None
def _get_memory(self):
return psutil.Process().memory_info().rss
def main(self):
while True:
t = time.time()
v = self._get_memory()
self.file.write(f'{t},{v}\n')
self.file.flush()
time.sleep(1)
def start(self):
self.thread = Thread(target=self.main, name=self.name, daemon=True)
self.thread.start()
def plot_memory(name):
filepath = 'memoryleak/memory_{}.txt'.format(name)
df_mem = pd.read_csv(filepath, index_col=0, names=['t', 'v'])
df_mem.index = pd.to_datetime(df_mem.index, unit='s')
df_mem.v = df_mem.v / 1024 / 1024
df_mem.plot(figsize=(16, 8))
Системная информация:
$ python -m requests.help
{
"chardet": {
"version": "3.0.4"
},
"cryptography": {
"version": ""
},
"idna": {
"version": "2.8"
},
"implementation": {
"name": "CPython",
"version": "3.7.4"
},
"platform": {
"release": "18.0.0",
"system": "Darwin"
},
"pyOpenSSL": {
"openssl_version": "",
"version": null
},
"requests": {
"version": "2.22.0"
},
"system_ssl": {
"version": "1010104f"
},
"urllib3": {
"version": "1.25.6"
},
"using_pyopenssl": false
}
Проблема утечки SSL упакована OpenSSL <= 3.7.4 в Windows и OSX, она не освобождает память из контекста должным образом
https://github.com/VeNoMouS/cloudscraper/issues/143#issuecomment -613092377
Самый полезный комментарий
Похожая проблема. Запросы съедают память при работе в потоке. Код для воспроизведения здесь:
В приведенном выше коде я передаю объект сеанса, но если я заменю его просто запущенным
requests.get
ничего не изменится.Выход:
А Pipfile выглядит так: