Eccezioni
Gerarchia
TrueAsync definisce una gerarchia specializzata di eccezioni per diversi tipi di errori:
\Cancellation -- classe base di cancellazione (alla pari con \Error e \Exception)
+-- Async\AsyncCancellation -- cancellazione della coroutine
+-- Async\OperationCanceledException -- operazione interrotta dal token di cancellazione
\Error
+-- Async\DeadlockError -- deadlock rilevato
\Exception
+-- Async\AsyncException -- errore generico di operazione asincrona
| +-- Async\ServiceUnavailableException -- servizio non disponibile (circuit breaker)
+-- Async\InputOutputException -- errore di I/O
+-- Async\DnsException -- errore di risoluzione DNS
+-- Async\TimeoutException -- timeout dell'operazione
+-- Async\PollException -- errore di operazione poll
+-- Async\ChannelException -- errore del canale
+-- Async\PoolException -- errore del pool di risorse
+-- Async\CompositeException -- contenitore per eccezioni multiple
AsyncCancellation
class Async\AsyncCancellation extends \Cancellation {}
Lanciata quando una coroutine viene cancellata. \Cancellation è la terza classe root Throwable alla pari con \Error e \Exception, quindi i normali blocchi catch (\Exception $e) e catch (\Error $e) non intercettano accidentalmente la cancellazione.
<?php
use Async\AsyncCancellation;
use function Async\spawn;
use function Async\await;
use function Async\delay;
$coroutine = spawn(function() {
try {
delay(10000);
} catch (AsyncCancellation $e) {
// Termina il lavoro in modo pulito
echo "Cancellata: " . $e->getMessage() . "\n";
}
});
delay(100);
$coroutine->cancel();
?>
Importante: Non catturare AsyncCancellation tramite catch (\Throwable $e) senza rilanciarla – questo viola il meccanismo di cancellazione cooperativa.
OperationCanceledException
class Async\OperationCanceledException extends Async\AsyncCancellation {}
Lanciata quando un’operazione in attesa viene interrotta da un token di cancellazione (cancellation token). L’eccezione originale del token è disponibile tramite $previous. Questo permette di distinguere l’attivazione del token da un’eccezione lanciata dall’oggetto awaitable stesso.
Riguarda tutte le operazioni con token di cancellazione: await(), await_*(), Future::await(), Channel::send()/recv(), Scope::awaitCompletion().
<?php
use Async\OperationCanceledException;
use function Async\spawn;
use function Async\await;
use function Async\timeout;
use function Async\delay;
$coroutine = spawn(function() {
delay(10000);
return "result";
});
try {
await($coroutine, timeout(1000));
} catch (OperationCanceledException $e) {
// Token di cancellazione attivato
echo "Operazione interrotta dal token\n";
echo "Motivo: " . $e->getPrevious()?->getMessage() . "\n";
} catch (\Exception $e) {
// Errore della coroutine stessa
echo "Errore: " . $e->getMessage() . "\n";
}
?>
DeadlockError
class Async\DeadlockError extends \Error {}
Lanciata quando lo scheduler rileva un deadlock – una situazione in cui le coroutine si attendono a vicenda e nessuna può procedere.
<?php
use function Async\spawn;
use function Async\await;
// Deadlock classico: due coroutine si attendono a vicenda
$c1 = spawn(function() use (&$c2) {
await($c2); // attende c2
});
$c2 = spawn(function() use (&$c1) {
await($c1); // attende c1
});
// DeadlockError: A deadlock was detected
?>
Esempio in cui una coroutine attende se stessa:
<?php
use function Async\spawn;
use function Async\await;
$coroutine = spawn(function() use (&$coroutine) {
await($coroutine); // attende se stessa
});
// DeadlockError
?>
AsyncException
class Async\AsyncException extends \Exception {}
Eccezione base per errori generici delle operazioni asincrone. Usata per errori che non rientrano nelle categorie specializzate.
TimeoutException
class Async\TimeoutException extends \Exception {}
Lanciata quando un timeout viene superato all’interno di una coroutine. Quando timeout() viene usato come token di cancellazione in await(), viene lanciata OperationCanceledException con TimeoutException in $previous:
<?php
use Async\OperationCanceledException;
use function Async\spawn;
use function Async\await;
use function Async\timeout;
use function Async\delay;
try {
$coroutine = spawn(function() {
delay(10000); // Operazione lunga
});
await($coroutine, timeout(1000)); // Timeout di 1 secondo
} catch (OperationCanceledException $e) {
// Token di cancellazione attivato. $e->getPrevious() — TimeoutException.
echo "L'operazione non è stata completata in tempo\n";
}
?>
InputOutputException
class Async\InputOutputException extends \Exception {}
Eccezione generica per errori di I/O: socket, file, pipe e altri descrittori di I/O.
DnsException
class Async\DnsException extends \Exception {}
Lanciata in caso di errori di risoluzione DNS (gethostbyname, gethostbyaddr, gethostbynamel).
PollException
class Async\PollException extends \Exception {}
Lanciata in caso di errori nelle operazioni di poll sui descrittori.
ServiceUnavailableException
class Async\ServiceUnavailableException extends Async\AsyncException {}
Lanciata quando il circuit breaker è nello stato INACTIVE e una richiesta al servizio viene rifiutata senza tentare l’esecuzione.
<?php
use Async\ServiceUnavailableException;
try {
$resource = $pool->acquire();
} catch (ServiceUnavailableException $e) {
echo "Il servizio è temporaneamente non disponibile\n";
}
?>
ChannelException
class Async\ChannelException extends Async\AsyncException {}
Lanciata in caso di errori nelle operazioni del canale: invio a un canale chiuso, ricezione da un canale chiuso, ecc.
PoolException
class Async\PoolException extends Async\AsyncException {}
Lanciata in caso di errori nelle operazioni del pool di risorse.
CompositeException
final class Async\CompositeException extends \Exception
{
public function addException(\Throwable $exception): void;
public function getExceptions(): array;
}
Un contenitore per eccezioni multiple. Usata quando diversi handler (es. finally nello Scope) lanciano eccezioni durante il completamento:
<?php
use Async\Scope;
use Async\CompositeException;
$scope = new Scope();
$scope->finally(function() {
throw new \Exception('Errore di pulizia 1');
});
$scope->finally(function() {
throw new \RuntimeException('Errore di pulizia 2');
});
$scope->setExceptionHandler(function($scope, $coroutine, $exception) {
if ($exception instanceof CompositeException) {
echo "Errori: " . count($exception->getExceptions()) . "\n";
foreach ($exception->getExceptions() as $e) {
echo " - " . $e->getMessage() . "\n";
}
}
});
$scope->dispose();
// Errori: 2
// - Errore di pulizia 1
// - Errore di pulizia 2
?>
Raccomandazioni
Gestire Correttamente AsyncCancellation
<?php
// Corretto: catturare eccezioni specifiche
try {
await($coroutine);
} catch (\Exception $e) {
// AsyncCancellation NON verrà catturata qui -- è \Cancellation
handleError($e);
}
<?php
// Se devi catturare tutto -- rilancia sempre AsyncCancellation
try {
await($coroutine);
} catch (Async\AsyncCancellation $e) {
throw $e; // Rilancia
} catch (\Throwable $e) {
handleError($e);
}
Protezione delle Sezioni Critiche
Usa protect() per operazioni che non devono essere interrotte dalla cancellazione:
<?php
use function Async\protect;
$db->beginTransaction();
protect(function() use ($db) {
$db->exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
$db->exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
$db->commit();
});
Vedi Anche
- Cancellazione – il meccanismo di cancellazione delle coroutine
- protect() – protezione dalla cancellazione
- Scope – gestione delle eccezioni negli scope