1 /******************************************************************************* 2 3 Application class that provides the standard features needed by applications 4 that run as a daemon: 5 * Command line parsing 6 * Version support 7 * Reading of config file 8 * Auto-configuration of loggers 9 * Periodic stats logging 10 * Re-opening of log files upon receipt of SIGHUP (intended to be used 11 in conjunction with logrotate) 12 13 Usage example: 14 See DaemonApp class' documented unittest 15 16 A note on epoll: 17 18 The daemon app does not currently interact with epoll in any way (either 19 registering clients or starting the event loop). This is a deliberate 20 choice, in order to leave the epoll handling up to the user, without 21 enforcing any required sequence of events. (This may come in the future.) 22 23 However, some extensions (namely, the SignalExt and TimerExt) require an 24 epoll instance for their internal event handling. For this reason, an epoll 25 instance must be passed to the DaemonApp. To do so, pass an epoll instance 26 to the startEventHandling method. 27 28 Copyright: 29 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 30 All rights reserved. 31 32 License: 33 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 34 Alternatively, this file may be distributed under the terms of the Tango 35 3-Clause BSD License (see LICENSE_BSD.txt for details). 36 37 *******************************************************************************/ 38 39 module ocean.util.app.DaemonApp; 40 41 import ocean.util.app.Application : Application; 42 import ocean.util.app.ext.model.IArgumentsExtExtension; 43 import ocean.util.app.ext.model.IConfigExtExtension; 44 import ocean.util.app.ext.model.ILogExtExtension; 45 import ocean.util.app.ext.model.ISignalExtExtension; 46 47 import ocean.meta.types.Qualifiers; 48 version (unittest) import ocean.core.Test; 49 import ocean.core.Verify; 50 import ocean.task.IScheduler; 51 52 import core.stdc.time; 53 54 /// ditto 55 public abstract class DaemonApp : Application, 56 IArgumentsExtExtension, IConfigExtExtension, ILogExtExtension, 57 ISignalExtExtension 58 { 59 import ocean.application.components.GCStats; 60 61 static import ocean.text.Arguments; 62 public alias ocean.text.Arguments.Arguments Arguments; 63 static import ocean.util.config.ConfigParser; 64 public alias ocean.util.config.ConfigParser.ConfigParser ConfigParser; 65 66 import ocean.util.log.Stats; 67 import ocean.io.select.EpollSelectDispatcher; 68 69 import ocean.sys.Stats; 70 import ocean.util.app.ext.ArgumentsExt; 71 import ocean.util.app.ext.ConfigExt; 72 import ocean.util.app.ext.VersionArgsExt; 73 protected import ocean.util.app.ext.VersionInfo : VersionInfo; 74 import ocean.util.app.ext.LogExt; 75 import ocean.util.app.ext.StatsExt; 76 import ocean.util.app.ext.TimerExt; 77 import ocean.util.app.ext.SignalExt; 78 import ocean.util.app.ext.ReopenableFilesExt; 79 import ocean.util.app.ext.PidLockExt; 80 import ocean.util.app.ext.UnixSocketExt; 81 import ocean.util.app.ext.TaskExt; 82 import ocean.util.app.ExitException; 83 import ocean.util.log.Logger; 84 import ocean.util.log.Stats; 85 import ocean.util.prometheus.collector.Collector; 86 87 static import core.sys.posix.signal; 88 89 /*************************************************************************** 90 91 Command line arguments used by the application. 92 93 ***************************************************************************/ 94 95 public Arguments args; 96 97 /*************************************************************************** 98 99 Command line arguments extension used by the application. 100 101 ***************************************************************************/ 102 103 public ArgumentsExt args_ext; 104 105 /*************************************************************************** 106 107 Configuration parser to use to parse the configuration files. 108 109 ***************************************************************************/ 110 111 public ConfigParser config; 112 113 /*************************************************************************** 114 115 Configuration parsing extension instance. 116 117 ***************************************************************************/ 118 119 public ConfigExt config_ext; 120 121 /*************************************************************************** 122 123 Logging extension instance. 124 125 ***************************************************************************/ 126 127 public LogExt log_ext; 128 129 /*************************************************************************** 130 131 Version information. 132 133 ***************************************************************************/ 134 135 public VersionInfo ver; 136 137 /*************************************************************************** 138 139 Version information extension. 140 141 ***************************************************************************/ 142 143 public VersionArgsExt ver_ext; 144 145 /*************************************************************************** 146 147 Stats log extension -- TODO auto configured or what? Why public? 148 getter for StatsLog instance? 149 150 ***************************************************************************/ 151 152 public StatsExt stats_ext; 153 154 /*************************************************************************** 155 156 Timer handler extension. 157 158 ***************************************************************************/ 159 160 public TimerExt timer_ext; 161 162 /*************************************************************************** 163 164 Signal handler extension. Directs registered signals to the onSignal() 165 method. 166 167 ***************************************************************************/ 168 169 public SignalExt signal_ext; 170 171 /*************************************************************************** 172 173 Reopenable files extension. Hooks into the stats, log, and signal 174 extentions, and automatically reopens logfiles upon receipt of the 175 SIGHUP signal (presumably sent from logrotate). 176 177 ***************************************************************************/ 178 179 public ReopenableFilesExt reopenable_files_ext; 180 181 /*************************************************************************** 182 183 PidLock extension. Tries to create and lock the pid lock file (if 184 specified in the config), ensuring that only one application instance 185 per pidlock may exist. 186 187 ***************************************************************************/ 188 189 public PidLockExt pidlock_ext; 190 191 /*************************************************************************** 192 193 Unix socket extension to register commands for the application to 194 respond to. 195 196 ***************************************************************************/ 197 198 public UnixSocketExt unix_socket_ext; 199 200 /*************************************************************************** 201 202 Extension to start `run` method inside a task. 203 204 ***************************************************************************/ 205 206 public TaskExt task_ext; 207 208 /*************************************************************************** 209 210 Cpu and memory collector instance. 211 212 ***************************************************************************/ 213 214 private CpuMemoryStats system_stats; 215 216 /*************************************************************************** 217 218 Garbage collector stats 219 220 ***************************************************************************/ 221 222 private GCStats gc_stats; 223 224 /*************************************************************************** 225 226 Epoll instance used internally. 227 228 ***************************************************************************/ 229 230 private EpollSelectDispatcher epoll; 231 232 /*************************************************************************** 233 234 Struct containing optional constructor arguments. There are enough of 235 these that handling them as default arguments to the ctor is cumbersome. 236 237 ***************************************************************************/ 238 239 public static struct OptionalSettings 240 { 241 import ocean.util.log.Appender; 242 import core.sys.posix.signal : SIGHUP; 243 244 /*********************************************************************** 245 246 How the program is supposed to be invoked. 247 248 ***********************************************************************/ 249 250 istring usage = null; 251 252 /*********************************************************************** 253 254 Long description of what the program does and how to use it. 255 256 ***********************************************************************/ 257 258 istring help = null; 259 260 /*********************************************************************** 261 262 Default configuration files to parse. 263 264 ***********************************************************************/ 265 266 istring[] default_configs = [ "etc/config.ini" ]; 267 268 /*********************************************************************** 269 270 If true, configuration files will be parsed in a more relaxed way. 271 272 ***********************************************************************/ 273 274 bool loose_config_parsing = false; 275 276 /*********************************************************************** 277 278 Configuration parser to use (if null, a new instance is created). 279 280 ***********************************************************************/ 281 282 ConfigParser config = null; 283 284 /*********************************************************************** 285 286 If true, any loggers which are configured to output to the console 287 (see ocean.util.log.Config) will use the InsertConsole appender, 288 rather than the AppendConsole appender. This is required by apps 289 which use ocean.io.console.AppStatus. 290 291 ***********************************************************************/ 292 293 bool use_insert_appender = false; 294 295 /*********************************************************************** 296 297 Set of signals to handle. 298 299 Note that the signals will be handled with a delay of up to 300 single epoll cycle. This is because the signal extension is synced 301 with the EpollSelectDispatcher. This makes it unsuitable to handle 302 critical signals (like `SIGABRT` or `SIGSEGV`) where the application 303 shouldn't be allowed to proceed in the general case; for these 304 cases setup an asynchronous signal handler using `sigaction` instead. 305 306 ***********************************************************************/ 307 308 int[] signals; 309 310 /*********************************************************************** 311 312 Signal to trigger reopening of files which are registered with the 313 ReopenableFilesExt. (Typically used for log rotation.) 314 315 If this field is set to 0, no signal handler will be installed for 316 file reopening. This is useful if your app is deployed to use a 317 different means of triggering file reopening (e.g. a UNIX socket 318 command). 319 320 ***********************************************************************/ 321 322 int reopen_signal = SIGHUP; 323 324 /*********************************************************************** 325 326 Unix domain socket command to trigger reopening of files which are 327 registered with the ReopenableFilesExt. (Typically used for log 328 rotation). 329 330 ***********************************************************************/ 331 332 istring reopen_command = "reopen_files"; 333 334 /*********************************************************************** 335 336 Unix domain socket command to print the `--version` output of the 337 application to the unix socket. 338 339 ***********************************************************************/ 340 341 istring show_version_command = "show_version"; 342 343 /*********************************************************************** 344 345 Set of signals to ignore. Delivery of the signals specified in this 346 set will have no effect on the application -- they are not passed 347 to the default signal handler. 348 349 ***********************************************************************/ 350 351 int[] ignore_signals; 352 353 /// Delegate for LogExt that instantiates a `Appender.Layout` from a name 354 Appender.Layout delegate (cstring name) make_layout; 355 356 /*********************************************************************** 357 358 By default TaskExt is disabled to prevent breaking change for 359 applications already configuring scheduler on their own. 360 361 ***********************************************************************/ 362 363 bool use_task_ext; 364 365 /*********************************************************************** 366 367 Only used if `use_task_ext` is set to `true`. Defines default 368 scheduler configuration to be used by TaskExt. 369 370 Fields present in config file will take priority over this. 371 372 ***********************************************************************/ 373 374 IScheduler.Configuration scheduler_config; 375 } 376 377 /*************************************************************************** 378 379 This constructor only sets up the internal state of the class, but does 380 not call any extension or user code. 381 382 Note: when calling this constructor, which does not accept an epoll 383 instance, you must pass the epoll instance to startEventHandling 384 instead. 385 386 Params: 387 name = Name of the application (to show in the help message) 388 desc = Short description of what the program does (should be 389 one line only, preferably less than 80 characters) 390 ver = application's version information 391 settings = optional settings (see OptionalSettings, above) 392 393 ***************************************************************************/ 394 395 public this ( istring name, istring desc, 396 VersionInfo ver, OptionalSettings settings = OptionalSettings.init ) 397 { 398 super(name, desc); 399 400 // If derived app does not handle signals explicitly, default 401 // DaemonApp handler will be used which handles SIGTERM 402 if (settings.signals.length == 0) 403 settings.signals = [ core.sys.posix.signal.SIGTERM ]; 404 405 // Create and register arguments extension 406 this.args_ext = new ArgumentsExt(name, desc, settings.usage, 407 settings.help); 408 this.args = this.args_ext.args; 409 this.args_ext.registerExtension(this); 410 this.registerExtension(this.args_ext); 411 412 // Create and register config extension 413 if ( settings.config is null ) 414 settings.config = new ConfigParser; 415 this.config_ext = new ConfigExt(settings.loose_config_parsing, 416 settings.default_configs, settings.config); 417 this.config = this.config_ext.config; 418 this.config_ext.registerExtension(this); 419 this.registerExtension(this.config_ext); 420 this.args_ext.registerExtension(this.config_ext); 421 422 // Create and register log extension 423 this.log_ext = new LogExt(settings.make_layout, 424 settings.use_insert_appender); 425 this.config_ext.registerExtension(this.log_ext); 426 427 // Create and register version extension 428 this.ver_ext = new VersionArgsExt(ver); 429 this.ver = this.ver_ext.ver; 430 this.args_ext.registerExtension(this.ver_ext); 431 this.log_ext.registerExtension(this.ver_ext); 432 this.registerExtension(this.ver_ext); 433 434 // Create and register stats extension 435 this.stats_ext = new StatsExt; 436 this.config_ext.registerExtension(this.stats_ext); 437 438 // Create and register signal extension 439 this.signal_ext = new SignalExt(settings.signals, 440 settings.ignore_signals); 441 this.signal_ext.registerExtension(this); 442 this.registerExtension(this.signal_ext); 443 444 this.pidlock_ext = new PidLockExt(); 445 this.config_ext.registerExtension(this.pidlock_ext); 446 this.registerExtension(this.pidlock_ext); 447 448 this.unix_socket_ext = new UnixSocketExt(); 449 this.config_ext.registerExtension(this.unix_socket_ext); 450 this.registerExtension(this.unix_socket_ext); 451 452 if (settings.show_version_command.length) 453 { 454 this.ver_ext.setupUnixSocketHandler(this, this.unix_socket_ext, 455 settings.show_version_command); 456 } 457 458 if (settings.use_task_ext) 459 { 460 this.task_ext = new TaskExt(settings.scheduler_config); 461 this.config_ext.registerExtension(this.task_ext); 462 } 463 464 // Create and register repoenable files extension 465 this.reopenable_files_ext = new ReopenableFilesExt(); 466 467 if (settings.reopen_signal) 468 { 469 this.reopenable_files_ext.setupSignalHandler(this.signal_ext, 470 settings.reopen_signal); 471 } 472 473 if (settings.reopen_command.length) 474 { 475 this.reopenable_files_ext.setupUnixSocketHandler( 476 this.unix_socket_ext, settings.reopen_command); 477 } 478 479 this.registerExtension(this.reopenable_files_ext); 480 481 this.system_stats = new CpuMemoryStats(); 482 this.gc_stats = new GCStats(); 483 } 484 485 /*************************************************************************** 486 487 This method must be called in order for signal and timer event handling 488 to start being processed. As it registers clients (the stats timer and 489 signal handler) with epoll which will always reregister themselves after 490 firing, you should call this method when you are about to start your 491 application's main event loop. 492 493 Note that, as this method constructs the timer extension, it may only be 494 used once this method has been called. 495 496 Params: 497 epoll = the epoll instance to use for event handling. If null is 498 passed, then the epoll-accepting-ctor must have been called. If 499 non-null is passed, then the other ctor must have been called. 500 501 ***************************************************************************/ 502 503 public void startEventHandling ( EpollSelectDispatcher epoll ) 504 { 505 verify( 506 (epoll !is null) ^ (this.epoll !is null), 507 "Must pass epoll either via ctor or startEventHandling " ~ 508 "argument (but not both)" 509 ); 510 511 if (this.epoll is null) 512 this.epoll = epoll; 513 514 verify(this.timer_ext is null); 515 516 // Create and register timer extension 517 this.timer_ext = new TimerExt(this.epoll); 518 this.registerExtension(this.timer_ext); 519 520 // Register stats timer with epoll 521 ulong initial_offset = timeToNextInterval(this.stats_ext.config.interval); 522 this.timer_ext.register( 523 &this.statsTimer, initial_offset, this.stats_ext.config.interval); 524 525 // Register signal event handler with epoll 526 this.epoll.register(this.signal_ext.selectClient()); 527 528 /// Initialize the unix socket with epoll. 529 this.unix_socket_ext.initializeSocket(this.epoll); 530 } 531 532 /*************************************************************************** 533 534 Params: 535 interval = interval used for calling the stats timer 536 current = current time, default to now (`time(null)`) 537 538 Returns: 539 function to calculate the amount of time to wait until the next 540 interval is reached. 541 542 ***************************************************************************/ 543 544 private static ulong timeToNextInterval (ulong interval, time_t current = time(null)) 545 { 546 return (current % interval) ? (interval - (current % interval)) : interval; 547 } 548 549 unittest 550 { 551 time_t orig = 704124854; // 14 seconds past the minute 552 test!("==")(timeToNextInterval(15, orig), 1); 553 test!("==")(timeToNextInterval(20, orig), 6); 554 test!("==")(timeToNextInterval(30, orig), 16); 555 test!("==")(timeToNextInterval(60, orig), 46); 556 test!("==")(timeToNextInterval(15, orig + 1), 15); 557 } 558 559 /*************************************************************************** 560 561 Run implementation that forwards to the abstract 562 run(Arguments, ConfigParser). 563 564 Params: 565 args = raw command line arguments 566 567 Returns: 568 status code to return to the OS 569 570 ***************************************************************************/ 571 572 override protected int run ( istring[] args ) 573 { 574 this.gc_stats.start(); 575 scope(exit) this.gc_stats.stop(); 576 577 if (this.task_ext is null) 578 return this.run(this.args, this.config); 579 580 this.startEventHandling(theScheduler.epoll()); 581 return this.task_ext.run(&this.mainForTaskExt); 582 } 583 584 /*************************************************************************** 585 586 Used inside `run` if TaskExt is enabled to workaround double `this` 587 issue with inline delegate literal 588 589 ***************************************************************************/ 590 591 private int mainForTaskExt ( ) 592 { 593 return this.run(this.args, this.config); 594 } 595 596 /*************************************************************************** 597 598 This method must be implemented by subclasses to do the actual 599 application work. 600 601 Params: 602 args = parsed command line arguments 603 config = parser instance with the parsed configuration 604 605 Returns: 606 status code to return to the OS 607 608 ***************************************************************************/ 609 610 abstract protected int run ( Arguments args, ConfigParser config ); 611 612 /*************************************************************************** 613 614 Exit cleanly from the application, passing the specified return code to 615 the OS and optionally printing the specified message to the console. 616 617 Calling exit() will properly unwind the stack and all the destructors 618 will be called. The method should be used only from the main application 619 thread, though, as it throws an ExitException which may not be handled 620 properly in other contexts. 621 622 Params: 623 status = status code to return to the OS 624 msg = optional message to show just before exiting 625 626 ***************************************************************************/ 627 628 override public void exit ( int status, istring msg = null ) 629 { 630 this.exit(status, msg, Logger.init); 631 } 632 633 /// Ditto 634 public void exit ( int status, istring msg, Logger logger ) 635 { 636 if (logger !is null) 637 { 638 logger.fatal(msg); 639 } 640 throw new ExitException(status, msg); 641 } 642 643 644 /*************************************************************************** 645 646 Called by the timer extension when the stats period fires. Calls 647 onStatsTimer() and returns true to keep the timer registered. 648 649 Returns: 650 true to re-register timer 651 652 ***************************************************************************/ 653 654 private bool statsTimer ( ) 655 { 656 this.onStatsTimer(); 657 return true; 658 } 659 660 /*************************************************************************** 661 662 Collects CPU and memory stats and reports it to stats log. Should be 663 called periodically (inside onStatsTimer). 664 665 ***************************************************************************/ 666 667 protected void reportSystemStats ( ) 668 { 669 this.stats_ext.stats_log.add(this.system_stats.collect()); 670 } 671 672 /*************************************************************************** 673 674 Collects CPU and memory stats for incoming prometheus' requests. Should 675 be sent, as a callback, to the CollectorRegistry instance used in 676 prometheus request listener. 677 678 ***************************************************************************/ 679 680 public void collectSystemStats ( Collector prometheus_collector ) 681 { 682 prometheus_collector.collect(this.system_stats.collect()); 683 } 684 685 /*************************************************************************** 686 687 Collects GC stats and reports them to stats log. Should be 688 called periodically (inside onStatsTimer). 689 690 ***************************************************************************/ 691 692 protected void reportGCStats ( ) 693 { 694 this.stats_ext.stats_log.add(this.gc_stats.collect()); 695 } 696 697 /*************************************************************************** 698 699 Collects GC stats for incoming prometheus' requests. Should be sent, 700 as a callback, to the CollectorRegistry instance used in prometheus 701 request listener. 702 703 ***************************************************************************/ 704 705 public void collectGCStats ( Collector prometheus_collector ) 706 { 707 prometheus_collector.collect(this.gc_stats.collect()); 708 } 709 710 /*************************************************************************** 711 712 Called by the timer extension when the stats period fires. By default 713 does nothing, but should be overridden to write the required stats. 714 715 ***************************************************************************/ 716 717 protected void onStatsTimer ( ) 718 { 719 } 720 721 /*************************************************************************** 722 723 ISignalExtExtension method default implementation. 724 725 This method is implemented with behaviour most commonly desired in apps 726 that don't do any custom signal handling - attempt cleaner shutdown on 727 SIGTERM signal. 728 729 See ISignalExtExtension documentation for more information on how to 730 override this method with own behaviour. `super.onSignal` is not needed 731 to be called when doing so. 732 733 Note that the default `onSignal` implementation handles `SIGTERM` and 734 calls `theScheduler.shutdown` upon receiving the signal. This results in 735 clean termination but may also cause some in-progress data loss from 736 killed tasks - any application that must never loose data needs to 737 implement own handler. 738 739 ***************************************************************************/ 740 741 override public void onSignal ( int signum ) 742 { 743 switch ( signum ) 744 { 745 case core.sys.posix.signal.SIGTERM: 746 // Default implementation to shut down cleanly 747 if (isSchedulerUsed()) 748 theScheduler.shutdown(); 749 else 750 this.epoll.shutdown(); 751 break; 752 default: 753 break; 754 } 755 } 756 757 /*************************************************************************** 758 759 IArgumentsExtExtension methods dummy implementation. 760 761 These methods are implemented with an "empty" implementation to ease 762 deriving from this class. 763 764 See IArgumentsExtExtension documentation for more information on how to 765 override these methods. 766 767 ***************************************************************************/ 768 769 override public void setupArgs ( IApplication app, Arguments args ) 770 { 771 // Dummy implementation of the interface 772 } 773 774 /// ditto 775 override public void preValidateArgs ( IApplication app, Arguments args ) 776 { 777 // Dummy implementation of the interface 778 } 779 780 /// ditto 781 override public cstring validateArgs ( IApplication app, Arguments args ) 782 { 783 // Dummy implementation of the interface 784 return null; 785 } 786 787 /// ditto 788 override public void processArgs ( IApplication app, Arguments args ) 789 { 790 // Dummy implementation of the interface 791 } 792 793 /*************************************************************************** 794 795 IConfigExtExtension methods dummy implementation. 796 797 These methods are implemented with an "empty" implementation to ease 798 deriving from this class. 799 800 See IConfigExtExtension documentation for more information on how to 801 override these methods. 802 803 ***************************************************************************/ 804 805 override public void preParseConfig ( IApplication app, ConfigParser config ) 806 { 807 // Dummy implementation of the interface 808 } 809 810 /// ditto 811 override public istring[] filterConfigFiles ( IApplication app, 812 ConfigParser config, 813 istring[] files ) 814 { 815 return files; 816 } 817 818 /// ditto 819 override public void processConfig ( IApplication app, ConfigParser config ) 820 { 821 // Dummy implementation of the interface 822 } 823 824 /*************************************************************************** 825 826 ILogExtExtension methods dummy implementation. 827 828 These methods are implemented with an "empty" implementation to ease 829 deriving from this class. 830 831 See IConfigExtExtension documentation for more information on how to 832 override these methods. 833 834 ***************************************************************************/ 835 836 override public void preConfigureLoggers ( IApplication app, 837 ConfigParser config, bool loose_config_parsing, 838 bool use_insert_appender ) 839 { 840 // Dummy implementation of the interface 841 } 842 843 /// ditto 844 override public void postConfigureLoggers ( IApplication app, 845 ConfigParser config, bool loose_config_parsing, 846 bool use_insert_appender ) 847 { 848 // Dummy implementation of the interface 849 } 850 } 851 852 /// 853 unittest 854 { 855 /*************************************************************************** 856 857 Example daemon application class. 858 859 ***************************************************************************/ 860 861 class MyApp : DaemonApp 862 { 863 import core.sys.posix.signal: SIGINT, SIGTERM; 864 865 import ocean.io.select.EpollSelectDispatcher; 866 867 this ( ) 868 { 869 870 // The name of your app and a short description of what it does. 871 istring name = "my_app"; 872 istring desc = "Dummy app for unittest."; 873 874 // The version info for your app. Normally you get this by importing 875 // Version and passing the AA which contains the version info 876 // (called versionInfo) to DaemonApp's constructor. 877 auto ver = VersionInfo.init; 878 879 // You may also pass an instance of OptionalSettings to DaemonApp's 880 // constructor, to specify non-mandatory options. In this example, 881 // we specify the help text and some signals that we want to handle. 882 DaemonApp.OptionalSettings settings; 883 settings.help = "Actually, this program does nothing. Sorry!"; 884 settings.signals = [SIGINT, SIGTERM]; 885 886 // Call the super class' ctor. 887 super(name, desc, ver, settings); 888 } 889 890 // Called after arguments and config file parsing. 891 override protected int run ( Arguments args, ConfigParser config ) 892 { 893 // In order for signal and timer handling to be processed, you must 894 // call this method. This registers one or more clients with epoll. 895 this.startEventHandling(new EpollSelectDispatcher); 896 897 // Application main logic. Usually you would call the epoll event 898 // loop here. 899 900 return 0; // return code to OS 901 } 902 903 // Handle those signals we were interested in 904 // 905 // Note that DaemonApp provides default `onSignal` implementation 906 // that handles `SIGTERM` and calls `theScheduler.shutdown` upon 907 // receiving the signal. This results in clean termination but may also 908 // cause some in-progress data loss from killed tasks - any application 909 // that must never loose data needs to implement own handler. 910 override public void onSignal ( int signal ) 911 { 912 switch ( signal ) 913 { 914 case SIGINT: 915 case SIGTERM: 916 // Termination logic. 917 break; 918 default: 919 } 920 } 921 922 // Handle stats output. 923 override protected void onStatsTimer ( ) 924 { 925 this.reportSystemStats(); 926 this.reportGCStats(); 927 struct Treasure 928 { 929 int copper, silver, gold; 930 } 931 Treasure loot; 932 this.stats_ext.stats_log.add(loot); 933 this.stats_ext.stats_log.flush(); 934 } 935 } 936 937 /*************************************************************************** 938 939 Your application's main() function should look something like this. 940 (This function is not called here as we don't want to actually run the 941 application in this unittest -- it will fail due to the lack of properly 942 configured etc/ and log/ directories.) 943 944 ***************************************************************************/ 945 946 int main ( istring[] cl_args ) 947 { 948 // Instantiate an instance of your app class. 949 auto my_app = new MyApp; 950 951 // Pass the raw command line arguments to its main function. 952 auto ret = my_app.main(cl_args); 953 954 // Return ret to the OS. 955 return ret; 956 } 957 }