MagicStack / MagicStack/uvloop

memory from transport buffer not freed after connection_lost

Ouverte
#548 2 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

Langage dominant
Cython
Étoiles
11.9k
Forks
616
Métriques de merge des PR
Aucune PR mergée en 30 j

Description

* **uvloop version**: 0.17.0
* **Python version**: 3.11.4
* **Platform**: fedora 38
* **Can you reproduce the bug with `PYTHONASYNCIODEBUG` in env?**: yes, with PYTHONASYNCIODEBUG=1
* **Does uvloop behave differently from vanilla asyncio? How?**: yes, keeps resident memory

If I flood transport.write() and then close the connection (e.g. having the client telnet session stop) the memory stays resident.
Under stock asyncio, the memory immediately is released.

To reproduce,

1. start this program,
2. watch it in top
3. , telnet to port 8888,
4. watch memory usage spike.
5. close the telnet session,
6. see that memory usage stays high.
7. repeat this experiment with stock asyncio runner,
8. the memory is freed immediately. after telnet session is closed

**This test script is a _malloc bomb_, so make sure you watch memory closely or you will consume all the memory on your machine**

```python
#!/usr/bin/env python3

import asyncio
import uvloop

class EchoServerProtocol(asyncio.Protocol):
def __init__(self, on_connect, on_disconnect):
self.on_connect = on_connect
self.on_disconnect = on_disconnect

def connection_lost(self, transport):
print('Connection lost')
self.on_disconnect()
self.transport = None

def connection_made(self, transport):
self.on_connect(transport)
peername = transport.get_extra_info('peername')
print('Connection from {}'.format(peername))
self.transport = transport

def data_received(self, data):
message = data.decode()
print('Data received: {!r}'.format(message))

print('Send: {!r}'.format(message))
self.transport.write(data)

print('Close the client socket')

async def send_data(transport):
message = b'Hello World!' * 1000
while True:
transport.write(message)
await asyncio.sleep(0)

task = None

def on_connect(transport):
global task
task = asyncio.create_task(send_data(transport))

def on_disconnect():
global task
task.cancel()

async def start():
# Get a reference to the event loop as we plan to use
# low-level APIs.
loop = asyncio.get_running_loop()

server = await loop.create_server(
lambda: EchoServerProtocol(on_connect, on_disconnect),
'127.0.0.1',
8888,
)

async with server:
await server.serve_forever()

if __name__ == '__main__':
# with asyncio.Runner() as runner:
with asyncio.Runner(loop_factory=uvloop.new_event_loop) as runner:
runner.run(start())
```

Guide de contribution

Aucun guide de contribution indexé pour ce dépôt

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez par exécuter la reproduction Python fournie avec uvloop.new_event_loop, en observant d’abord la mémoire après la fermeture de la session telnet, puis en la comparant avec le runner asyncio standard. Suivez le tampon du transport et le chemin de connection_lost ; c’est terminé lorsque la mémoire est libérée après la déconnexion comme avec asyncio.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
python
Domaine
networking
Type d'issue
Bug
Difficulté
4/5
Temps estimé
3-5 jours
Activité
À l'abandon
Clarté
À clarifier
Accessibilité débutants
35/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.