HiAPI
Blog

Tutoriale

hiapi /v1/tasks attività sospese o in timeout: manuale di diagnosi e retry

Manuale pratico per sviluppatori: eseguire il debug di attività asincrone bloccate, regolare il ritmo di polling e decidere quando riprovare e quando inviare di nuovo

Oct 31, 2026
hiapi 博客封面图:错位叠放的骨白色纸张如套印偏移,边缘呈炭黑色,中间夹一张锈红色纸页,平面落地投影。

Perché le attività asincrone si bloccano e vanno in timeout

Le attività asincrone su /v1/tasks sono un contratto tra tre parti: il tuo client, la piattaforma HiAPI e la risorsa di calcolo che esegue realmente il lavoro. Un blocco è quasi sempre un livello che attende un altro, e la prima cosa da fare nel debug è capire quale livello — perché ogni livello si corregge in modo diverso, e riprovare alla cieca non fa che peggiorare le cose.

Il ciclo di vita che devi tenere a mente è il seguente:

Il ciclo di vita, fase per fase

  1. Invio (Submit) — il tuo client invia la richiesta, la piattaforma la accetta e restituisce un identificativo dell'attività.
  2. Accodamento (Enqueue) — l'attività viene pianificata. Questa fase è la più soggetta a rallentamenti sotto carico elevato, ed è anche la meno visibile per te.
  3. Esecuzione (Run) — la risorsa di calcolo elabora l'attività. Il tempo qui dipende interamente dal carico di lavoro, non dal tuo client HTTP.
  4. Completamento (Complete) — viene registrato uno stato terminale, leggibile tramite lo stesso identificativo dell'attività.

Un "blocco" raramente è un evento singolo: di solito rientra in una delle seguenti situazioni:

  • L'attività è effettivamente ancora in esecuzione. Input lunghi, output grandi o code occupate allungano la fase 3. Non si è rotto nulla, sei solo troppo impaziente.
  • L'attività è in coda ma non è ancora iniziata. La fase 2 può durare a lungo e, dal tuo lato, sembra identica a un blocco.
  • Il tuo client ha rinunciato troppo presto. Hai impostato l'HTTP timeout sulla lettura dello stato invece che sull'attività, quindi la piattaforma funziona perfettamente ma il tuo processo si interrompe.
  • L'attività è entrata in uno stato terminale ma non l'hai rilevato. Il tuo ciclo di polling si è fermato, è andato in crash o è uscito prima di ottenere la risposta.
  • Un vero guasto lato piattaforma. Più raro, ma esiste, ed è l'unico caso in cui dovresti pensare a un retry a livello di piattaforma.

L'equivoco principale

Il timeout autoinflitto più comune è confondere due orologi. La richiesta HTTP che usi per interrogare lo stato deve essere breve — è solo una lettura. L'attività ha una propria scadenza, molto più lunga. Se usi lo stesso valore di timeout per entrambi, annullerai attività che sarebbero sane per poi riprovarle, duplicando un lavoro che stava per completarsi.

Prima di modificare qualsiasi cosa, stabilisci in quale delle cinque situazioni sopra ti trovi realmente. Il resto di questo manuale dipende da questa risposta.

Passo uno: raccogliere le prove dell'attività

Resisti all'impulso di modificare il codice o lanciare un retry nel primo minuto. Un blocco è uno stato che si può diagnosticare a posteriori solo se hai registrato abbastanza informazioni. Acquisisci quanto segue prima di intervenire.

Cosa registrare per ogni attività

  • L'identificativo dell'attività restituito all'invio. È la chiave di tutto; senza di esso puoi solo tirare a indovinare.
  • L'ultima stringa di stato che hai osservato, copiata alla lettera, senza parafrasi.
  • Le marche temporali: quando hai inviato, quando è stata emessa ogni interrogazione dello stato, quando è tornata ogni interrogazione.
  • Il numero di interrogazioni dello stato eseguite e i relativi intervalli.
  • La forma del payload della richiesta — non necessariamente il contenuto completo, ma abbastanza da riprodurla: dimensione dell'input, parametri e qualsiasi cosa influenzi la durata di esecuzione.
  • La scadenza lato client che hai impostato (se presente) e se è scattata.

