SproutCMS

This is the code documentation for the SproutCMS project

source of /sprout/Helpers/Worker.php

  1. <?php
  2. /*
  3.  * Copyright (C) 2017 Karmabunny Pty Ltd.
  4.  *
  5.  * This file is a part of SproutCMS.
  6.  *
  7.  * SproutCMS is free software: you can redistribute it and/or modify it under the terms
  8.  * of the GNU General Public License as published by the Free Software Foundation, either
  9.  * version 2 of the License, or (at your option) any later version.
  10.  *
  11.  * For more information, visit <http://getsproutcms.com>.
  12.  */
  13.  
  14. namespace Sprout\Helpers;
  15.  
  16. use Exception;
  17.  
  18.  
  19. /**
  20. * Functions called by worker libraries to indicate status, etc
  21. **/
  22. class Worker
  23. {
  24. protected static $pdo;
  25. protected static $stmt_message;
  26. protected static $stmt_metric;
  27. public static $job_id;
  28. public static $starttime;
  29.  
  30.  
  31. /**
  32.   * Called just before the worker thread is started.
  33.   **/
  34. public static function start($job_id)
  35. {
  36. self::$job_id = $job_id;
  37. self::$starttime = time();
  38.  
  39. if ($_SERVER['DOCUMENT_ROOT']) {
  40. throw new Exception('Worker jobs MUST be run in CLI mode.');
  41. }
  42.  
  43. ini_set('memory_limit', '32M');
  44.  
  45. set_exception_handler(array('Sprout\\Helpers\\Worker', 'exceptionHandler'));
  46. register_shutdown_function(array('Sprout\\Helpers\\Worker', 'shutdown'));
  47.  
  48. $update_fields = array();
  49. $update_fields['date_started'] = Pdb::now();
  50. $update_fields['date_modified'] = Pdb::now();
  51. $update_fields['pid'] = getmypid();
  52. $update_fields['log'] = '';
  53. $update_fields['memuse'] = memory_get_usage(true);
  54. $update_fields['status'] = 'Running';
  55.  
  56. Pdb::update('worker_jobs', $update_fields, array('id' => $job_id));
  57.  
  58. self::$pdo = Pdb::connect('default');
  59. $pf = Pdb::prefix();
  60.  
  61. // Prepare a statement for message updating; this is lots faster than direct queries
  62. $q = "UPDATE {$pf}worker_jobs
  63. SET
  64. log = CONCAT(log, '[', :date, '] ', :message, '\n'), memuse = :memuse, date_modified = NOW()
  65. WHERE
  66. id = " . self::$job_id;
  67. self::$stmt_message = self::$pdo->prepare($q);
  68.  
  69. // There are three metrics, so prepare three statements
  70. for ($num = 1; $num <= 3; $num++) {
  71. $q = "UPDATE {$pf}worker_jobs
  72. SET
  73. metric{$num}val = :value, date_modified = NOW()
  74. WHERE
  75. id = " . self::$job_id;
  76. self::$stmt_metric[$num] = self::$pdo->prepare($q);
  77. }
  78.  
  79. self::message('Starting job');
  80.  
  81. return true;
  82. }
  83.  
  84.  
  85. /**
  86.   * Save and output log message for the currently running worker job
  87.   *
  88.   * @param string $message The message to log
  89.   * @return void
  90.   **/
  91. public static function message($message)
  92. {
  93. echo $message, "\n";
  94. flush();
  95.  
  96. if (!self::$job_id) return;
  97.  
  98. // Only do the splitting by newline if required, to save string copies and therefore RAM.
  99. if (strpos($message, "\n") === false) {
  100. self::$stmt_message->execute([
  101. ':date' => date('h:i:s a'),
  102. ':message' => $message,
  103. ':memuse' => memory_get_usage(true),
  104. ]);
  105. } else {
  106. // Multiline messages are inserted multiple times as individual lines
  107. // Only compute the date and memory usage once though
  108. $args = [
  109. ':date' => date('h:i:s a'),
  110. ':memuse' => memory_get_usage(true),
  111. ];
  112. foreach (explode("\n", $message) as $ln) {
  113. $args[':message'] = $ln;
  114. self::$stmt_message->execute($args);
  115. }
  116. }
  117. }
  118.  
  119.  
  120. /**
  121.   * Set a metric
  122.   *
  123.   * @param int $num The metric index; 1, 2, or 3
  124.   * @param int $value The metric value
  125.   * @return void
  126.   **/
  127. public static function metric($num, $value)
  128. {
  129. if (!self::$job_id) return;
  130.  
  131. self::$stmt_metric[$num]->execute([
  132. ':value' => $value,
  133. ]);
  134. }
  135.  
  136.  
  137. /**
  138.   * Report that a worker job failed.
  139.   * Terminates the script.
  140.   *
  141.   * @param string $message Optional message to be logged
  142.   * @return void Terminates script
  143.   **/
  144. public static function failure($message = '')
  145. {
  146. if ($message != '') {
  147. self::message($message);
  148. }
  149.  
  150. if (self::$job_id) {
  151. $pf = Pdb::prefix();
  152. $q = "UPDATE {$pf}worker_jobs SET
  153. status = 'Failed', date_failure = NOW(), date_modified = NOW(), pid = 0
  154. WHERE
  155. id = " . self::$job_id;
  156. self::$pdo->query($q);
  157. }
  158.  
  159. self::message('Worker terminated');
  160. exit(1);
  161. }
  162.  
  163.  
  164. /**
  165.   * Report the successful completion of the worker job.
  166.   * Terminates the script.
  167.   *
  168.   * @return void Terminates script
  169.   **/
  170. public static function success()
  171. {
  172. self::message('Done.');
  173.  
  174. $jobtime = round(time() - self::$starttime);
  175. $peakmem = File::humanSize(memory_get_peak_usage());
  176.  
  177. self::message('');
  178. self::message('Total time: ' . $jobtime . ' second' . ($jobtime == 1 ? '' : 's'));
  179. self::message('Peak memory use: ' . $peakmem);
  180.  
  181. if (self::$job_id) {
  182. $pf = Pdb::prefix();
  183. $q = "UPDATE {$pf}worker_jobs SET
  184. status = 'Success', date_success = NOW(), date_modified = NOW(), pid = 0
  185. WHERE
  186. id = " . self::$job_id;
  187. self::$pdo->query($q);
  188. }
  189.  
  190. echo "\n";
  191. exit(0);
  192. }
  193.  
  194.  
  195. /**
  196.   * Exception and error handling for worker jobs
  197.   **/
  198. public static function exceptionHandler($exception, $message = NULL, $file = NULL, $line = NULL)
  199. {
  200. self::message('EXCEPTION ' . get_class($exception));
  201. self::message('Message: ' . $exception->getMessage());
  202. self::message('File: ' . $exception->getFile());
  203. self::message('Line: ' . $exception->getLine());
  204. self::message('');
  205. self::message($exception->getTraceAsString());
  206. self::failure();
  207. }
  208.  
  209. /**
  210.   * Shutdown function, for catching fatal errors
  211.   **/
  212. public static function shutdown()
  213. {
  214. $error = error_get_last();
  215. if ($error['type'] == 1) {
  216. self::failure('FATAL ERROR: ' . $error['message']);
  217. }
  218. }
  219.  
  220. }
  221.  
  222.  
  223.