hosting_queued.drush.inc

Dispatcher daemon

This file is the heart of the dispatcher drush command. It implements most of the backend functionality.

Functions

Namesort descending Description
drush_hosting_queued Drush command to execute hosting tasks.
hosting_queued_db_close_all Close all database fiel descriptors, as exec() doesn't close them
hosting_queued_drush_command Implements hook_drush_command().
hosting_queued_drush_find_drush Get the proper way to call drush again.
hosting_queued_restart Restart the dispatcher to work around memory leaks
hosting_queued_stop Handle interruption signals gracefully

File

queued/hosting_queued.drush.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * Dispatcher daemon
  5. *
  6. * This file is the heart of the dispatcher drush command. It
  7. * implements most of the backend functionality.
  8. */
  9. // This is necessary for signal handling to work
  10. declare (ticks = 1);
  11. /**
  12. * Implements hook_drush_command().
  13. */
  14. function hosting_queued_drush_command() {
  15. $items = array();
  16. $items['hosting-queued'] = array(
  17. 'description' => 'Runs the tasks queue',
  18. 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL,
  19. 'drupal dependencies' => array(
  20. 'hosting_queued',
  21. ),
  22. );
  23. return $items;
  24. }
  25. /**
  26. * Drush command to execute hosting tasks.
  27. */
  28. function drush_hosting_queued() {
  29. if (function_exists('pcntl_signal')) {
  30. // reload the server on SIGHUP
  31. pcntl_signal(SIGHUP, 'hosting_queued_restart');
  32. pcntl_signal(SIGINT, 'hosting_queued_stop');
  33. pcntl_signal(SIGTERM, 'hosting_queued_stop');
  34. }
  35. // Set a nice high time limit, if we can:
  36. if (function_exists('set_time_limit')) {
  37. @set_time_limit(0);
  38. }
  39. // in some environments (e.g. in "productin") ENV is not actually
  40. // set (!) so try to guess from $_SERVER
  41. if (strpos(ini_get('variables_order'), 'E') === FALSE) {
  42. if (strpos(ini_get('variables_order'), 'S') === FALSE) {
  43. drush_log(dt('Neither $_ENV nor $_SERVER are available to set up proper environment inheritance; ensure E and/or S is set in your php.ini\'s "variables_order" setting.'), 'warning');
  44. }
  45. else {
  46. $_ENV = $_SERVER;
  47. }
  48. }
  49. $end_time = variable_get('hosting_queued_process_lifetime', 3600) + REQUEST_TIME;
  50. // Record the fact that we're running, so we can give some feedback in the
  51. // frontend.
  52. variable_set('hosting_queued_process_started', REQUEST_TIME);
  53. watchdog('hosting_queued', 'Started Hosting queue daemon, waiting for new tasks');
  54. while (TRUE) {
  55. try {
  56. // Should we terminate.
  57. if (REQUEST_TIME > $end_time) {
  58. // Restart the daemon to recycle leaked memory
  59. hosting_queued_restart();
  60. }
  61. }
  62. catch (Exception $e) {
  63. // Check if there was a database error, so we can recover gracefully if needed.
  64. // See: https://drupal.org/node/1992254.
  65. drush_log('Caught database error.', 'warning');
  66. $db = drush_convert_db_from_db_url($GLOBALS['db_url']);
  67. drush_log('Waiting for database to become available.', 'warning');
  68. do {
  69. // Give the database time to come back.
  70. sleep(1);
  71. // Check connection
  72. $connect = @mysqli_connect($db['host'], $db['username'], $db['password']);
  73. drush_log('.', 'warning');
  74. } while (!$connect);
  75. // Close connection
  76. mysqli_close($connect);
  77. drush_log('Restarting queue daemon.', 'warning');
  78. hosting_queued_restart();
  79. }
  80. // Get some tasks to run
  81. if ($tasks = @_hosting_get_new_tasks()) {
  82. if (lock_acquire('hosting_queue_tasks_running', HOSTING_QUEUE_LOCK_TIMEOUT)) {
  83. drush_log('Acquired lock on task queue.');
  84. foreach ($tasks as $task) {
  85. // We sleep for a second just in case others want to run the task first.
  86. // This guards against other processes that want to add a hosting task
  87. // with arguments and run it immediately, they should be able to do this
  88. // without us getting in there first.
  89. // This is a workaround for http://drupal.org/node/1003536
  90. drush_log(dt('Found task to execute. Pausing before execution.'));
  91. sleep(1);
  92. watchdog('hosting_queued', 'Running task @nid.', array('@nid' => $task->nid));
  93. // Execute the task in the backend
  94. drush_invoke_process('@self', 'hosting-task', array($task->nid), array('strict' => FALSE), array('interactive' => TRUE));
  95. drush_log(dt('Finished executing task.'));
  96. // Delay for a configurable amount of time.
  97. $delay = variable_get('hosting_queued_post_task_delay', 0);
  98. if (!empty($delay)) {
  99. drush_log(dt('Going to sleep for @count seconds after completing task.', array('@count' => $delay)));
  100. sleep($delay);
  101. }
  102. // We're done with this task, this unset might help reduce memory usage.
  103. unset($task);
  104. // Should we terminate.
  105. if (REQUEST_TIME > $end_time) {
  106. // Restart the daemon to recycle leaked memory
  107. hosting_queued_restart();
  108. }
  109. }
  110. drush_log('Releasing lock on task queue.');
  111. lock_release('hosting_queue_tasks_running');
  112. }
  113. }
  114. // wait here only if we didn't process tasks
  115. // this is to avoid an infinite loop if there are no tasks in the queue
  116. if (!$tasks) {
  117. sleep(1);
  118. }
  119. if (drush_get_option('onetime')) {
  120. drush_log(dt("exiting after processing all tasks, as requested by --onetime"));
  121. break;
  122. }
  123. unset($tasks);
  124. }
  125. }
  126. /**
  127. * Handle interruption signals gracefully
  128. *
  129. * We do not want to interrupt children tasks, so we wait for them
  130. * before stopping.
  131. */
  132. function hosting_queued_stop($signal) {
  133. watchdog('hosting_queued', 'Received signal @signal, waiting for children to die.', array('@signal' => $signal));
  134. $status = NULL;
  135. pcntl_wait($status);
  136. drush_log('Releasing lock on task queue.');
  137. lock_release('hosting_queue_tasks_running');
  138. watchdog('hosting_queued', 'Stopped daemon');
  139. exit($status);
  140. }
  141. /**
  142. * Restart the dispatcher to work around memory leaks
  143. */
  144. function hosting_queued_restart($signal = NULL) {
  145. // If we received a singal, process it.
  146. if (!is_null($signal)) {
  147. watchdog('hosting_queued', 'Received signal @signal, waiting for children to die.', array('@signal' => $signal));
  148. $status = NULL;
  149. pcntl_wait($status);
  150. }
  151. // We need the PCNTL extension to be able to auto restart.
  152. if (function_exists('pcntl_exec')) {
  153. $args = $_ENV['argv'];
  154. $drush = array_shift($args);
  155. // Strip sub-array to avoid warning "Array to string conversion"
  156. unset($_ENV['argv']);
  157. watchdog('hosting_queued', 'Restarting queue daemon with @drush @args.', array('@drush' => $drush, '@args' => implode(" ", $args)));
  158. drush_log('Releasing lock on task queue.');
  159. lock_release('hosting_queue_tasks_running');
  160. // close all open database file descriptors
  161. hosting_queued_db_close_all();
  162. pcntl_exec($drush, $args, $_ENV);
  163. watchdog('hosting_queued', 'Could not restart the queue daemon, aborting.', array(), WATCHDOG_ERROR);
  164. /* NOTREACHED */
  165. }
  166. else {
  167. watchdog('hosting_queued', 'PCNTL not installed, unable to auto-restart.', array(), WATCHDOG_WARNING);
  168. }
  169. drush_log('Releasing lock on task queue.');
  170. lock_release('hosting_queue_tasks_running');
  171. // Explicit exit in case we're handling a signal
  172. exit(1);
  173. }
  174. /**
  175. * Close all database fiel descriptors, as exec() doesn't close them
  176. *
  177. * This only supports MySQLi right now, because there's no db_close() in
  178. * Drupal.
  179. */
  180. function hosting_queued_db_close_all() {
  181. global $db_url, $active_db;
  182. if (is_array($db_url)) {
  183. foreach ($db_url as $name => $url) {
  184. db_set_active($name);
  185. mysqli_close($active_db);
  186. }
  187. }
  188. else {
  189. mysqli_close($active_db);
  190. }
  191. }
  192. /**
  193. * Get the proper way to call drush again.
  194. *
  195. * Note that unlike drush_find_drush() we return an array of parts, and we trim
  196. * the 'php' option of extra single quotes.
  197. *
  198. * @see drush_find_drush()
  199. */
  200. function hosting_queued_drush_find_drush() {
  201. $php = drush_get_option('php');
  202. if (isset($php)) {
  203. $php = trim($php, "'");
  204. $drush = array($php, realpath($_SERVER['argv'][0]), "--php='$php'");
  205. }
  206. else {
  207. $drush = array(realpath($_SERVER['argv']['0']));
  208. }
  209. return $drush;
  210. }