]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/SugarQueue/SugarCronJobs.php
Release 6.5.0
[Github/sugarcrm.git] / include / SugarQueue / SugarCronJobs.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM Community Edition is a customer relationship management program developed by
5  * SugarCRM, Inc. Copyright (C) 2004-2012 SugarCRM Inc.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38 require_once 'include/SugarQueue/SugarJobQueue.php';
39 require_once 'modules/Schedulers/Scheduler.php';
40
41 /**
42  * CRON driver for job queue
43  * @api
44  */
45 class SugarCronJobs
46 {
47     /**
48      * Max number of jobs per cron run
49      * @var int
50      */
51     public $max_jobs = 10;
52     /**
53      * Max time per cron run
54      * @var int
55      */
56     public $max_runtime = 60;
57     /**
58      * Min time between cron runs
59      * @var int
60      */
61     public $min_interval = 30;
62
63     /**
64      * Lock file to ensure the jobs aren't run too fast
65      * @var string
66      */
67     public $lockfile;
68
69     /**
70      * Currently running job
71      * @var SchedulersJob
72      */
73     public $job;
74
75     /**
76      * Is current queue run OK?
77      * @var bool
78      */
79     public $runOk = true;
80
81     /**
82      * Should the driver print reports to stdout?
83      * @var bool
84      */
85     public $verbose = false;
86
87     /**
88      * This allows to disable schedulers cycle, e.g. for testing
89      * @var bool
90      */
91     public $disable_schedulers = false;
92
93     public function __construct()
94     {
95         $this->queue = new SugarJobQueue();
96         $this->lockfile = sugar_cached("modules/Schedulers/lastrun");
97         if(!empty($GLOBALS['sugar_config']['cron']['max_jobs'])) {
98             $this->max_jobs = $GLOBALS['sugar_config']['cron']['max_cron_jobs'];
99         }
100         if(!empty($GLOBALS['sugar_config']['cron']['max_cron_runtime'])) {
101             $this->max_jobs = $GLOBALS['sugar_config']['cron']['max_cron_runtime'];
102         }
103         if(isset($GLOBALS['sugar_config']['cron']['min_cron_interval'])) {
104             $this->min_interval = $GLOBALS['sugar_config']['cron']['min_cron_interval'];
105         }
106     }
107
108     /**
109      * Remember last time it was run
110      */
111     protected function markLastRun()
112     {
113         if(!file_put_contents($this->lockfile, time())) {
114             $GLOBALS['log']->fatal('Scheduler cannot write PID file.  Please check permissions on '.$this->lockfile);
115         }
116     }
117
118     /**
119      * Check if we aren't running jobs too frequently
120      * @return bool OK to run?
121      */
122     public function throttle()
123     {
124         if($this->min_interval == 0) {
125             return true;
126         }
127         create_cache_directory($this->lockfile);
128         if(!file_exists($this->lockfile)) {
129             $this->markLastRun();
130             return true;
131         } else {
132             $ts = file_get_contents($this->lockfile);
133             $this->markLastRun();
134             $now = time();
135             if($now - $ts < $this->min_interval) {
136                 // run too frequently
137                 return false;
138             }
139         }
140         return true;
141     }
142
143     /**
144      * What to do if one of the jobs failed
145      * @param SchedulersJob $job
146      */
147     protected function jobFailed($job = null)
148     {
149         $this->runOk = false;
150         if(!empty($job)) {
151             $GLOBALS['log']->fatal("Job {$job->id} ({$job->name}) failed in CRON run");
152             if($this->verbose) {
153                 printf(translate('ERR_JOB_FAILED_VERBOSE', 'SchedulersJobs'), $job->id, $job->name);
154             }
155         }
156     }
157
158     /**
159      * Shutdown handler to be called if something breaks in the middle of the job
160      */
161     public function unexpectedExit()
162     {
163         if(!empty($this->job)) {
164             $this->jobFailed($this->job);
165             $this->job->failJob(translate('ERR_FAILED', 'SchedulersJobs'));
166             $this->job = null;
167         }
168     }
169
170     /**
171      * Return ID for this client
172      * @return string
173      */
174     public function getMyId()
175     {
176         return 'CRON'.$GLOBALS['sugar_config']['unique_key'].':'.getmypid();
177     }
178
179     /**
180      * Execute given job
181      * @param SchedulersJob $job
182      */
183     public function executeJob($job)
184     {
185         if(!$this->job->runJob()) {
186             // if some job fails, change run status
187             $this->jobFailed($this->job);
188         }
189     }
190
191     /**
192      * Run CRON cycle:
193      * - cleanup
194      * - schedule new jobs
195      * - execute pending jobs
196      */
197     public function runCycle()
198     {
199         // throttle
200         if(!$this->throttle()) {
201             $GLOBALS['log']->fatal("Job runs too frequently, throttled to protect the system.");
202             return;
203         }
204         // clean old stale jobs
205         if(!$this->queue->cleanup()) {
206             $this->jobFailed();
207         }
208         // run schedulers
209         if(!$this->disable_schedulers) {
210             $this->queue->runSchedulers();
211         }
212         // run jobs
213         $cutoff = time()+$this->max_runtime;
214         register_shutdown_function(array($this, "unexpectedExit"));
215         $myid = $this->getMyId();
216         for($count=0;$count<$this->max_jobs;$count++) {
217             $this->job = $this->queue->nextJob($myid);
218             if(empty($this->job)) {
219                 return;
220             }
221             $this->executeJob($this->job);
222             if(time() >= $cutoff) {
223                 break;
224             }
225         }
226         $this->job = null;
227     }
228
229     /**
230      * Check if the queue run was fine
231      */
232     public function runOk()
233     {
234         return $this->runOk;
235     }
236 }