DESIGN 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. Dinit Design
  2. ============
  3. NOTE: This document is intended to provide an overview. Specifics are generally included as
  4. comments/documentation within source files.
  5. Design philosophy
  6. -----------------
  7. Software design always involves balancing different concerns. Important concerns considered during
  8. development of Dinit include:
  9. - Usability. The software should be easy to use and understand. Dangerous actions should be
  10. guarded (eg stopping a service via dinitctl requires a "--force" argument if it would also cause
  11. dependent services to stop). Common use cases should be supported by the feature set.
  12. Documentation should be complete. Error messages should be useful. Traceability/debugging is
  13. important.
  14. - Compatibility and interopability. The software should work together with other pre-existing
  15. software; it is a piece of a larger puzzle. New interfaces should only be created when they
  16. provide a tangible benefit (eg Dinit supports the same readiness protocol as S6, and works with
  17. legacy processes that fork after launching via the bgprocess service type. It logs via the
  18. standard syslog mechanism).
  19. - Scope. The feature set is restricted, but is large enough to provide utility and usability.
  20. - Generality. Arbitrary restrictions should be avoided. Size is kept small and memory use low to
  21. make Dinit usable on systems with limited resources. Code remains portable across different
  22. operating systems.
  23. - Maintainability. Code should be readable and well-documented; it should leverage the compiler's
  24. optimisations to remove abstraction penalty, wherever possible, so that the code can be more
  25. expressive, and comments should clearly explain why optimisations live "in the code" in cases
  26. where it is otherwise necessary.
  27. Of course, there are always trade-offs, but I believe that Dinit strikes a good balance with
  28. regard to the concerns listed above.
  29. Design fundamentals
  30. -------------------
  31. The design is based around an event loop: events include process termination, signal reception,
  32. and I/O coming in from control sockets (used to issue service start/stop commands etc via
  33. dinitctl). The Dasynq library provides the backend event loop functionality; it is included
  34. with the Dinit source, but has a separate web page and documentation:
  35. http://davmac.org/projects/dasynq/
  36. In Dinit, service actions are often performed by setting "propagation" flags, inserting the
  37. service in a queue, and then processing the queues. Thus a lot of service actions are performed
  38. by first calling a function on a service and then draining the propagation queues. More
  39. information can be found in comments at the beginning of the includes/service.h header.
  40. The logging subsystem is designed to log to two different sinks - typically, one is the console
  41. and one is the syslog facility, but other arrangements are possible. It uses a circular buffer
  42. for each output sink, and if the buffer becomes full it discards additional messages to avoid
  43. blocking (though it flags that this has occurred and later outputs an overflow indication to the
  44. sink). Services may acquire the console for their exclusive use, and in this case dinit's own
  45. logging output is discarded and no longer buffered (though it will of course continue to be logged
  46. to the other sink).
  47. Control protocol handling uses a circular receive buffer, but allocates storage for outgoing
  48. packets dynamically. If allocation fails an "out of memory" flag is set and the connection is
  49. closed after writing an "out of memory" information packet.
  50. Key considerations
  51. ------------------
  52. Dinit's overall function is fairly straightforward but there are some considerations that cause
  53. its design to be more complicated than it might otherwise be. The heart of the issue is that
  54. Dinit should not stall on I/O and its essential operation should never fail, which means that many
  55. code paths cannot perform memory allocation for example, and that log messages all go through a
  56. buffer.
  57. Note that blocking actions are avoided: non-blocking I/O is used where possible; Dinit must
  58. remain responsive at all times.
  59. In general operation Dinit methods should avoid throwing exceptions and be declared as 'noexcept',
  60. or otherwise be clearly documented as throwing particular exceptions. Errors should always be
  61. handled as gracefully as possible and should not prevent Dinit's continued operation. Particular
  62. care is needed for dynamic allocations: C++ style allocations (including adding elements to C++
  63. containers, or appending to strings) will raise 'std::bad_alloc' if they cannot allocate memory,
  64. and this must be handled appropriately. Functions in 'std::vector' and 'std::string' are also able
  65. to raise 'std::length_error' and it should probably be assumed that other containers can do so as
  66. well (the C++ language specification is annoyingly unspecific) - although we don't expect to see
  67. 'length_error' in practice, it should be handled anyway.
  68. Repeating for emphasis: Once it has started regular operation, dinit must not terminate due to an
  69. error condition, even if the error is an allocation failure or another exception.
  70. Services
  71. --------
  72. There are presently five "exposed" service types: internal, triggered, process, bgprocess and
  73. scripted. The latter three share the fact that they execute external processes and so naturally
  74. share some implementation. The base service class is "service_record" - this directly supports
  75. "internal" services (triggered services differ only slightly, but are still implemented via a
  76. subclass, "triggered_service").
  77. The "base_process_service" class extends service_record and serves as a base class for the
  78. remaining three service types (which all manage external processes) and provides common
  79. functionality.
  80. Various functions in the service_record / base_process_service classes are virtual, so that they
  81. can be overridden by the subclasses.
  82. All execution of child processes related to services currently goes through
  83. service_record::run_child_proc() (after forking).
  84. Services are grouped in a "service_set". This provides the essential interface between the event
  85. loop and services.
  86. A "placeholder_service" service type also exists; it is used as a placeholder for when a service
  87. needs to maintain a link to another service but that other service has not yet been loaded (this
  88. is used for example for "after" and "before" service ordering, since those don't require loading
  89. the linked service, and "consumer-of" for similar reasons). When any service is loaded, it
  90. replaces any placeholder service that already exists for it (and links from other services to the
  91. placeholder are updated to refer to the newly loaded service instead). In a similar way, a service
  92. which is unloaded but that still has persistent links from other services can be replaced with a
  93. placeholder.
  94. Source code organisation
  95. ------------------------
  96. Most function comments and general documentation can be found in header files rather than in the
  97. corresponding source files.
  98. dinit-main.cc - just a wrapper around the entry point; reports exceptions
  99. dinit.cc - main entry point, command line parsing, general initialisation, event loop processing,
  100. signal handling, system management (shutdown / reboot etc)
  101. service.cc - base service logic (see service.h)
  102. proc-service.cc, baseproc-service.cc - process-based service logic (see service.h, proc-service.h)
  103. This builds on functionality in service.cc
  104. run-child-proc.cc - contains service_record::run_child_proc(), used to run child processes.
  105. load-service.cc - service loading (see load-service.h)
  106. control.cc - control protocol handling
  107. dinit-log.cc - logging subsystem
  108. dinit-env.cc - mainly a function to read an environment file into an "environment" wrapper (as
  109. defined in dinit-env.h)
  110. tests/ - various tests
  111. igr-tests/ - integration tests
  112. The utility sources are:
  113. dinitctl.cc - the control/status utility
  114. dinitcheck.cc - dinitcheck utility
  115. dinit-monitor.cc - the dinit-monitor utility
  116. shutdown.cc - shutdown/halt/reboot utility
  117. Testing
  118. -------
  119. The unit tests work by mocking out parts of Dinit, and some system calls, in order to isolate
  120. functional units. In the src/tests/test-includes directory are some mock headers. When compiling
  121. tests, these headers are put on the include path before the regular includes directory.
  122. Note that systems calls are not mocked directly, instead, system calls are wrapped in the bp_sys
  123. namespace, as defined in the baseproc-sys.h header; this header is one of the headers mocked
  124. for tests. (This avoids problems that might arise from replacing important system calls, and in
  125. particular avoids interfering with the test harness itself).
  126. It is important when writing new code in Dinit to avoid calling system calls directly, and to
  127. instead call the wrapper in bp_sys.