BxDolBackgroundJobs.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. <?php defined('BX_DOL') or die('hack attempt');
  2. /**
  3. * Copyright (c) UNA, Inc - https://una.io
  4. * MIT License - https://opensource.org/licenses/MIT
  5. *
  6. * @defgroup UnaCore UNA Core
  7. * @{
  8. */
  9. define('BX_DOL_BG_JOBS_STATUS_PENDING', 'pending');
  10. define('BX_DOL_BG_JOBS_STATUS_PROCESSING', 'processing');
  11. define('BX_DOL_BG_JOBS_STATUS_ERROR', 'error');
  12. class BxDolBackgroundJobs extends BxDolFactory implements iBxDolSingleton
  13. {
  14. protected $_oQuery;
  15. protected $_sObjectLog;
  16. protected $_sParamWorkersLimit;
  17. protected $_iAttemptsMax;
  18. protected function __construct()
  19. {
  20. if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
  21. trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
  22. parent::__construct();
  23. $this->_sObjectLog = 'sys_background_jobs';
  24. $this->_sParamWorkersLimit = 'sys_bg_jobs_workers_limit';
  25. $this->_iAttemptsMax = 3;
  26. $this->_oQuery = new BxDolBackgroundJobsQuery();
  27. }
  28. /**
  29. * Get singleton instance of the class
  30. */
  31. public static function getInstance()
  32. {
  33. if(!isset($GLOBALS['bxDolClasses'][__CLASS__]))
  34. $GLOBALS['bxDolClasses'][__CLASS__] = new BxDolBackgroundJobs();
  35. return $GLOBALS['bxDolClasses'][__CLASS__];
  36. }
  37. public static function pruning()
  38. {
  39. return BxDolBackgroundJobs::getInstance()->prune(86400 * (int)getParam('sys_bg_jobs_cleanup_timeout'));
  40. }
  41. /**
  42. * Prevent cloning the instance
  43. */
  44. public function __clone()
  45. {
  46. if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
  47. trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
  48. }
  49. public function add($sName, $mixedServiceCall, $iPriority = 0)
  50. {
  51. if(is_array($mixedServiceCall))
  52. $mixedServiceCall = call_user_func_array(['BxDolService', 'getSerializedService'], $mixedServiceCall);
  53. if(!$this->_oQuery->addJob($sName, $mixedServiceCall, $iPriority))
  54. return false;
  55. bx_log($this->_sObjectLog, "Added: " . $sName, BX_LOG_INFO);
  56. return true;
  57. }
  58. public function delete($sName)
  59. {
  60. if(!$this->_oQuery->deleteJob($sName))
  61. return false;
  62. bx_log($this->_sObjectLog, "Deleted: " . $sName, BX_LOG_INFO);
  63. return true;
  64. }
  65. public function exists($sName)
  66. {
  67. $aJob = $this->_oQuery->getJobs([
  68. 'sample' => 'name',
  69. 'name' => $sName
  70. ]);
  71. return !empty($aJob) && is_array($aJob);
  72. }
  73. public function process($mixedJob)
  74. {
  75. if(!empty($mixedJob) && !is_array($mixedJob))
  76. $mixedJob = $this->_oQuery->getJobs(['sample' => 'name', 'name' => $mixedJob]);
  77. if(empty($mixedJob) || !is_array($mixedJob))
  78. return false;
  79. if(empty($mixedJob['service_call']) || !BxDolService::isSerializedService($mixedJob['service_call']))
  80. return false;
  81. if($mixedJob['status'] != BX_DOL_BG_JOBS_STATUS_PROCESSING)
  82. $this->_oQuery->updateJob($mixedJob['name'], [
  83. 'status' => BX_DOL_BG_JOBS_STATUS_PROCESSING
  84. ]);
  85. $fStart = microtime(true);
  86. $sError = '';
  87. $mixedResult = '';
  88. try {
  89. $mixedResult = BxDolService::callSerialized($mixedJob['service_call']);
  90. }
  91. catch (Throwable $e) {
  92. $sError = $e->getMessage();
  93. }
  94. $fEnd = microtime(true);
  95. if ($sError) {
  96. $iAvailableAt = 0;
  97. $sStatus = BX_DOL_BG_JOBS_STATUS_ERROR;
  98. $iAttempts = (int)$mixedJob['attempts'] - 1;
  99. if($iAttempts) {
  100. $iAvailableAt = time() + 60 * pow(3, ($this->_iAttemptsMax - $iAttempts));
  101. $sStatus = BX_DOL_BG_JOBS_STATUS_PENDING;
  102. }
  103. $this->_oQuery->updateJob($mixedJob['name'], [
  104. 'claim_token' => '',
  105. 'reserved_at' => 0,
  106. 'available_at' => $iAvailableAt,
  107. 'attempts' => $iAttempts,
  108. 'error' => $sError . ' / result: [' . $mixedResult . ']',
  109. 'status' => $sStatus
  110. ]);
  111. }
  112. else
  113. $this->_oQuery->deleteJob($mixedJob['name']);
  114. bx_log($this->_sObjectLog, "Processed: " . $mixedJob['name'] . " / timing: " . ($fEnd - $fStart) . " / memory: " . memory_get_usage(), BX_LOG_INFO);
  115. return true;
  116. }
  117. public function processAll($iLimit = 0)
  118. {
  119. if($this->_isWorkersLimitReached())
  120. return false;
  121. $sClaimToken = bin2hex(random_bytes(16));
  122. $this->_oQuery->claimJobs($sClaimToken, $iLimit ?: (getParam('sys_bg_jobs_process_per_run') ?: 5));
  123. $aJobs = $this->_oQuery->getClaimedJobs($sClaimToken);
  124. if(!$aJobs || !is_array($aJobs))
  125. return true;
  126. $this->_oQuery->updateJobByIds(array_keys($aJobs), [
  127. 'reserved_at' => time()
  128. ]);
  129. $iProcessed = 0;
  130. foreach($aJobs as $aJob)
  131. if($this->process($aJob))
  132. $iProcessed += 1;
  133. bx_log($this->_sObjectLog, "Processed: all (" . $iProcessed . " from " . count($aJobs) . ")", BX_LOG_INFO);
  134. return true;
  135. }
  136. public function prune($iTimeout, $sStatus = BX_DOL_BG_JOBS_STATUS_ERROR)
  137. {
  138. return (int)$this->_oQuery->deleteJobs([
  139. 'sample' => 'outdated',
  140. 'timeout' => $iTimeout,
  141. 'status' => $sStatus
  142. ]);
  143. }
  144. protected function _isWorkersLimitReached()
  145. {
  146. $aJobs = $this->_oQuery->getJobs(['sample' => 'running']);
  147. $iWorkers = $aJobs ? count($aJobs) : 0;
  148. if ($iWorkers >= (int)getParam($this->_sParamWorkersLimit))
  149. return true;
  150. return false;
  151. }
  152. }
  153. /** @} */