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