Persistent-child-process line-protocol plumbing shared by the external-tool bridges: Classifier_Extern (TRAIN/TEST label protocol) and Criterion_Extern_Value (VALUE protocol). More...
#include <extern_bridge.hpp>
Public Member Functions | |
| Extern_Bridge (const char *component) | |
component names the owning component (e.g. "Classifier_Extern"), prefixing every diagnostic; does not spawn anything yet | |
| Extern_Bridge (const Extern_Bridge &)=delete | |
| Extern_Bridge & | operator= (const Extern_Bridge &)=delete |
| void | set_command (const std::string &cmd) |
| sets the external command line; must be called before the child process is spawned (ensure_process()) | |
| const std::string & | get_command () const |
| returns the configured command line | |
| bool | running () const |
| true once ensure_process() has successfully spawned a child that has not since been shut down | |
| std::FILE * | to_child () |
| for owner-side row streaming (%.17g formatting stays with the owner) | |
| void | flush_to_child () |
| flushes buffered writes to the child's stdin (call after a batch of send_line()/owner-side writes, before reading a response) | |
| void | die_on_write () const |
| shared loud-failure path for owner-side writes | |
| void | ensure_process () |
| Lazily spawns the child process if not already running() (a no-op otherwise). Requires get_command() to be non-empty. Spawns are serialized process-wide via extern_spawn_mutex() – without this, a concurrently spawning clone's child (threaded search = one child per criterion/classifier clone) could inherit this clone's pipe ends in the window before they are marked non-inheritable, which would keep a sibling child's stdin open past this instance's shutdown_process() and deadlock the destructor's wait. Prints a one-time process-wide NOTE that the command executes user-supplied code through the shell. Platform-specific pipe setup follows: CreateProcess via "cmd.exe /C <cmd>" on Windows, fork()+"/bin/sh -c <cmd>" on POSIX. | |
| void | shutdown_process () |
| Terminates the child, if any, and returns the bridge to a not-running() state. Closing our end of stdin/stdout (fclose) signals EOF to the child, the conventional terminate request; the call then WAITS for the child to actually exit (waitpid() on POSIX – unbounded, so a child that ignores stdin-EOF hangs this call and thus the destructor; WaitForSingleObject with a 2s timeout followed by a forced TerminateProcess on Windows). Idempotent: every handle is individually null-checked, so repeated calls (e.g. destructor after an explicit error-path shutdown) are safe. Called from the destructor and from every "the child misbehaved" error path (a desynced or dead child is dropped rather than reused; the next ensure_process() respawns cleanly). | |
| void | send_line (const std::string &line) |
Writes line followed by a newline to the child's stdin. Requires running(). Throws (via die_on_write()) if either write fails, i.e. the child stopped reading. Does NOT flush – callers that need the data to arrive before reading a response must call flush_to_child() themselves (e.g. after a batch of send_line()/row writes). | |
| std::string | read_line () |
| Reads one line from the child's stdout, with the trailing "\n" (and a preceding "\r", for CRLF-emitting children) stripped. Requires running(). Throws fst_error if the child produced no response at all (EOF/died mid-read) or if more than 1 MB accumulates without a newline – a runaway/non-protocol child must fail loudly rather than exhaust memory; that case also calls shutdown_process() first so the dead/misbehaving child is not reused. | |
Private Member Functions | |
| void | init_process_handles () |
| Resets the handles/pid to their "not running" sentinel values (nullptr / -1). Called from the constructor to establish that initial state; NOT called by shutdown_process() itself (which null/reset each handle individually as it closes it). | |
Private Attributes | |
| std::string | _component |
| owning component name, prefixes every diagnostic | |
| std::string | _cmd |
| the configured external command line (see set_command()) | |
| std::FILE * | _to_child |
| write end of the pipe to the child's stdin, or nullptr when not running() | |
| std::FILE * | _from_child |
| read end of the pipe from the child's stdout, or nullptr when not running() | |
| pid_t | _child_pid |
| POSIX pid of the running child, or -1 when not running(). | |
Persistent-child-process line-protocol plumbing shared by the external-tool bridges: Classifier_Extern (TRAIN/TEST label protocol) and Criterion_Extern_Value (VALUE protocol).
Owns the child process handles and the raw line I/O; the protocol blocks themselves (headers, data rows, response parsing) live in the owning component. Error messages carry the owning component's name, so a diagnostic always says which bridge failed.
SINGLE OWNER BY DESIGN: exactly one instance per owning component instance, and the class is non-copyable. A copy would duplicate the FILE* pipe ends and the child handle without duplicating the child, so two objects would write into one child's stdin, interleave their protocol blocks, and each try to close and reap the same process in its destructor. Clones of the owners therefore construct FRESH bridges with their own child process - pipes are never shared - which is also what keeps threaded evaluation isolation-safe.
All LOUD CAVEATS of the extern mechanism apply: the command is user-supplied code executed through the system shell, reproducibility guarantees end at the bridge (whatever the external tool does is outside FST4's control), there is no timeout machinery, and see the SIGPIPE note on ensure_process().
|
inline |
Lazily spawns the child process if not already running() (a no-op otherwise). Requires get_command() to be non-empty. Spawns are serialized process-wide via extern_spawn_mutex() – without this, a concurrently spawning clone's child (threaded search = one child per criterion/classifier clone) could inherit this clone's pipe ends in the window before they are marked non-inheritable, which would keep a sibling child's stdin open past this instance's shutdown_process() and deadlock the destructor's wait. Prints a one-time process-wide NOTE that the command executes user-supplied code through the shell. Platform-specific pipe setup follows: CreateProcess via "cmd.exe /C <cmd>" on Windows, fork()+"/bin/sh -c <cmd>" on POSIX.
| fst_error | on any setup failure (pipe/fork/CreateProcess, or a launch failure). |