Remove unused/redundant imports
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / background / BackgroundQueueHandler.class.php
CommitLineData
db6698ad
TD
1<?php
2namespace wcf\system\background;
6b4fb98a 3use wcf\data\user\User;
db6698ad 4use wcf\system\background\job\AbstractBackgroundJob;
fc332b27 5use wcf\system\exception\SystemException;
4205bd62 6use wcf\system\session\SessionHandler;
db6698ad
TD
7use wcf\system\SingletonFactory;
8use wcf\system\WCF;
9
10/**
11 * Manages the background queue.
a5f8fd9f 12 *
db6698ad
TD
13 * @author Tim Duesterhus
14 * @copyright 2001-2015 WoltLab GmbH
15 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
16 * @package com.woltlab.wcf
17 * @subpackage system.background.job
18 * @category Community Framework
63b02e3f 19 * @since 2.2
db6698ad
TD
20 */
21class BackgroundQueueHandler extends SingletonFactory {
27f8a148
TD
22 /**
23 * Forces checking whether a background queue item is due.
24 * This means that the AJAX request to BackgroundQueuePerformAction is triggered.
25 */
26 public function forceCheck() {
27 WCF::getTPL()->assign([
28 'forceBackgroundQueuePerform' => true
29 ]);
30 }
31
fc332b27 32 /**
70f9375b 33 * Enqueues the given job(s) for execution in the specified number of
fc332b27
TD
34 * seconds. Defaults to "as soon as possible" (0 seconds).
35 *
70f9375b
TD
36 * @param mixed $jobs Either an instance of \wcf\system\background\job\AbstractBackgroundJob or an array of these
37 * @param int $time Minimum number of seconds to wait before performing the job.
fc332b27
TD
38 * @see \wcf\system\background\BackgroundQueueHandler::enqueueAt()
39 */
70f9375b
TD
40 public function enqueueIn($jobs, $time = 0) {
41 return self::enqueueAt($jobs, TIME_NOW + $time);
fc332b27
TD
42 }
43
db6698ad 44 /**
70f9375b 45 * Enqueues the given job(s) for execution at the given time.
db6698ad
TD
46 * Note: The time is a minimum time. Depending on the size of
47 * the queue the job can be performed later as well!
a5f8fd9f 48 *
70f9375b
TD
49 * @param mixed $jobs Either an instance of \wcf\system\background\job\AbstractBackgroundJob or an array of these
50 * @param int $time Earliest time to consider the job for execution.
db6698ad 51 */
70f9375b 52 public function enqueueAt($jobs, $time) {
fc332b27
TD
53 if ($time < TIME_NOW) {
54 throw new SystemException("You may not schedule a job in the past (".$time." is smaller than the current timestamp ".TIME_NOW.").");
55 }
70f9375b
TD
56 if (!is_array($jobs)) $jobs = [ $jobs ];
57 foreach ($jobs as $job) {
58 if (!($job instanceof AbstractBackgroundJob)) {
59 throw new SystemException('$jobs contains an item that does not extend \wcf\system\background\job\AbstractBackgroundJob.');
60 }
61 }
fc332b27 62
70f9375b 63 WCF::getDB()->beginTransaction();
db6698ad
TD
64 $sql = "INSERT INTO wcf".WCF_N."_background_job
65 (job, time)
66 VALUES (?, ?)";
67 $statement = WCF::getDB()->prepareStatement($sql);
70f9375b
TD
68 foreach ($jobs as $job) {
69 $statement->execute([
70 serialize($job),
71 $time
72 ]);
73 }
74 WCF::getDB()->commitTransaction();
db6698ad 75 }
3ea5d8d7
TD
76
77 /**
78 * Immediatly performs the given job.
79 * This method automatically handles requeuing in case of failure.
80 *
81 * This method is used internally by performNextJob(), but it can
82 * be useful if you wish immediate execution of a certain job, but
83 * don't want to miss the automated error handling mechanism of the
84 * queue.
85 *
86 * @param \wcf\system\background\job\AbstractBackgroundJob $job The job to perform.
87 */
88 public function performJob(AbstractBackgroundJob $job) {
6b4fb98a
TD
89 $user = WCF::getUser();
90
3ea5d8d7 91 try {
6b4fb98a 92 SessionHandler::getInstance()->changeUser(new User(null), true);
3ea5d8d7
TD
93 $job->perform();
94 }
dc072501
TD
95 catch (\Throwable $e) {
96 // gotta catch 'em all
97 $job->fail();
98
99 if ($job->getFailures() <= $job::MAX_FAILURES) {
100 $this->enqueueIn($job, $job->retryAfter());
101 }
102 else {
103 // job failed too often: log
104 \wcf\functions\exception\logThrowable($e);
105 }
106 }
3ea5d8d7
TD
107 catch (\Exception $e) {
108 // gotta catch 'em all
109 $job->fail();
110
111 if ($job->getFailures() <= $job::MAX_FAILURES) {
fc332b27 112 $this->enqueueIn($job, $job->retryAfter());
3ea5d8d7
TD
113 }
114 else {
115 // job failed too often: log
280b49db 116 \wcf\functions\exception\logThrowable($e);
3ea5d8d7
TD
117 }
118 }
6b4fb98a
TD
119 finally {
120 SessionHandler::getInstance()->changeUser($user, true);
121 }
3ea5d8d7
TD
122 }
123
db6698ad
TD
124 /**
125 * Performs the (single) job that is due next.
126 * This method automatically handles requeuing in case of failure.
127 */
3ea5d8d7 128 public function performNextJob() {
db6698ad
TD
129 WCF::getDB()->beginTransaction();
130 $commited = false;
131 try {
132 $sql = "SELECT jobID, job
133 FROM wcf".WCF_N."_background_job
134 WHERE status = ?
135 AND time <= ?
136 ORDER BY time ASC, jobID ASC
137 FOR UPDATE";
138 $statement = WCF::getDB()->prepareStatement($sql, 1);
139 $statement->execute([
140 'ready',
141 TIME_NOW
142 ]);
143 $row = $statement->fetchSingleRow();
144 if (!$row) {
145 // nothing to do here
146 return;
147 }
148
149 // lock job
150 $sql = "UPDATE wcf".WCF_N."_background_job
151 SET status = ?,
152 time = ?
153 WHERE jobID = ?
154 AND status = ?";
155 $statement = WCF::getDB()->prepareStatement($sql);
156 $statement->execute([
157 'processing',
158 TIME_NOW,
159 $row['jobID'],
160 'ready'
161 ]);
162 if ($statement->getAffectedRows() != 1) {
163 // somebody stole the job
164 // this cannot happen unless MySQL violates it's contract to lock the row
165 // -> silently ignore, there will be plenty of other oppurtunities to perform a job
166 return;
167 }
168 WCF::getDB()->commitTransaction();
169 $commited = true;
170 }
171 finally {
172 if (!$commited) WCF::getDB()->rollbackTransaction();
173 }
a5f8fd9f 174
db6698ad
TD
175 $job = null;
176 try {
177 // no shut up operator, exception will be caught
178 $job = unserialize($row['job']);
179 if ($job) {
3ea5d8d7 180 $this->performJob($job);
db6698ad
TD
181 }
182 }
dc072501
TD
183 catch (\Throwable $e) {
184 // job is completely broken: log
185 \wcf\functions\exception\logThrowable($e);
186 }
db6698ad 187 catch (\Exception $e) {
3ea5d8d7 188 // job is completely broken: log
280b49db 189 \wcf\functions\exception\logThrowable($e);
db6698ad
TD
190 }
191 finally {
192 // remove entry of processed job
193 $sql = "DELETE FROM wcf".WCF_N."_background_job
194 WHERE jobID = ?";
195 $statement = WCF::getDB()->prepareStatement($sql);
196 $statement->execute([ $row['jobID'] ]);
197 }
198 }
72db0118
TD
199
200 /**
201 * Returns how many items are due.
202 * Note: Do not rely on the return value being correct, some other process may
203 * have modified the queue contents, before this method returns. Think of it as an
204 * approximation to know whether you should spend some time to clear the queue.
205 *
206 * @return int
207 */
208 public function getRunnableCount() {
209 $sql = "SELECT COUNT(*)
210 FROM wcf".WCF_N."_background_job
211 WHERE status = ?
212 AND time <= ?";
213 $statement = WCF::getDB()->prepareStatement($sql);
214 $statement->execute([ 'ready', TIME_NOW ]);
215
216 return $statement->fetchSingleColumn();
217 }
6032b6a7 218}