1 /*******************************************************************************
2 
3     Posix process with epoll integration of output streams (stdout & stderr).
4 
5     Usage example:
6 
7     ---
8 
9         import ocean.io.Stdout;
10         import ocean.io.select.client.EpollProcess;
11         import ocean.io.select.EpollSelectDispatcher;
12 
13         // Simple epoll process class which uses curl to download data from a
14         // url
15         class CurlProcess : EpollProcess
16         {
17             this ( EpollSelectDispatcher epoll )
18             {
19                 super(epoll);
20             }
21 
22             // Starts the process downloading a url
23             public void start ( char[] url )
24             {
25                 super.start("curl", [url]);
26             }
27 
28             // Called by the super class when the process sends data to stdout.
29             // (In the case of curl this is data downloaded from the url.)
30             protected void stdout ( ubyte[] data )
31             {
32                 Stdout.formatln("Received: '{}'", data);
33             }
34 
35             // Called by the super class when the process sends data to stderr.
36             // (In the case of curl this is progress & error messages, which we
37             // just ignore in this example.)
38             protected void stderr ( ubyte[] data )
39             {
40             }
41 
42             // Called by the super class when the process is finished.
43             protected void finished ( bool exited_ok, int exit_code )
44             {
45                 if ( exited_ok )
46                 {
47                     Stdout.formatln("Process exited with code {}", exit_code);
48                 }
49                 else
50                 {
51                     Stdout.formatln("Process terminated abnormally");
52                 }
53             }
54         }
55 
56         // Create epoll selector instance.
57         auto epoll = new EpollSelectDispatcher;
58 
59         // Create a curl process instance.
60         auto process = new CurlProcess(epoll);
61 
62         // Start the process running, executing a curl command to download data
63         // from a url.
64         process.start("http://www.google.com");
65 
66         // Handle arriving data.
67         epoll.eventLoop;
68 
69     ---
70 
71     It is sometimes desirable to use more than one
72     EpollSelectDispatcher instance with various EpollProcess instances.
73     One example of such usage is when an application needs to create
74     short-lived EpollProcess instance(s) in a unittest block. In this case
75     one EpollSelectDispatcher instance would be needed in the unittest
76     block, and a different one in the application's main logic.
77 
78     This will work provided that all processes created during the test have
79     terminated before the main application starts.
80 
81     Copyright:
82         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
83         All rights reserved.
84 
85     License:
86         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
87         Alternatively, this file may be distributed under the terms of the Tango
88         3-Clause BSD License (see LICENSE_BSD.txt for details).
89 
90 *******************************************************************************/
91 
92 module ocean.io.select.client.EpollProcess;
93 
94 
95 
96 
97 import ocean.transition;
98 
99 import ocean.core.Verify;
100 
101 import ocean.util.container.map.Map;
102 
103 import ocean.io.select.client.model.ISelectClient;
104 
105 import ocean.io.select.client.SelectEvent;
106 
107 import ocean.io.select.EpollSelectDispatcher;
108 
109 import ocean.io.model.IConduit;
110 
111 import ocean.stdc.posix.sys.wait;
112 
113 import ocean.sys.Process;
114 
115 debug import ocean.io.Stdout;
116 
117 import core.stdc.errno;
118 
119 import ocean.util.log.Logger;
120 
121 import core.sys.posix.signal : SIGCHLD;
122 
123 
124 
125 /*******************************************************************************
126 
127     Static module logger
128 
129 *******************************************************************************/
130 
131 static private Logger log;
132 static this ( )
133 {
134     log = Log.lookup("ocean.io.select.client.EpollProcess");
135 }
136 
137 
138 /*******************************************************************************
139 
140     Posix process with epoll integration of output streams (stdout & stderr).
141 
142 *******************************************************************************/
143 
144 public abstract class EpollProcess
145 {
146     /***************************************************************************
147 
148         Class to monitor and handle inter-process signals via a signal event
149         registered with epoll.
150 
151         SIGCHLD event handling is an intrinsically global operation, so this
152         class exists only as a singleton.
153 
154     ***************************************************************************/
155 
156     private static class GlobalProcessMonitor
157     {
158         /***********************************************************************
159 
160             Select event instance which is triggered when a SIGCHLD signal is
161             generated, indicating that a child process has terminated.
162             Registered with epoll when one or more EpollProcesses are running.
163 
164         ***********************************************************************/
165 
166         private SelectEvent sigchild_event;
167 
168 
169         /***********************************************************************
170 
171             Mapping from a process id to an EpollProcess instance.
172 
173         ***********************************************************************/
174 
175         private StandardKeyHashingMap!(EpollProcess, int) processes;
176 
177 
178         /***********************************************************************
179 
180             Allocate the map from a free list, to reduce memory allocation.
181 
182         ***********************************************************************/
183 
184         import FreeListAllocator =
185             ocean.util.container.map.model.BucketElementFreeList;
186 
187 
188         /***********************************************************************
189 
190             Constructor.
191 
192         ***********************************************************************/
193 
194         public this ( )
195         {
196             // Allocate the map using a free list
197 
198             this.processes = new StandardKeyHashingMap!(EpollProcess, int)(
199                 FreeListAllocator.instantiateAllocator!(
200                     StandardKeyHashingMap!(EpollProcess, int)), 20);
201 
202             this.sigchild_event = new SelectEvent(&this.selectEventHandler);
203 
204         }
205 
206 
207         /***********************************************************************
208 
209             Enables or disables the SIGCHLD signal handler.
210 
211             If the handler is enabled, then when a SIGCHLD event occurs, the
212             sigchild_event will be triggered.
213 
214             Params:
215                 enable = true to enable the handler, false to restore default
216                     signal handling
217 
218         ***********************************************************************/
219 
220         private void enableSigChildHander ( bool enable )
221         {
222 
223             sigaction_t sa;
224 
225             // SA_RESTART must be specified, to avoid problems with
226             // poorly-written code that does not handle EINTR correctly.
227 
228             sa.sa_flags = SA_RESTART;
229             sa.sa_handler = enable ? &this.sigchld_handler : SIG_DFL;
230 
231             sigaction(SIGCHLD, &sa, null);
232         }
233 
234 
235         /***********************************************************************
236 
237             Signal handler for SIGCHLD.
238             Triggers the sigchild_event select event when a SIGCHLD signal
239             was received.
240 
241             Params:
242                 sig = the signal which has happened (always SIGCHLD)
243 
244         ***********************************************************************/
245 
246         static extern(C) void sigchld_handler ( int sig )
247         {
248             // It is legal to call SignalEvent.trigger() from inside a signal
249             // handler. This is because it is implemented using write(), which
250             // is included in the POSIX,1 2004 list of "safe functions" for
251             // signal handlers.
252 
253             if ( EpollProcess.process_monitor.sigchild_event )
254             {
255                 EpollProcess.process_monitor.sigchild_event.trigger();
256             }
257         }
258 
259 
260         /***********************************************************************
261 
262             Adds an EpollProcess instance to the set of running processes. The
263             SIGCHLD event handler is registered with the epoll instance used
264             by the EpollProcess, and will call the signalHandler() method when
265             a child process terminates.
266 
267             Params:
268                 process = process which has just started
269 
270         ***********************************************************************/
271 
272         public void add ( EpollProcess process )
273         {
274             this.processes[process.process.pid] = process;
275 
276             process.epoll.register(this.sigchild_event);
277 
278             this.enableSigChildHander(true);
279         }
280 
281 
282         /***********************************************************************
283 
284             Event handler for the SIGCHILD SelectEvent.
285 
286             Fired by the signal handler when a SIGCHLD signal occurs. Calls
287             waitpid to find out which child process caused the signal to fire,
288             informs the corresponding EpollProcess instance that the process has
289             exited, and removes that process from the set of running signals.
290             If there are no further running processes, the event is unregistered
291             from epoll and the signal handler is disabled.
292 
293             Returns:
294                 true if the event should fire again, false if it should be
295                 unregistered from epoll
296 
297         ***********************************************************************/
298 
299         private bool selectEventHandler ( )
300         {
301             debug ( EpollProcess ) Stdout.formatln("Sigchild fired in epoll: ");
302 
303             pid_t pid;
304             do
305             {
306                 int status;
307                 pid = waitpid(-1, &status, WNOHANG);
308 
309                 // waitpid returns -1 and error ECHILD if the calling process
310                 // has no children
311 
312                 if (pid == -1)
313                 {
314                     verify(errno() == ECHILD);
315                     verify(this.processes.length == 0);
316                     return false;
317                 }
318 
319                 // waitpid returns 0 in the case where it would hang (if no
320                 // pid has changed state).
321                 if ( pid )
322                 {
323                     debug ( EpollProcess )
324                         Stdout.formatln("Signal fired in epoll: pid = {}", pid);
325 
326                     auto exited_ok = WIFEXITED(status);
327                     int exit_code = exited_ok ? WEXITSTATUS(status) : 0;
328 
329                     auto process = pid in this.processes;
330                     if ( process )
331                     {
332                         debug ( EpollProcess )
333                             Stdout.formatln("pid {} finished, ok = {}, code = {}",
334                                             pid, exited_ok, exit_code);
335 
336                         auto epoll = process.epoll;
337 
338                         process.exit(exited_ok, exit_code);
339 
340                         this.processes.remove(pid);
341 
342                         this.unregisterEpollIfFinished(epoll);
343                     }
344 
345                 }
346             }
347             while ( pid );
348 
349             return true;
350         }
351 
352         /***********************************************************************
353 
354             Unregister the SIGCHLD event with epoll, if there are no more
355             processes using this epoll instance
356 
357             Params:
358                 epoll = epoll instance to use for unregistering
359 
360         ***********************************************************************/
361 
362         private void unregisterEpollIfFinished ( EpollSelectDispatcher epoll )
363         {
364             foreach ( pid, process ; this.processes )
365             {
366                 if ( process.epoll == epoll )
367                 {
368                     return;
369                 }
370             }
371 
372             // There are no remaining processes using this epoll instance, so
373             // unregister the event.
374 
375             epoll.unregister(this.sigchild_event);
376 
377             // If there are no more processes using _any_ epoll instance,
378             // disconnect the signal handler.
379 
380             if ( !this.processes.length )
381             {
382                 this.enableSigChildHander(false);
383             }
384         }
385     }
386 
387 
388     /***************************************************************************
389 
390         ISelectClient implementation of an output stream. Enables a stdout or
391         stderr stream to be registered with an EpollSelectDispatcher.
392 
393     ***************************************************************************/
394 
395     abstract private static class OutputStreamHandler : ISelectClient
396     {
397         /***********************************************************************
398 
399             Stream buffer. Receives data from stream.
400 
401         ***********************************************************************/
402 
403         private ubyte[1024] buf;
404 
405 
406         /***********************************************************************
407 
408             Events to register for
409 
410         ***********************************************************************/
411 
412         public override Event events ( )
413         {
414             return Event.EPOLLIN;
415         }
416 
417 
418         /***********************************************************************
419 
420             Catches exceptions thrown by the handle() method.
421 
422             Params:
423                 exception = Exception thrown by handle()
424                 event     = Selector event while exception was caught
425 
426         ***********************************************************************/
427 
428         protected override void error_ ( Exception exception, Event event )
429         {
430             log.error("EPOLL error {} at {} {} event = {}", exception.toString(),
431                            exception.file, exception.line, event);
432         }
433 
434 
435         /***********************************************************************
436 
437             ISelectClient handle method. Called by epoll when a read event fires
438             for this stream. The stream is provided by the abstract stream()
439             method.
440 
441             Data is read from the stream into this.buf and the abstract
442             handle_() method is called to process the received data. The client
443             is left registered with epoll unless a Hangup event occurs. Hangup
444             occurs in all cases when the process which owns the stream being
445             read from exits (both error and success).
446 
447             Params:
448                 event = event which fired in epoll
449 
450             Returns:
451                 true to stay registered with epoll and be called again when a
452                 read event fires for this stream, false to unregister.
453 
454         ***********************************************************************/
455 
456         public override bool handle ( Event event )
457         {
458             /* It is possible to get Event.Read _and_ Hangup
459              * simultaneously. If this happens, just deal with the
460              * Read. We will be called again with the Hangup.
461              */
462 
463             size_t received = ( event & event.EPOLLIN ) ?
464                     this.stream.read(this.buf) : 0;
465 
466 
467             if ( received > 0 && received != InputStream.Eof )
468             {
469                 this.handle_(this.buf[0..received]);
470             }
471             else
472             {
473                 if ( event & Event.EPOLLHUP )
474                 {
475                     return false;
476                 }
477             }
478 
479             return true;
480         }
481 
482 
483         /***********************************************************************
484 
485             Returns:
486                 the stream being read from
487 
488         ***********************************************************************/
489 
490         abstract protected InputStream stream ( );
491 
492 
493         /***********************************************************************
494 
495             Handles data received from the stream.
496 
497             Params:
498                 data = data received from stream
499 
500         ***********************************************************************/
501 
502         abstract protected void handle_ ( ubyte[] data );
503     }
504 
505 
506     /***************************************************************************
507 
508         Epoll stdout handler for the process being executed by the outer class.
509 
510     ***************************************************************************/
511 
512     private class StdoutHandler : OutputStreamHandler
513     {
514         /***********************************************************************
515 
516             Returns:
517                 file descriptor to register with epoll
518 
519         ***********************************************************************/
520 
521         public override Handle fileHandle ( )
522         {
523             return this.outer.process.stdout.fileHandle;
524         }
525 
526 
527         /***********************************************************************
528 
529             ISelectClient finalizer. Called from the epoll selector when a
530             client finishes (due to being unregistered or an error).
531 
532             Calls the outer class' finalize() method.
533 
534         ***********************************************************************/
535 
536         override public void finalize ( FinalizeStatus status )
537         {
538             this.outer.stdoutFinalize();
539         }
540 
541 
542         /***********************************************************************
543 
544             Returns:
545                 the stream being read from
546 
547         ***********************************************************************/
548 
549         protected override InputStream stream ( )
550         {
551             return this.outer.process.stdout;
552         }
553 
554 
555         /***********************************************************************
556 
557             Handles data received from the stream, passing it to the stdout()
558             method of the outer class.
559 
560             Params:
561                 data = data received from stream
562 
563         ***********************************************************************/
564 
565         protected override void handle_ ( ubyte[] data )
566         {
567             verify(!this.outer.stdout_finalized);
568             this.outer.stdout(data);
569         }
570     }
571 
572 
573     /***************************************************************************
574 
575         Epoll stderr handler for the process being executed by the outer class.
576 
577     ***************************************************************************/
578 
579     private class StderrHandler : OutputStreamHandler
580     {
581         /***********************************************************************
582 
583             Returns:
584                 file descriptor to register with epoll
585 
586         ***********************************************************************/
587 
588         public override Handle fileHandle ( )
589         {
590             return this.outer.process.stderr.fileHandle;
591         }
592 
593 
594         /***********************************************************************
595 
596             ISelectClient finalizer. Called from the epoll selector when a
597             client finishes (due to being unregistered or an error).
598 
599             Calls the outer class' finalize() method.
600 
601         ***********************************************************************/
602 
603         override public void finalize ( FinalizeStatus status )
604         {
605             this.outer.stderrFinalize();
606         }
607 
608 
609         /***********************************************************************
610 
611             Returns:
612                 the stream being read from
613 
614         ***********************************************************************/
615 
616         protected override InputStream stream ( )
617         {
618             return this.outer.process.stderr;
619         }
620 
621 
622         /***********************************************************************
623 
624             Handles data received from the stream, passing it to the stderr()
625             method of the outer class.
626 
627             Params:
628                 data = data received from stream
629 
630         ***********************************************************************/
631 
632         protected override void handle_ ( ubyte[] data )
633         {
634             verify(!this.outer.stderr_finalized);
635             this.outer.stderr(data);
636         }
637     }
638 
639 
640     /***************************************************************************
641 
642         Singleton instance of GlobalProcessMonitor
643 
644     ***************************************************************************/
645 
646     private mixin(global("static GlobalProcessMonitor process_monitor"));
647 
648 
649     /***************************************************************************
650 
651         Handlers integrating the stdout & stderr of the executing process with
652         epoll.
653 
654     ***************************************************************************/
655 
656     private StdoutHandler stdout_handler;
657 
658     private StderrHandler stderr_handler;
659 
660 
661     /***************************************************************************
662 
663         Flag indicating whether the exit() method has been called.
664 
665     ***************************************************************************/
666 
667     private bool exited;
668 
669 
670     /***************************************************************************
671 
672         Flag indicating whether the process exited normally, in which case the
673         exit_code member is valid. If the process did not exit normally,
674         exit_code will be 0 and invalid.
675 
676         Set by the exit() method.
677 
678     ***************************************************************************/
679 
680     private bool exited_ok;
681 
682 
683     /***************************************************************************
684 
685         Process exit code. Set by the exit() method.
686 
687     ***************************************************************************/
688 
689     private int exit_code;
690 
691 
692     /***************************************************************************
693 
694         Flag indicating whether the finalize() method has been called.
695 
696     ***************************************************************************/
697 
698     private bool stdout_finalized;
699 
700     private bool stderr_finalized;
701 
702 
703     /***************************************************************************
704 
705         Epoll selector instance. Passed as a reference into the constructor.
706 
707     ***************************************************************************/
708 
709     private EpollSelectDispatcher epoll;
710 
711 
712     /***************************************************************************
713 
714         Process state.
715 
716     ***************************************************************************/
717 
718     private enum State
719     {
720         None,
721         Running,
722         Suspended
723     }
724 
725     private State state;
726 
727 
728     /***************************************************************************
729 
730         Process being executed.
731 
732     ***************************************************************************/
733 
734     protected Process process;
735 
736 
737 
738     /***************************************************************************
739 
740         Constructor.
741 
742         Note: the constructor does not actually start a process, the start()
743         method does that.
744 
745         Params:
746             epoll = epoll selector to use
747 
748     ***************************************************************************/
749 
750     public this ( EpollSelectDispatcher epoll )
751     {
752         this.epoll = epoll;
753 
754         this.process = new Process;
755         this.stdout_handler = new StdoutHandler;
756         this.stderr_handler = new StderrHandler;
757 
758         if ( this.process_monitor is null )
759         {
760             debug ( EpollProcess )
761                 Stdout.formatln("Creating the implicit singleton process monitor");
762 
763             this.process_monitor = new GlobalProcessMonitor();
764         }
765     }
766 
767 
768     /***************************************************************************
769 
770         Starts the process with the specified command and arguments. Registers
771         the handlers for the process' stdout and stderr streams with epoll, so
772         that notifications will be triggered when the process generates output.
773         The command to execute is args_with_command[0].
774 
775         Params:
776             args_with_command = command followed by arguments
777 
778     ***************************************************************************/
779 
780     public void start ( Const!(mstring)[] args_with_command )
781     {
782         verify(this.state == State.None); // TODO: error notification?
783 
784         this.stdout_finalized = false;
785         this.stderr_finalized = false;
786         this.exited = false;
787 
788         this.process.argsWithCommand(args_with_command);
789         this.process.execute();
790 
791         debug ( EpollProcess )
792             Stdout.formatln("Starting process pid {}, {}",
793                             this.process.pid, args_with_command);
794 
795         this.epoll.register(this.stdout_handler);
796         this.epoll.register(this.stderr_handler);
797 
798         this.state = State.Running;
799 
800         verify(this.process_monitor !is null,
801                "Implicit singleton process monitor not initialised");
802         this.process_monitor.add(this);
803     }
804 
805 
806     /***************************************************************************
807 
808         Suspends the output of a process. This is achieved simply by
809         unregistering its stdout handler from epoll. This will have the effect
810         that the process will, at some point, reach the capacity of its stdout
811         buffer, and will then pause until the buffer has been emptied.
812 
813     ***************************************************************************/
814 
815     public void suspend ( )
816     {
817         if ( this.state == State.Running )
818         {
819             this.state = State.Suspended;
820 
821             if ( !this.stdout_finalized )
822             {
823                 this.epoll.unregister(this.stdout_handler);
824             }
825 
826             if ( !this.stderr_finalized )
827             {
828                 this.epoll.unregister(this.stderr_handler);
829             }
830         }
831     }
832 
833 
834     /***************************************************************************
835 
836         Returns:
837             true if the process has been suspended using the suspend() method.
838 
839     ***************************************************************************/
840 
841     public bool suspended ( )
842     {
843         return this.state == State.Suspended;
844     }
845 
846 
847     /***************************************************************************
848 
849         Resumes the process if it has been suspended using the suspend() method.
850         The stdout handler is reregistered with epoll.
851 
852     ***************************************************************************/
853 
854     public void resume ( )
855     {
856         if ( this.state == State.Suspended )
857         {
858             this.state = State.Running;
859 
860             if ( !this.stdout_finalized )
861             {
862                 this.epoll.register(this.stdout_handler);
863             }
864 
865             if ( !this.stderr_finalized )
866             {
867                 this.epoll.register(this.stderr_handler);
868             }
869         }
870     }
871 
872 
873     /***************************************************************************
874 
875         Abstract method called when data is received from the process' stdout
876         stream.
877 
878         Params:
879             data = data read from stdout
880 
881     ***************************************************************************/
882 
883     abstract protected void stdout ( ubyte[] data );
884 
885 
886     /***************************************************************************
887 
888         Abstract method called when data is received from the process' stderr
889         stream.
890 
891         Params:
892             data = data read from stderr
893 
894     ***************************************************************************/
895 
896     abstract protected void stderr ( ubyte[] data );
897 
898 
899     /***************************************************************************
900 
901         Abstract method called when the process has finished. Once this method
902         has been called, it is guaraneteed that stdout() will not be called
903         again.
904 
905         Params:
906             exited_ok = if true, the process exited normally and the exit_code
907                 parameter is valid. Otherwise the process exited abnormally, and
908                 exit_code will be 0.
909             exit_code = the process' exit code, if exited_ok is true. Otherwise
910                 0.
911 
912     ***************************************************************************/
913 
914     abstract protected void finished ( bool exited_ok, int exit_code );
915 
916 
917     /***************************************************************************
918 
919         Called when the process' stdout handler is finalized by epoll. This
920         occurs when the process terminates and all data from its stdout buffer
921         has been read.
922 
923         The checkFinished() method is called once the stdoutFinished(),
924         stderrFinished() and exit() methods have been called, ensuring that no
925         more data will be received after this point.
926 
927     ***************************************************************************/
928 
929     private void stdoutFinalize ( )
930     {
931         debug ( EpollProcess ) Stdout.formatln("Finalized stdout pid {}",
932                                                this.process.pid);
933         this.stdout_finalized = true;
934         this.checkFinished();
935     }
936 
937 
938     /***************************************************************************
939 
940         Called when the process' stderr handler is finalized by epoll. This
941         occurs when the process terminates and all data from its stderr buffer
942         has been read.
943 
944         The checkFinished() method is called once the stdoutFinished(),
945         stderrFinished() and exit() methods have been called, ensuring that no
946         more data will be received after this point.
947 
948     ***************************************************************************/
949 
950     private void stderrFinalize ( )
951     {
952         debug ( EpollProcess ) Stdout.formatln("Finalized stderr pid {}",
953                                                this.process.pid);
954         this.stderr_finalized = true;
955         this.checkFinished();
956     }
957 
958 
959     /***************************************************************************
960 
961         Called when the process exits, by the process monitor that is
962         responsible for this process. The process monitor, in turn, was notified
963         of this via a SIGCHLD signal.
964 
965         The checkFinished() method is called once the stdoutFinished(),
966         stderrFinished() and exit() methods have been called, ensuring that no
967         more data will be received after this point.
968 
969         Params:
970             exited_ok = if true, the process exited normally and the exit_code
971                 parameter is valid. Otherwise the process exited abnormally, and
972                 exit_code will be 0.
973             exit_code = the process' exit code, if exited_ok is true. Otherwise
974                 0.
975 
976     ***************************************************************************/
977 
978     private void exit ( bool exited_ok, int exit_code )
979     {
980         debug ( EpollProcess ) Stdout.formatln("Set exit status pid {}",
981                                                this.process.pid);
982         this.exited_ok = exited_ok;
983         this.exit_code = exit_code;
984         this.exited = true;
985 
986         // We know the process has already exited, as we have explicitly been
987         // notified about this by the SIGCHLD signal (handled by the
988         // signalHandler() method of ProcessMonitor, above). However the
989         // Process instance contains a flag (running_) which needs to be reset.
990         // This can be achieved by calling wait(), which internally calls
991         // waitpid() again. In this case waitpid() will return immediately with
992         // an error code (as the child process no longer exists).
993         this.process.wait();
994 
995         this.checkFinished();
996     }
997 
998 
999     /***************************************************************************
1000 
1001         Calls the protected finished() method once both the finalize() and
1002         exit() methods have been called, ensuring that no more data will be
1003         received after this point.
1004 
1005     ***************************************************************************/
1006 
1007     private void checkFinished ( )
1008     {
1009         if ( this.stdout_finalized && this.stderr_finalized && this.exited )
1010         {
1011             this.state = State.None;
1012 
1013             debug ( EpollProcess )
1014                 Stdout.formatln("Streams finalised & process exited");
1015             this.finished(this.exited_ok, this.exit_code);
1016         }
1017     }
1018 }
1019 
1020 /*******************************************************************************
1021 
1022     Unit tests
1023 
1024 *******************************************************************************/
1025 
1026 version ( UnitTest )
1027 {
1028     import ocean.core.Test;
1029 }
1030 
1031 unittest
1032 {
1033     class MyProcess : EpollProcess
1034     {
1035         public this ( EpollSelectDispatcher epoll )
1036         {
1037             super(epoll);
1038         }
1039         protected override void stdout ( ubyte[] data ) { }
1040         protected override void stderr ( ubyte[] data ) { }
1041         protected override void finished ( bool exited_ok, int exit_code ) { }
1042     }
1043 
1044     scope epoll1 = new EpollSelectDispatcher;
1045     scope epoll2 = new EpollSelectDispatcher;
1046 
1047     // It is ok to have two different epoll instances.
1048 
1049     scope proc1 = new MyProcess(epoll1);
1050 
1051     scope proc2 = new MyProcess(epoll2);
1052 }
1053