Saltar al contenido

Los errores de una tarea que nadie espera

Un comando que falla rechaza una promesa; una tarea de fondo que falla no le cuenta nada a nadie. Los tres destinos que necesita un error sin dueño, y por qué el registro no basta.

Las tres formas de perderlo

Rust
// 1 · el resultado descartadotauri::async_runtime::spawn(async move {    sincronizar().await;              // devuelve Result y nadie lo mira});// 2 · el operador de propagación en una tarea que devuelve ()tauri::async_runtime::spawn(async move {    let datos = descargar().await?;   // ✗ ni siquiera compila… o sí, si el bloque    guardar(datos).await?;            //   devuelve Result, y entonces se descarta igual});// 3 · el pánicotauri::async_runtime::spawn(async move {    let x = mapa.get("clave").unwrap();   // si no está, la tarea muere en silencio});

El tercero es el peor: un pánico dentro de una tarea no tumba la aplicación ni imprime nada visible. La tarea desaparece, y lo único que se nota es que aquello que debía pasar no pasa. Si esa tarea era un bucle periódico, deja de haber sincronización para siempre y nadie lo sabe.

Los tres destinos

Todo error de una tarea de fondo necesita ir a tres sitios, y son distintos:

Los dos primeros son obligatorios y el tercero es una decisión. El error frecuente es hacer solo el primero: registrar y seguir. El registro sirve para el diagnóstico después; la aplicación mientras tanto sigue creyendo que todo va bien.

El patrón

Rust
tauri::async_runtime::spawn({    let app = app.clone();    async move {        let estado = app.state::<Sincronizacion>();        estado.marcar_en_curso();        match sincronizar(&app).await {            Ok(resumen) => {                estado.marcar_ok(resumen.cambios);                let _ = app.emit(eventos::SYNC_OK, &resumen);            }            Err(e) => {                log::error!("la sincronización falló: {e:?}");   // 1 · registro                estado.marcar_error(&e);                          // 2 · estado                if e.requiere_accion_del_usuario() {              // 3 · usuario                    let _ = app.emit(eventos::SYNC_ERROR, ErrorPayload::from(e));                }            }        }    }});

Y para el pánico, que el match no cubre, hace falta una capa más: capturar el pánico en el límite de la tarea, registrarlo y dejar el estado en error como si fuera un fallo normal. Sin eso, un pánico deja la sincronización marcada «en curso» para siempre.

El aviso repetido

Una tarea periódica que falla siempre —el servidor está caído, la credencial caducó— avisa cada minuto y se convierte en un ruido que el usuario aprende a ignorar. Tres correcciones que se combinan:

  • Avisar en el primer fallo y callar hasta que cambie el estado. No en cada intento: cuando pase de correcto a error, y cuando vuelva.
  • Espera creciente entre intentos. Un minuto, dos, cuatro, hasta un máximo. Reduce el ruido y el gasto.
  • Que la interfaz muestre el estado, no una notificación. Un indicador permanente que diga «sin sincronizar desde las 10:42» informa mejor que cinco avisos, y no interrumpe.