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