Skip to main content

subsystem-summary-of-process

read this skill for a token-efficient summary of the process subsystem

Informations de source

Dépôt
stellar/stellar-core
Dernière activité de la source
11 février 2026 à 01:44
Langue détectée de SKILL.md
anglais
Étoiles
3 302
Forks
1 079

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
subsystem-summary-of-process
description
read this skill for a token-efficient summary of the process subsystem
# Process Subsystem — Technical Summary ## Overview The process subsystem provides asynchronous subprocess management for stellar-core, wrapping platform-specific process spawning (POSIX `posix_spawnp` and Windows `CreateProcess`) behind a unified asio-integrated interface. It enables running external commands (e.g., history archival tools like `gzip`, `gunzip`, `curl`) asynchronously, with configurable concurrency limits, output file capture, graceful shutdown, and process lifecycle tracking. No facilities exist for reading/writing subprocess I/O ports — this is strictly for "run a command, wait to see if it worked." ## Key Files - **ProcessManager.h** — Abstract interface `ProcessManager` and the `ProcessExitEvent` class for async process completion notification. - **ProcessManagerImpl.h / ProcessManagerImpl.cpp** — Concrete implementation of `ProcessManager`; contains all lifecycle management, platform-specific spawning, signal handling, and shutdown logic. - **PosixSpawnFileActions.h / PosixSpawnFileActions.cpp** — POSIX-only RAII wrapper around `posix_spawn_file_actions_t` for redirecting subprocess stdout to a file. --- ## Key Classes and Data Structures ### `ProcessManager` (abstract, inherits `std::enable_shared_from_this<ProcessManager>`, `NonMovableOrCopyable`) The public interface for subprocess management. One `ProcessManager` exists per `Application` instance, created via the static factory `ProcessManager::create(Application&)`. **Key virtual methods:** - `runProcess(cmdLine, outputFile)` → `std::weak_ptr<ProcessExitEvent>` — Queues or immediately launches a subprocess. If `outputFile` is non-empty, stdout is captured to a temp file and atomically renamed on success. - `getNumRunningProcesses()` — Count of active (non-shutting-down) child processes. - `getNumRunningOrShuttingDownProcesses()` — Count of all tracked child processes including those being terminated. - `tryProcessShutdown(pe)` — Synchronously cancels a `ProcessExitEvent` and attempts to terminate the associated process (SIGTERM on POSIX, `GenerateConsoleCtrlEvent` on Windows). Returns `true` if the termination signal was sent successfully. - `shutdown()` — Marks the manager as shut down, cancels all pending processes, and attempts polite shutdown of all running processes. - `isShutdown()` — Returns whether shutdown has been initiated. ### `ProcessExitEvent` An asio-compatible event object that clients use to await subprocess completion. It simulates an event notifier using a `RealTimer` set to maximum duration. When the subprocess exits (or is cancelled), the timer is cancelled with an appropriate error code. **Members:** - `mTimer` (`std::shared_ptr<RealTimer>`) — The underlying asio timer used for async waiting. - `mImpl` (`std::shared_ptr<Impl>`) — Platform-specific implementation details (command line, output file, process handle, lifecycle state). - `mEc` (`std::shared_ptr<asio::error_code>`) — Shared error code for communicating the real exit status past asio's timer cancellation (which always delivers `operation_aborted`). **Key methods:** - `async_wait(handler)` — Registers a callback that fires when the process exits. The handler receives an `asio::error_code` where a zero value means success, and a non-zero value encodes the process exit status. ### `ProcessExitEvent::Impl` (internal, inherits `std::enable_shared_from_this`) Holds all per-process state and manages the platform-specific spawning logic. **Members:** - `mOuterTimer` / `mOuterEc` — Shared pointers back to the owning `ProcessExitEvent`'s timer and error code. - `mCmdLine` (`std::string const`) — The command line to execute. - `mOutFile` (`std::string const`) — The desired final output file path (may be empty if no capture). - `mTempFile` (`std::string const`) — Temporary file path for capturing stdout before atomic rename. - `mLifecycle` (`ProcessLifecycle`) — Tracks the process through its states. - `mProcessId` (`int`) — PID of the spawned process (-1 before launch). - `mProcessHandle` (Windows only, `asio::windows::object_handle`) — Waitable handle for the Windows process object. - `mProcManagerImpl` (`std::weak_ptr<ProcessManagerImpl>`) — Weak back-reference to the owning manager. **Key methods:** - `run()` — Platform-specific process spawning. On POSIX: splits the command line, sets up `PosixSpawnFileActions` for output redirection, sets `FD_CLOEXEC` on all open file descriptors ≥ 3, then calls `posix_spawnp()`. On Windows: sets up `STARTUPINFOEX` with inheritable handles, calls `CreateProcess()`, and registers an async wait on the process handle. - `finish()` — Called on termination; renames the temp file to the final output file (atomic move). Returns `false` if the rename fails. - `cancel(ec)` — Sets the outer error code and cancels the outer timer, firing all registered `async_wait` handlers. ### `ProcessManagerImpl` (inherits `ProcessManager`) The concrete implementation that manages the full lifecycle of subprocesses. **Members:** - `mProcessesMutex` (`std::recursive_mutex`) — Guards `mProcesses` and `mPending` since subprocess exits arrive asynchronously. - `mProcesses` (`std::map<int, std::shared_ptr<ProcessExitEvent>>`) — Maps PID to running or shutting-down processes. - `mPending` (`std::deque<std::shared_ptr<ProcessExitEvent>>`) — Queue of processes waiting to be launched (when concurrency limit is reached). - `mIsShutdown` (`bool`) — Set once `shutdown()` is called. - `mMaxProcesses` (`size_t const`) — Maximum concurrent subprocesses, from `Config::MAX_CONCURRENT_SUBPROCESSES`. - `mIOContext` (`asio::io_context&`) — The application's I/O context for async operations. - `mSigChild` (`asio::signal_set`) — On POSIX, listens for `SIGCHLD` to detect child process exits. Unused on Windows. - `mTmpDir` (`std::unique_ptr<TmpDir>`) — Temporary directory for capturing subprocess output files. - `mTempFileCount` (`uint64_t`) — Monotonic counter for generating unique temp file names. ### `ProcessLifecycle` (enum, anonymous namespace) Tracks the state of a subprocess through its lifetime: - `PENDING` (0) — Queued, not yet spawned. - `RUNNING` (1) — Spawned, waiting for exit. - `TRIED_POLITE_SHUTDOWN` (2) — SIGTERM (POSIX) or CTRL_C_EVENT (Windows) sent. - `TRIED_FORCED_SHUTDOWN` (3) — SIGKILL (POSIX) or TerminateProcess (Windows) sent. - `TERMINATED` (5) — Exit detected and handled. ### `PosixSpawnFileActions` (POSIX only) RAII wrapper around `posix_spawn_file_actions_t`. Lazily initializes the actions object on first `addOpen()` call. Provides an implicit conversion to `posix_spawn_file_actions_t*` (returns `nullptr` if never initialized, meaning no file actions). **Key methods:** - `addOpen(fildes, fileName, oflag, mode)` — Registers a file-open action for the child process (used to redirect fd 1 / stdout to a temp file). - `initialize()` — Calls `posix_spawn_file_actions_init()`; idempotent. - Destructor calls `posix_spawn_file_actions_destroy()` if initialized. --- ## Key Control Flows ### Process Launch Flow 1. Client calls `ProcessManagerImpl::runProcess(cmdLine, outFile)`. 2. A new `ProcessExitEvent` is created with a `RealTimer` set to max duration. 3. A `ProcessExitEvent::Impl` is created holding the command line, output file, a generated temp file path, and a weak reference to the manager. 4. The event is pushed onto `mPending`. 5. `maybeRunPendingProcesses()` is called, which pops events from `mPending` while `getNumRunningOrShuttingDownProcesses() < mMaxProcesses`. 6. For each dequeued event, `Impl::run()` is called: - **POSIX:** Command line is split on whitespace into argv. `PosixSpawnFileActions` is set up if output capture is needed. All file descriptors ≥ 3 are marked `FD_CLOEXEC`. `posix_spawnp()` is called. Lifecycle transitions to `RUNNING`. - **Windows:** `STARTUPINFOEX` and handle inheritance are configured. `CreateProcess()` is called with `CREATE_NEW_PROCESS_GROUP`. An async wait is registered on the process handle. Lifecycle transitions to `RUNNING`. 7. The PID is recorded in `mProcesses`. 8. A `weak_ptr<ProcessExitEvent>` is returned to the caller, who calls `async_wait()` to register a completion handler. ### Process Exit Handling (POSIX) 1. `SIGCHLD` arrives, handled by `asio::signal_set` → `handleSignalChild()`. 2. `handleSignalChild()` re-registers the signal handler (via `startWaitingForSignalChild()`), then calls `reapChildren()`. 3. `reapChildren()` iterates all tracked PIDs, calling `waitpid(pid, &status, WNOHANG)` for each. 4. For each successfully reaped child, `handleProcessTermination(pid, status)` is called. 5. `handleProcessTermination()` maps the exit status to an `asio::error_code` (via `mapExitStatusToErrorCode`), calls `Impl::finish()` to rename the temp output file, removes the process from `mProcesses`, calls `maybeRunPendingProcesses()` to launch queued processes, then fires the callback via `Impl::cancel(ec)`. ### Process Exit Handling (Windows) 1. The `asio::windows::object_handle::async_wait` fires when the process handle becomes signaled. 2. The callback calls `GetExitCodeProcess()` and passes the result to `handleProcessTermination()`. 3. The rest follows the same flow as POSIX. ### Shutdown Flow 1. `shutdown()` sets `mIsShutdown = true`. 2. All pending (not yet launched) processes are cancelled with `ABORT_ERROR_CODE`. 3. `tryProcessShutdownAll()` iterates all running processes and calls `tryProcessShutdown()` on each. 4. `tryProcessShutdown()` uses a two-phase approach based on lifecycle state: - If `RUNNING`: calls `politeShutdown()` (SIGTERM / CTRL_C_EVENT), advances to `TRIED_POLITE_SHUTDOWN`. - If `TRIED_POLITE_SHUTDOWN`: calls `forcedShutdown()` (SIGKILL / TerminateProcess), advances to `TRIED_FORCED_SHUTDOWN`. 5. The destructor (`~ProcessManagerImpl()`) ensures cleanup: it cancels the `SIGCHLD` handler, calls `shutdown()`, then loops up to 3 times sleeping 10ms, reaping children, and re-triggering progressively more forceful shutdown. ### Concurrency Control - The maximum number of concurrent subprocesses is controlled by `mMaxProcesses` (from `Config::MAX_CONCURRENT_SUBPROCESSES`). - When the limit is reached, new processes are queued in `mPending` (a FIFO deque). - After each process exit (`handleProcessTermination`), `maybeRunPendingProcesses()` is called to launch queued processes up to the limit. - All access to `mProcesses` and `mPending` is guarded by `mProcessesMutex` (a recursive mutex), since process exits arrive asynchronously from signal handlers or async I/O callbacks. --- ## Ownership Relationships ``` Application └── ProcessManagerImpl (shared_ptr, via ProcessManager::create) ├── mProcesses: map<pid, shared_ptr<ProcessExitEvent>> │ └── ProcessExitEvent │ ├── mTimer: shared_ptr<RealTimer> │ ├── mEc: shared_ptr<asio::error_code> │ └── mImpl: shared_ptr<ProcessExitEvent::Impl> │ └── mProcManagerImpl: weak_ptr<ProcessManagerImpl> (back-ref) ├── mPending: deque<shared_ptr<ProcessExitEvent>> ├── mSigChild: asio::signal_set (POSIX only) └── mTmpDir: unique_ptr<TmpDir> ``` - `ProcessManagerImpl` owns all `ProcessExitEvent` objects (via `mProcesses` and `mPending`). - `ProcessExitEvent::Impl` holds a `weak_ptr` back to `ProcessManagerImpl` to avoid circular ownership. - Callers receive a `weak_ptr<ProcessExitEvent>` from `runProcess()`, so the manager controls the event's lifetime. - The `Impl::run()` method on Windows captures a `shared_from_this()` to keep `Impl` alive through the async wait callback. --- ## Key Data Flows 1. **Command → Process:** `runProcess(cmdLine, outFile)` → queued in `mPending` → dequeued by `maybeRunPendingProcesses()` → `Impl::run()` spawns the OS process. 2. **Process Exit → Callback:** OS signal (SIGCHLD) or handle wait → `reapChildren()` / handle callback → `handleProcessTermination()` → `Impl::finish()` (rename temp file) → `Impl::cancel(ec)` → timer cancelled → `async_wait` handler fires with exit code. 3. **Output Capture:** Subprocess stdout is redirected to a temp file in `mTmpDir`. On successful termination, `Impl::finish()` atomically renames the temp file to the requested output file path. If the output file already exists, the rename fails and an error is propagated. 4. **Exit Code Mapping:** `mapExitStatusToErrorCode()` translates OS-level exit status (including POSIX `WIFEXITED`/`WEXITSTATUS` macros) into `asio::error_code`. Special handling for exit code 127 on Linux (likely missing command). On Windows, the exit code is used directly. 5. **Shutdown Signal Flow:** `shutdown()` → cancel pending → polite shutdown (SIGTERM) → forced shutdown (SIGKILL) → destructor reaps remaining children with retry loop. --- ## Platform Differences | Aspect | POSIX | Windows | |--------|-------|---------| | Process spawning | `posix_spawnp()` | `CreateProcess()` with `EXTENDED_STARTUPINFO_PRESENT` | | Exit detection | `SIGCHLD` via `asio::signal_set` + `waitpid(WNOHANG)` | `asio::windows::object_handle::async_wait` | | Output redirection | `posix_spawn_file_actions_addopen()` on fd 1 | `CreateFile()` + `STARTF_USESTDHANDLES` | | Polite shutdown | `kill(pid, SIGTERM)` | `GenerateConsoleCtrlEvent(CTRL_C_EVENT, pid)` | | Forced shutdown | `kill(pid, SIGKILL)` | `TerminateProcess()` | | FD cleanup | `FD_CLOEXEC` on fds 3..SC_OPEN_MAX (with gap heuristic) | Handle inheritance via `PROC_THREAD_ATTRIBUTE_HANDLE_LIST` | --- ## Error Handling Notes - If `posix_spawnp()` or `CreateProcess()` fails, the `ProcessExitEvent` is cancelled with `std::errc::io_error` and the error is logged. - On Linux, exit code 127 triggers a prominent warning about a likely missing command (since `posix_spawnp` does not fault on file-not-found in the parent). - The timer-based `async_wait` pattern works around asio always delivering `operation_aborted` on timer cancel: the real error code is stored in a shared `mEc` variable and the handler reads from that instead of using the asio-provided code. - `checkInvariants()` validates consistency: pending processes must be in `PENDING` state, running processes must not be `PENDING`, and PIDs must match map keys. Called at key state transitions.
Voir sur GitHub