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