1 /******************************************************************************* 2 3 SafeFork 4 5 Offers some wrappers for the usage of fork to call expensive blocking 6 functions without interrupting the main process and without the need to 7 synchronize. 8 9 Useful version switches: 10 TimeFork = measures and displays the time taken by the linux fork() call 11 12 Copyright: 13 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 14 All rights reserved. 15 16 License: 17 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 18 Alternatively, this file may be distributed under the terms of the Tango 19 3-Clause BSD License (see LICENSE_BSD.txt for details). 20 21 *******************************************************************************/ 22 23 module ocean.sys.SafeFork; 24 25 26 27 28 import ocean.sys.ErrnoException; 29 30 import core.sys.posix.stdlib : exit; 31 32 import core.sys.posix.unistd : fork; 33 34 import ocean.stdc.posix.sys.wait; 35 36 import core.sys.posix.signal; 37 38 import core.stdc.errno; 39 40 import core.stdc.string; 41 42 43 44 45 version ( TimeFork ) 46 { 47 import ocean.io.Stdout; 48 49 import ocean.time.StopWatch; 50 } 51 52 53 54 /******************************************************************************* 55 56 External C 57 58 // TODO: forced to be public to be used with reflection, must be moved to 59 C bindings 60 61 *******************************************************************************/ 62 63 extern (C) 64 { 65 enum idtype_t 66 { 67 P_ALL, /* Wait for any child. */ 68 P_PID, /* Wait for specified process. */ 69 P_PGID /* Wait for members of process group. */ 70 }; 71 72 int waitid(idtype_t, id_t, siginfo_t*, int); 73 74 static immutable WEXITED = 0x00000004; 75 static immutable WNOWAIT = 0x01000000; 76 } 77 78 79 80 /******************************************************************************* 81 82 SafeFork 83 84 Offers some wrappers for the usage of fork to call expensive blocking 85 functions without interrupting the main process and without the need to 86 synchronize. 87 88 Usage Example: 89 ----- 90 import ocean.sys.SafeFork; 91 92 void main ( ) 93 { 94 auto dont_block = new SafeFork(&blocking_function); 95 96 dont_block.call(); // call blocking_function 97 98 if ( !dont_block.call() ) 99 { 100 Stdout("blocking function is currently running and not done yet!"); 101 } 102 103 while ( dont_block.isRunning() ) 104 { 105 Stdout("blocking function is still running!"); 106 } 107 108 if ( !dont_block.call() ) 109 { 110 Stdout("blocking function is currently running and not done yet!"); 111 } 112 113 dont_block.call(true); // wait for a unfinished fork and then call 114 // blocking_function without forking 115 } 116 ----- 117 118 *******************************************************************************/ 119 120 public class SafeFork 121 { 122 /*************************************************************************** 123 124 Exception, reusable 125 126 ***************************************************************************/ 127 128 private ErrnoException exception; 129 130 /*************************************************************************** 131 132 Pid of the forked child 133 134 ***************************************************************************/ 135 136 private int child_pid = 0; 137 138 /*************************************************************************** 139 140 Delegate to call 141 142 ***************************************************************************/ 143 144 private void delegate () dg; 145 146 /*************************************************************************** 147 148 Constructor 149 150 Params: 151 dg = delegate to call 152 153 ***************************************************************************/ 154 155 public this ( scope void delegate () dg ) 156 { 157 this.dg = dg; 158 159 this.exception = new ErrnoException; 160 } 161 162 /*************************************************************************** 163 164 Find out whether the fork is still running or not 165 166 Returns: 167 true if the fork is still running, else false 168 169 ***************************************************************************/ 170 171 public bool isRunning ( ) 172 { 173 return this.child_pid == 0 174 ? false 175 : this.isRunning(false, false); 176 } 177 178 /*************************************************************************** 179 180 Call the delegate, possibly within a fork. 181 Ensures that the delegate will only be called when there is not already 182 a fork running. The fork exits after the delegate returned. 183 184 Note that the host process is not informed about any errors in 185 the forked process. 186 187 Params: 188 block = if true, wait for a currently running fork and don't fork 189 when calling the delegate 190 if false, don't do anything when a fork is currently running 191 192 Returns: 193 true when the delegate was called 194 195 See_Also: 196 SafeFork.isRunning 197 198 ***************************************************************************/ 199 200 public bool call ( bool block = false ) 201 { 202 if ( this.child_pid == 0 || !this.isRunning(block) ) 203 { 204 if ( block ) 205 { 206 version ( TimeFork ) 207 { 208 Stdout.formatln("Running task without forking..."); 209 } 210 this.dg(); 211 212 this.child_pid = 0; 213 214 return true; 215 } 216 else 217 { 218 version ( TimeFork ) 219 { 220 Stdout.formatln("Running task in fork..."); 221 StopWatch sw; 222 sw.start; 223 } 224 225 this.child_pid = fork(); 226 227 version ( TimeFork ) 228 { 229 Stdout.formatln("Fork took {}s", 230 (cast(float)sw.microsec) / 1_000_000.0f); 231 } 232 233 this.exception.enforce(this.child_pid >= 0, "failed to fork"); 234 235 if ( this.child_pid == 0 ) 236 { 237 this.dg(); 238 exit(0); 239 } 240 241 return true; 242 } 243 } 244 else 245 { 246 return false; 247 } 248 } 249 250 /*************************************************************************** 251 252 Checks whether the forked process is already running. 253 254 Params: 255 block = if true, wait for a currently running fork 256 if false, don't do anything when a fork is currently running 257 clear = if true, the waiting status of the forked process is cleared 258 259 Returns: 260 true if the forked process is running 261 262 See_Also: 263 SafeFork.isRunning 264 265 ***************************************************************************/ 266 267 private bool isRunning ( bool block, bool clear = true ) 268 { 269 if ( this.child_pid < 0 ) 270 return false; 271 272 siginfo_t siginfo; 273 274 // In the case where we are blocking, we need to consider signals 275 // arriving while we wait, and resume the waiting if EINTR is returned 276 int waitid_ret = void; 277 do 278 { 279 waitid_ret = waitid(idtype_t.P_PID, this.child_pid, &siginfo, 280 WEXITED | (block ? 0 : WNOHANG) | (clear ? 0 : WNOWAIT)); 281 } 282 while (waitid_ret == -1 && errno == EINTR); 283 284 this.exception.enforce(waitid_ret == 0, "waitid failed", "waitid"); 285 286 return siginfo._sifields._kill.si_pid == 0; 287 } 288 } 289