PageRenderTime 45ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/app/code/core/Mage/Cron/Model/Observer.php

https://github.com/rgranadino/magento-mirror
PHP | 266 lines | 164 code | 31 blank | 71 comment | 25 complexity | 00629c9987021cc41179ef8e2e2aee72 MD5 | raw file
  1. <?php
  2. /**
  3. * Magento
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@magentocommerce.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade Magento to newer
  18. * versions in the future. If you wish to customize Magento for your
  19. * needs please refer to http://www.magentocommerce.com for more information.
  20. *
  21. * @category Mage
  22. * @package Mage_Cron
  23. * @copyright Copyright (c) 2011 Magento Inc. (http://www.magentocommerce.com)
  24. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  25. */
  26. /**
  27. * Crontab observer
  28. *
  29. * @category Mage
  30. * @package Mage_Cron
  31. * @author Magento Core Team <core@magentocommerce.com>
  32. */
  33. class Mage_Cron_Model_Observer
  34. {
  35. const CACHE_KEY_LAST_SCHEDULE_GENERATE_AT = 'cron_last_schedule_generate_at';
  36. const CACHE_KEY_LAST_HISTORY_CLEANUP_AT = 'cron_last_history_cleanup_at';
  37. const XML_PATH_SCHEDULE_GENERATE_EVERY = 'system/cron/schedule_generate_every';
  38. const XML_PATH_SCHEDULE_AHEAD_FOR = 'system/cron/schedule_ahead_for';
  39. const XML_PATH_SCHEDULE_LIFETIME = 'system/cron/schedule_lifetime';
  40. const XML_PATH_HISTORY_CLEANUP_EVERY = 'system/cron/history_cleanup_every';
  41. const XML_PATH_HISTORY_SUCCESS = 'system/cron/history_success_lifetime';
  42. const XML_PATH_HISTORY_FAILURE = 'system/cron/history_failure_lifetime';
  43. const REGEX_RUN_MODEL = '#^([a-z0-9_]+/[a-z0-9_]+)::([a-z0-9_]+)$#i';
  44. protected $_pendingSchedules;
  45. /**
  46. * Process cron queue
  47. * Geterate tasks schedule
  48. * Cleanup tasks schedule
  49. *
  50. * @param Varien_Event_Observer $observer
  51. */
  52. public function dispatch($observer)
  53. {
  54. $schedules = $this->getPendingSchedules();
  55. $scheduleLifetime = Mage::getStoreConfig(self::XML_PATH_SCHEDULE_LIFETIME) * 60;
  56. $now = time();
  57. $jobsRoot = Mage::getConfig()->getNode('crontab/jobs');
  58. $defaultJobsRoot = Mage::getConfig()->getNode('default/crontab/jobs');
  59. foreach ($schedules->getIterator() as $schedule) {
  60. $jobConfig = $jobsRoot->{$schedule->getJobCode()};
  61. if (!$jobConfig || !$jobConfig->run) {
  62. $jobConfig = $defaultJobsRoot->{$schedule->getJobCode()};
  63. if (!$jobConfig || !$jobConfig->run) {
  64. continue;
  65. }
  66. }
  67. $runConfig = $jobConfig->run;
  68. $time = strtotime($schedule->getScheduledAt());
  69. if ($time > $now) {
  70. continue;
  71. }
  72. try {
  73. $errorStatus = Mage_Cron_Model_Schedule::STATUS_ERROR;
  74. $errorMessage = Mage::helper('cron')->__('Unknown error.');
  75. if ($time < $now - $scheduleLifetime) {
  76. $errorStatus = Mage_Cron_Model_Schedule::STATUS_MISSED;
  77. Mage::throwException(Mage::helper('cron')->__('Too late for the schedule.'));
  78. }
  79. if ($runConfig->model) {
  80. if (!preg_match(self::REGEX_RUN_MODEL, (string)$runConfig->model, $run)) {
  81. Mage::throwException(Mage::helper('cron')->__('Invalid model/method definition, expecting "model/class::method".'));
  82. }
  83. if (!($model = Mage::getModel($run[1])) || !method_exists($model, $run[2])) {
  84. Mage::throwException(Mage::helper('cron')->__('Invalid callback: %s::%s does not exist', $run[1], $run[2]));
  85. }
  86. $callback = array($model, $run[2]);
  87. $arguments = array($schedule);
  88. }
  89. if (empty($callback)) {
  90. Mage::throwException(Mage::helper('cron')->__('No callbacks found'));
  91. }
  92. if (!$schedule->tryLockJob()) {
  93. // another cron started this job intermittently, so skip it
  94. continue;
  95. }
  96. /**
  97. though running status is set in tryLockJob we must set it here because the object
  98. was loaded with a pending status and will set it back to pending if we don't set it here
  99. */
  100. $schedule
  101. ->setStatus(Mage_Cron_Model_Schedule::STATUS_RUNNING)
  102. ->setExecutedAt(strftime('%Y-%m-%d %H:%M:%S', time()))
  103. ->save();
  104. call_user_func_array($callback, $arguments);
  105. $schedule
  106. ->setStatus(Mage_Cron_Model_Schedule::STATUS_SUCCESS)
  107. ->setFinishedAt(strftime('%Y-%m-%d %H:%M:%S', time()));
  108. } catch (Exception $e) {
  109. $schedule->setStatus($errorStatus)
  110. ->setMessages($e->__toString());
  111. }
  112. $schedule->save();
  113. }
  114. $this->generate();
  115. $this->cleanup();
  116. }
  117. public function getPendingSchedules()
  118. {
  119. if (!$this->_pendingSchedules) {
  120. $this->_pendingSchedules = Mage::getModel('cron/schedule')->getCollection()
  121. ->addFieldToFilter('status', Mage_Cron_Model_Schedule::STATUS_PENDING)
  122. ->load();
  123. }
  124. return $this->_pendingSchedules;
  125. }
  126. /**
  127. * Generate cron schedule
  128. *
  129. * @return Mage_Cron_Model_Observer
  130. */
  131. public function generate()
  132. {
  133. /**
  134. * check if schedule generation is needed
  135. */
  136. $lastRun = Mage::app()->loadCache(self::CACHE_KEY_LAST_SCHEDULE_GENERATE_AT);
  137. if ($lastRun > time() - Mage::getStoreConfig(self::XML_PATH_SCHEDULE_GENERATE_EVERY)*60) {
  138. return $this;
  139. }
  140. $schedules = $this->getPendingSchedules();
  141. $exists = array();
  142. foreach ($schedules->getIterator() as $schedule) {
  143. $exists[$schedule->getJobCode().'/'.$schedule->getScheduledAt()] = 1;
  144. }
  145. /**
  146. * generate global crontab jobs
  147. */
  148. $config = Mage::getConfig()->getNode('crontab/jobs');
  149. if ($config instanceof Mage_Core_Model_Config_Element) {
  150. $this->_generateJobs($config->children(), $exists);
  151. }
  152. /**
  153. * generate configurable crontab jobs
  154. */
  155. $config = Mage::getConfig()->getNode('default/crontab/jobs');
  156. if ($config instanceof Mage_Core_Model_Config_Element) {
  157. $this->_generateJobs($config->children(), $exists);
  158. }
  159. /**
  160. * save time schedules generation was ran with no expiration
  161. */
  162. Mage::app()->saveCache(time(), self::CACHE_KEY_LAST_SCHEDULE_GENERATE_AT, array('crontab'), null);
  163. return $this;
  164. }
  165. /**
  166. * Generate jobs for config information
  167. *
  168. * @param $jobs
  169. * @param array $exists
  170. * @return Mage_Cron_Model_Observer
  171. */
  172. protected function _generateJobs($jobs, $exists)
  173. {
  174. $scheduleAheadFor = Mage::getStoreConfig(self::XML_PATH_SCHEDULE_AHEAD_FOR)*60;
  175. $schedule = Mage::getModel('cron/schedule');
  176. foreach ($jobs as $jobCode => $jobConfig) {
  177. $cronExpr = null;
  178. if ($jobConfig->schedule->config_path) {
  179. $cronExpr = Mage::getStoreConfig((string)$jobConfig->schedule->config_path);
  180. }
  181. if (empty($cronExpr) && $jobConfig->schedule->cron_expr) {
  182. $cronExpr = (string)$jobConfig->schedule->cron_expr;
  183. }
  184. if (!$cronExpr) {
  185. continue;
  186. }
  187. $now = time();
  188. $timeAhead = $now + $scheduleAheadFor;
  189. $schedule->setJobCode($jobCode)
  190. ->setCronExpr($cronExpr)
  191. ->setStatus(Mage_Cron_Model_Schedule::STATUS_PENDING);
  192. for ($time = $now; $time < $timeAhead; $time += 60) {
  193. $ts = strftime('%Y-%m-%d %H:%M:00', $time);
  194. if (!empty($exists[$jobCode.'/'.$ts])) {
  195. // already scheduled
  196. continue;
  197. }
  198. if (!$schedule->trySchedule($time)) {
  199. // time does not match cron expression
  200. continue;
  201. }
  202. $schedule->unsScheduleId()->save();
  203. }
  204. }
  205. return $this;
  206. }
  207. public function cleanup()
  208. {
  209. // check if history cleanup is needed
  210. $lastCleanup = Mage::app()->loadCache(self::CACHE_KEY_LAST_HISTORY_CLEANUP_AT);
  211. if ($lastCleanup > time() - Mage::getStoreConfig(self::XML_PATH_HISTORY_CLEANUP_EVERY)*60) {
  212. return $this;
  213. }
  214. $history = Mage::getModel('cron/schedule')->getCollection()
  215. ->addFieldToFilter('status', array('in'=>array(
  216. Mage_Cron_Model_Schedule::STATUS_SUCCESS,
  217. Mage_Cron_Model_Schedule::STATUS_MISSED,
  218. Mage_Cron_Model_Schedule::STATUS_ERROR,
  219. )))->load();
  220. $historyLifetimes = array(
  221. Mage_Cron_Model_Schedule::STATUS_SUCCESS => Mage::getStoreConfig(self::XML_PATH_HISTORY_SUCCESS)*60,
  222. Mage_Cron_Model_Schedule::STATUS_MISSED => Mage::getStoreConfig(self::XML_PATH_HISTORY_FAILURE)*60,
  223. Mage_Cron_Model_Schedule::STATUS_ERROR => Mage::getStoreConfig(self::XML_PATH_HISTORY_FAILURE)*60,
  224. );
  225. $now = time();
  226. foreach ($history->getIterator() as $record) {
  227. if (strtotime($record->getExecutedAt()) < $now-$historyLifetimes[$record->getStatus()]) {
  228. $record->delete();
  229. }
  230. }
  231. // save time history cleanup was ran with no expiration
  232. Mage::app()->saveCache(time(), self::CACHE_KEY_LAST_HISTORY_CLEANUP_AT, array('crontab'), null);
  233. return $this;
  234. }
  235. }