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 }