Merge "Fix handling of strings containing \0 in SQLite."
[lhc/web/wiklou.git] / maintenance / nextJobDB.php
1 <?php
2 /**
3 * Pick a database that has pending jobs
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @todo Make this work on PostgreSQL and maybe other database servers
22 * @ingroup Maintenance
23 */
24
25 require_once( __DIR__ . '/Maintenance.php' );
26
27 /**
28 * Maintenance script that picks a database that has pending jobs.
29 *
30 * @ingroup Maintenance
31 */
32 class nextJobDB extends Maintenance {
33 public function __construct() {
34 parent::__construct();
35 $this->mDescription = "Pick a database that has pending jobs";
36 $this->addOption( 'type', "The type of job to search for", false, true );
37 }
38
39 public function execute() {
40 global $wgMemc;
41 $type = $this->getOption( 'type', false );
42
43 $memcKey = 'jobqueue:dbs:v2';
44 $pendingDBs = $wgMemc->get( $memcKey );
45
46 // If the cache entry wasn't present, or in 1% of cases otherwise,
47 // regenerate the cache.
48 if ( !$pendingDBs || mt_rand( 0, 100 ) == 0 ) {
49 $pendingDBs = $this->getPendingDbs();
50 $wgMemc->set( $memcKey, $pendingDBs, 300 );
51 }
52
53 if ( !$pendingDBs ) {
54 return;
55 }
56
57 do {
58 $again = false;
59
60 if ( $type === false ) {
61 $candidates = call_user_func_array( 'array_merge', $pendingDBs );
62 } elseif ( isset( $pendingDBs[$type] ) ) {
63 $candidates = $pendingDBs[$type];
64 } else {
65 $candidates = array();
66 }
67 if ( !$candidates ) {
68 return;
69 }
70
71 $candidates = array_values( $candidates );
72 $db = $candidates[ mt_rand( 0, count( $candidates ) - 1 ) ];
73 if ( !$this->checkJob( $type, $db ) ) {
74 // This job is not available in the current database. Remove it from
75 // the cache.
76 if ( $type === false ) {
77 foreach ( $pendingDBs as $type2 => $dbs ) {
78 $pendingDBs[$type2] = array_diff( $pendingDBs[$type2], array( $db ) );
79 }
80 } else {
81 $pendingDBs[$type] = array_diff( $pendingDBs[$type], array( $db ) );
82 }
83
84 $wgMemc->set( $memcKey, $pendingDBs, 300 );
85 $again = true;
86 }
87 } while ( $again );
88
89 $this->output( $db . "\n" );
90 }
91
92 /**
93 * Check if the specified database has a job of the specified type in it.
94 * The type may be false to indicate "all".
95 * @param $type string
96 * @param $dbName string
97 * @return bool
98 */
99 function checkJob( $type, $dbName ) {
100 global $wgJobTypesExcludedFromDefaultQueue;
101
102 if ( $type === false ) {
103 $lb = wfGetLB( $dbName );
104 $db = $lb->getConnection( DB_MASTER, array(), $dbName );
105 $conds = array();
106 if ( count( $wgJobTypesExcludedFromDefaultQueue ) > 0 ) {
107 foreach ( $wgJobTypesExcludedFromDefaultQueue as $cmdType ) {
108 $conds[] = "job_cmd != " . $db->addQuotes( $cmdType );
109 }
110 }
111 $exists = (bool)$db->selectField( 'job', '1', $conds, __METHOD__ );
112 $lb->reuseConnection( $db );
113 } else {
114 $exists = !JobQueueGroup::singleton( $dbName )->get( $type )->isEmpty();
115 }
116
117 return $exists;
118 }
119
120 /**
121 * Get all databases that have a pending job
122 * @return array
123 */
124 private function getPendingDbs() {
125 global $wgLocalDatabases;
126 $pendingDBs = array();
127 # Cross-reference DBs by master DB server
128 $dbsByMaster = array();
129 foreach ( $wgLocalDatabases as $db ) {
130 $lb = wfGetLB( $db );
131 $dbsByMaster[$lb->getServerName( 0 )][] = $db;
132 }
133
134 foreach ( $dbsByMaster as $dbs ) {
135 $dbConn = wfGetDB( DB_MASTER, array(), $dbs[0] );
136
137 # Padding row for MySQL bug
138 $pad = str_repeat( '-', 40 );
139 $sql = "(SELECT '$pad' as db, '$pad' as job_cmd)";
140 foreach ( $dbs as $wikiId ) {
141 if ( $sql != '' ) {
142 $sql .= ' UNION ';
143 }
144
145 list( $dbName, $tablePrefix ) = wfSplitWikiID( $wikiId );
146 $dbConn->tablePrefix( $tablePrefix );
147 $jobTable = $dbConn->tableName( 'job' );
148
149 $sql .= "(SELECT DISTINCT '$wikiId' as db, job_cmd FROM $dbName.$jobTable GROUP BY job_cmd)";
150 }
151 $res = $dbConn->query( $sql, __METHOD__ );
152 $first = true;
153 foreach ( $res as $row ) {
154 if ( $first ) {
155 // discard padding row
156 $first = false;
157 continue;
158 }
159 $pendingDBs[$row->job_cmd][] = $row->db;
160 }
161 }
162 return $pendingDBs;
163 }
164 }
165
166 $maintClass = "nextJobDb";
167 require_once( RUN_MAINTENANCE_IF_MAIN );