RunJobQueueTask::execute()   D
last analyzed

Complexity

Conditions 9
Paths 2052

Size

Total Lines 101
Code Lines 54

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 54
c 3
b 0
f 0
dl 0
loc 101
rs 4.1147
cc 9
nc 2052
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/******************************************************************************
3
 * Wikipedia Account Creation Assistance tool                                 *
4
 *                                                                            *
5
 * All code in this file is released into the public domain by the ACC        *
6
 * Development Team. Please see team.json for a list of contributors.         *
7
 ******************************************************************************/
8
9
namespace Waca\ConsoleTasks;
10
11
use Exception;
12
use PDO;
13
use Waca\Background\BackgroundTaskBase;
14
use Waca\Background\Task\BotCreationTask;
15
use Waca\Background\Task\UserCreationTask;
16
use Waca\Background\Task\WelcomeUserTask;
17
use Waca\DataObjects\JobQueue;
18
use Waca\ExceptionHandler;
19
use Waca\Exceptions\ApplicationLogicException;
20
use Waca\Helpers\Logger;
21
use Waca\PdoDatabase;
22
use Waca\Tasks\ConsoleTaskBase;
23
24
class RunJobQueueTask extends ConsoleTaskBase
25
{
26
    private $taskList = array(
27
        WelcomeUserTask::class,
28
        BotCreationTask::class,
29
        UserCreationTask::class
30
    );
31
32
    public function execute()
33
    {
34
        $database = $this->getDatabase();
35
36
        // ensure we're running inside a tx here.
37
        if (!$database->hasActiveTransaction()) {
38
            $database->beginTransaction();
39
        }
40
41
        $sql = 'SELECT * FROM jobqueue WHERE status = :status ORDER BY enqueue LIMIT :lim';
42
        $statement = $database->prepare($sql);
43
        $statement->execute(array(
44
            ':status' => JobQueue::STATUS_READY,
45
            ':lim' => $this->getSiteConfiguration()->getJobQueueBatchSize()
46
        ));
47
48
        /** @var JobQueue[] $queuedJobs */
49
        $queuedJobs = $statement->fetchAll(PDO::FETCH_CLASS, JobQueue::class);
50
51
        // mark all the jobs as running, and commit the txn so we're not holding onto long-running transactions.
52
        // We'll re-lock the row when we get to it.
53
        foreach ($queuedJobs as $job) {
54
            $job->setDatabase($database);
55
            $job->setStatus(JobQueue::STATUS_WAITING);
56
            $job->setError(null);
57
            $job->setAcknowledged(null);
58
            $job->save();
59
        }
60
61
        $database->commit();
62
63
        set_error_handler(array(RunJobQueueTask::class, 'errorHandler'), E_ALL);
64
65
        foreach ($queuedJobs as $job) {
66
            try {
67
                // refresh from the database
68
                /** @var JobQueue $job */
69
                $job = JobQueue::getById($job->getId(), $database);
70
71
                if ($job->getStatus() !== JobQueue::STATUS_WAITING) {
72
                    continue;
73
                }
74
75
                $database->beginTransaction();
76
                $job->setStatus(JobQueue::STATUS_RUNNING);
77
                $job->save();
78
                $database->commit();
79
80
                $database->beginTransaction();
81
82
                // re-lock the job
83
                $job->setStatus(JobQueue::STATUS_RUNNING);
84
                $job->save();
85
86
                // validate we're allowed to run the requested task (whitelist)
87
                if (!in_array($job->getTask(), $this->taskList)) {
88
                    throw new ApplicationLogicException('Job task not registered');
89
                }
90
91
                // Create a task.
92
                $taskName = $job->getTask();
93
94
                if (!class_exists($taskName)) {
95
                    throw new ApplicationLogicException('Job task does not exist');
96
                }
97
98
                /** @var BackgroundTaskBase $task */
99
                $task = new $taskName;
100
101
                $this->setupTask($task, $job);
102
                $task->run();
103
            }
104
            catch (Exception $ex) {
105
                $database->rollBack();
106
                try {
107
                    $database->beginTransaction();
108
109
                    /** @var JobQueue $job */
110
                    $job = JobQueue::getById($job->getId(), $database);
111
                    $job->setDatabase($database);
112
                    $job->setStatus(JobQueue::STATUS_FAILED);
113
                    $job->setError($ex->getMessage());
114
                    $job->setAcknowledged(0);
115
                    $job->save();
116
117
                    Logger::backgroundJobIssue($this->getDatabase(), $job);
118
119
                    $database->commit();
120
                }
121
                catch (Exception $ex) {
122
                    // oops, something went horribly wrong trying to handle this in a nice way; let's just fall back to
123
                    // logging this to disk for a tool root to investigate.
124
                    ExceptionHandler::logExceptionToDisk($ex, $this->getSiteConfiguration());
125
                }
126
            }
127
            finally {
128
                $database->commit();
129
            }
130
        }
131
132
        $this->stageQueuedTasks($database);
133
    }
134
135
    /**
136
     * @param BackgroundTaskBase $task
137
     * @param JobQueue           $job
138
     */
139
    private function setupTask(BackgroundTaskBase $task, JobQueue $job)
140
    {
141
        $task->setJob($job);
142
        $task->setDatabase($this->getDatabase());
143
        $task->setHttpHelper($this->getHttpHelper());
144
        $task->setOauthProtocolHelper($this->getOAuthProtocolHelper());
145
        $task->setEmailHelper($this->getEmailHelper());
146
        $task->setSiteConfiguration($this->getSiteConfiguration());
147
        $task->setNotificationHelper($this->getNotificationHelper());
148
    }
149
150
    /** @noinspection PhpUnusedParameterInspection */
151
    public static function errorHandler($errno, $errstr, $errfile, $errline)
152
    {
153
        throw new Exception($errfile . "@" . $errline . ": " . $errstr);
154
    }
155
156
    /**
157
     * Stages tasks for execution during the *next* jobqueue run.
158
     *
159
     * This is to build in some delay between enqueue and execution to allow for accidentally-triggered tasks to be
160
     * cancelled.
161
     *
162
     * @param PdoDatabase $database
163
     */
164
    protected function stageQueuedTasks(PdoDatabase $database): void
165
    {
166
        try {
167
            $database->beginTransaction();
168
169
            $sql = 'SELECT * FROM jobqueue WHERE status = :status ORDER BY enqueue LIMIT :lim';
170
            $statement = $database->prepare($sql);
171
172
            // use a larger batch size than the main runner, but still keep it limited in case things go crazy.
173
            $statement->execute(array(
174
                ':status' => JobQueue::STATUS_QUEUED,
175
                ':lim' => $this->getSiteConfiguration()->getJobQueueBatchSize() * 2
176
            ));
177
178
            /** @var JobQueue[] $queuedJobs */
179
            $queuedJobs = $statement->fetchAll(PDO::FETCH_CLASS, JobQueue::class);
180
181
            foreach ($queuedJobs as $job) {
182
                $job->setDatabase($database);
183
                $job->setStatus(JobQueue::STATUS_READY);
184
                $job->save();
185
            }
186
187
            $database->commit();
188
        }
189
        catch (Exception $ex) {
190
            $database->rollBack();
191
            ExceptionHandler::logExceptionToDisk($ex, $this->getSiteConfiguration());
192
        }
193
    }
194
}
195