Sommaire.
Programme en cours d'exécution normalement
Programme consommant toute la RAM jusqu'Ă ce qu'il cesse de fonctionner
Pseudocode :
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
}
Veuillez nous fournir la sortie de
python -m requests.help
Si cela n'est pas disponible sur votre version de Requests, veuillez fournir des informations de base sur votre systĂšme (version Python, systĂšme d'exploitation, etc.).
@sigmavirus24 Terminé
Salut @munroc , quelques questions rapides sur votre implémentation de threading car elle n'est pas incluse dans le pseudo-code.
Créez-vous une nouvelle session pour chaque thread et quelle est la taille du pool de threads que vous utilisez ?
Quel outil utilisez-vous pour dĂ©terminer d'oĂč vient la fuite ? Cela vous dĂ©rangerait-il de partager les rĂ©sultats ?
Nous avons des indices de fuites de mémoire autour des sessions depuis un certain temps maintenant, mais je ne suis pas sûr que nous ayons trouvé une arme fumante ou un impact vraiment confirmé.
@nateprewitt Bonjour, oui je crée une nouvelle session pour chaque thread. Le pool de threads est de 30. J'ai essayé avec 2 à 200 threads et des fuites de mémoire de toute façon. Je n'utilise pas d'outil, je viens d'apporter ces modifications à la fonction :
mettre return 0 avant login = session.get et pas de fuite de mémoire. si je mets le retour 0 aprÚs la connexion = la mémoire session.get commence à fuir. Si vous voulez, je peux vous envoyer mon code source n'est pas trop volumineux.
@Munroc si nous avons le code complet, alors je pense qu'il serait plus facile d'isoler la cause réelle. Mais sur la base de l'essentiel du code qui a été fourni, je pense qu'il est trÚs difficile de conclure qu'il y a une fuite de mémoire.
Comme vous l'avez mentionné, si vous return
juste avant d'appeler session.get
, alors seuls les objets proxies
et session
existeront dans la mémoire (simplifié.. mais je j'espÚre que vous avez l'idée :sourire:). Cependant, une fois que vous appelez session.get(url, proxies=proxies)
, le code HTML du url
sera récupéré et enregistré localement dans la variable login
. Ce qui signifie que chaque appel de session.get
"ressemblera" à une fuite de mémoire, mais ils se comportent en fait normalement en (mémoire) augmentant linéairement de la taille du résultat de url
.
Cependant, disons que vous utilisiez des threads et que vous les .join()
immédiatement aprÚs. Dans ce cas, je pense que nous devons examiner comment vos threads ont été gérés - et s'ils ont été fermés/nettoyés correctement.
@LeoSZN Je pense que dans votre exemple spécifique, vous fermez uniquement le dernier objet Process
aprÚs avoir généré plusieurs Process
par urls
éléments.
Pourriez-vous essayer de les démoniser en utilisant p.daemon = True
et de les exécuter (de sorte qu'une fois le thread principal terminé, tous les processus enfants générés meurent également) ? Sinon, stockez les processus générés dans un tableau séparé et assurez-vous de les fermer tous à l'aide d'une boucle.
@initbar
Dois-je exécuter p.daemon = True
dans la boucle ou en dehors de la boucle avant p.join()
? Au fait, ai-je encore besoin de p.join()
aprÚs avoir appliqué p.daemon = True
?
_Ok, j'ai été expulsé du nouveau sujet vers celui-ci, alors laissez-moi rejoindre le vÎtre.
Peut-ĂȘtre que ce problĂšme fournira plus d'informations et accĂ©lĂ©rera la rĂ©solution du problĂšme..._
J'exĂ©cute le bot Telegram et j'ai remarquĂ© la dĂ©gradation de la mĂ©moire libre lors de l'exĂ©cution du bot pendant une longue pĂ©riode. PremiĂšrement, je suspecte mon code ; puis je soupçonne bot et enfin j'en suis venu aux requĂȘtes. :)
J'ai utilisé len(gc.get_objects()) pour identifier que le problÚme existe. J'ai localisé les routines de communication, puis j'ai effacé tout le code du bot et j'en viens à l'exemple qui augmente le nombre d'objets gc à chaque itération.
len(gc.get_objects()) devrait donner le mĂȘme rĂ©sultat Ă chaque itĂ©ration de boucle
La valeur de len(gc.get_objects()) augmente à chaque itération de boucle.
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
}
_Le mĂȘme comportement que j'avais sur Python 3.5.3 sur Windows10._
@LeoSZN
@initbar
Dois-je exécuter
p.daemon = True
dans la boucle ou en dehors de la boucle avantp.join()
? Au fait, ai-je encore besoin dep.join()
aprÚs avoir appliquép.daemon = True
?
# ..
for i in urls:
p = Process(target=main, args=(i,))
p.daemon = True # before `.start`
p.start()
# ..
Comme note mineure, vous pouvez toujours .join
processus dĂ©mons - mais ils sont presque garantis d'ĂȘtre tuĂ©s lorsque leur processus parent se termine (Ă moins qu'ils ne deviennent orphelins involontairement ; dans ce cas, veuillez me le faire savoir ! Je J'adore en savoir plus).
Sinon, vous pouvez stocker les objets Process
séparément sous forme de tableau et les joindre à la fin :
# ..
processes = [
Process(target=main, args=(i,))
for i in urls
]
# start the process activity.
résultat attendu
len(gc.get_objects()) devrait donner le mĂȘme rĂ©sultat Ă chaque itĂ©ration de boucle
La raison de ce comportement a été trouvée dans le mécanisme de cache "demandes".
Cela fonctionne incorrectement (soupçonné): il ajoute un enregistrement de cache à chaque appel à l'URL de l'API Telegram (au lieu de le mettre en cache une fois). Mais cela n'entraßne pas de fuite de mémoire, car la taille du cache est limitée à 20 et le cache est réinitialisé aprÚs avoir atteint cette limite et le nombre croissant d'objets sera ramené à sa valeur initiale.
ProblĂšme similaire. Les requĂȘtes consomment de la mĂ©moire lors de l'exĂ©cution dans le thread. Code Ă reproduire ici :
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()
Dans le code donné ci-dessus, je passe un objet de session, mais si je le remplace par l'exécution de requests.get
rien ne change.
La sortie est :
â 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
Et Pipfile ressemble Ă ceci :
[[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, je rencontre également une fuite de mémoire similaire à celle de @jotunskij, voici plus d'informations
J'ai Ă©galement le mĂȘme problĂšme oĂč l'utilisation de request.get avec le threading consomme en fait de la mĂ©moire d'environ 0,1 Ă 0,9 par requĂȘte et elle ne s'efface pas aprĂšs les requĂȘtes mais l'enregistre.
Idem ici, un travail autour?
Ăditer
Mon problĂšme semble ĂȘtre dĂ» Ă l'utilisation de verify=False
dans les requĂȘtes, j'ai signalĂ© un bogue sous #5215
Avoir le mĂȘme problĂšme. J'ai un script simple qui gĂ©nĂšre un thread, ce thread appelle une fonction qui exĂ©cute une boucle while, cette boucle interroge une API pour vĂ©rifier une valeur d'Ă©tat, puis dort pendant 10 secondes, puis la boucle s'exĂ©cutera Ă nouveau jusqu'Ă ce que le script soit arrĂȘtĂ©.
Lorsque vous utilisez la fonction requests.get
, je peux voir l'utilisation de la mémoire augmenter lentement via le gestionnaire de tùches en observant le processus généré.
Mais si je supprime l'appel requests.get
de la boucle ou que j'utilise urllib3
directement pour faire la requĂȘte get, il y a trĂšs peu ou pas de fluage de l'utilisation de la mĂ©moire.
J'ai regardé cela sur une période de deux heures dans les deux cas et lors de l'utilisation de requests.get
l'utilisation urllib3
l'utilisation
Python 3.7.4 et requĂȘtes 2.22.0
Il semble que Requests soit encore en phase bĂȘta avec des fuites de mĂ©moire comme celle-lĂ . Allez, les gars, arrangez ça ! ??
Une mise Ă jour pour ceci? Une simple requĂȘte POST avec un tĂ©lĂ©chargement de fichier crĂ©e Ă©galement le mĂȘme problĂšme de fuite de mĂ©moire.
Idem pour moi... la fuite lors de l'exécution de threadpool est également sur Windows python38.
demandes 2.22.0
Pareil pour moi
Voici mon problÚme de fuite de mémoire, quelqu'un peut-il m'aider ? https://stackoverflow.com/questions/59746125/memory-keep-growing-when-using-mutil-thread-download-file
Appelez Session.close()
et Response.close()
peut éviter la fuite de mémoire.
Et SSL consommera plus de mémoire, donc la fuite de mémoire sera plus remarquable lors de la demande d'URL https.
Je fais d'abord 4 cas de test :
Pseudo-code :
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
Graphique d'utilisation de la mĂ©moire (axe des y : Mo, axe des x : temps), les requĂȘtes utilisent beaucoup de mĂ©moire et la mĂ©moire augmente trĂšs rapidement, tandis que l'utilisation de la mĂ©moire aiohttp est stable :
Ensuite, j'ajoute Session.close()
et teste à nouveau :
def run(url):
session = requests.session()
response = session.get(url)
session.close() # close session !!
L'utilisation de la mémoire a considérablement diminué, mais l'utilisation de la mémoire continue d'augmenter avec le temps :
Enfin, j'ajoute Response.close()
et teste à nouveau :
def run(url):
session = requests.session()
response = session.get(url)
session.close() # close session !!
response.close() # close response !!
L'utilisation de la mémoire a de nouveau diminué et n'a pas augmenté avec le temps :
Comparez aiohttp et les requĂȘtes montrent que la fuite de mĂ©moire n'est pas causĂ©e par SSL, elle est causĂ©e par des ressources de connexion non fermĂ©es.
Scripts utiles :
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))
Informations systÚme :
$ 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
}
Le problÚme de fuite SSL est empaqueté OpenSSL <= 3.7.4 sous Windows et OSX, il ne libÚre pas correctement la mémoire du contexte
https://github.com/VeNoMouS/cloudscraper/issues/143#issuecomment -613092377
Commentaire le plus utile
ProblĂšme similaire. Les requĂȘtes consomment de la mĂ©moire lors de l'exĂ©cution dans le thread. Code Ă reproduire ici :
Dans le code donné ci-dessus, je passe un objet de session, mais si je le remplace par l'exécution de
requests.get
rien ne change.La sortie est :
Et Pipfile ressemble Ă ceci :