Trabaja con operaciones de larga duración

En ocasiones, una API puede exponer un método que tarda una cantidad significativa de tiempo en completarse. En lugar de bloquear mientras se ejecuta la tarea, puedes mostrar una promesa y permitir que el usuario verifique el estado.

Las bibliotecas cliente para Rust proporcionan asistentes para trabajar con estas operaciones de larga duración (LRO). Google Cloud En esta guía, se muestra cómo iniciar LRO y esperar a que se completen.

Requisitos previos

En esta guía, se usa el servicio de Cloud Storage para mantener concretos los fragmentos de código. Estos conceptos se aplican a cualquier otro servicio que use LRO.

Antes de seguir esta guía, debes hacer lo siguiente:

Para obtener instrucciones de configuración completas para las bibliotecas de Rust, consulta Configura tu entorno de desarrollo.

Dependencias

Declara Google Cloud las dependencias en tu Cargo.toml archivo:

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

También necesitas varias funciones de tokio:

cargo add tokio --features full,macros

Inicia una operación de larga duración

En este ejemplo, se usa la carpeta de cambio de nombre. Esta operación puede tardar mucho tiempo para las carpetas grandes, pero es relativamente rápida para las carpetas más pequeñas.

Para iniciar una operación de larga duración, debes inicializar un cliente y realizar la RPC.

Primero, agrega declaraciones use para evitar nombres de paquetes largos:

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

A continuación, crea el cliente:

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

En las bibliotecas cliente de Rust, cada solicitud se representa con un método que muestra un compilador de solicitudes. Llama al método en el cliente para crear el compilador de solicitudes:

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

Las funciones de muestra aceptan los nombres de bucket y carpeta como argumentos:

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

Realiza la solicitud y espera a que se muestre una operación para que se devuelva. Esta Operation actúa como una promesa para el resultado de la solicitud de larga duración:

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

Esta solicitud inicia la operación en segundo plano. Espera hasta que se complete la operación para determinar si se realizó correctamente.

Sondea automáticamente una operación de larga duración

Para configurar el sondeo automático, iniciarás una operación de larga duración con un Poller en lugar de .send().wait, de la siguiente manera:

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

Primero, introduce el rasgo Poller en el alcance con una declaración use:

use google_cloud_lro::Poller;

Luego, inicializa el cliente y prepara la solicitud como antes:

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

Sondea hasta que se complete la operación y muestra el resultado:

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

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

Sondea una operación de larga duración con resultados intermedios

El método .until_done() es conveniente, pero omite los informes de progreso parciales de las operaciones de larga duración. Si tu aplicación requiere esta información, usa el sondeo directamente:

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

Luego, usa el sondeo en un bucle:

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;
}

Este bucle espera explícitamente antes de volver a sondear. El período de sondeo depende de la operación específica y su carga útil. Consulta la documentación del servicio o experimenta con tus datos para determinar un buen valor.

Sondea manualmente una operación de larga duración

Si bien recomendamos enfoques de sondeo automatizados, también es posible sondear manualmente una operación de larga duración. Para obtener más información, consulta la documentación de referencia del mensaje Operation.

Inicia la operación de larga duración con el cliente:

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

Inicia un bucle de sondeo y verifica si la operación se completó con el campo done:

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

Cuando se completa la operación, suele contener un resultado. El campo de resultado es opcional porque el servicio podría mostrar done como verdadero sin un resultado. Por ejemplo, una operación de eliminación exitosa no tiene valor de retorno. En este ejemplo, el servicio de Cloud Storage siempre muestra un valor:

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

Es posible que una operación iniciada no se complete correctamente. El resultado puede ser un error o una respuesta válida. Primero, verifica si hay errores:

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

El tipo de error es un tipo de mensaje de estado. Esto NO implementa el rasgo Error estándar. Conviértelo manualmente a un error válido con Error::service.

Si el resultado es exitoso, extrae el tipo de respuesta. Encuentra este tipo en la documentación del método LRO o en la documentación de la API del servicio:

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

Ten en cuenta que la extracción del valor puede fallar si el tipo no coincide con lo que envió el servicio.

Google Cloud Los tipos pueden agregar campos y ramas en el futuro. Las Google Cloud bibliotecas cliente para Rust marcan todas las estructuras y enumeraciones como #[non_exhaustive]. Controla este caso:

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

Si la operación no se completó, puede contener metadatos. Algunos servicios incluyen información inicial sobre la solicitud, mientras que otros incluyen informes de progreso parciales. Puedes extraer y generar informes de estos metadatos:

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

Espera antes de volver a sondear. Considera ajustar el período de sondeo con la retirada exponencial truncada. En este ejemplo, se sondea cada 500 ms:

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

Consulta el estado de la operación:

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

Para simplificar, en este ejemplo, se ignoran todos los errores. En tu aplicación, puedes tratar un subconjunto de errores como no recuperables y limitar la cantidad de intentos de sondeo.

¿Qué sigue?

  • Consulta el código fuente de los ejemplos en GitHub.