Interroga manualmente di nuovo lo stesso identificativo

Prima di trarre qualsiasi conclusione, esegui una nuova interrogazione dello stato sullo stesso identificativo dell'attività. Ci sono due esiti possibili:

  • Lo stato è avanzato. L'attività è viva; hai incontrato un bug di polling o di timeout, non un bug dell'attività. Passa alla sezione sul polling.
  • Lo stato non è cambiato e l'attività è molto vecchia. Ora hai un'attività realmente sospetta di essere bloccata. Annota da quanto tempo permane in quello stato e confrontalo con le tue aspettative.

Scrivilo

Mantieni una riga di log breve per ogni tentativo: identificativo, stato, tempo trascorso, decisione presa. I blocchi ricorrenti si diagnosticano quasi sempre grazie allo storico, non all'evento in sé. Se non riesci a ricostruire cosa faceva un'attività un'ora fa, continuerai a fare il debug dello stesso guasto.

C'è un'altra prova che si tende a dimenticare: l'ora del giorno e il tipo di carico di lavoro. Se i blocchi si concentrano su un certo tipo di input, hai scoperto uno schema da segnalare, non un guasto sporadico da riprovare.

Interpretare correttamente lo stato dell'attività

Lo stato è la base fattuale di ogni decisione di retry. Trattalo come una piccola macchina a stati, in cui ogni stato corrisponde esattamente a un'azione, così non riproverai mai un'attività ancora in esecuzione.

Gli stati che vedrai e il loro significato

  • Queued / pending (in coda/in attesa) — accettata, non iniziata. Restare qui a lungo indica pressione sulla pianificazione. Non riprovare; l'attività esiste già.
  • Running / processing (in esecuzione/in elaborazione) — la risorsa di calcolo sta lavorando. Non c'è alcun problema. Attendere è l'azione corretta.
  • Succeeded / completed (riuscita/completata) — stato terminale. Leggi il risultato e chiudi.
  • Failed / error (fallita/errore) — stato terminale, il cui payload di errore è il tuo miglior materiale di debug. Leggilo prima di prendere qualsiasi decisione.
  • Cancelled / expired (annullata/scaduta) — stato terminale dovuto a una policy o a una tua azione. Se riprovare è una decisione del tutto nuova.
  • Unknown o assente — identificativo non riconosciuto. Non è uno stato di fallimento, ma uno stato di prova. Rileggi l'identificativo e verifica di stare guardando quello giusto.

Due domande da porsi davanti a qualsiasi stato

  1. È uno stato terminale? Se no, l'unica azione corretta è continuare ad attendere entro il tuo budget.
  2. Se è terminale, è riprovabile? Il successo non è riprovabile. Il fallimento può esserlo o meno — leggi prima l'errore. Un identificativo sconosciuto richiede indagine, non retry.

Non dedurre lo stato dal tempo trascorso

"È in esecuzione da dieci minuti, quindi è sicuramente bloccata" è una congettura, non una diagnosi. Il tempo trascorso ti dice solo quando interrogare, mai quale sia lo stato. Un'attività può restare in coda a lungo e poi completarsi perfettamente. Lascia che sia la stringa di stato a trarre le conclusioni; il tuo orologio decide solo ogni quanto chiedere.

Progettare un ritmo di polling ragionevole

La maggior parte dei "blocchi" sono bug di polling mascherati. Ci sono due modelli di guasto principali: un polling troppo aggressivo che danneggia la tua stessa affidabilità, e un polling troppo ingenuo che non rileva mai lo stato terminale. Un ritmo con limiti risolve entrambi.

Parti piano, con backoff moderato

Non fare polling in un ciclo serrato. Inizia con un intervallo breve, così le attività veloci danno subito un risultato, poi aumenta l'intervallo man mano che l'attività invecchia:

import time

