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 }