Exceptions

Hierarchie

TrueAsync definit une hierarchie d’exceptions specialisee pour differents types d’erreurs :

\Cancellation                              -- classe de base d'annulation (au meme rang que \Error et \Exception)
+-- Async\AsyncCancellation                -- annulation de coroutine
    +-- Async\OperationCanceledException   -- opération interrompue par un jeton d'annulation

\Error
+-- Async\DeadlockError                    -- interblocage detecte

\Exception
+-- Async\AsyncException                   -- erreur generale d'operation asynchrone
|   +-- Async\ServiceUnavailableException  -- service indisponible (circuit breaker)
+-- Async\InputOutputException             -- erreur d'E/S
+-- Async\DnsException                     -- erreur de resolution DNS
+-- Async\TimeoutException                 -- timeout d'operation
+-- Async\PollException                    -- erreur d'operation poll
+-- Async\ChannelException                 -- erreur de channel
+-- Async\PoolException                    -- erreur de pool de ressources
+-- Async\CompositeException               -- conteneur pour exceptions multiples

AsyncCancellation

class Async\AsyncCancellation extends \Cancellation {}

Lancee lorsqu’une coroutine est annulee. \Cancellation est la troisieme classe racine Throwable au meme rang que \Error et \Exception, donc les blocs habituels catch (\Exception $e) et catch (\Error $e) n’interceptent pas accidentellement l’annulation.

<?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) {
        // Terminer gracieusement le travail
        echo "Annule : " . $e->getMessage() . "\n";
    }
});

delay(100);
$coroutine->cancel();
?>

Important : N’interceptez pas AsyncCancellation via catch (\Throwable $e) sans la relancer – cela viole le mecanisme d’annulation cooperative.

OperationCanceledException

class Async\OperationCanceledException extends Async\AsyncCancellation {}

Levée lorsqu’une opération en attente est interrompue par un jeton d’annulation (cancellation token). L’exception originale du jeton est disponible via $previous. Cela permet de distinguer le déclenchement du jeton d’une exception levée par l’objet awaitable lui-même.

Concerne toutes les opérations avec jeton d’annulation : 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) {
    // Jeton d'annulation déclenché
    echo "Opération interrompue par le jeton\n";
    echo "Raison : " . $e->getPrevious()?->getMessage() . "\n";
} catch (\Exception $e) {
    // Erreur de la coroutine elle-même
    echo "Erreur : " . $e->getMessage() . "\n";
}
?>

DeadlockError

class Async\DeadlockError extends \Error {}

Lancee lorsque l’ordonnanceur detecte un interblocage – une situation ou des coroutines s’attendent mutuellement et aucune ne peut progresser.

<?php
use function Async\spawn;
use function Async\await;

// Interblocage classique : deux coroutines s'attendent mutuellement
$c1 = spawn(function() use (&$c2) {
    await($c2); // attend c2
});

$c2 = spawn(function() use (&$c1) {
    await($c1); // attend c1
});
// DeadlockError: A deadlock was detected
?>

Exemple ou une coroutine s’attend elle-meme :

<?php
use function Async\spawn;
use function Async\await;

$coroutine = spawn(function() use (&$coroutine) {
    await($coroutine); // s'attend elle-meme
});
// DeadlockError
?>

AsyncException

class Async\AsyncException extends \Exception {}

Exception de base pour les erreurs generales d’operations asynchrones. Utilisee pour les erreurs qui n’entrent pas dans les categories specialisees.

TimeoutException

class Async\TimeoutException extends \Exception {}

Lancee lorsqu’un timeout est depasse a l’interieur d’une coroutine. Lorsque timeout() est utilise comme jeton d’annulation dans await(), OperationCanceledException est levee avec TimeoutException dans $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); // Operation longue
    });
    await($coroutine, timeout(1000)); // Timeout de 1 seconde
} catch (OperationCanceledException $e) {
    // Jeton d'annulation declenche. $e->getPrevious() — TimeoutException.
    echo "L'operation ne s'est pas terminee a temps\n";
}
?>

InputOutputException

class Async\InputOutputException extends \Exception {}

Exception generale pour les erreurs d’E/S : sockets, fichiers, pipes et autres descripteurs d’E/S.

DnsException

class Async\DnsException extends \Exception {}

Lancee lors d’erreurs de resolution DNS (gethostbyname, gethostbyaddr, gethostbynamel).

PollException

class Async\PollException extends \Exception {}

Lancee lors d’erreurs d’operations poll sur les descripteurs.

ServiceUnavailableException

class Async\ServiceUnavailableException extends Async\AsyncException {}

Lancee lorsque le circuit breaker est dans l’etat INACTIVE et qu’une requete de service est rejetee sans tentative d’execution.

<?php
use Async\ServiceUnavailableException;

try {
    $resource = $pool->acquire();
} catch (ServiceUnavailableException $e) {
    echo "Le service est temporairement indisponible\n";
}
?>

ChannelException

class Async\ChannelException extends Async\AsyncException {}

Lancee lors d’erreurs d’operation sur les channels : envoi vers un channel ferme, reception depuis un channel ferme, etc.

PoolException

class Async\PoolException extends Async\AsyncException {}

Lancee lors d’erreurs d’operation sur le pool de ressources.

CompositeException

final class Async\CompositeException extends \Exception
{
    public function addException(\Throwable $exception): void;
    public function getExceptions(): array;
}

Un conteneur pour exceptions multiples. Utilise lorsque plusieurs gestionnaires (par ex., finally dans Scope) lancent des exceptions lors de la completion :

<?php
use Async\Scope;
use Async\CompositeException;

$scope = new Scope();

$scope->finally(function() {
    throw new \Exception('Erreur de nettoyage 1');
});

$scope->finally(function() {
    throw new \RuntimeException('Erreur de nettoyage 2');
});

$scope->setExceptionHandler(function($scope, $coroutine, $exception) {
    if ($exception instanceof CompositeException) {
        echo "Erreurs : " . count($exception->getExceptions()) . "\n";
        foreach ($exception->getExceptions() as $e) {
            echo "  - " . $e->getMessage() . "\n";
        }
    }
});

$scope->dispose();
// Erreurs : 2
//   - Erreur de nettoyage 1
//   - Erreur de nettoyage 2
?>

Recommandations

Gestion correcte de AsyncCancellation

<?php
// Correct : intercepter les exceptions specifiques
try {
    await($coroutine);
} catch (\Exception $e) {
    // AsyncCancellation ne sera PAS interceptee ici -- c'est \Cancellation
    handleError($e);
}
<?php
// Si vous devez tout intercepter -- toujours relancer AsyncCancellation
try {
    await($coroutine);
} catch (Async\AsyncCancellation $e) {
    throw $e; // Relancer
} catch (\Throwable $e) {
    handleError($e);
}

Protection des sections critiques

Utilisez protect() pour les operations qui ne doivent pas etre interrompues par l’annulation :

<?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();
});

Voir aussi