PageRenderTime 45ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/classes/shutdown_manager.php

https://github.com/mackensen/moodle
PHP | 235 lines | 122 code | 22 blank | 91 comment | 26 complexity | 59bd8d44e678a18232273dd4d7fa83cc MD5 | raw file
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Shutdown management class.
  18. *
  19. * @package core
  20. * @copyright 2013 Petr Skoda {@link http://skodak.org}
  21. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22. */
  23. defined('MOODLE_INTERNAL') || die();
  24. /**
  25. * Shutdown management class.
  26. *
  27. * @package core
  28. * @copyright 2013 Petr Skoda {@link http://skodak.org}
  29. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  30. */
  31. class core_shutdown_manager {
  32. /** @var array list of custom callbacks */
  33. protected static $callbacks = [];
  34. /** @var array list of custom signal callbacks */
  35. protected static $signalcallbacks = [];
  36. /** @var bool is this manager already registered? */
  37. protected static $registered = false;
  38. /**
  39. * Register self as main shutdown handler.
  40. *
  41. * @private to be called from lib/setup.php only!
  42. */
  43. public static function initialize() {
  44. if (self::$registered) {
  45. debugging('Shutdown manager is already initialised!');
  46. }
  47. self::$registered = true;
  48. register_shutdown_function(array('core_shutdown_manager', 'shutdown_handler'));
  49. // Signal handlers should only be used when dealing with a CLI script.
  50. // In the case of PHP called in a web server the server is the owning process and should handle the signal chain
  51. // properly itself.
  52. // The 'pcntl' extension is optional and not available on Windows.
  53. if (CLI_SCRIPT && extension_loaded('pcntl') && function_exists('pcntl_async_signals')) {
  54. // We capture and handle SIGINT (Ctrl+C) and SIGTERM (termination requested).
  55. pcntl_async_signals(true);
  56. pcntl_signal(SIGINT, ['core_shutdown_manager', 'signal_handler']);
  57. pcntl_signal(SIGTERM, ['core_shutdown_manager', 'signal_handler']);
  58. }
  59. }
  60. /**
  61. * Signal handler for SIGINT, and SIGTERM.
  62. *
  63. * @param int $signo The signal being handled
  64. */
  65. public static function signal_handler(int $signo) {
  66. // Note: There is no need to manually call the shutdown handler.
  67. // The fact that we are calling exit() in this script means that the standard shutdown handling is performed
  68. // anyway.
  69. switch ($signo) {
  70. case SIGTERM:
  71. // Replicate native behaviour.
  72. echo "Terminated: {$signo}\n";
  73. // The standard exit code for SIGTERM is 143.
  74. $exitcode = 143;
  75. break;
  76. case SIGINT:
  77. // Replicate native behaviour.
  78. echo "\n";
  79. // The standard exit code for SIGINT (Ctrl+C) is 130.
  80. $exitcode = 130;
  81. break;
  82. default:
  83. // The signal handler was called with a signal it was not expecting.
  84. // We should exit and complain.
  85. echo "Warning: \core_shutdown_manager::signal_handler() was called with an unexpected signal ({$signo}).\n";
  86. $exitcode = 1;
  87. }
  88. // Normally we should exit unless a callback tells us to wait.
  89. $shouldexit = true;
  90. foreach (self::$signalcallbacks as $data) {
  91. list($callback, $params) = $data;
  92. try {
  93. array_unshift($params, $signo);
  94. $shouldexit = call_user_func_array($callback, $params) && $shouldexit;
  95. } catch (Throwable $e) {
  96. // phpcs:ignore
  97. error_log('Exception ignored in signal function ' . get_callable_name($callback) . ': ' . $e->getMessage());
  98. }
  99. }
  100. if ($shouldexit) {
  101. exit ($exitcode);
  102. }
  103. }
  104. /**
  105. * Register custom signal handler function.
  106. *
  107. * If a handler returns false the signal will be ignored.
  108. *
  109. * @param callable $callback
  110. * @param array $params
  111. * @return void
  112. */
  113. public static function register_signal_handler($callback, array $params = null): void {
  114. if (!is_callable($callback)) {
  115. error_log('Invalid custom signal function detected ' . var_export($callback, true)); // phpcs:ignore
  116. }
  117. self::$signalcallbacks[] = [$callback, $params ?? []];
  118. }
  119. /**
  120. * Register custom shutdown function.
  121. *
  122. * @param callable $callback
  123. * @param array $params
  124. * @return void
  125. */
  126. public static function register_function($callback, array $params = null): void {
  127. if (!is_callable($callback)) {
  128. error_log('Invalid custom shutdown function detected '.var_export($callback, true)); // phpcs:ignore
  129. }
  130. self::$callbacks[] = [$callback, $params ? array_values($params) : []];
  131. }
  132. /**
  133. * @private - do NOT call directly.
  134. */
  135. public static function shutdown_handler() {
  136. global $DB;
  137. // Custom stuff first.
  138. foreach (self::$callbacks as $data) {
  139. list($callback, $params) = $data;
  140. try {
  141. call_user_func_array($callback, $params);
  142. } catch (Throwable $e) {
  143. // phpcs:ignore
  144. error_log('Exception ignored in shutdown function '.get_callable_name($callback).': '.$e->getMessage());
  145. }
  146. }
  147. // Handle DB transactions, session need to be written afterwards
  148. // in order to maintain consistency in all session handlers.
  149. if ($DB->is_transaction_started()) {
  150. if (!defined('PHPUNIT_TEST') or !PHPUNIT_TEST) {
  151. // This should not happen, it usually indicates wrong catching of exceptions,
  152. // because all transactions should be finished manually or in default exception handler.
  153. $backtrace = $DB->get_transaction_start_backtrace();
  154. error_log('Potential coding error - active database transaction detected during request shutdown:'."\n".format_backtrace($backtrace, true));
  155. }
  156. $DB->force_transaction_rollback();
  157. }
  158. // Close sessions - do it here to make it consistent for all session handlers.
  159. \core\session\manager::write_close();
  160. // Other cleanup.
  161. self::request_shutdown();
  162. // Stop profiling.
  163. if (function_exists('profiling_is_running')) {
  164. if (profiling_is_running()) {
  165. profiling_stop();
  166. }
  167. }
  168. // NOTE: do not dispose $DB and MUC here, they might be used from legacy shutdown functions.
  169. }
  170. /**
  171. * Standard shutdown sequence.
  172. */
  173. protected static function request_shutdown() {
  174. global $CFG;
  175. // Help apache server if possible.
  176. $apachereleasemem = false;
  177. if (function_exists('apache_child_terminate') && function_exists('memory_get_usage') && ini_get_bool('child_terminate')) {
  178. $limit = (empty($CFG->apachemaxmem) ? 64*1024*1024 : $CFG->apachemaxmem); // 64MB default.
  179. if (memory_get_usage() > get_real_size($limit)) {
  180. $apachereleasemem = $limit;
  181. @apache_child_terminate();
  182. }
  183. }
  184. // Deal with perf logging.
  185. if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
  186. if ($apachereleasemem) {
  187. error_log('Mem usage over '.$apachereleasemem.': marking Apache child for reaping.');
  188. }
  189. if (defined('MDL_PERFTOLOG')) {
  190. $perf = get_performance_info();
  191. error_log("PERF: " . $perf['txt']);
  192. }
  193. if (defined('MDL_PERFINC')) {
  194. $inc = get_included_files();
  195. $ts = 0;
  196. foreach ($inc as $f) {
  197. if (preg_match(':^/:', $f)) {
  198. $fs = filesize($f);
  199. $ts += $fs;
  200. $hfs = display_size($fs);
  201. error_log(substr($f, strlen($CFG->dirroot)) . " size: $fs ($hfs)", null, null, 0);
  202. } else {
  203. error_log($f , null, null, 0);
  204. }
  205. }
  206. if ($ts > 0 ) {
  207. $hts = display_size($ts);
  208. error_log("Total size of files included: $ts ($hts)");
  209. }
  210. }
  211. }
  212. }
  213. }