Feature Selection Toolbox FST4
UTIA · Czech Academy of Sciences
Loading...
Searching...
No Matches
extern_bridge.hpp
Go to the documentation of this file.
1#ifndef FSTEXTERNBRIDGE_H
2#define FSTEXTERNBRIDGE_H
3 /*
25=========================================================================
26Copyright and license:
27 * FST4 software (with the exception of any bundled or externally linked
28 third-party components) is Copyright (C) 2000-present Institute of
29 Information Theory and Automation of the Czech Academy of Sciences (UTIA).
30 * FST4 is free software: you can redistribute it and/or modify it under
31 the terms of the GNU Affero General Public License as published by the
32 Free Software Foundation, version 3. The full text of the license is in
33 the file LICENSE in the distribution root and at
34 https://www.gnu.org/licenses/agpl-3.0.html.
35 * FST4 is distributed in the hope that it will be useful, but WITHOUT ANY
36 WARRANTY; without even the implied warranty of MERCHANTABILITY or
37 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
38 License for more details.
39 * Closed-source use without the AGPL's copyleft obligations requires a
40 commercial license from UTIA; in case of interest write to
41 utia@utia.cas.cz. LICENSING.md in the distribution root explains both
42 options, the contributor license agreement that every contribution
43 requires, and the contacts (technical questions and pull requests:
44 somol@utia.cas.cz, with "FST" in the subject line).
45========================================================================== */
46
47#include <string>
48#include <vector>
49#include <cstdio>
50#include <cstdlib>
51#include <cassert>
52#include <iostream>
53#include <sstream>
54#include <mutex>
55#include "error.hpp"
56#include "global.hpp"
57
58// ---- the ONLY platform-sensitive process plumbing in FST4, kept in this single header
59// so that both external-tool bridges share one implementation instead of duplicating
60// the fork/CreateProcess code ----
61#ifdef _WIN32
62 #ifndef NOMINMAX
63 #define NOMINMAX // keep windows.h from defining min/max macros that break std::min/max and numeric_limits textually after this include
64 #endif
65 #ifndef WIN32_LEAN_AND_MEAN
66 #define WIN32_LEAN_AND_MEAN
67 #endif
68 #include <windows.h>
69 #include <io.h>
70 #include <fcntl.h>
71#else
72 #include <unistd.h>
73 #include <sys/wait.h>
74 #include <signal.h>
75 #include <fcntl.h>
76#endif
77
78namespace FST {
79
82inline std::mutex& extern_spawn_mutex() {static std::mutex m; return m;}
83
106public:
107 explicit Extern_Bridge(const char *component) : _component(component) {init_process_handles();}
109 Extern_Bridge(const Extern_Bridge&) = delete; // owners construct fresh bridges for their clones instead
110 Extern_Bridge& operator=(const Extern_Bridge&) = delete;
111
112 void set_command(const std::string &cmd) {assert(!running()); _cmd=cmd;}
113 const std::string& get_command() const {return _cmd;}
114 bool running() const {return _to_child!=0;}
115 std::FILE* to_child() {assert(_to_child); return _to_child;}
116 void flush_to_child() {assert(_to_child); std::fflush(_to_child);}
117 void die_on_write() const {throw fst_error(_component+": the external command '"+_cmd+"' stopped accepting input (died?)");}
118
137 {
138 if(running()) return;
139 if(_cmd.empty()) throw fst_error(_component+": no command set (see --extern-cmd)");
140 // serialize spawns process-wide: without this, a concurrently spawning clone's child
141 // (threaded search = one child per criterion clone) can inherit this clone's pipe ends
142 // in the window before they are marked non-inheritable below - an inherited write end
143 // keeps a sibling child's stdin open past shutdown_process()'s fclose, the child never
144 // sees EOF, and the destructor's waitpid deadlocks
145 std::unique_lock<std::mutex> spawn_lock(extern_spawn_mutex());
146 { // one-time runtime notice: someone running an untrusted third-party config
147 // should see that it executes code; the CLI's prose plumbing swallows this under
148 // `--verbosity` silent and routes it to stderr under machine output formats
149 static bool notice_fired=false; // guarded by the spawn lock
150 if(!notice_fired) {
151 notice_fired=true;
152 std::ostringstream sos; sos << "NOTE: --extern-cmd executes user-supplied code through the system shell: " << _cmd << std::endl;
153 syncout::print(std::cout,sos);
154 }
155 }
156#ifdef _WIN32
157 // ---- Windows plumbing (CreateProcess through cmd.exe) ----
158 SECURITY_ATTRIBUTES sa; sa.nLength=sizeof(sa); sa.bInheritHandle=TRUE; sa.lpSecurityDescriptor=NULL;
159 HANDLE in_rd=0,in_wr=0,out_rd=0,out_wr=0;
160 if(!CreatePipe(&in_rd,&in_wr,&sa,0) || !CreatePipe(&out_rd,&out_wr,&sa,0))
161 throw fst_error(_component+": CreatePipe failed");
162 SetHandleInformation(in_wr,HANDLE_FLAG_INHERIT,0); // our ends must not leak to the child
163 SetHandleInformation(out_rd,HANDLE_FLAG_INHERIT,0);
164 STARTUPINFOA si; ZeroMemory(&si,sizeof(si)); si.cb=sizeof(si);
165 si.dwFlags=STARTF_USESTDHANDLES;
166 si.hStdInput=in_rd; si.hStdOutput=out_wr; si.hStdError=GetStdHandle(STD_ERROR_HANDLE);
167 PROCESS_INFORMATION pi; ZeroMemory(&pi,sizeof(pi));
168 std::string full=std::string("cmd.exe /C ")+_cmd;
169 std::vector<char> cmdbuf(full.begin(),full.end()); cmdbuf.push_back('\0');
170 if(!CreateProcessA(NULL,cmdbuf.data(),NULL,NULL,TRUE,0,NULL,NULL,&si,&pi))
171 {CloseHandle(in_rd);CloseHandle(in_wr);CloseHandle(out_rd);CloseHandle(out_wr);
172 throw fst_error(_component+": cannot launch '"+_cmd+"'");}
173 CloseHandle(pi.hThread);
174 CloseHandle(in_rd); CloseHandle(out_wr); // child's ends
175 _child_process=pi.hProcess;
176 _to_child=_fdopen(_open_osfhandle((intptr_t)in_wr,_O_WRONLY|_O_BINARY),"wb");
177 _from_child=_fdopen(_open_osfhandle((intptr_t)out_rd,_O_RDONLY|_O_BINARY),"rb");
178 if(!_to_child || !_from_child) {shutdown_process(); throw fst_error(_component+": cannot attach pipes to '"+_cmd+"'");}
179#else
180 // ---- POSIX plumbing (fork + /bin/sh -c) ----
181 int to_pipe[2], from_pipe[2];
182 if(pipe(to_pipe)!=0 || pipe(from_pipe)!=0) throw fst_error(_component+": pipe() failed");
183 fcntl(to_pipe[1],F_SETFD,FD_CLOEXEC); // our ends must not leak into later children
184 fcntl(from_pipe[0],F_SETFD,FD_CLOEXEC); // (the Windows twin: SetHandleInformation above)
185 // SIGPIPE discipline: a child that stops reading (crashed mid-search) must surface as
186 // the send-path fst_errors, not kill fst4 silently (default SIGPIPE disposition
187 // terminates the process before fputs/fputc can even return EOF - observed as exit 141
188 // with zero output, intermittently masked by kernel pipe buffering on small payloads)
189#ifdef F_SETNOSIGPIPE
190 fcntl(to_pipe[1],F_SETNOSIGPIPE,1); // macOS/BSD: per-fd, write fails with EPIPE instead of raising SIGPIPE
191#else
192 // no per-fd control on this platform, so SIGPIPE is ignored PROCESS-WIDE and stays
193 // ignored for the rest of the run - a side effect on the whole embedding program,
194 // documented as a warning on ensure_process() above
195 signal(SIGPIPE,SIG_IGN);
196#endif
197 const pid_t pid=fork();
198 if(pid<0) throw fst_error(_component+": fork() failed");
199 if(pid==0) { // child: wire the pipes to stdin/stdout and exec the command via the shell
200 dup2(to_pipe[0],0); dup2(from_pipe[1],1);
201 close(to_pipe[0]); close(to_pipe[1]); close(from_pipe[0]); close(from_pipe[1]);
202 execl("/bin/sh","sh","-c",_cmd.c_str(),(char*)0);
203 _exit(127); // exec failed
204 }
205 close(to_pipe[0]); close(from_pipe[1]);
206 _child_pid=pid;
207 _to_child=fdopen(to_pipe[1],"w");
208 _from_child=fdopen(from_pipe[0],"r");
209 if(!_to_child || !_from_child) {shutdown_process(); throw fst_error(_component+": cannot attach pipes to '"+_cmd+"'");}
210#endif
211 }
212
223 {
224 if(_to_child) {std::fclose(_to_child); _to_child=0;} // EOF on the child's stdin = terminate request
225 if(_from_child) {std::fclose(_from_child); _from_child=0;}
226#ifdef _WIN32
227 if(_child_process) {WaitForSingleObject(_child_process,2000); TerminateProcess(_child_process,0); CloseHandle(_child_process); _child_process=0;}
228#else
229 if(_child_pid>0) {int status=0; waitpid(_child_pid,&status,0); _child_pid=-1;}
230#endif
231 }
232
237 void send_line(const std::string &line)
238 {
239 assert(_to_child);
240 if(std::fputs(line.c_str(),_to_child)==EOF || std::fputc('\n',_to_child)==EOF)
241 die_on_write();
242 }
243
250 std::string read_line()
251 {
252 assert(_from_child);
253 std::string out;
254 char buf[256];
255 for(;;)
256 {
257 if(!std::fgets(buf,sizeof(buf),_from_child))
258 throw fst_error(_component+": the external command '"+_cmd+"' produced no response (died?)");
259 out+=buf;
260 if(!out.empty() && out.back()=='\n') {out.pop_back(); if(!out.empty() && out.back()=='\r') out.pop_back(); return out;}
261 if(out.size()>1048576) // a child streaming bytes without newline must fail loudly, not exhaust memory
262 {shutdown_process(); throw fst_error(_component+": the external command '"+_cmd+"' produced a response line exceeding 1 MB (runaway output?)");}
263 }
264 }
265
266private:
267 std::string _component;
268 std::string _cmd;
269 std::FILE *_to_child;
270 std::FILE *_from_child;
271#ifdef _WIN32
272 HANDLE _child_process;
273#else
275#endif
280 {
282#ifdef _WIN32
283 _child_process=0;
284#else
285 _child_pid=-1;
286#endif
287 }
288};
289
290} // namespace
291#endif // FSTEXTERNBRIDGE_H ///:~
Persistent-child-process line-protocol plumbing shared by the external-tool bridges: Classifier_Exter...
Definition extern_bridge.hpp:105
pid_t _child_pid
POSIX pid of the running child, or -1 when not running().
Definition extern_bridge.hpp:274
void die_on_write() const
shared loud-failure path for owner-side writes
Definition extern_bridge.hpp:117
std::FILE * _to_child
write end of the pipe to the child's stdin, or nullptr when not running()
Definition extern_bridge.hpp:269
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(...
Definition extern_bridge.hpp:237
void init_process_handles()
Resets the handles/pid to their "not running" sentinel values (nullptr / -1). Called from the constru...
Definition extern_bridge.hpp:279
void ensure_process()
Lazily spawns the child process if not already running() (a no-op otherwise). Requires get_command() ...
Definition extern_bridge.hpp:136
std::FILE * to_child()
for owner-side row streaming (%.17g formatting stays with the owner)
Definition extern_bridge.hpp:115
void set_command(const std::string &cmd)
sets the external command line; must be called before the child process is spawned (ensure_process())
Definition extern_bridge.hpp:112
Extern_Bridge(const char *component)
component names the owning component (e.g. "Classifier_Extern"), prefixing every diagnostic; does not...
Definition extern_bridge.hpp:107
void shutdown_process()
Terminates the child, if any, and returns the bridge to a not-running() state. Closing our end of std...
Definition extern_bridge.hpp:222
std::string _component
owning component name, prefixes every diagnostic
Definition extern_bridge.hpp:267
std::string _cmd
the configured external command line (see set_command())
Definition extern_bridge.hpp:268
const std::string & get_command() const
returns the configured command line
Definition extern_bridge.hpp:113
std::string read_line()
Reads one line from the child's stdout, with the trailing "\n" (and a preceding "\r",...
Definition extern_bridge.hpp:250
void flush_to_child()
flushes buffered writes to the child's stdin (call after a batch of send_line()/owner-side writes,...
Definition extern_bridge.hpp:116
std::FILE * _from_child
read end of the pipe from the child's stdout, or nullptr when not running()
Definition extern_bridge.hpp:270
bool running() const
true once ensure_process() has successfully spawned a child that has not since been shut down
Definition extern_bridge.hpp:114
FST4 specific error passing class.
Definition error.hpp:61
static void print(std::ostream &os, std::ostringstream &sos)
Writes sos's buffered content to os, serializing concurrent writers under FST_THREADED via a shared m...
Definition global.hpp:86
FST4 specific error passing class.
Global definitions.
The Feature Selection Toolbox 4 library: every library component – data access, criteria and classifi...
Definition data_accessor.hpp:80
std::mutex & extern_spawn_mutex()
Definition extern_bridge.hpp:82
Copyright © 2000–present ÚTIA, Institute of Information Theory and Automation of the Czech Academy of Sciences · Contacts · This site sets no cookies and uses no analytics.