* (bug 3837) Leave <center> as is instead of doing an unsafe text replacement
[lhc/web/wiklou.git] / includes / JobQueue.php
1 <?php
2
3 if ( !defined( 'MEDIAWIKI' ) ) {
4 die( "This file is part of MediaWiki, it is not a valid entry point\n" );
5 }
6
7 class Job {
8 var $command,
9 $title,
10 $params,
11 $id,
12 $removeDuplicates,
13 $error;
14
15 /*-------------------------------------------------------------------------
16 * Static functions
17 *------------------------------------------------------------------------*/
18 /**
19 * Add an array of refreshLinks jobs to the queue
20 * @param array $titles Array of title objects.
21 * @static
22 */
23 function queueLinksJobs( $titles ) {
24 $fname = 'Job::queueLinksJobs';
25 wfProfileIn( $fname );
26 $batchSize = 100;
27 for( $i = 0; $i < count( $titles ); $i += $batchSize ) {
28 $batch = array_slice( $titles, $i, $batchSize, true );
29 $jobs = array();
30 foreach( $batch as $title ) {
31 $jobs[] = new Job( 'refreshLinks', $title );
32 }
33 Job::batchInsert( $jobs );
34 }
35 wfProfileOut( $fname );
36 }
37
38 /**
39 * Pop a job off the front of the queue
40 * @static
41 * @return Job or false if there's no jobs
42 */
43 function pop() {
44 $fname = 'Job::pop';
45 wfProfileIn( $fname );
46
47 $dbr =& wfGetDB( DB_SLAVE );
48
49 // Get a job from the slave
50 $row = $dbr->selectRow( 'job', '*', '', $fname,
51 array( 'ORDER BY' => 'job_id', 'LIMIT' => 1 )
52 );
53
54 if ( $row === false ) {
55 wfProfileOut( $fname );
56 return false;
57 }
58
59 // Try to delete it from the master
60 $dbw =& wfGetDB( DB_MASTER );
61 $dbw->delete( 'job', array( 'job_id' => $row->job_id ), $fname );
62 $affected = $dbw->affectedRows();
63 $dbw->immediateCommit();
64
65 if ( !$affected ) {
66 // Failed, someone else beat us to it
67 // Try getting a random row
68 $row = $dbw->selectRow( 'job', array( 'MIN(job_id) as minjob',
69 'MAX(job_id) as maxjob' ), '', $fname );
70 if ( $row === false || is_null( $row->minjob ) || is_null( $row->maxjob ) ) {
71 // No jobs to get
72 wfProfileOut( $fname );
73 return false;
74 }
75 // Get the random row
76 $row = $dbw->selectRow( 'job', '*',
77 array( 'job_id' => mt_rand( $row->minjob, $row->maxjob ) ), $fname );
78 if ( $row === false ) {
79 // Random job gone before we got the chance to select it
80 // Give up
81 wfProfileOut( $fname );
82 return false;
83 }
84 // Delete the random row
85 $dbw->delete( 'job', array( 'job_id' => $row->job_id ), $fname );
86 $affected = $dbw->affectedRows();
87 $dbw->immediateCommit();
88
89 if ( !$affected ) {
90 // Random job gone before we exclusively deleted it
91 // Give up
92 wfProfileOut( $fname );
93 return false;
94 }
95 }
96
97 // If execution got to here, there's a row in $row that has been deleted from the database
98 // by this thread. Hence the concurrent pop was successful.
99 $namespace = $row->job_namespace;
100 $dbkey = $row->job_title;
101 $title = Title::makeTitleSafe( $namespace, $dbkey );
102 $job = new Job( $row->job_cmd, $title, $row->job_params, $row->job_id );
103
104 // Remove any duplicates it may have later in the queue
105 $dbw->delete( 'job', $job->insertFields(), $fname );
106
107 wfProfileOut( $fname );
108 return $job;
109 }
110
111 /*-------------------------------------------------------------------------
112 * Non-static functions
113 *------------------------------------------------------------------------*/
114
115 function Job( $command, $title, $params = '', $id = 0 ) {
116 $this->command = $command;
117 $this->title = $title;
118 $this->params = $params;
119 $this->id = $id;
120
121 // A bit of premature generalisation
122 // Oh well, the whole class is premature generalisation really
123 $this->removeDuplicates = true;
124 }
125
126 /**
127 * Insert a single job into the queue.
128 */
129 function insert() {
130 $fname = 'Job::insert';
131
132 $fields = $this->insertFields();
133
134 $dbw =& wfGetDB( DB_MASTER );
135
136 if ( $this->removeDuplicates ) {
137 $res = $dbw->select( 'job', array( '1' ), $fields, $fname );
138 if ( $dbw->numRows( $res ) ) {
139 return;
140 }
141 }
142 $fields['job_id'] = $dbw->nextSequenceValue( 'job_job_id_seq' );
143 $dbw->insert( 'job', $fields, $fname );
144 }
145
146 protected function insertFields() {
147 return array(
148 'job_cmd' => $this->command,
149 'job_namespace' => $this->title->getNamespace(),
150 'job_title' => $this->title->getDBkey(),
151 'job_params' => $this->params
152 );
153 }
154
155 /**
156 * Batch-insert a group of jobs into the queue.
157 * This will be wrapped in a transaction with a forced commit.
158 *
159 * This may add duplicate at insert time, but they will be
160 * removed later on, when the first one is popped.
161 *
162 * @param $jobs array of Job objects
163 */
164 static function batchInsert( $jobs ) {
165 $fname = __CLASS__ . '::' . __FUNCTION__;
166
167 if( count( $jobs ) ) {
168 $dbw = wfGetDB( DB_MASTER );
169 $dbw->begin();
170 foreach( $jobs as $job ) {
171 $rows[] = $job->insertFields();
172 }
173 $dbw->insert( 'job', $rows, $fname, 'IGNORE' );
174 $dbw->immediateCommit();
175 }
176 }
177
178 /**
179 * Run the job
180 * @return boolean success
181 */
182 function run() {
183 $fname = 'Job::run';
184 wfProfileIn( $fname );
185 switch ( $this->command ) {
186 case 'refreshLinks':
187 $retval = $this->refreshLinks();
188 break;
189 default:
190 $this->error = "Invalid job type {$this->command}, ignoring";
191 wfDebug( $this->error . "\n" );
192 $retval = false;
193 }
194 wfProfileOut( $fname );
195 return $retval;
196 }
197
198 /**
199 * Run a refreshLinks job
200 * @return boolean success
201 */
202 function refreshLinks() {
203 global $wgParser;
204 $fname = 'Job::refreshLinks';
205 wfProfileIn( $fname );
206
207 # FIXME: $dbw never used.
208 $dbw =& wfGetDB( DB_MASTER );
209
210 $linkCache =& LinkCache::singleton();
211 $linkCache->clear();
212
213 if ( is_null( $this->title ) ) {
214 $this->error = "refreshLinks: Invalid title";
215 wfProfileOut( $fname );
216 return false;
217 }
218
219 $revision = Revision::newFromTitle( $this->title );
220 if ( !$revision ) {
221 $this->error = 'refreshLinks: Article not found "' . $this->title->getPrefixedDBkey() . '"';
222 wfProfileOut( $fname );
223 return false;
224 }
225
226 wfProfileIn( "$fname-parse" );
227 $options = new ParserOptions;
228 $parserOutput = $wgParser->parse( $revision->getText(), $this->title, $options, true, true, $revision->getId() );
229 wfProfileOut( "$fname-parse" );
230 wfProfileIn( "$fname-update" );
231 $update = new LinksUpdate( $this->title, $parserOutput, false );
232 $update->doUpdate();
233 wfProfileOut( "$fname-update" );
234 wfProfileOut( $fname );
235 return true;
236 }
237
238 function toString() {
239 if ( is_object( $this->title ) ) {
240 $s = "{$this->command} " . $this->title->getPrefixedDBkey();
241 if ( $this->params !== '' ) {
242 $s .= ', ' . $this->params;
243 }
244 return $s;
245 } else {
246 return "{$this->command} {$this->params}";
247 }
248 }
249
250 function getLastError() {
251 return $this->error;
252 }
253 }
254 ?>