MagicStack / MagicStack/uvloop

memory from transport buffer not freed after connection_lost

Offen
#548 2 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

Vorherrschende Sprache
Cython
Sterne
11.9k
Forks
616
PR-Merge-Kennzahlen
Keine gemergten PRs in 30 T.

Beschreibung

* **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())
```

Beitragsleitfaden

Für dieses Repository ist kein Beitragsleitfaden indexiert

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne damit, die bereitgestellte Python-Reproduktion mit uvloop.new_event_loop auszuführen, beobachte zuerst den Speicher, nachdem die Telnet-Sitzung geschlossen wurde, und vergleiche ihn anschließend mit dem Standard-asyncio-Runner. Verfolge den Transportpuffer und den Pfad von connection_lost; abgeschlossen ist die Aufgabe, wenn der Speicher nach der Trennung ebenso freigegeben wird wie unter asyncio.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
python
Bereich
networking
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Veraltet
Klarheit
Muss geklärt werden
Anfängerfreundlichkeit
35/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.