Travailler avec des opérations de longue durée

Il arrive qu'une API expose une méthode dont l'exécution prend beaucoup de temps. Au lieu de bloquer l'exécution de la tâche, vous pouvez renvoyer une promesse et laisser l'utilisateur vérifier l'état.

Les bibliothèques clientes pour Rust fournissent des outils pour travailler avec ces opérations de longue durée. Google Cloud Ce guide vous explique comment démarrer des opérations de longue durée et attendre qu'elles se terminent.

Prérequis

Ce guide utilise le service Cloud Storage pour rendre les extraits de code concrets. Ces concepts s'appliquent à tout autre service qui utilise des opérations de longue durée.

Avant de suivre ce guide, vous devez :

Pour obtenir des instructions complètes sur la configuration des bibliothèques Rust, consultez la page Configurer votre environnement de développement.

Dépendances

Déclarez les dépendances dans votre fichier Cargo.toml : Google Cloud

cargo add google-cloud-storage google-cloud-lro google-cloud-longrunning

Vous avez également besoin de plusieurs fonctionnalités tokio :

cargo add tokio --features full,macros

Démarrer une opération de longue durée

Cet exemple utilise le renommage de dossier. Cette opération peut prendre beaucoup de temps pour les dossiers volumineux, mais elle est relativement rapide pour les dossiers plus petits.

Pour démarrer une opération de longue durée, vous devez initialiser un client et effectuer l'appel RPC.

Commencez par ajouter des déclarations use pour éviter les noms de packages longs :

use anyhow::anyhow;
use google_cloud_longrunning as longrunning;
use google_cloud_storage::client::StorageControl;

Ensuite, créez le client :

let client = StorageControl::builder().build().await?;

Dans les bibliothèques clientes Rust, chaque requête est représentée par une méthode qui renvoie un générateur de requêtes. Appelez la méthode sur le client pour créer le générateur de requêtes :

let operation = client
    .rename_folder()
    .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
    .set_destination_folder_id(dest)

Les fonctions d'exemple acceptent les noms de bucket et de dossier comme arguments :

pub async fn manual(bucket: &str, folder: &str, dest: &str) -> anyhow::Result<()> {

Effectuez la requête et attendez qu'une opération soit renvoyée. Cette Operation fait office de promesse pour le résultat de la requête de longue durée :

    let operation =
        // ...
        .send()
        .await?;

Cette requête démarre l'opération en arrière-plan. Attendez que l'opération se termine pour déterminer si elle a réussi.

Interroger automatiquement une opération de longue durée

Pour configurer l'interrogation automatique, vous démarrerez une opération de longue durée avec un Poller plutôt que .send().wait, comme suit :

.poller()
.until_done()
.await?;

Commencez par introduire le trait Poller dans le champ d'application à l'aide d'une déclaration use :

use google_cloud_lro::Poller;

Initialisez ensuite le client et préparez la requête comme précédemment :

let response = client
    .rename_folder()
    .set_name(format!("projects/_/buckets/{bucket}/folders/{folder}"))
    .set_destination_folder_id(dest)

Interrogez jusqu'à ce que l'opération se termine et affichez le résultat :

    .poller()
    .until_done()
    .await?;

println!("LRO completed, response={response:?}");

Interroger une opération de longue durée avec des résultats intermédiaires

La méthode .until_done() est pratique, mais elle omet les rapports d'avancement partiels des opérations de longue durée. Si votre application nécessite ces informations, utilisez directement l'interrogateur :

    let mut poller = client
        .rename_folder()
        /* more stuff */
        .poller();

Utilisez ensuite l'interrogateur dans une boucle :

while let Some(p) = poller.poll().await {
    match p {
        PollingResult::Completed(r) => {
            println!("LRO completed, response={r:?}");
        }
        PollingResult::InProgress(m) => {
            println!("LRO in progress, metadata={m:?}");
        }
        PollingResult::PollingError(e) => {
            println!("Transient error polling the LRO: {e}");
        }
    }
    tokio::time::sleep(std::time::Duration::from_millis(500)).await;
}

Cette boucle attend explicitement avant d'interroger à nouveau. La période d'interrogation dépend de l'opération spécifique et de sa charge utile. Consultez la documentation du service ou expérimentez avec vos données pour déterminer une bonne valeur.

Interroger manuellement une opération de longue durée

Bien que nous vous recommandions d'utiliser des approches d'interrogation automatisées, il est également possible d'interroger manuellement une opération de longue durée. Pour en savoir plus, consultez la documentation de référence sur le message Operation.

Démarrez l'opération de longue durée à l'aide du client :

    let mut operation = client
        .rename_folder()
        /* more stuff */
        .send()
        .await?;

Démarrez une boucle d'interrogation et vérifiez si l'opération est terminée à l'aide du champ done :

let response: anyhow::Result<Folder> = loop {
    if operation.done {

Une fois l'opération terminée, elle contient généralement un résultat. Le champ de résultat est facultatif, car le service peut renvoyer done comme "true" sans résultat. Par exemple, une opération de suppression réussie n'a pas de valeur de retour. Dans cet exemple, le service Cloud Storage renvoie toujours une valeur :

match &operation.result {
    None => {
        break Err(anyhow!("missing result for finished operation"));
    }

Une opération démarrée peut ne pas se terminer correctement. Le résultat peut être une erreur ou une réponse valide. Vérifiez d'abord les erreurs :

Some(r) => {
    break match r {
        longrunning::model::operation::Result::Error(s) => {
            Err(anyhow!("operation completed with error {s:?}"))
        }

Le type d'erreur est un type de message Status. Cela n'implémente PAS le trait Error standard. Convertissez-le manuellement en une erreur valide à l'aide de Error::service.

Si le résultat est positif, extrayez le type de réponse. Recherchez ce type dans la documentation de la méthode LRO ou dans la documentation de l'API du service :

longrunning::model::operation::Result::Response(any) => {
    let response = any.to_msg::<Folder>()?;
    Ok(response)
}

Notez que l'extraction de la valeur peut échouer si le type ne correspond pas à ce que le service a envoyé.

Google Cloud Les types peuvent ajouter des champs et des branches à l'avenir. Les Google Cloud bibliothèques clientes pour Rust marquent toutes les structures et tous les énumérations comme #[non_exhaustive]. Gérez ce cas :

_ => Err(anyhow!("unexpected result branch {r:?}")),

Si l'opération n'est pas terminée, elle peut contenir des métadonnées. Certains services incluent des informations initiales sur la requête, tandis que d'autres incluent des rapports d'avancement partiels. Vous pouvez extraire et signaler ces métadonnées :

if let Some(any) = &operation.metadata {
    let metadata = any.to_msg::<RenameFolderMetadata>()?;
    println!("LRO in progress, metadata={metadata:?}");
}

Attendez avant d'interroger à nouveau. Envisagez d'ajuster la période d'interrogation à l'aide d'un intervalle exponentiel tronqué entre les tentatives. Cet exemple interroge toutes les 500 ms :

tokio::time::sleep(std::time::Duration::from_millis(500)).await;

Interrogez l'état de l'opération :

if let Ok(attempt) = client
    .get_operation()
    .set_name(&operation.name)
    .send()
    .await
{
    operation = attempt;
}

Par souci de simplicité, cet exemple ignore toutes les erreurs. Dans votre application, vous pouvez traiter un sous-ensemble d'erreurs comme non récupérables et limiter le nombre de tentatives d'interrogation.

Étape suivante

  • Affichez le code source des exemples sur GitHub.