Passed
Push — master ( b4e3f0...d6a119 )
by Robbie
04:16 queued 02:31
created

Processors/SearchUpdateCommitJobProcessor.php (6 issues)

1
<?php
2
3
namespace SilverStripe\FullTextSearch\Search\Processors;
4
5
use DateTime;
6
use DateInterval;
7
use SilverStripe\FullTextSearch\Search\FullTextSearch;
8
use SilverStripe\Core\Config\Config;
9
use SilverStripe\Core\Injector\Injector;
10
use SilverStripe\ORM\FieldType\DBDatetime;
11
use stdClass;
12
use Symbiote\QueuedJobs\Services\QueuedJob;
0 ignored issues
show
The type Symbiote\QueuedJobs\Services\QueuedJob was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
13
use Symbiote\QueuedJobs\Services\QueuedJobService;
0 ignored issues
show
The type Symbiote\QueuedJobs\Services\QueuedJobService was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
14
15
if (!interface_exists(QueuedJob::class)) {
16
    return;
17
}
18
19
class SearchUpdateCommitJobProcessor implements QueuedJob
20
{
21
    /**
22
     * The QueuedJob queue to use when processing commits
23
     *
24
     * @config
25
     * @var string
26
     */
27
    private static $commit_queue = QueuedJob::QUEUED;
0 ignored issues
show
The private property $commit_queue is not used, and could be removed.
Loading history...
28
29
    /**
30
     * List of indexes to commit
31
     *
32
     * @var array
33
     */
34
    protected $indexes = array();
35
36
    /**
37
     * True if this job is skipped to be be re-scheduled in the future
38
     *
39
     * @var boolean
40
     */
41
    protected $skipped = false;
42
43
    /**
44
     * List of completed indexes
45
     *
46
     * @var array
47
     */
48
    protected $completed = array();
49
50
    /**
51
     * List of messages
52
     *
53
     * @var array
54
     */
55
    protected $messages = array();
56
57
    /**
58
     * List of dirty indexes to be committed
59
     *
60
     * @var array
61
     */
62
    public static $dirty_indexes = array();
63
64
    /**
65
     * If solrindex::commit has already been performed, but additional commits are necessary,
66
     * how long do we wait before attempting to touch the index again?
67
     *
68
     * {@see http://stackoverflow.com/questions/7512945/how-to-fix-exceeded-limit-of-maxwarmingsearchers}
69
     *
70
     * @var int
71
     * @config
72
     */
73
    private static $cooldown = 300;
0 ignored issues
show
The private property $cooldown is not used, and could be removed.
Loading history...
74
75
    /**
76
     * True if any commits have been executed this request. If so, any attempts to run subsequent commits
77
     * should be delayed until next queuedjob to prevent solr reaching maxWarmingSearchers
78
     *
79
     * {@see http://stackoverflow.com/questions/7512945/how-to-fix-exceeded-limit-of-maxwarmingsearchers}
80
     *
81
     * @var boolean
82
     */
83
    public static $has_run = false;
84
85
    /**
86
     * This method is invoked once indexes with dirty ids have been updapted and a commit is necessary
87
     *
88
     * @param boolean $dirty Marks all indexes as dirty by default. Set to false if there are known comitted and
89
     * clean indexes
90
     * @param string $startAfter Start date
91
     * @return int The ID of the next queuedjob to run. This could be a new one or an existing one.
92
     */
93
    public static function queue($dirty = true, $startAfter = null)
94
    {
95
        $commit = Injector::inst()->create(__CLASS__);
96
        $id = singleton(QueuedJobService::class)->queueJob($commit, $startAfter);
97
98
        if ($dirty) {
99
            $indexes = FullTextSearch::get_indexes();
100
            static::$dirty_indexes = array_keys($indexes);
101
        }
102
        return $id;
103
    }
104
105
    public function getJobType()
106
    {
107
        return Config::inst()->get(__CLASS__, 'commit_queue');
108
    }
109
110
    public function getSignature()
111
    {
112
        return sha1(get_class($this) . time() . mt_rand(0, 100000));
113
    }
114
115
    public function getTitle()
116
    {
117
        return "FullTextSearch Commit Job";
118
    }
119
120
    /**
121
     * Get the list of index names we should process
122
     *
123
     * @return array
124
     */
125
    public function getAllIndexes()
126
    {
127
        if (empty($this->indexes)) {
128
            $indexes = FullTextSearch::get_indexes();
129
            $this->indexes = array_keys($indexes);
130
        }
131
        return $this->indexes;
132
    }
133
134
    public function jobFinished()
135
    {
136
        // If we've indexed exactly as many as we would like, we are done
137
        return $this->skipped
138
            || (count($this->getAllIndexes()) <= count($this->completed));
139
    }
140
141
    public function prepareForRestart()
142
    {
143
        // NOOP
144
    }
145
146
    public function afterComplete()
147
    {
148
        // NOOP
149
    }
150
151
    /**
152
     * Abort this job, potentially rescheduling a replacement if there is still work to do
153
     */
154
    protected function discardJob()
155
    {
156
        $this->skipped = true;
157
158
        // If we do not have dirty records, then assume that these dirty records were committed
159
        // already this request (but probably another job), so we don't need to commit anything else.
160
        // This could occur if we completed multiple searchupdate jobs in a prior request, and
161
        // we only need one commit job to commit all of them in the current request.
162
        if (empty(static::$dirty_indexes)) {
163
            $this->addMessage("Indexing already completed this request: Discarding this job");
164
            return;
165
        }
166
167
168
        // If any commit has run, but some (or all) indexes are un-comitted, we must re-schedule this task.
169
        // This could occur if we completed a searchupdate job in a prior request, as well as in
170
        // the current request
171
        $cooldown = Config::inst()->get(__CLASS__, 'cooldown');
172
        $now = new DateTime(DBDatetime::now()->getValue());
173
        $now->add(new DateInterval('PT' . $cooldown . 'S'));
174
        $runat = $now->Format('Y-m-d H:i:s');
175
176
        $this->addMessage("Indexing already run this request, but incomplete. Re-scheduling for {$runat}");
177
178
        // Queue after the given cooldown
179
        static::queue(false, $runat);
180
    }
181
182
    public function process()
183
    {
184
        // If we have already run an instance of SearchUpdateCommitJobProcessor this request, immediately
185
        // quit this job to prevent hitting warming search limits in Solr
186
        if (static::$has_run) {
187
            $this->discardJob();
188
            return true;
189
        }
190
191
        // To prevent other commit jobs from running this request
192
        static::$has_run = true;
193
194
        // Run all incompleted indexes
195
        $indexNames = $this->getAllIndexes();
196
        foreach ($indexNames as $name) {
197
            $index = singleton($name);
198
            $this->commitIndex($index);
199
        }
200
201
        $this->addMessage("All indexes committed");
202
203
        return true;
204
    }
205
206
    /**
207
     * Commits a specific index
208
     *
209
     * @param SolrIndex $index
0 ignored issues
show
The type SilverStripe\FullTextSea...ch\Processors\SolrIndex was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
210
     * @throws Exception
211
     */
212
    protected function commitIndex($index)
213
    {
214
        // Skip index if this is already complete
215
        $name = get_class($index);
216
        if (in_array($name, $this->completed)) {
217
            $this->addMessage("Skipping already comitted index {$name}");
218
            return;
219
        }
220
221
        // Bypass SolrIndex::commit exception handling so that queuedjobs can handle the error
222
        $this->addMessage("Committing index {$name}");
223
        $index->getService()->commit(false, false, false);
224
        $this->addMessage("Committing index {$name} was successful");
225
226
        // If this index is currently marked as dirty, it's now clean
227
        if (in_array($name, static::$dirty_indexes)) {
228
            static::$dirty_indexes = array_diff(static::$dirty_indexes, array($name));
229
        }
230
231
        // Mark complete
232
        $this->completed[] = $name;
233
    }
234
235
    public function setup()
236
    {
237
        // NOOP
238
    }
239
240
    public function getJobData()
241
    {
242
        $data = new stdClass();
243
        $data->totalSteps = count($this->getAllIndexes());
244
        $data->currentStep = count($this->completed);
245
        $data->isComplete = $this->jobFinished();
246
        $data->messages = $this->messages;
247
248
        $data->jobData = new stdClass();
249
        $data->jobData->skipped = $this->skipped;
250
        $data->jobData->completed = $this->completed;
251
        $data->jobData->indexes = $this->getAllIndexes();
252
253
        return $data;
254
    }
255
256
    public function setJobData($totalSteps, $currentStep, $isComplete, $jobData, $messages)
257
    {
258
        $this->isComplete = $isComplete;
0 ignored issues
show
Bug Best Practice introduced by
The property isComplete does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
259
        $this->messages = $messages;
260
261
        $this->skipped = $jobData->skipped;
262
        $this->completed = $jobData->completed;
263
        $this->indexes = $jobData->indexes;
264
    }
265
266
    public function addMessage($message, $severity = 'INFO')
267
    {
268
        $severity = strtoupper($severity);
269
        $this->messages[] = '[' . date('Y-m-d H:i:s') . "][$severity] $message";
270
    }
271
272
    public function getMessages()
273
    {
274
        return $this->messages;
275
    }
276
}
277