---
name: process_runner
kind: component
lang: cpp
domain: core
version: "1.0.0"
purity: impure
signature: "class fn_ui::ProcessRunner { is_busy(); state(); message(); reset(); }; void runner_trigger(ProcessRunner&, std::function<bool(std::string&)>); void runner_status(const ProcessRunner&, const char* label)"
description: "Ejecuta una tarea en std::thread en background y expone estado thread-safe (idle/running/success/error). Incluye widget inline con spinner + resultado."
tags: [imgui, ui, async, thread, runner, tokens]
uses_functions: ["process_state_machine_cpp_core", "tokens_cpp_core"]
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: [imgui, thread, mutex, atomic]
tested: false
tests: []
test_file_path: ""
file_path: "cpp/functions/core/process_runner.cpp"
framework: imgui
params: []
output: "ProcessRunner: objeto que encapsula un thread + estado. runner_trigger lanza la tarea. runner_status renderiza spinner + mensaje segun state."
---

# process_runner

Wrapper para ejecutar operaciones lentas (HTTP requests, subprocesos) sin bloquear el UI thread. Threading simple con `std::thread` y estado `std::atomic<int>` + mensaje protegido por `std::mutex`.

## Uso tipico — reindex desde un boton

```cpp
#include "core/process_runner.h"
#include "core/button.h"

static fn_ui::ProcessRunner reindex_runner;

// En el render:
if (fn_ui::button("Reindex", ButtonVariant::Primary) && !reindex_runner.is_busy()) {
    fn_ui::runner_trigger(reindex_runner, [](std::string& out) -> bool {
        // Esto corre en el thread en background:
        return http_post("http://127.0.0.1:8484/api/reindex", "", &out);
    });
}
ImGui::SameLine();
fn_ui::runner_status(reindex_runner, "Reindexing...");
```

Mientras corre muestra un circulo con un punto rotando + "Reindexing...". Al terminar muestra un ✓ o ✗ con el `message` devuelto por la tarea.

## Thread safety

- `state()` es lock-free (atomic load).
- `message()` toma lock (copia string).
- La tarea recibe un `std::string&` que SE MUTA desde el thread; el lock lo toma internamente al final para guardar el resultado.
- `runner_trigger` ignora la llamada si el runner ya esta Running (no permite concurrencia sobre el mismo runner — usar runners separados para operaciones paralelas).

## Notas

- El destructor hace `join()` del thread, asi que si la tarea tarda, cerrar la app espera. HTTP calls con timeout razonable hacen esto seguro.
- `reset()` pasa a Idle si no esta Running — util despues de mostrar el resultado para limpiar el UI.
