Index: branches/FileBackend/phase3/maintenance/locking/LockServerDaemon.php |
— | — | @@ -35,11 +35,14 @@ |
36 | 36 | /** @var Array */ |
37 | 37 | protected $sessionIndexEx = array(); // (session => key => 1) |
38 | 38 | |
| 39 | + protected $address; // string (IP/hostname) |
| 40 | + protected $port; // integer |
39 | 41 | protected $authKey; // string key |
40 | 42 | protected $connTimeout; // array ( 'sec' => integer, 'usec' => integer ) |
41 | 43 | protected $lockTimeout; // integer number of seconds |
42 | 44 | protected $maxLocks; // integer |
43 | 45 | protected $maxClients; // integer |
| 46 | + protected $maxBacklog; // integer |
44 | 47 | |
45 | 48 | protected $startTime; // integer UNIX timestamp |
46 | 49 | protected $lockCount = 0; // integer |
— | — | @@ -70,7 +73,10 @@ |
71 | 74 | } |
72 | 75 | } |
73 | 76 | |
| 77 | + $this->address = $config['address']; |
| 78 | + $this->port = $config['port']; |
74 | 79 | $this->authKey = $config['authKey']; |
| 80 | + |
75 | 81 | $connTimeout = isset( $config['connTimeout'] ) |
76 | 82 | ? $config['connTimeout'] |
77 | 83 | : 1.5; |
— | — | @@ -87,10 +93,15 @@ |
88 | 94 | $this->maxClients = isset( $config['maxClients'] ) |
89 | 95 | ? $config['maxClients'] |
90 | 96 | : 100; |
91 | | - $backlog = isset( $config['maxBacklog'] ) |
| 97 | + $this->maxBacklog = isset( $config['maxBacklog'] ) |
92 | 98 | ? $config['maxBacklog'] |
93 | 99 | : 10; |
| 100 | + } |
94 | 101 | |
| 102 | + /** |
| 103 | + * @return void |
| 104 | + */ |
| 105 | + protected function setupSocket() { |
95 | 106 | if ( !function_exists( 'socket_create' ) ) { |
96 | 107 | throw new Exception( "PHP sockets extension missing from PHP CLI mode." ); |
97 | 108 | } |
— | — | @@ -99,10 +110,10 @@ |
100 | 111 | throw new Exception( "socket_create(): " . socket_strerror( socket_last_error() ) ); |
101 | 112 | } |
102 | 113 | socket_set_option( $sock, SOL_SOCKET, SO_REUSEADDR, 1 ); // bypass 2MLS |
103 | | - if ( socket_bind( $sock, $config['address'], $config['port'] ) === false ) { |
| 114 | + if ( socket_bind( $sock, $this->address, $this->port ) === false ) { |
104 | 115 | throw new Exception( "socket_bind(): " . |
105 | 116 | socket_strerror( socket_last_error( $sock ) ) ); |
106 | | - } elseif ( socket_listen( $sock, $backlog ) === false ) { |
| 117 | + } elseif ( socket_listen( $sock, $this->maxBacklog ) === false ) { |
107 | 118 | throw new Exception( "socket_listen(): " . |
108 | 119 | socket_strerror( socket_last_error( $sock ) ) ); |
109 | 120 | } |
— | — | @@ -115,6 +126,8 @@ |
116 | 127 | * @return void |
117 | 128 | */ |
118 | 129 | public function main() { |
| 130 | + // Setup socket and start listing |
| 131 | + $this->setupSocket(); |
119 | 132 | // Create a list of all the clients that will be connected to us. |
120 | 133 | $clients = array( $this->sock ); // start off with listening socket |
121 | 134 | do { |
Index: branches/FileBackend/phase3/includes/filerepo/backend/FileBackendMultiWrite.php |
— | — | @@ -93,6 +93,10 @@ |
94 | 94 | return $status; // abort |
95 | 95 | } |
96 | 96 | |
| 97 | + // Clear any cache entries (after locks acquired) |
| 98 | + foreach ( $this->fileBackends as $backend ) { |
| 99 | + $backend->clearCache(); |
| 100 | + } |
97 | 101 | // Actually attempt the operation batch... |
98 | 102 | $status->merge( FileOp::attemptBatch( $performOps, $opts ) ); |
99 | 103 | |
— | — | @@ -116,7 +120,7 @@ |
117 | 121 | $newOp[$par] = preg_replace( |
118 | 122 | '!^mwstore://' . preg_quote( $this->name ) . '/!', |
119 | 123 | 'mwstore://' . $backend->getName() . '/', |
120 | | - $newOp[$par] |
| 124 | + $newOp[$par] // string or array |
121 | 125 | ); |
122 | 126 | } |
123 | 127 | } |
Index: branches/FileBackend/phase3/includes/filerepo/backend/FSFileBackend.php |
— | — | @@ -38,7 +38,7 @@ |
39 | 39 | return null; |
40 | 40 | } |
41 | 41 | |
42 | | - function store( array $params ) { |
| 42 | + protected function doStore( array $params ) { |
43 | 43 | $status = Status::newGood(); |
44 | 44 | |
45 | 45 | list( $c, $dest ) = $this->resolveStoragePath( $params['dst'] ); |
— | — | @@ -79,7 +79,7 @@ |
80 | 80 | return $status; |
81 | 81 | } |
82 | 82 | |
83 | | - function copy( array $params ) { |
| 83 | + protected function doCopy( array $params ) { |
84 | 84 | $status = Status::newGood(); |
85 | 85 | |
86 | 86 | list( $c, $source ) = $this->resolveStoragePath( $params['src'] ); |
— | — | @@ -127,7 +127,7 @@ |
128 | 128 | return $status; |
129 | 129 | } |
130 | 130 | |
131 | | - function move( array $params ) { |
| 131 | + protected function doMove( array $params ) { |
132 | 132 | $status = Status::newGood(); |
133 | 133 | |
134 | 134 | list( $c, $source ) = $this->resolveStoragePath( $params['src'] ); |
— | — | @@ -176,7 +176,7 @@ |
177 | 177 | return $status; |
178 | 178 | } |
179 | 179 | |
180 | | - function delete( array $params ) { |
| 180 | + protected function doDelete( array $params ) { |
181 | 181 | $status = Status::newGood(); |
182 | 182 | |
183 | 183 | list( $c, $source ) = $this->resolveStoragePath( $params['src'] ); |
— | — | @@ -203,7 +203,7 @@ |
204 | 204 | return $status; |
205 | 205 | } |
206 | 206 | |
207 | | - function concatenate( array $params ) { |
| 207 | + protected function doConcatenate( array $params ) { |
208 | 208 | $status = Status::newGood(); |
209 | 209 | |
210 | 210 | list( $c, $dest ) = $this->resolveStoragePath( $params['dst'] ); |
— | — | @@ -301,7 +301,7 @@ |
302 | 302 | return $status; |
303 | 303 | } |
304 | 304 | |
305 | | - function create( array $params ) { |
| 305 | + protected function doCreate( array $params ) { |
306 | 306 | $status = Status::newGood(); |
307 | 307 | |
308 | 308 | list( $c, $dest ) = $this->resolveStoragePath( $params['dst'] ); |
Index: branches/FileBackend/phase3/includes/filerepo/backend/FileBackend.php |
— | — | @@ -356,6 +356,10 @@ |
357 | 357 | * @since 1.19 |
358 | 358 | */ |
359 | 359 | abstract class FileBackend extends FileBackendBase { |
| 360 | + /** @var Array */ |
| 361 | + protected $cache = array(); // (storage path => key => value) |
| 362 | + protected $maxCacheSize = 50; // integer; max paths with entries |
| 363 | + |
360 | 364 | /** |
361 | 365 | * Store a file into the backend from a file on disk. |
362 | 366 | * Do not call this function from places outside FileBackend and FileOp. |
— | — | @@ -367,9 +371,18 @@ |
368 | 372 | * @param $params Array |
369 | 373 | * @return Status |
370 | 374 | */ |
371 | | - abstract public function store( array $params ); |
| 375 | + final public function store( array $params ) { |
| 376 | + $status = $this->doStore( $params ); |
| 377 | + $this->clearCache( array( $params['dst'] ) ); |
| 378 | + return $status; |
| 379 | + } |
372 | 380 | |
373 | 381 | /** |
| 382 | + * @see FileBackend::store() |
| 383 | + */ |
| 384 | + abstract protected function doStore( array $params ); |
| 385 | + |
| 386 | + /** |
374 | 387 | * Copy a file from one storage path to another in the backend. |
375 | 388 | * Do not call this function from places outside FileBackend and FileOp. |
376 | 389 | * $params include: |
— | — | @@ -380,9 +393,18 @@ |
381 | 394 | * @param $params Array |
382 | 395 | * @return Status |
383 | 396 | */ |
384 | | - abstract public function copy( array $params ); |
| 397 | + final public function copy( array $params ) { |
| 398 | + $status = $this->doCopy( $params ); |
| 399 | + $this->clearCache( array( $params['dst'] ) ); |
| 400 | + return $status; |
| 401 | + } |
385 | 402 | |
386 | 403 | /** |
| 404 | + * @see FileBackend::copy() |
| 405 | + */ |
| 406 | + abstract protected function doCopy( array $params ); |
| 407 | + |
| 408 | + /** |
387 | 409 | * Delete a file at the storage path. |
388 | 410 | * Do not call this function from places outside FileBackend and FileOp. |
389 | 411 | * $params include: |
— | — | @@ -391,9 +413,18 @@ |
392 | 414 | * @param $params Array |
393 | 415 | * @return Status |
394 | 416 | */ |
395 | | - abstract public function delete( array $params ); |
| 417 | + final public function delete( array $params ) { |
| 418 | + $status = $this->doDelete( $params ); |
| 419 | + $this->clearCache( array( $params['src'] ) ); |
| 420 | + return $status; |
| 421 | + } |
396 | 422 | |
397 | 423 | /** |
| 424 | + * @see FileBackend::delete() |
| 425 | + */ |
| 426 | + abstract protected function doDelete( array $params ); |
| 427 | + |
| 428 | + /** |
398 | 429 | * Move a file from one storage path to another in the backend. |
399 | 430 | * Do not call this function from places outside FileBackend and FileOp. |
400 | 431 | * $params include: |
— | — | @@ -404,7 +435,16 @@ |
405 | 436 | * @param $params Array |
406 | 437 | * @return Status |
407 | 438 | */ |
408 | | - public function move( array $params ) { |
| 439 | + final public function move( array $params ) { |
| 440 | + $status = $this->doMove( $params ); |
| 441 | + $this->clearCache( array( $params['src'], $params['dst'] ) ); |
| 442 | + return $status; |
| 443 | + } |
| 444 | + |
| 445 | + /** |
| 446 | + * @see FileBackend::move() |
| 447 | + */ |
| 448 | + protected function doMove( array $params ) { |
409 | 449 | // Copy source to dest |
410 | 450 | $status = $this->backend->copy( $params ); |
411 | 451 | if ( !$status->isOK() ) { |
— | — | @@ -426,9 +466,18 @@ |
427 | 467 | * @param $params Array |
428 | 468 | * @return Status |
429 | 469 | */ |
430 | | - abstract public function concatenate( array $params ); |
| 470 | + final public function concatenate( array $params ) { |
| 471 | + $status = $this->doConcatenate( $params ); |
| 472 | + $this->clearCache( array( $params['dst'] ) ); |
| 473 | + return $status; |
| 474 | + } |
431 | 475 | |
432 | 476 | /** |
| 477 | + * @see FileBackend::concatenate() |
| 478 | + */ |
| 479 | + abstract protected function doConcatenate( array $params ); |
| 480 | + |
| 481 | + /** |
433 | 482 | * Create a file in the backend with the given contents. |
434 | 483 | * Do not call this function from places outside FileBackend and FileOp. |
435 | 484 | * $params include: |
— | — | @@ -439,8 +488,17 @@ |
440 | 489 | * @param $params Array |
441 | 490 | * @return Status |
442 | 491 | */ |
443 | | - abstract public function create( array $params ); |
| 492 | + final public function create( array $params ) { |
| 493 | + $status = $this->doCreate( $params ); |
| 494 | + $this->clearCache( array( $params['dst'] ) ); |
| 495 | + return $status; |
| 496 | + } |
444 | 497 | |
| 498 | + /** |
| 499 | + * @see FileBackend::create() |
| 500 | + */ |
| 501 | + abstract protected function doCreate( array $params ); |
| 502 | + |
445 | 503 | public function prepare( array $params ) { |
446 | 504 | return Status::newGood(); |
447 | 505 | } |
— | — | @@ -454,11 +512,20 @@ |
455 | 513 | } |
456 | 514 | |
457 | 515 | public function getFileSha1Base36( array $params ) { |
458 | | - $fsFile = $this->getLocalReference( array( 'src' => $params['src'] ) ); |
| 516 | + $path = $params['src']; |
| 517 | + if ( isset( $this->cache[$path]['sha1'] ) ) { |
| 518 | + return $this->cache[$path]['sha1']; |
| 519 | + } |
| 520 | + $fsFile = $this->getLocalReference( array( 'src' => $path ) ); |
459 | 521 | if ( !$fsFile ) { |
460 | 522 | return false; |
461 | 523 | } else { |
462 | | - return $fsFile->getSha1Base36(); |
| 524 | + $sha1 = $fsFile->getSha1Base36(); |
| 525 | + if ( $sha1 !== false ) { // don't cache negatives |
| 526 | + $this->trimCache(); // limit memory |
| 527 | + $this->cache[$path]['sha1'] = $sha1; |
| 528 | + } |
| 529 | + return $sha1; |
463 | 530 | } |
464 | 531 | } |
465 | 532 | |
— | — | @@ -565,6 +632,8 @@ |
566 | 633 | } |
567 | 634 | } |
568 | 635 | |
| 636 | + // Clear any cache entries (after locks acquired) |
| 637 | + $this->clearCache(); |
569 | 638 | // Actually attempt the operation batch... |
570 | 639 | $status->merge( FileOp::attemptBatch( $performOps, $opts ) ); |
571 | 640 | |
— | — | @@ -572,6 +641,35 @@ |
573 | 642 | } |
574 | 643 | |
575 | 644 | /** |
| 645 | + * Invalidate the file existence and property cache |
| 646 | + * |
| 647 | + * @param $paths Array Clear cache for specific files |
| 648 | + * @return void |
| 649 | + */ |
| 650 | + final public function clearCache( array $paths = null ) { |
| 651 | + if ( $paths === null ) { |
| 652 | + $this->cache = array(); |
| 653 | + } else { |
| 654 | + foreach ( $paths as $path ) { |
| 655 | + unset( $this->cache[$path] ); |
| 656 | + } |
| 657 | + } |
| 658 | + } |
| 659 | + |
| 660 | + /** |
| 661 | + * Prune the cache if it is too big to add an item |
| 662 | + * |
| 663 | + * @return void |
| 664 | + */ |
| 665 | + protected function trimCache() { |
| 666 | + if ( count( $this->cache ) >= $this->maxCacheSize ) { |
| 667 | + reset( $this->cache ); |
| 668 | + $key = key( $this->cache ); |
| 669 | + unset( $this->cache[$key] ); |
| 670 | + } |
| 671 | + } |
| 672 | + |
| 673 | + /** |
576 | 674 | * Check if a given path is a mwstore:// path. |
577 | 675 | * This does not do any actual validation or existence checks. |
578 | 676 | * |