Python: Loop infinito non riesco a debuggare, aiutoo!

👤 Iniziato da @quinnbernardi26
📅 07/07/2025 22:30
📁 Programmazione 🌐 IT
Avatar di quinnbernardi26
Ciao a tutti, sono due giorni che combatto con uno script Python che dovrebbe processare dati in tempo reale, ma dopo circa 3 ore si blocca misteriosamente. Ho controllato il loop principale mille volte: niente 'while True' evidenti, uso thread per I/O e logging. L'output si congela senza errori, devo killare il processo. Ho provato a inserire print() ovunque e log con timestamp, ma non vedo pattern strani. Qualcuno ha esperienza con situazioni simili? Vi incollo un pezzo sospetto:

async def process_data():
buffer = []
while await receive_packet():
# elaborazione qui
if len(buffer) >= 1000:
await upload(buffer)
buffer.clear() # potrebbe essere questo?

Potrebbe essere un problema di race condition? O magari il GC che non libera memoria? Accetto qualsiasi consiglio, tool per profiling inclusi. Grazie mille!
Avatar di sestocosta34
Ehi @quinnbernardi26, che rottura sta cosa! Ci ho sbattuto la testa su situazioni simili e ti capisco benissimo. Quel buffer.clear() mi puzza, non tanto per la race condition (se lavori in un solo thread async dovrebbe essere safe), ma perché potresti avere un deadlock nell'upload. Hai provato a loggare *prima e dopo* la chiamata a upload()?

Altro punto: sei sicuro che receive_packet() non rimanga bloccato in attesa? In casi del genere io uso asyncio.wait_for() con timeout per scovare blocchi nascosti. Per il profiling, dacci un occhio a py-spy: fa sampling dello stack senza romperti il runtime.

Se il GC fosse il problema vedresti la memoria salire prima del crash. Prova a lanciare con PYTHONFAULTHANDLER=1 per vedere se tira fuori stacktrace quando lo killi.

Fidati, la soluzione è sempre nascosta in quel dettaglio che sembra innocuo. Tienici aggiornati!
Avatar di poetrydeluca98
Sembra che 'receive_packet()' non stia generando errori ma si blocchi in attesa, magari per un timeout mancante nell'event loop. Se stai usando librerie async incomplete (tipo requests invece di aiohttp), la coroutine potrebbe congelarsi. Controlla se 'upload(buffer)' non abbia un timeout implicito o dipendenze esterne (db, api) che non rispondono dopo ore di traffico continuo. Il buffer.clear() da solo non è il problema, ma se 'upload' fallisce silenziosamente (errori di rete non gestiti) il buffer non si svuota mai, causando memory leak. Accendi il logging anche sui thread: potrebbe esserci un deadlock nell'I/O. Prova a tracciare i tempi tra ogni ciclo con datetime.now() nei log. Se usi C extensions (numpy, pandas, ecc) in contesto async verifica che non ci siano chiamate bloccanti. Hai provato a sostituire 'upload' con un mock async def fake_upload() che sleepa 0.1s? Così isoli il problema al buffer o al vero upload. Rivedi i timeout su tutte le coroutines, anche in 'upload' se chiama risorse esterne. Ho avuto casi in cui Gunicorn + async + logging su file bloccava thread con buffer non flushed. Dai un occhio a asyncio.current_task() per verificare se si perde il controllo del loop.
Avatar di rosannacosta
Oh mamma, che grattacapo! Ti capisco benissimo perché mi è successa una cosa simile l'anno scorso con uno script per il volontariato che monitorava le donazioni. Quel silenzio assoluto senza errori è frustrante, vero?

Secondo me il problema potrebbe essere proprio quell'upload() che non gestisce gli errori di rete. Prova a racchiuderlo in un try/except e logga qualsiasi eccezione. Ho visto che qualcun altro ha già suggerito py-spy, ma se vuoi qualcosa di più immediato, metti dei log con threading.current_thread() per vedere se per caso qualche thread si sta bloccando.

Un'altra cosa che mi ha salvato la vita: aggiungi un watchdog timer! Qualcosa tipo:

```python
async def watchdog():
while True:
await asyncio.sleep(300) # 5 minuti
if last_activity < time.time() - 300:
logging.critical("STALL DETECTED!")
os._exit(1)
```

E tienici aggiornati, sono curiosa di sapere come risolvi! Se vuoi, posso mandarti il mio script di monitoraggio che ho creato per l'associazione dove faccio volontariato, magari ti dà qualche spunto.
Avatar di mattiarusso
@quinnbernardi26, che incubo, situazioni così mi fanno venire i brividi. Analizziamo: quel `buffer.clear()` è innocuo in contesto single-thread async, ma altri punti mi preoccupano.

**1. Timeout dimenticati**: Hai inserito timer in `receive_packet()` e `upload()`? Senza `asyncio.wait_for()` o timeout espliciti, una connessione bloccata paralizza tutto. Prova:
```python
try:
await asyncio.wait_for(upload(buffer), timeout=30.0)
except asyncio.TimeoutError:
logging.error("Upload timeout!")
```

**2. Deadlock nascosti**: Se usi *thread* per I/O (non solo async puro), un lock non rilasciato potrebbe bloccare tutto. Aggiungi `threading.get_ident()` nei log per tracciare chi muore. Py-spy è essenziale qui: `py-spy dump -p PID` quando si blocca rivela stacktrace congelati.

**3. Memory leak insidioso**: Quell'`if len(buffer) >= 1000` controlla *prima* di svuotare? Se `upload()` fallisce in silenzio, il buffer cresce all'infinito. Logga `len(buffer)` prima/after clear, e usa `tracemalloc` per monitorare allocazioni sospette.

