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 moduleocean.io.select.client.EpollProcess;
93 94 importocean.core.Verify;
95 importocean.io.select.client.model.ISelectClient;
96 importocean.io.select.client.SelectEvent;
97 importocean.io.select.EpollSelectDispatcher;
98 debugimportocean.io.Stdout;
99 importocean.io.model.IConduit;
100 importocean.meta.types.Qualifiers;
101 importocean.sys.Process;
102 importocean.util.container.map.Map;
103 importocean.util.log.Logger;
104 105 importcore.stdc.errno;
106 importcore.sys.posix.signal : SIGCHLD;
107 importcore.sys.posix.sys.wait;
108 109 110 /*******************************************************************************
111 112 Static module logger
113 114 *******************************************************************************/115 116 staticprivateLoggerlog;
117 staticthis ( )
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 publicabstractclassEpollProcess130 {
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 privatestaticclassGlobalProcessMonitor142 {
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 privateSelectEventsigchild_event;
152 153 154 /***********************************************************************
155 156 Mapping from a process id to an EpollProcess instance.
157 158 ***********************************************************************/159 160 privateStandardKeyHashingMap!(EpollProcess, int) processes;
161 162 163 /***********************************************************************
164 165 Allocate the map from a free list, to reduce memory allocation.
166 167 ***********************************************************************/168 169 importFreeListAllocator =
170 ocean.util.container.map.model.BucketElementFreeList;
171 172 173 /***********************************************************************
174 175 Constructor.
176 177 ***********************************************************************/178 179 publicthis ( )
180 {
181 // Allocate the map using a free list182 183 this.processes = newStandardKeyHashingMap!(EpollProcess, int)(
184 FreeListAllocator.instantiateAllocator!(
185 StandardKeyHashingMap!(EpollProcess, int)), 20);
186 187 this.sigchild_event = newSelectEvent(&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 privatevoidenableSigChildHander ( boolenable )
206 {
207 208 sigaction_tsa;
209 210 // SA_RESTART must be specified, to avoid problems with211 // 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 staticextern(C) voidsigchld_handler ( intsig )
232 {
233 // It is legal to call SignalEvent.trigger() from inside a signal234 // handler. This is because it is implemented using write(), which235 // is included in the POSIX,1 2004 list of "safe functions" for236 // 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 publicvoidadd ( EpollProcessprocess )
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 privateboolselectEventHandler ( )
285 {
286 debug ( EpollProcess ) Stdout.formatln("Sigchild fired in epoll: ");
287 288 pid_tpid;
289 do290 {
291 intstatus;
292 pid = waitpid(-1, &status, WNOHANG);
293 294 // waitpid returns -1 and error ECHILD if the calling process295 // has no children296 297 if (pid == -1)
298 {
299 verify(errno() == ECHILD);
300 verify(this.processes.length == 0);
301 returnfalse;
302 }
303 304 // waitpid returns 0 in the case where it would hang (if no305 // pid has changed state).306 if ( pid )
307 {
308 debug ( EpollProcess )
309 Stdout.formatln("Signal fired in epoll: pid = {}", pid);
310 311 autoexited_ok = WIFEXITED(status);
312 intexit_code = exited_ok ? WEXITSTATUS(status) : 0;
313 314 autoprocess = pidinthis.processes;
315 if ( process )
316 {
317 debug ( EpollProcess )
318 Stdout.formatln("pid {} finished, ok = {}, code = {}",
319 pid, exited_ok, exit_code);
320 321 autoepoll = 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 returntrue;
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 privatevoidunregisterEpollIfFinished ( EpollSelectDispatcherepoll )
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, so358 // 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 abstractprivatestaticclassOutputStreamHandler : ISelectClient381 {
382 /***********************************************************************
383 384 Stream buffer. Receives data from stream.
385 386 ***********************************************************************/387 388 privateubyte[1024] buf;
389 390 391 /***********************************************************************
392 393 Events to register for
394 395 ***********************************************************************/396 397 publicoverrideEventevents ( )
398 {
399 returnEvent.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 protectedoverridevoiderror_ ( Exceptionexception, Eventevent )
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 publicoverrideboolhandle ( Eventevent )
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_treceived = ( 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 else457 {
458 if ( event & Event.EPOLLHUP )
459 {
460 returnfalse;
461 }
462 }
463 464 returntrue;
465 }
466 467 468 /***********************************************************************
469 470 Returns:
471 the stream being read from
472 473 ***********************************************************************/474 475 abstractprotectedInputStreamstream ( );
476 477 478 /***********************************************************************
479 480 Handles data received from the stream.
481 482 Params:
483 data = data received from stream
484 485 ***********************************************************************/486 487 abstractprotectedvoidhandle_ ( ubyte[] data );
488 }
489 490 491 /***************************************************************************
492 493 Epoll stdout handler for the process being executed by the outer class.
494 495 ***************************************************************************/496 497 privateclassStdoutHandler : OutputStreamHandler498 {
499 /***********************************************************************
500 501 Returns:
502 file descriptor to register with epoll
503 504 ***********************************************************************/505 506 publicoverrideHandlefileHandle ( )
507 {
508 returnthis.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 overridepublicvoidfinalize ( FinalizeStatusstatus )
522 {
523 this.outer.stdoutFinalize();
524 }
525 526 527 /***********************************************************************
528 529 Returns:
530 the stream being read from
531 532 ***********************************************************************/533 534 protectedoverrideInputStreamstream ( )
535 {
536 returnthis.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 protectedoverridevoidhandle_ ( 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 privateclassStderrHandler : OutputStreamHandler565 {
566 /***********************************************************************
567 568 Returns:
569 file descriptor to register with epoll
570 571 ***********************************************************************/572 573 publicoverrideHandlefileHandle ( )
574 {
575 returnthis.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 overridepublicvoidfinalize ( FinalizeStatusstatus )
589 {
590 this.outer.stderrFinalize();
591 }
592 593 594 /***********************************************************************
595 596 Returns:
597 the stream being read from
598 599 ***********************************************************************/600 601 protectedoverrideInputStreamstream ( )
602 {
603 returnthis.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 protectedoverridevoidhandle_ ( 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__gsharedstaticGlobalProcessMonitorprocess_monitor;
632 633 634 /***************************************************************************
635 636 Handlers integrating the stdout & stderr of the executing process with
637 epoll.
638 639 ***************************************************************************/640 641 privateStdoutHandlerstdout_handler;
642 643 privateStderrHandlerstderr_handler;
644 645 646 /***************************************************************************
647 648 Flag indicating whether the exit() method has been called.
649 650 ***************************************************************************/651 652 privateboolexited;
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 privateboolexited_ok;
666 667 668 /***************************************************************************
669 670 Process exit code. Set by the exit() method.
671 672 ***************************************************************************/673 674 privateintexit_code;
675 676 677 /***************************************************************************
678 679 Flag indicating whether the finalize() method has been called.
680 681 ***************************************************************************/682 683 privateboolstdout_finalized;
684 685 privateboolstderr_finalized;
686 687 688 /***************************************************************************
689 690 Epoll selector instance. Passed as a reference into the constructor.
691 692 ***************************************************************************/693 694 privateEpollSelectDispatcherepoll;
695 696 697 /***************************************************************************
698 699 Process state.
700 701 ***************************************************************************/702 703 privateenumState704 {
705 None,
706 Running,
707 Suspended708 }
709 710 privateStatestate;
711 712 713 /***************************************************************************
714 715 Process being executed.
716 717 ***************************************************************************/718 719 protectedProcessprocess;
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 publicthis ( EpollSelectDispatcherepoll )
736 {
737 this.epoll = epoll;
738 739 this.process = newProcess;
740 this.stdout_handler = newStdoutHandler;
741 this.stderr_handler = newStderrHandler;
742 743 if ( this.process_monitorisnull )
744 {
745 debug ( EpollProcess )
746 Stdout.formatln("Creating the implicit singleton process monitor");
747 748 this.process_monitor = newGlobalProcessMonitor();
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 publicvoidstart ( 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 !isnull,
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 publicvoidsuspend ( )
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 publicboolsuspended ( )
827 {
828 returnthis.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 publicvoidresume ( )
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 abstractprotectedvoidstdout ( 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 abstractprotectedvoidstderr ( 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 abstractprotectedvoidfinished ( boolexited_ok, intexit_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 privatevoidstdoutFinalize ( )
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 privatevoidstderrFinalize ( )
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 privatevoidexit ( boolexited_ok, intexit_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 been972 // notified about this by the SIGCHLD signal (handled by the973 // signalHandler() method of ProcessMonitor, above). However the974 // Process instance contains a flag (running_) which needs to be reset.975 // This can be achieved by calling wait(), which internally calls976 // waitpid() again. In this case waitpid() will return immediately with977 // 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 privatevoidcheckFinished ( )
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 importocean.core.Test;
1014 }
1015 1016 unittest1017 {
1018 classMyProcess : EpollProcess1019 {
1020 publicthis ( EpollSelectDispatcherepoll )
1021 {
1022 super(epoll);
1023 }
1024 protectedoverridevoidstdout ( ubyte[] data ) { }
1025 protectedoverridevoidstderr ( ubyte[] data ) { }
1026 protectedoverridevoidfinished ( boolexited_ok, intexit_code ) { }
1027 }
1028 1029 scopeepoll1 = newEpollSelectDispatcher;
1030 scopeepoll2 = newEpollSelectDispatcher;
1031 1032 // It is ok to have two different epoll instances.1033 1034 scopeproc1 = newMyProcess(epoll1);
1035 1036 scopeproc2 = newMyProcess(epoll2);
1037 }