Mecanisme d’attente et de reveil des coroutines

Pour stocker le contexte d’attente d’une coroutine, TrueAsync utilise la structure Waker. Elle sert de lien entre une coroutine et les evenements auxquels elle est abonnee. Grace au Waker, une coroutine sait toujours exactement quels evenements elle attend.

Structure du Waker

Pour des raisons d’optimisation memoire, le waker est integre directement dans la structure de la coroutine (zend_coroutine_t), ce qui evite des allocations supplementaires et simplifie la gestion memoire, bien qu’un pointeur zend_async_waker_t *waker soit utilise dans le code pour la compatibilite ascendante.

Le Waker contient une liste des evenements attendus et agregue le resultat de l’attente ou l’exception.

struct _zend_async_waker_s {
    ZEND_ASYNC_WAKER_STATUS status;

    // Evenements que la coroutine attend
    HashTable events;

    // Evenements declenches lors de la derniere iteration
    HashTable *triggered_events;

    // Resultat du reveil
    zval result;

    // Erreur (si le reveil a ete cause par une erreur)
    zend_object *error;

    // Point de creation (pour le debogage)
    zend_string *filename;
    uint32_t lineno;

    // Destructeur
    zend_async_waker_dtor dtor;
};

Statuts du Waker

A chaque etape de la vie d’une coroutine, le Waker se trouve dans l’un des cinq etats :

Statuts du Waker

typedef enum {
    ZEND_ASYNC_WAKER_NO_STATUS, // Le Waker n'est pas actif
    ZEND_ASYNC_WAKER_WAITING,   // La coroutine attend des evenements
    ZEND_ASYNC_WAKER_QUEUED,    // La coroutine est en file d'attente pour l'execution
    ZEND_ASYNC_WAKER_IGNORED,   // La coroutine a ete ignoree
    ZEND_ASYNC_WAKER_RESULT     // Le resultat est disponible
} ZEND_ASYNC_WAKER_STATUS;

Une coroutine demarre avec NO_STATUS – le Waker existe mais n’est pas actif ; la coroutine s’execute. Lorsque la coroutine appelle SUSPEND(), le Waker passe a WAITING et commence a surveiller les evenements.

Lorsqu’un des evenements se declenche, le Waker passe a QUEUED : le resultat est sauvegarde, et la coroutine est placee dans la file du Scheduler en attente d’un changement de contexte.

Le statut IGNORED est necessaire pour les cas ou une coroutine est deja dans la file mais doit etre detruite. Dans ce cas, le Scheduler ne lance pas la coroutine mais finalise immediatement son etat.

Lorsque la coroutine se reveille, le Waker passe a l’etat RESULT. A ce moment, waker->error est transfere a EG(exception). S’il n’y a pas d’erreurs, la coroutine peut utiliser waker->result. Par exemple, result est ce que la fonction await() retourne.

Creation d’un Waker

// Obtenir le waker (le creer s'il n'existe pas)
zend_async_waker_t *waker = zend_async_waker_define(coroutine);

// Reinitialiser le waker pour une nouvelle attente
zend_async_waker_t *waker = zend_async_waker_new(coroutine);

// Avec timeout et annulation
zend_async_waker_t *waker = zend_async_waker_new_with_timeout(
    coroutine, timeout_ms, cancellation_event);

zend_async_waker_new() detruit le waker existant et le reinitialise a son etat initial. Cela permet de reutiliser le waker sans allocations.

Abonnement aux evenements

Le module zend_async_API.c fournit plusieurs fonctions pretes a l’emploi pour lier une coroutine a un evenement :

zend_async_resume_when(
    coroutine,        // Quelle coroutine reveiller
    event,            // A quel evenement s'abonner
    trans_event,      // Transferer la propriete de l'evenement
    callback,         // Fonction de callback
    event_callback    // Callback de coroutine (ou NULL)
);

resume_when est la fonction d’abonnement principale. Elle cree un zend_coroutine_event_callback_t, le lie a l’evenement et au waker de la coroutine.

Comme fonction de callback, vous pouvez utiliser l’une des trois fonctions standard, selon la maniere dont vous souhaitez reveiller la coroutine :

// Resultat reussi
zend_async_waker_callback_resolve(event, callback, result, exception);

// Annulation
zend_async_waker_callback_cancel(event, callback, result, exception);

// Timeout
zend_async_waker_callback_timeout(event, callback, result, exception);