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