Files
fn_registry/dev/issues/0071f-extract-subprocess-streamer.md

4.0 KiB

id, title, status, type, domain, scope, priority, depends, blocks, related, created, updated, tags
id title status type domain scope priority depends blocks related created updated tags
0071f Extraer `subprocess_streamer` a cpp/functions/core/ (sub-issue de 0071) pendiente feature
registry-quality
registry-only media
2026-05-10 2026-05-17

Contexto

Sub-issue derivado de 0071 tras auditar paneles C++. La regla "rule of three" se cumple HOY para una primitiva Tier 4: spawn de subprocesos con captura de stdout/stderr en stream, sin wrapper compartido.

Tres reimplementaciones del mismo patron en projects/osint_graph/apps/graph_explorer/:

Sitio Linea Mecanismo
chat.cpp 295 popen(...) + fgets loop
chat.cpp 412 execvp(...) con pipes manuales
jobs.cpp 817 execvp(...) con pipes + read-thread
extract_panel.cpp 507 execvp(...) para Python enricher

Cada una hace: setup pipes, fork/exec, read loop, parse JSON line-by-line, push a UI queue. Codigo duplicado ~60-80 LoC en cada sitio.

Objetivo

Funcion subprocess_streamer en cpp/functions/core/ que encapsule:

  • Spawn (POSIX fork+execvp, Windows CreateProcess)
  • Pipes para stdin/stdout/stderr
  • Read-thread con callback por linea
  • Cancel/kill
  • Wait + exit code

API propuesta

namespace fn_core {

struct SubprocessConfig {
    std::vector<std::string> argv;        // [exe, arg1, arg2, ...]
    std::vector<std::string> env;         // ["KEY=VAL", ...]; vacio = heredar
    std::string cwd;                      // vacio = heredar
    bool merge_stderr_to_stdout = false;
    std::function<void(const std::string&)> on_stdout_line;
    std::function<void(const std::string&)> on_stderr_line;
    std::function<void(int exit_code)> on_exit;
};

struct SubprocessHandle;  // opaco

// Lanza subproceso. on_* se llaman desde un read-thread interno.
SubprocessHandle* subprocess_spawn(const SubprocessConfig& cfg);

// Escribe en stdin. Thread-safe. Devuelve bytes escritos o -1.
int subprocess_write_stdin(SubprocessHandle* h, const char* data, size_t n);

// Cierra stdin (EOF al child).
void subprocess_close_stdin(SubprocessHandle* h);

// Mata el proceso (SIGTERM en POSIX, TerminateProcess en Win).
void subprocess_kill(SubprocessHandle* h);

// Espera fin. Devuelve exit_code. Si ya termino, retorna inmediato.
int subprocess_wait(SubprocessHandle* h);

// Libera recursos. Si el proceso sigue vivo, lo mata primero.
void subprocess_destroy(SubprocessHandle* h);

}  // namespace fn_core

Tests

  • Spawn echo hello → on_stdout_line recibe "hello", exit 0.
  • Spawn cat con stdin "abc\n" → on_stdout_line recibe "abc", exit 0 tras close_stdin.
  • Spawn false → exit 1.
  • Spawn comando inexistente → handle nullable o exit code distintivo.
  • Kill proceso vivo → wait retorna codigo de señal.

cpp/apps/primitives_gallery/ añade demo "Subprocess Streamer" con boton "spawn echo hello" + lista de lineas recibidas.

Migracion de consumidores (orden)

  1. Crear cpp/functions/core/subprocess_streamer.{h,cpp} + .md + tests.
  2. Migrar chat.cpp primero (mas critico, define el flujo de error). Si funciona en chat → patron validado.
  3. Migrar jobs.cpp.
  4. Migrar extract_panel.cpp.

Cada migracion en su propio commit, validando por inspeccion manual que el panel sigue funcionando.

Definicion de hecho

  • subprocess_streamer.{h,cpp,md} registrado en registry.db (fn index).
  • Tests pasan (al menos los 5 listados).
  • 3 consumidores migrados (chat, jobs, extract_panel).
  • app.md de graph_explorer actualizado: uses_functions añade subprocess_streamer_cpp_core.
  • LoC eliminadas en graph_explorer: ~150-200 (3 reimplementaciones colapsadas a 1 import).

Anti-patrones a evitar

  • Async/promesa con std::future — over-engineering. Callbacks bastan.
  • Buffering "smart" line-aware sobre bytes raw — usar std::getline simple.
  • Soporte ptys (terminal real) — fuera de scope. Si una app necesita pty, abre issue propio.
  • Prematura abstraccion de "shell vs exec". Solo argv directo (execvp). Si alguien quiere shell pipes, lo hace en bash -c "...".