Line data Source code
1 : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 : Copyright (c) 2018-2023 The plumed team 3 : (see the PEOPLE file at the root of the distribution for a list of names) 4 : 5 : See http://www.plumed.org for more information. 6 : 7 : This file is part of plumed, version 2. 8 : 9 : plumed is free software: you can redistribute it and/or modify 10 : it under the terms of the GNU Lesser General Public License as published by 11 : the Free Software Foundation, either version 3 of the License, or 12 : (at your option) any later version. 13 : 14 : plumed is distributed in the hope that it will be useful, 15 : but WITHOUT ANY WARRANTY; without even the implied warranty of 16 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 : GNU Lesser General Public License for more details. 18 : 19 : You should have received a copy of the GNU Lesser General Public License 20 : along with plumed. If not, see <http://www.gnu.org/licenses/>. 21 : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ 22 : #include "Subprocess.h" 23 : #include "Exception.h" 24 : #include "Tools.h" 25 : #ifdef __PLUMED_HAS_SUBPROCESS 26 : #include <unistd.h> 27 : #include <csignal> 28 : #include <cerrno> 29 : #include <chrono> 30 : #include <thread> 31 : #include <sys/wait.h> 32 : #endif 33 : 34 : namespace PLMD { 35 : 36 : /// Retrieve PLUMED_ENABLE_SIGNALS. 37 : /// Inline static so that it can store a static variable (for quicker access) 38 : /// without adding a unique global symbol to a library including this header file. 39 38 : inline static bool SubprocessPidGetenvSignals() noexcept { 40 38 : static const bool res=std::getenv("PLUMED_ENABLE_SIGNALS"); 41 38 : return res; 42 : } 43 : 44 : /// Small utility class, used to avoid inclusion of unistd.h> in a header file. 45 : /// 46 : /// The subprocess is launched as `/bin/sh -c cmd` and is made leader of a new 47 : /// process group (with pgid equal to its pid). All signals are sent to the whole 48 : /// process group (`kill(-pid,...)`), so that they also reach the processes 49 : /// spawned by the shell (e.g. the python interpreter launched by a script). 50 : /// Sending them only to the shell would leave the actual worker untouched. 51 : class SubprocessPid { 52 : #ifdef __PLUMED_HAS_SUBPROCESS 53 : /// Time given to the process group to exit after SIGTERM before sending SIGKILL. 54 : static constexpr std::chrono::milliseconds gracePeriod{1000}; 55 : /// Returns true if the shell process has exited. 56 : /// The process is NOT reaped (WNOWAIT), so that its pid (and thus the process group id) 57 : /// cannot be recycled by the system before we are done signaling the group. 58 34890 : bool exited() const noexcept { 59 : while(true) { 60 : siginfo_t info; 61 34890 : info.si_pid=0; 62 34890 : if(waitid(P_PID,pid,&info,WEXITED|WNOHANG|WNOWAIT)==0) { 63 34890 : return info.si_pid==pid; 64 : } 65 0 : if(errno!=EINTR) { 66 : // ECHILD or other errors: nothing to wait for 67 : return true; 68 : } 69 0 : } 70 : } 71 : public: 72 : const pid_t pid; 73 5033 : explicit SubprocessPid(pid_t pid_): 74 5033 : pid(pid_) { 75 5033 : plumed_assert(pid!=0 && pid!=-1); 76 5033 : } 77 21 : void stop() noexcept { 78 : // Signals give problems with MPI on Travis. 79 : // I disable them for now. 80 21 : if(SubprocessPidGetenvSignals()) { 81 3 : kill(-pid,SIGSTOP); 82 : } 83 21 : } 84 17 : void cont() noexcept { 85 : // Signals give problems with MPI on Travis. 86 : // I disable them for now. 87 17 : if(SubprocessPidGetenvSignals()) { 88 1 : kill(-pid,SIGCONT); 89 : } 90 17 : } 91 5033 : ~SubprocessPid() { 92 : // The destructor implies we do not need the subprocess anymore. 93 : // At this point its stdin has already been closed. 94 : // We first ask the whole process group to terminate with SIGTERM, which 95 : // allows a clean exit (e.g., shell EXIT traps removing temporary files). 96 : // SIGCONT is needed because the group might have been stopped with SIGSTOP 97 : // (see stop()), in which case SIGTERM would remain pending forever. 98 5033 : kill(-pid,SIGTERM); 99 5033 : kill(-pid,SIGCONT); 100 : // Poll with an increasing interval, so that the common case 101 : // (immediate exit) is fast. 102 5033 : const auto start=std::chrono::steady_clock::now(); 103 5033 : std::chrono::microseconds interval{50}; 104 34890 : while(!exited() && std::chrono::steady_clock::now()-start<gracePeriod) { 105 24824 : std::this_thread::sleep_for(interval); 106 24824 : if(interval<std::chrono::milliseconds(20)) { 107 : interval*=2; 108 : } 109 : } 110 : // If the shell did not exit within the grace period (e.g. because it ignores SIGTERM), 111 : // kill the whole process group. 112 : // This is safe since the shell has not been reaped yet, so the pgid cannot 113 : // have been recycled. 114 : // Notice that if the shell has exited we do not send SIGKILL to the rest of the group: 115 : // processes started by the shell received SIGTERM as well, and they might still be 116 : // running their cleanup (e.g. a bash EXIT trap in a script launched by a 117 : // shell that does not exec it). 118 5033 : if(!exited()) { 119 1 : kill(-pid,SIGKILL); 120 : } 121 : // Reap the shell. This is anyway required to avoid leaks 122 : int status; 123 10066 : while(waitpid(pid, &status, 0)<0 && errno==EINTR) {} 124 5033 : } 125 : #endif 126 : }; 127 : 128 5033 : Subprocess::Subprocess(const std::string & cmd) { 129 : #ifdef __PLUMED_HAS_SUBPROCESS 130 5033 : char* arr [] = { 131 : // const_cast are necessary here due to the declaration of execv 132 : const_cast<char*>("/bin/sh"), 133 : const_cast<char*>("-c"), 134 : const_cast<char*>(cmd.c_str()), 135 : nullptr 136 5033 : }; 137 : int cp[2]; 138 : int pc[2]; 139 5033 : if(pipe(pc)<0) { 140 0 : plumed_error()<<"error creating parent to child pipe"; 141 : } 142 5033 : if(pipe(cp)<0) { 143 0 : plumed_error()<<"error creating child to parent pipe"; 144 : } 145 5033 : pid_t forkedPid=fork(); 146 5033 : switch(forkedPid) { 147 0 : case -1: 148 0 : plumed_error()<<"error forking"; 149 : break; 150 : // CHILD: 151 0 : case 0: { 152 0 : if(close(1)<0) { 153 0 : plumed_error()<<"error closing file"; 154 : } 155 0 : if(dup(cp[1])<0) { 156 0 : plumed_error()<<"error duplicating file"; 157 : } 158 0 : if(close(0)<0) { 159 0 : plumed_error()<<"error closing file"; 160 : } 161 0 : if(dup(pc[0])<0) { 162 0 : plumed_error()<<"error duplicating file"; 163 : } 164 0 : if(close(pc[1])<0) { 165 0 : plumed_error()<<"error closing file"; 166 : } 167 0 : if(close(cp[0])<0) { 168 0 : plumed_error()<<"error closing file"; 169 : } 170 : // Make the child the leader of a new process group, so that signals 171 : // can be sent to it and to all the processes it spawns. 172 0 : if(setpgid(0,0)<0) { 173 0 : plumed_error()<<"error setting process group"; 174 : } 175 0 : auto err=execv(arr[0],arr); 176 0 : plumed_error()<<"error in script file " << cmd << ", execv returned "<<err; 177 : } 178 : // PARENT:: 179 5033 : default: 180 : // setpgid is also called here to avoid a race condition: 181 : // the parent might send a signal to the group before the child has created it. 182 : // Errors are ignored: the call fails with EACCES if the child has already called execv, 183 : // but in that case the child has already set its process group. 184 5033 : setpgid(forkedPid,forkedPid); 185 5033 : pid=Tools::make_unique<SubprocessPid>(forkedPid); 186 5033 : if(close(pc[0])<0) { 187 0 : plumed_error()<<"error closing file"; 188 : } 189 5033 : if(close(cp[1])<0) { 190 0 : plumed_error()<<"error closing file"; 191 : } 192 5033 : fpc=pc[1]; 193 5033 : fcp=cp[0]; 194 5033 : fppc=fdopen(fpc,"w"); 195 5033 : parent_to_child.link(fppc); 196 5033 : fpcp=fdopen(fcp,"r"); 197 5033 : child_to_parent.link(fpcp); 198 : } 199 : #else 200 : plumed_error()<<"Subprocess not supported"; 201 : #endif 202 5033 : } 203 : 204 5033 : Subprocess::~Subprocess() { 205 : #ifdef __PLUMED_HAS_SUBPROCESS 206 : // close files: 207 5033 : fclose(fppc); 208 5033 : fclose(fpcp); 209 : // fclose also closes the underlying descriptors, 210 : // so this is not needed: 211 5033 : close(fpc); 212 5033 : close(fcp); 213 : // after closing the communication, the subprocess is killed 214 : // in pid's destructor 215 : #endif 216 5033 : } 217 : 218 190 : bool Subprocess::available() noexcept { 219 : #ifdef __PLUMED_HAS_SUBPROCESS 220 190 : return true; 221 : #else 222 : return false; 223 : #endif 224 : } 225 : 226 21 : void Subprocess::stop() noexcept { 227 : #ifdef __PLUMED_HAS_SUBPROCESS 228 21 : pid->stop(); 229 : #endif 230 21 : } 231 : 232 17 : void Subprocess::cont() noexcept { 233 : #ifdef __PLUMED_HAS_SUBPROCESS 234 17 : pid->cont(); 235 : #endif 236 17 : } 237 : 238 16 : void Subprocess::flush() { 239 16 : parent_to_child.flush(); 240 16 : } 241 : 242 47 : Subprocess & Subprocess::getline(std::string & line) { 243 47 : child_to_parent.getline(line); 244 47 : if(!child_to_parent) { 245 0 : plumed_error() <<"error reading subprocess"; 246 : } 247 47 : return (*this); 248 : } 249 : 250 17 : Subprocess::Handler::Handler(Subprocess *subp) noexcept: 251 17 : sp(subp) { 252 17 : subp->cont(); 253 17 : } 254 : 255 17 : Subprocess::Handler::~Handler() { 256 17 : if(sp) { 257 17 : sp->stop(); 258 : } 259 17 : } 260 : 261 0 : Subprocess::Handler::Handler(Handler && handler) noexcept : 262 0 : sp(handler.sp) { 263 0 : handler.sp=nullptr; 264 0 : } 265 : 266 0 : Subprocess::Handler & Subprocess::Handler::operator=(Handler && handler) noexcept { 267 0 : if(this!=&handler) { 268 0 : if(sp) { 269 0 : sp->stop(); 270 : } 271 0 : sp=handler.sp; 272 0 : handler.sp=nullptr; 273 : } 274 0 : return *this; 275 : } 276 : 277 : 278 : }