def poll(job_id, check_status, deadline_s=600):
    start = time.monotonic()
    interval = 1.0
    while True:
        status = check_status(job_id)
        if status in ("succeeded", "failed", "cancelled", "expired"):
            return status
        if time.monotonic() - start > deadline_s:
            return "client_deadline_exceeded"
        time.sleep(interval)
        interval = min(interval * 1.5, 30.0)  # imposta un limite

Proprietà chiave: un intervallo iniziale abbastanza breve da catturare le attività veloci, un fattore di crescita che mantiene basso il costo per le attività lunghe, e un limite massimo che ti impedisce di dormire per un'ora e perdere il risultato.

Imposta sempre una scadenza lato client

Non dare mai per scontato che la piattaforma segnali il completamento al posto tuo. Stabilisci la tua scadenza — il tempo massimo che tu sei disposto ad attendere — e considera il superamento come un esito a sé stante, non come un fallimento. La distinzione è importante: l'esito client_deadline_exceeded significa "non guardo più", non "l'attività è morta". Registralo di conseguenza.

Due regole pratiche

  • Limita il numero totale di interrogazioni. Un ciclo senza numero massimo di iterazioni è un blocco in attesa di accadere. Calcolalo in base alla tua scadenza e al limite dell'intervallo.
  • Separa il timeout di lettura dalla scadenza dell'attività. La chiamata HTTP per ottenere lo stato deve avere un proprio timeout breve. Se la lettura dello stato va in timeout, riprova quella lettura — mai l'attività.

Rispetta l'endpoint

Il polling è economico, ma non gratuito. Se devi controllare centinaia di attività, sfalsale e riutilizza lo stesso client. Nel dubbio, più lento è quasi sempre la direzione più sicura: un'attività che vale la pena attendere per sei minuti merita anche sei minuti e trenta secondi.

Scegliere tra retry e nuovo invio

È la decisione in cui correttezza e costo si scontrano. Riprovare la stessa attività e inviarne una nuova non sono operazioni intercambiabili: scegliere male significa duplicare il lavoro o lasciare in sospeso un'attività sana.

Regola decisionale

Procedi in ordine:

  1. L'attività è in uno stato terminale? Se no, non riprovare e non inviare di nuovo. Attendi. Riprovare un'attività ancora in esecuzione duplica il lavoro.
  2. Se è terminale, cosa dice lo stato? Successo significa che hai finito. Il fallimento porta un payload di errore — leggilo prima.
  3. Il fallimento può essere temporaneo? Timeout sotto carico elevato, indisponibilità breve, limitazione delle richieste — sono tutti candidati al retry.
  4. Il fallimento può essere permanente? Input non valido, parametri non supportati, payload malformati — falliranno allo stesso modo qualunque sia il numero di tentativi. Correggi la richiesta.

Riprova la stessa attività quando…

L'attività esiste ed è entrata in uno stato terminale di fallimento per una causa che potrebbe non ripetersi, e la semantica della piattaforma per quell'attività consente un nuovo tentativo. Riusi l'identificativo, mantieni una cronologia e eviti di creare una seconda attività che fa la stessa cosa.

Invia come nuova attività quando…

  • L'identificativo è sconosciuto o non più recuperabile — non c'è più nulla da riprovare.
  • L'attività originale è scaduta esplicitamente per policy, e il retry non ha più a cui agganciarsi.
  • Devi modificare l'input. Riprovare è la stessa richiesta; correggere è una nuova richiesta. Non lasciare mai che un retry abbia un significato diverso dalla richiesta originale.

L'asimmetria dei costi

Un retry di un'attività ancora in esecuzione raddoppia il lavoro per un solo risultato. Un nuovo invio è un'attività esplicitamente nuova — nuovo identificativo, nuovo ciclo di vita e un nuovo costo. Entrambi sono strumenti legittimi; entrambi vanno usati con giudizio. Regola empirica: riprova quando sei incerto sull'infrastruttura, invia di nuovo quando sei certo sull'input.

Rendere sicuri i retry

Un retry che duplica silenziosamente il lavoro è peggio di un blocco, perché fallisce in modo silenzioso e ti costa denaro. Integra la sicurezza prima che ti serva.

