Index: trunk/phase3/includes/db/Database.php |
— | — | @@ -343,17 +343,13 @@ |
344 | 344 | |
345 | 345 | wfProfileIn("dbconnect-$server"); |
346 | 346 | |
| 347 | + # Try to connect up to three times |
347 | 348 | # The kernel's default SYN retransmission period is far too slow for us, |
348 | | - # so we use a short timeout plus a manual retry. Retrying means that a small |
349 | | - # but finite rate of SYN packet loss won't cause user-visible errors. |
| 349 | + # so we use a short timeout plus a manual retry. |
350 | 350 | $this->mConn = false; |
351 | | - if ( ini_get( 'mysql.connect_timeout' ) <= 3 ) { |
352 | | - $numAttempts = 2; |
353 | | - } else { |
354 | | - $numAttempts = 1; |
355 | | - } |
| 351 | + $max = 3; |
356 | 352 | $this->installErrorHandler(); |
357 | | - for ( $i = 0; $i < $numAttempts && !$this->mConn; $i++ ) { |
| 353 | + for ( $i = 0; $i < $max && !$this->mConn; $i++ ) { |
358 | 354 | if ( $i > 1 ) { |
359 | 355 | usleep( 1000 ); |
360 | 356 | } |
— | — | @@ -369,28 +365,30 @@ |
370 | 366 | } |
371 | 367 | } |
372 | 368 | $phpError = $this->restoreErrorHandler(); |
373 | | - # Always log connection errors |
374 | 369 | if ( !$this->mConn ) { |
375 | 370 | $error = $this->lastError(); |
376 | 371 | if ( !$error ) { |
377 | 372 | $error = $phpError; |
378 | 373 | } |
379 | 374 | wfLogDBError( "Error connecting to {$this->mServer}: $error\n" ); |
380 | | - wfDebug( "DB connection error\n" ); |
381 | | - wfDebug( "Server: $server, User: $user, Password: " . |
382 | | - substr( $password, 0, 3 ) . "..., error: " . mysql_error() . "\n" ); |
383 | | - $success = false; |
384 | 375 | } |
385 | 376 | |
386 | 377 | wfProfileOut("dbconnect-$server"); |
387 | 378 | |
388 | | - if ( $dbName != '' && $this->mConn !== false ) { |
389 | | - $success = @/**/mysql_select_db( $dbName, $this->mConn ); |
390 | | - if ( !$success ) { |
391 | | - $error = "Error selecting database $dbName on server {$this->mServer} " . |
392 | | - "from client host {$wguname['nodename']}\n"; |
393 | | - wfLogDBError(" Error selecting database $dbName on server {$this->mServer} \n"); |
394 | | - wfDebug( $error ); |
| 379 | + if ( $dbName != '' ) { |
| 380 | + if ( $this->mConn !== false ) { |
| 381 | + $success = @/**/mysql_select_db( $dbName, $this->mConn ); |
| 382 | + if ( !$success ) { |
| 383 | + $error = "Error selecting database $dbName on server {$this->mServer} " . |
| 384 | + "from client host {$wguname['nodename']}\n"; |
| 385 | + wfLogDBError(" Error selecting database $dbName on server {$this->mServer} \n"); |
| 386 | + wfDebug( $error ); |
| 387 | + } |
| 388 | + } else { |
| 389 | + wfDebug( "DB connection error\n" ); |
| 390 | + wfDebug( "Server: $server, User: $user, Password: " . |
| 391 | + substr( $password, 0, 3 ) . "..., error: " . mysql_error() . "\n" ); |
| 392 | + $success = false; |
395 | 393 | } |
396 | 394 | } else { |
397 | 395 | # Delay USE query |
— | — | @@ -2507,13 +2505,6 @@ |
2508 | 2506 | |
2509 | 2507 | $text = str_replace( '$1', $this->error, $noconnect ); |
2510 | 2508 | |
2511 | | - /* |
2512 | | - if ( $GLOBALS['wgShowExceptionDetails'] ) { |
2513 | | - $text .= '</p><p>Backtrace:</p><p>' . |
2514 | | - nl2br( htmlspecialchars( $this->getTraceAsString() ) ) . |
2515 | | - "</p>\n"; |
2516 | | - }*/ |
2517 | | - |
2518 | 2509 | if($wgUseFileCache) { |
2519 | 2510 | if($wgTitle) { |
2520 | 2511 | $t =& $wgTitle; |
Index: trunk/phase3/includes/db/LoadBalancer.php |
— | — | @@ -398,20 +398,11 @@ |
399 | 399 | /** |
400 | 400 | * Get a connection by index |
401 | 401 | * This is the main entry point for this class. |
402 | | - * @param int $i Database |
403 | | - * @param array $groups Query groups |
404 | | - * @param string $wiki Wiki ID |
405 | 402 | */ |
406 | 403 | public function &getConnection( $i, $groups = array(), $wiki = false ) { |
407 | 404 | global $wgDBtype; |
408 | 405 | wfProfileIn( __METHOD__ ); |
409 | 406 | |
410 | | - if ( $i == DB_LAST ) { |
411 | | - throw new MWException( 'Attempt to call ' . __METHOD__ . ' with deprecated server index DB_LAST' ); |
412 | | - } elseif ( $i === null || $i === false ) { |
413 | | - throw new MWException( 'Attempt to call ' . __METHOD__ . ' with invalid server index' ); |
414 | | - } |
415 | | - |
416 | 407 | if ( $wiki === wfWikiID() ) { |
417 | 408 | $wiki = false; |
418 | 409 | } |
— | — | @@ -441,13 +432,13 @@ |
442 | 433 | # Operation-based index |
443 | 434 | if ( $i == DB_SLAVE ) { |
444 | 435 | $i = $this->getReaderIndex( false, $wiki ); |
445 | | - # Couldn't find a working server in getReaderIndex()? |
446 | | - if ( $i === false ) { |
447 | | - $this->mLastError = 'No working slave server: ' . $this->mLastError; |
448 | | - $this->reportConnectionError( $this->mErrorConnection ); |
449 | | - return false; |
450 | | - } |
| 436 | + } elseif ( $i == DB_LAST ) { |
| 437 | + throw new MWException( 'Attempt to call ' . __METHOD__ . ' with deprecated server index DB_LAST' ); |
451 | 438 | } |
| 439 | + # Couldn't find a working server in getReaderIndex()? |
| 440 | + if ( $i === false ) { |
| 441 | + $this->reportConnectionError( $this->mErrorConnection ); |
| 442 | + } |
452 | 443 | |
453 | 444 | # Now we have an explicit index into the servers array |
454 | 445 | $conn = $this->openConnection( $i, $wiki ); |
— | — | @@ -527,7 +518,7 @@ |
528 | 519 | } else { |
529 | 520 | $server = $this->mServers[$i]; |
530 | 521 | $server['serverIndex'] = $i; |
531 | | - $conn = $this->reallyOpenConnection( $server, false ); |
| 522 | + $conn = $this->reallyOpenConnection( $server ); |
532 | 523 | if ( $conn->isOpen() ) { |
533 | 524 | $this->mConns['local'][$i][0] = $conn; |
534 | 525 | } else { |
— | — | @@ -669,7 +660,6 @@ |
670 | 661 | $reporting = true; |
671 | 662 | if ( !is_object( $conn ) ) { |
672 | 663 | // No last connection, probably due to all servers being too busy |
673 | | - wfLogDBError( "LB failure with no last connection\n" ); |
674 | 664 | $conn = new Database; |
675 | 665 | if ( $this->mFailFunction ) { |
676 | 666 | $conn->failFunction( $this->mFailFunction ); |
— | — | @@ -685,7 +675,6 @@ |
686 | 676 | $conn->failFunction( false ); |
687 | 677 | } |
688 | 678 | $server = $conn->getProperty( 'mServer' ); |
689 | | - wfLogDBError( "Connection error: {$this->mLastError} ({$server})\n" ); |
690 | 679 | $conn->reportConnectionError( "{$this->mLastError} ({$server})" ); |
691 | 680 | } |
692 | 681 | $reporting = false; |
Index: trunk/phase3/includes/Revision.php |
— | — | @@ -761,13 +761,20 @@ |
762 | 762 | $flags = Revision::compressRevisionText( $data ); |
763 | 763 | |
764 | 764 | # Write to external storage if required |
765 | | - if( $wgDefaultExternalStore ) { |
| 765 | + if ( $wgDefaultExternalStore ) { |
| 766 | + if ( is_array( $wgDefaultExternalStore ) ) { |
| 767 | + // Distribute storage across multiple clusters |
| 768 | + $store = $wgDefaultExternalStore[mt_rand(0, count( $wgDefaultExternalStore ) - 1)]; |
| 769 | + } else { |
| 770 | + $store = $wgDefaultExternalStore; |
| 771 | + } |
766 | 772 | // Store and get the URL |
767 | | - $data = ExternalStore::randomInsert( $data ); |
768 | | - if( !$data ) { |
769 | | - throw new MWException( "Unable to store text to external storage" ); |
| 773 | + $data = ExternalStore::insert( $store, $data ); |
| 774 | + if ( !$data ) { |
| 775 | + # This should only happen in the case of a configuration error, where the external store is not valid |
| 776 | + throw new MWException( "Unable to store text to external storage $store" ); |
770 | 777 | } |
771 | | - if( $flags ) { |
| 778 | + if ( $flags ) { |
772 | 779 | $flags .= ','; |
773 | 780 | } |
774 | 781 | $flags .= 'external'; |
Index: trunk/phase3/includes/ExternalStore.php |
— | — | @@ -14,7 +14,7 @@ |
15 | 15 | */ |
16 | 16 | class ExternalStore { |
17 | 17 | /* Fetch data from given URL */ |
18 | | - static function fetchFromURL( $url ) { |
| 18 | + static function fetchFromURL($url) { |
19 | 19 | global $wgExternalStores; |
20 | 20 | |
21 | 21 | if( !$wgExternalStores ) |
— | — | @@ -44,7 +44,7 @@ |
45 | 45 | |
46 | 46 | $class = 'ExternalStore' . ucfirst( $proto ); |
47 | 47 | /* Any custom modules should be added to $wgAutoLoadClasses for on-demand loading */ |
48 | | - if( !class_exists( $class ) ) { |
| 48 | + if( !class_exists( $class ) ){ |
49 | 49 | return false; |
50 | 50 | } |
51 | 51 | |
— | — | @@ -66,47 +66,4 @@ |
67 | 67 | return $store->store( $params, $data ); |
68 | 68 | } |
69 | 69 | } |
70 | | - |
71 | | - /** |
72 | | - * Like insert() above, but does more of the work for us. |
73 | | - * This function does not need a url param, it builds it by |
74 | | - * itself. It also fails-over to the next possible clusters. |
75 | | - * |
76 | | - * @param string $data |
77 | | - * Returns the URL of the stored data item, or false on error |
78 | | - */ |
79 | | - public static function randomInsert( $data ) { |
80 | | - global $wgDefaultExternalStore; |
81 | | - $tryStores = (array)$wgDefaultExternalStore; |
82 | | - $error = false; |
83 | | - while ( count( $tryStores ) > 0 ) { |
84 | | - $index = mt_rand(0, count( $tryStores ) - 1); |
85 | | - $storeUrl = $tryStores[$index]; |
86 | | - wfDebug( __METHOD__.": trying $storeUrl\n" ); |
87 | | - list( $proto, $params ) = explode( '://', $storeUrl, 2 ); |
88 | | - $store = self::getStoreObject( $proto ); |
89 | | - if ( $store === false ) { |
90 | | - throw new MWException( "Invalid external storage protocol - $storeUrl" ); |
91 | | - } |
92 | | - try { |
93 | | - $url = $store->store( $params, $data ); // Try to save the object |
94 | | - } catch ( DBConnectionError $error ) { |
95 | | - $url = false; |
96 | | - } |
97 | | - if ( $url ) { |
98 | | - return $url; // Done! |
99 | | - } else { |
100 | | - unset( $tryStores[$index] ); // Don't try this one again! |
101 | | - $tryStores = array_values( $tryStores ); // Must have consecutive keys |
102 | | - wfDebugLog( 'ExternalStorage', "Unable to store text to external storage $storeUrl" ); |
103 | | - } |
104 | | - } |
105 | | - // All stores failed |
106 | | - if ( $error ) { |
107 | | - // Rethrow the last connection error |
108 | | - throw $error; |
109 | | - } else { |
110 | | - throw new MWException( "Unable to store text to external storage" ); |
111 | | - } |
112 | | - } |
113 | 70 | } |
Index: trunk/phase3/includes/ExternalStoreDB.php |
— | — | @@ -40,7 +40,7 @@ |
41 | 41 | /** @todo Document.*/ |
42 | 42 | function &getMaster( $cluster ) { |
43 | 43 | $lb =& $this->getLoadBalancer( $cluster ); |
44 | | - return $lb->getConnection( DB_MASTER, array() ); |
| 44 | + return $lb->getConnection( DB_MASTER ); |
45 | 45 | } |
46 | 46 | |
47 | 47 | /** @todo Document.*/ |
— | — | @@ -56,7 +56,7 @@ |
57 | 57 | * Fetch data from given URL |
58 | 58 | * @param string $url An url of the form DB://cluster/id or DB://cluster/id/itemid for concatened storage. |
59 | 59 | */ |
60 | | - function fetchFromURL( $url ) { |
| 60 | + function fetchFromURL($url) { |
61 | 61 | $path = explode( '/', $url ); |
62 | 62 | $cluster = $path[2]; |
63 | 63 | $id = $path[3]; |
— | — | @@ -122,14 +122,12 @@ |
123 | 123 | * @return string URL |
124 | 124 | */ |
125 | 125 | function store( $cluster, $data ) { |
126 | | - $dbw = $this->getMaster( $cluster ); |
127 | | - if( !$dbw ) { |
128 | | - return false; |
129 | | - } |
| 126 | + $fname = 'ExternalStoreDB::store'; |
| 127 | + |
| 128 | + $dbw =& $this->getMaster( $cluster ); |
| 129 | + |
130 | 130 | $id = $dbw->nextSequenceValue( 'blob_blob_id_seq' ); |
131 | | - $dbw->insert( $this->getTable( $dbw ), |
132 | | - array( 'blob_id' => $id, 'blob_text' => $data ), |
133 | | - __METHOD__ ); |
| 131 | + $dbw->insert( $this->getTable( $dbw ), array( 'blob_id' => $id, 'blob_text' => $data ), $fname ); |
134 | 132 | $id = $dbw->insertId(); |
135 | 133 | if ( $dbw->getFlag( DBO_TRX ) ) { |
136 | 134 | $dbw->immediateCommit(); |