bee40656020e1309735508d72b99f4a664d7d661
[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( dirname( __FILE__ ) . '/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 $lb = wfGetLB( $dbName );
101 $db = $lb->getConnection( DB_MASTER, array(), $dbName );
102 if ( $type === false ) {
103 $conds = Job::defaultQueueConditions( );
104 } else {
105 $conds = array( 'job_cmd' => $type );
106 }
107
108
109 $exists = (bool) $db->selectField( 'job', '1', $conds, __METHOD__ );
110 $lb->reuseConnection( $db );
111 return $exists;
112 }
113
114 /**
115 * Get all databases that have a pending job
116 * @return array
117 */
118 private function getPendingDbs() {
119 global $wgLocalDatabases;
120 $pendingDBs = array();
121 # Cross-reference DBs by master DB server
122 $dbsByMaster = array();
123 foreach ( $wgLocalDatabases as $db ) {
124 $lb = wfGetLB( $db );
125 $dbsByMaster[$lb->getServerName( 0 )][] = $db;
126 }
127
128 foreach ( $dbsByMaster as $dbs ) {
129 $dbConn = wfGetDB( DB_MASTER, array(), $dbs[0] );
130
131 # Padding row for MySQL bug
132 $pad = str_repeat( '-', 40 );
133 $sql = "(SELECT '$pad' as db, '$pad' as job_cmd)";
134 foreach ( $dbs as $wikiId ) {
135 if ( $sql != '' ) {
136 $sql .= ' UNION ';
137 }
138
139 list( $dbName, $tablePrefix ) = wfSplitWikiID( $wikiId );
140 $dbConn->tablePrefix( $tablePrefix );
141 $jobTable = $dbConn->tableName( 'job' );
142
143 $sql .= "(SELECT DISTINCT '$wikiId' as db, job_cmd FROM $dbName.$jobTable GROUP BY job_cmd)";
144 }
145 $res = $dbConn->query( $sql, __METHOD__ );
146 $first = true;
147 foreach ( $res as $row ) {
148 if ( $first ) {
149 // discard padding row
150 $first = false;
151 continue;
152 }
153 $pendingDBs[$row->job_cmd][] = $row->db;
154 }
155 }
156 return $pendingDBs;
157 }
158 }
159
160 $maintClass = "nextJobDb";
161 require_once( RUN_MAINTENANCE_IF_MAIN );