Ragiona in termini di idempotenza

Per ogni tipo di attività che esegui chiediti: se venisse eseguita due volte, il risultato sarebbe comunque corretto? Se la risposta è no — per esempio l'attività aggiunge una riga a un registro o attiva effetti collaterali a valle — allora la tua logica di retry deve essere filtrata dallo stato, mai da un timer. Riprova solo i fallimenti terminali, e solo dopo che una nuova interrogazione dello stato ha confermato quello stato.

Limita il numero di tentativi

Ogni ciclo di retry ha bisogno di un numero massimo di tentativi e di un tempo totale massimo, entrambi verificati esplicitamente. Un ciclo di retry illimitato non è che un blocco più lento e più costoso. Quando il budget si esaurisce, esponi il fallimento — non inghiottirlo silenziosamente.

Inserisci un backoff tra i tentativi

Anche i tentativi di retry devono essere distanziati e aumentare a ogni fallimento. Riprovare immediatamente una dipendenza già guasta di solito non fa che fallire di nuovo e aggiungere carico a un sistema già in difficoltà.

Registra i tentativi, non solo gli errori

Per ogni tentativo annota: identificativo dell'attività, stato al momento della decisione, numero del tentativo, intervallo usato ed esito. È la differenza tra "a volte si blocca" e "si blocca il martedì pomeriggio con input grandi". La seconda è correggibile, la prima no.

Non mascherare mai i veri fallimenti

Se un'attività fallisce tre volte e il tuo wrapper restituisce solo un errore generico, hai scartato i payload di errore che ne spiegavano la causa. Conserva lo stato e i dettagli dell'errore dell'ultimo tentativo fino al chiamante. Il retry è un meccanismo di recupero, non di occultamento — deve ridurre l'occasione in cui il problema di fondo riemerge, non l'occasione in cui viene visto.

Checklist post-mortem per blocchi ricorrenti

Un blocco accade una volta: basta risolvere quell'attività. Accade due volte: esegui questa checklist — altrimenti pagherai per il retry per sempre.

Checklist

  • Classifica l'evento. Era un'attività ancora in esecuzione che hai smesso di osservare, un'attività realmente bloccata, un timeout del client o uno stato che non sei riuscito a leggere? Se riesci a dirlo, hai già risolto metà del problema.
  • Controlla il calcolo del polling. Il limite dell'intervallo, la scadenza e il numero massimo di tentativi funzionano davvero? C'è qualche ciclo senza limiti?
  • Verifica che i timeout siano separati. Il timeout di lettura dello stato è mai stato confuso con la scadenza dell'attività?
  • Riesamina le decisioni di retry. Qualche retry è stato lanciato su un'attività non terminale? Qualche nuovo invio ha duplicato un'attività ancora in esecuzione?
  • Controlla i payload di fallimento. Gli errori sono stati conservati o inghiottiti dal wrapper?
  • Cerca schemi negli input. I fallimenti si concentrano per dimensione del payload, parametri o tipo di carico di lavoro?
  • Rivedi le aspettative. L'attività ha superato la tua scadenza perché il lavoro è davvero così grande? Se sì, il bug è nella scadenza.

Trasforma le scoperte in configurazione predefinita

Ognuno dei punti sopra dovrebbe diventare una pratica predefinita nel tuo client: un poller con limiti, un esito di scadenza distinto, una strategia di retry guidata dallo stato e log strutturati con chiave l'identificativo dell'attività. Fatto questo, il prossimo blocco sarà una riga nel log, non un pomeriggio perso.

Un'attività asincrona è un contratto, e il contratto regge finché rispetti il suo stato. Distingui ancora in esecuzione da mai completabile, attendi con un ritmo con limiti, riprova solo ciò che è sicuro riprovare — così un blocco resta un fastidio e non diventa un guasto.

Inizia a creare con artefatti IA persistenti

Ottieni la tua chiave API e chiama modelli di immagini, video e audio tramite un'unica API basata sui task.

Leggi la documentazione API