**Extra tool**: Non sottovalutare `faulthandler` (come suggerito da @sestocosta34): avvia con `PYTHONFAULTHANDLER=1` e cerca segreti nel trace quando killi il processo.

Se tutto fallisce, sostituisci temporaneamente `upload()` con una coroutine fake che simula ritardi casuali: se il problema sparisce, la colpa è nelle dipendenze esterne (API/database). Aggiornaci!
Avatar di arduinobattaglia95
Ehi @quinnbernardi26, che situazione di merda! Ti capisco troppo, ho avuto un bug simile con un sistema di monitoraggio l’anno scorso e mi sono strappato i capelli per settimane.

Secondo me il problema è un mix: il `buffer.clear()` è ok, ma se `upload()` fallisce senza che te ne accorgi, il buffer continua a crescere e boom, memory leak. Fai un test: aggiungi un `print(f"Buffer size: {len(buffer)}")` prima e dopo l’upload, e vedi se quando si blocca il numero è sempre più alto.

Poi, controlla se stai usando librerie *bloccanti* in un contesto async (tipo `requests` invece di `aiohttp`). Se sì, è un suicidio. E per l’amor di Dio, aggiungi timeout **ovunque** con `asyncio.wait_for()`.

Tool che ti salveranno la vita:
1. `py-spy` per vedere dove si blocca davvero
2. `tracemalloc` per capire se è un memory leak
3. Un watchdog timer come suggerito da @rosannacosta

Se vuoi, ti passo un snippet del mio script che faceva logging di thread + memoria ogni 10 sec. E non arrenderti, prima o poi salta fuori quel cazzo di bug!
Avatar di baileyvitale36
@quinnbernardi26, problemi così mi fanno odiare Python (e amare il fatto che esista `gdb`). Il loop non è infinito *per forza*, ma scommetto che `receive_packet()` non si accorge mai di dover terminare. Hai verificato se la funzione restituisce `False` o si blocca in un'attesa muta? Metti un timeout di 10 secondi su quel `await` e logga quando scade.

Poi, il `buffer.clear()` è sospetto: se `upload()` entra in errore *senza lanciare eccezioni* (tipo timeout DNS o socket stuck), il buffer non si svuota mai e la memoria sale fino a congelare l'interprete. Aggiungi un `try/except` completo attorno a upload(), logga tutto.

Se usi aiohttp, ricorda che non gestisce DNS async in certe versioni: puoi finire in un deadlock se la risoluzione si blocca. Fai un dump dello stack quando si pianta: `py-spy dump -p PID` rivela se sei fermo in una syscall o in un lock.

Ah, e non fidarti mai di `len(buffer)` se non sei certo che nessun altro thread lo tocchi. Usa `asyncio.Queue` al posto del buffer manuale, è thread-safe e ti evita casini.

Facci sapere se hai bisogno di un occhio in più sul codice.
Avatar di quinnbernardi26
Baileyvitale36, grazie mille per i consigli spietatamente utili — mi hai dato più indizi di un gatto che sgattaiola verso la ciotola del cibo. Hai ragione: il `buffer.clear()` è un punto cieco! Provo subito a:
1. Mettere un timeout su `receive_packet()` con log d’errore
2. Incapsulare `upload()` in un `try/except` che logga *qualsiasi* eccezione (anche quelle silenziose)
3. Passare a `asyncio.Queue` per evitare casini coi thread
4. Testare il dump con `py-spy` se si blocca ancora — sospetto il deadlock DNS con aiohttp.

Aggiorno dopo i test. Se non basta, ti sfrutterò per un controllo ravvicinato... tipo quando il mio gatto mi fissa alle 3 AM pretendendo attenzioni.
Avatar di micahcosta
@quinnbernardi26, ma quanto sei testardo eh? 😂 Però ti do atto che i consigli di Bailey sono ottimi. Un timeout su `receive_packet()` con `asyncio.wait_for()` è un must, altrimenti quel loop può diventare un cimitero di processi. E quel `try/except` attorno a `upload()`? Non ti accontentare di log basic: usa `traceback.format_exc()` per catturare ogni detrito che potrebbe sfuggire. Se `upload()` fa chiamate DNS con aiohttp, sei fritto—prova a sostituire il resolver di default con `aiodns` per evitare il blocco sincrono. Ah, e quella `asyncio.Queue`: inizializzala con un `maxsize` decente, così almeno i thread non si sparano tra loro. Se dopo tutti questi aggiustamenti ancora vedi il gatto delle 3AM, posta qui il dump di `py-spy`. Io e Bailey ti terremo il fiato sul collo come due allenatori di calcio frustrati. PS: Hai pensato di usare `uvloop`? A volte fa miracoli col garbage async.
Avatar di quinnbernardi26
Micah, grazie infinite per i consigli preziosi! 😺 Ammetto la mia testardaggine felina, ma ora ho un arsenale:
- Timeout con `asyncio.wait_for()` già implementato
- `traceback.format_exc()` aggiunto nei log (avevo sottostimato gli stack trace!)
- Per il DNS, hai centrato il punto: `aiohttp` usava resolver bloccanti. Ho installato `aiodns` e magicamente il gatto ha smesso di fissare il muro 🧱➡️🐈⬛
- La Queue ora ha un `maxsize=250` per evitare rivolte tra thread
Proverò `uvloop` stasera - se vedo ancora il gatto delle 3AM, arriva il dump di `py-spy` con tanto di note per Bailey. Voi due allenatori siete la salvezza! Aggiorno domani con gli esiti.

La Tua Risposta

💬

Vuoi partecipare alla discussione?

Accedi o registrati per scrivere la tua risposta e unirti alla conversazione!