r110222 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r110221‎ | r110222 | r110223 >
Date:20:54, 28 January 2012
Author:aaron
Status:deferred
Tags:core 
Comment:
In LockServerDaemon:
* r109802: fixed references to bogus vars that are now in LockHolder.
* Tweaked doCommand() so that if a client re-connects, it's removed from the deadSessions member.
* Made all socket operations non-blocking. Uses new SocketArray helper class.
* Bumped default 'maxLocks' parameter to 10000.
Modified paths:
  • /trunk/phase3/maintenance/locking/LockServerDaemon.php (modified) (history)

Diff [purge]

Index: trunk/phase3/maintenance/locking/LockServerDaemon.php
@@ -1,5 +1,7 @@
22 <?php
3 -
 3+/**
 4+ * This code should not require MediaWiki setup or PHP files.
 5+ */
46 if ( php_sapi_name() !== 'cli' ) {
57 die( "This is not a valid entry point.\n" );
68 }
@@ -10,13 +12,12 @@
1113 LockServerDaemon::init(
1214 getopt( '', array(
1315 'address:', 'port:', 'authKey:',
14 - 'connTimeout::', 'lockTimeout::', 'maxClients::', 'maxBacklog::', 'maxLocks::',
 16+ 'lockTimeout::', 'maxClients::', 'maxBacklog::', 'maxLocks::',
1517 ) )
1618 )->main();
1719
1820 /**
19 - * Simple lock server daemon that accepts lock/unlock requests.
20 - * This should not require MediaWiki setup or PHP files.
 21+ * Simple lock server daemon that accepts lock/unlock requests
2122 */
2223 class LockServerDaemon {
2324 /** @var resource */
@@ -29,10 +30,9 @@
3031 /** @var LockHolder */
3132 protected $lockHolder;
3233
33 - protected $address; // string (IP/hostname)
 34+ protected $address; // string IP address
3435 protected $port; // integer
3536 protected $authKey; // string key
36 - protected $connTimeout; // array ( 'sec' => integer, 'usec' => integer )
3737 protected $lockTimeout; // integer number of seconds
3838 protected $maxBacklog; // integer
3939 protected $maxClients; // integer
@@ -40,6 +40,7 @@
4141 protected $startTime; // integer UNIX timestamp
4242 protected $ticks = 0; // integer counter
4343
 44+ /* @var LockServerDaemon */
4445 protected static $instance = null;
4546
4647 /**
@@ -54,7 +55,7 @@
5556 if ( !isset( $config[$par] ) ) {
5657 die( "Usage: php LockServerDaemon.php " .
5758 "--address <address> --port <port> --authkey <key> " .
58 - "[--connTimeout <seconds>] [--lockTimeout <seconds>] " .
 59+ "[--lockTimeout <seconds>] " .
5960 "[--maxLocks <integer>] [--maxClients <integer>] [--maxBacklog <integer>]"
6061 );
6162 }
@@ -72,13 +73,6 @@
7374 $this->port = $config['port'];
7475 $this->authKey = $config['authKey'];
7576 // Parameters with defaults...
76 - $connTimeout = isset( $config['connTimeout'] )
77 - ? $config['connTimeout']
78 - : 1.5;
79 - $this->connTimeout = array(
80 - 'sec' => floor( $connTimeout ),
81 - 'usec' => floor( ( $connTimeout - floor( $connTimeout ) ) * 1e6 )
82 - );
8377 $this->lockTimeout = isset( $config['lockTimeout'] )
8478 ? (int)$config['lockTimeout']
8579 : 60;
@@ -90,7 +84,7 @@
9185 : 100;
9286 $maxLocks = isset( $config['maxLocks'] )
9387 ? (int)$config['maxLocks']
94 - : 5000;
 88+ : 10000;
9589
9690 $this->lockHolder = new LockHolder( $maxLocks );
9791 }
@@ -98,7 +92,7 @@
9993 /**
10094 * @return void
10195 */
102 - protected function setupSocket() {
 96+ protected function setupServerSocket() {
10397 if ( !function_exists( 'socket_create' ) ) {
10498 throw new Exception( "PHP sockets extension missing from PHP CLI mode." );
10599 }
@@ -116,75 +110,69 @@
117111 socket_strerror( socket_last_error( $sock ) ) );
118112 }
119113 $this->sock = $sock;
120 -
121114 $this->startTime = time();
122115 }
123116
124117 /**
125 - * @return void
 118+ * Entry-point function that listens to the server socket, accepts
 119+ * new clients, and recieves/responds to requests to lock resources.
126120 */
127121 public function main() {
128 - // Setup socket and start listing
129 - $this->setupSocket();
130 - // Create a list of all the clients that will be connected to us.
131 - $clients = array( $this->sock ); // start off with listening socket
 122+ $this->setupServerSocket(); // setup listening socket
 123+ $socketArray = new SocketArray(); // sockets being serviced
 124+ $socketArray->addSocket( $this->sock ); // add listening socket
132125 do {
133 - // Create a copy, so $clients doesn't get modified by socket_select()
134 - $read = $clients; // clients-with-data (plus listening socket)
135 - // Get a list of all the clients that have data to be read from
136 - $changed = socket_select( $read, $write = NULL, $except = NULL, NULL );
137 - if ( $changed === false ) {
138 - trigger_error( 'socket_listen(): ' . socket_strerror( socket_last_error() ) );
139 - continue;
140 - } elseif ( $changed < 1 ) {
 126+ list( $read, $write ) = $socketArray->socketsForSelect();
 127+ if ( socket_select( $read, $write, $except = NULL, NULL ) < 1 ) {
141128 continue; // wait
142129 }
143130 // Check if there is a client trying to connect...
144 - if ( in_array( $this->sock, $read ) && count( $clients ) < $this->maxClients ) {
145 - // Accept the new client...
146 - $newsock = socket_accept( $this->sock );
147 - if ( $newsock ) {
148 - socket_set_option( $newsock, SOL_SOCKET, SO_KEEPALIVE, 1 );
149 - socket_set_option( $newsock, SOL_SOCKET, SO_RCVTIMEO, $this->connTimeout );
150 - socket_set_option( $newsock, SOL_SOCKET, SO_SNDTIMEO, $this->connTimeout );
151 - $clients[] = $newsock;
152 - // Remove the listening socket from the clients-with-data array...
153 - $key = array_search( $this->sock, $read );
154 - unset( $read[$key] );
 131+ if ( in_array( $this->sock, $read ) && $socketArray->size() < $this->maxClients ) {
 132+ $newSock = socket_accept( $this->sock );
 133+ if ( $newSock ) {
 134+ socket_set_option( $newSock, SOL_SOCKET, SO_KEEPALIVE, 1 );
 135+ socket_set_nonblock( $newSock ); // don't block on read()/write()
 136+ $socketArray->addSocket( $newSock );
155137 }
156138 }
157139 // Loop through all the clients that have data to read...
158140 foreach ( $read as $read_sock ) {
159 - // Read until newline or 65535 bytes are recieved.
160 - // socket_read show errors when the client is disconnected.
161 - $data = @socket_read( $read_sock, 65535, PHP_NORMAL_READ );
 141+ if ( $read_sock === $this->sock ) {
 142+ continue; // skip listening socket
 143+ }
 144+ // Avoids PHP_NORMAL_READ per https://bugs.php.net/bug.php?id=33471
 145+ $data = socket_read( $read_sock, 65535 );
162146 // Check if the client is disconnected
163 - if ( $data === false ) {
164 - // Remove client from $clients list
165 - $key = array_search( $read_sock, $clients );
166 - unset( $clients[$key] );
167 - // Remove socket's session from tracking (if it exists)
168 - $session = array_search( $read_sock, $this->sessions );
169 - if ( $session !== false ) {
170 - unset( $this->sessions[$session] );
171 - // Record recently killed sessions that still have locks
172 - if ( isset( $this->sessionIndexSh[$session] )
173 - || isset( $this->sessionIndexEx[$session] ) )
174 - {
175 - $this->deadSessions[$session] = time();
176 - }
177 - }
178 - } else {
 147+ if ( $data === false || $data === '' ) {
 148+ $socketArray->closeSocket( $read_sock );
 149+ $this->recordDeadSocket( $read_sock ); // remove session
 150+ // Check if we reached the end of a message
 151+ } elseif ( substr( $data, -1 ) === "\n" ) {
 152+ // Newline is the last char (given ping-pong message usage)
 153+ $cmd = $socketArray->readRcvBuffer( $read_sock ) . $data;
179154 // Perform the requested command...
180 - $response = $this->doCommand( trim( $data ), $read_sock );
 155+ $response = $this->doCommand( rtrim( $cmd ), $read_sock );
181156 // Send the response to the client...
182 - if ( socket_write( $read_sock, "$response\n" ) === false ) {
183 - trigger_error( 'socket_write(): ' .
184 - socket_strerror( socket_last_error( $read_sock ) ) );
185 - }
 157+ $socketArray->appendSndBuffer( $read_sock, $response . "\n" );
 158+ // Otherwise, we just have more message data to append
 159+ } elseif ( !$socketArray->appendRcvBuffer( $read_sock, $data ) ) {
 160+ $socketArray->closeSocket( $read_sock ); // too big
 161+ $this->recordDeadSocket( $read_sock ); // remove session
186162 }
187163 }
188 - // Prune dead locks every 10 socket events...
 164+ // Loop through all the clients that have data to write...
 165+ foreach ( $write as $write_sock ) {
 166+ $bytes = socket_write( $write_sock, $socketArray->readSndBuffer( $write_sock ) );
 167+ // Check if the client is disconnected
 168+ if ( $bytes === false ) {
 169+ $socketArray->closeSocket( $write_sock );
 170+ $this->recordDeadSocket( $write_sock ); // remove session
 171+ // Otherwise, truncate these bytes from the start of the write buffer
 172+ } else {
 173+ $socketArray->consumeSndBuffer( $write_sock, $bytes );
 174+ }
 175+ }
 176+ // Prune dead locks every few socket events...
189177 if ( ++$this->ticks >= 9 ) {
190178 $this->ticks = 0;
191179 $this->purgeExpiredLocks();
@@ -206,6 +194,7 @@
207195 // On first command, track the session => sock correspondence
208196 if ( !isset( $this->sessions[$session] ) ) {
209197 $this->sessions[$session] = $sourceSock;
 198+ unset( $this->deadSessions[$session] ); // renew if dead
210199 }
211200 if ( $function === 'ACQUIRE' ) {
212201 return $this->lockHolder->lock( $session, $type, $resources );
@@ -260,13 +249,33 @@
261250 }
262251
263252 /**
 253+ * Remove a socket's corresponding session from tracking and
 254+ * store it in the dead session tracking if it still has locks.
 255+ *
 256+ * @param $socket resource
 257+ * @return book
 258+ */
 259+ protected function recordDeadSocket( $socket ) {
 260+ $session = array_search( $socket, $this->sessions );
 261+ if ( $session !== false ) {
 262+ unset( $this->sessions[$session] );
 263+ // Record recently killed sessions that still have locks
 264+ if ( $this->lockHolder->sessionHasLocks( $session ) ) {
 265+ $this->deadSessions[$session] = time();
 266+ }
 267+ return true;
 268+ }
 269+ return false;
 270+ }
 271+
 272+ /**
264273 * Clear locks for sessions that have been dead for a while
265274 *
266275 * @return integer Number of sessions purged
267276 */
268277 protected function purgeExpiredLocks() {
 278+ $count = 0;
269279 $now = time();
270 - $count = 0;
271280 foreach ( $this->deadSessions as $session => $timestamp ) {
272281 if ( ( $now - $timestamp ) > $this->lockTimeout ) {
273282 $this->lockHolder->release( $session );
@@ -288,9 +297,144 @@
289298 }
290299
291300 /**
292 - * LockServerDaemon helper class that keeps track of the locks.
293 - * This should not require MediaWiki setup or PHP files.
 301+ * LockServerDaemon helper class that keeps track socket states
294302 */
 303+class SocketArray {
 304+ /* @var Array */
 305+ protected $clients = array(); // array of client sockets
 306+ /* @var Array */
 307+ protected $rBuffers = array(); // corresponding socket read buffers
 308+ /* @var Array */
 309+ protected $wBuffers = array(); // corresponding socket write buffers
 310+
 311+ const BUFFER_SIZE = 65535;
 312+
 313+ /**
 314+ * @return Array (list of sockets to read, list of sockets to write)
 315+ */
 316+ public function socketsForSelect() {
 317+ $rSockets = array();
 318+ $wSockets = array();
 319+ foreach ( $this->clients as $key => $socket ) {
 320+ if ( $this->wBuffers[$key] !== '' ) {
 321+ $wSockets[] = $socket; // wait for writing to unblock
 322+ } else {
 323+ $rSockets[] = $socket; // wait for reading to unblock
 324+ }
 325+ }
 326+ return array( $rSockets, $wSockets );
 327+ }
 328+
 329+ /**
 330+ * @return integer Number of client sockets
 331+ */
 332+ public function size() {
 333+ return count( $this->clients );
 334+ }
 335+
 336+ /**
 337+ * @param $sock resource
 338+ * @return bool
 339+ */
 340+ public function addSocket( $sock ) {
 341+ $this->clients[] = $sock;
 342+ $this->rBuffers[] = '';
 343+ $this->wBuffers[] = '';
 344+ return true;
 345+ }
 346+
 347+ /**
 348+ * @param $sock resource
 349+ * @return bool
 350+ */
 351+ public function closeSocket( $sock ) {
 352+ $key = array_search( $sock, $this->clients );
 353+ if ( $key === false ) {
 354+ return false;
 355+ }
 356+ socket_close( $sock );
 357+ unset( $this->clients[$key] );
 358+ unset( $this->rBuffers[$key] );
 359+ unset( $this->wBuffers[$key] );
 360+ return true;
 361+ }
 362+
 363+ /**
 364+ * @param $sock resource
 365+ * @param $data string
 366+ * @return bool
 367+ */
 368+ public function appendRcvBuffer( $sock, $data ) {
 369+ $key = array_search( $sock, $this->clients );
 370+ if ( $key === false ) {
 371+ return false;
 372+ } elseif ( ( strlen( $this->rBuffers[$key] ) + strlen( $data ) ) > self::BUFFER_SIZE ) {
 373+ return false;
 374+ }
 375+ $this->rBuffers[$key] .= $data;
 376+ return true;
 377+ }
 378+
 379+ /**
 380+ * @param $sock resource
 381+ * @return string|false
 382+ */
 383+ public function readRcvBuffer( $sock ) {
 384+ $key = array_search( $sock, $this->clients );
 385+ if ( $key === false ) {
 386+ return false;
 387+ }
 388+ $data = $this->rBuffers[$key];
 389+ $this->rBuffers[$key] = ''; // consume data
 390+ return $data;
 391+ }
 392+
 393+ /**
 394+ * @param $sock resource
 395+ * @param $data string
 396+ * @return bool
 397+ */
 398+ public function appendSndBuffer( $sock, $data ) {
 399+ $key = array_search( $sock, $this->clients );
 400+ if ( $key === false ) {
 401+ return false;
 402+ } elseif ( ( strlen( $this->wBuffers[$key] ) + strlen( $data ) ) > self::BUFFER_SIZE ) {
 403+ return false;
 404+ }
 405+ $this->wBuffers[$key] .= $data;
 406+ return true;
 407+ }
 408+
 409+ /**
 410+ * @param $sock resource
 411+ * @return bool
 412+ */
 413+ public function readSndBuffer( $sock ) {
 414+ $key = array_search( $sock, $this->clients );
 415+ if ( $key === false ) {
 416+ return false;
 417+ }
 418+ return $this->wBuffers[$key];
 419+ }
 420+
 421+ /**
 422+ * @param $sock resource
 423+ * @param $bytes integer
 424+ * @return bool
 425+ */
 426+ public function consumeSndBuffer( $sock, $bytes ) {
 427+ $key = array_search( $sock, $this->clients );
 428+ if ( $key === false ) {
 429+ return false;
 430+ }
 431+ $this->wBuffers[$key] = (string)substr( $this->wBuffers[$key], $bytes );
 432+ return true;
 433+ }
 434+}
 435+
 436+/**
 437+ * LockServerDaemon helper class that keeps track of the locks
 438+ */
295439 class LockHolder {
296440 /** @var Array */
297441 protected $shLocks = array(); // (key => session => 1)
@@ -314,6 +458,15 @@
315459
316460 /**
317461 * @param $session string
 462+ * @return bool
 463+ */
 464+ public function sessionHasLocks( $session ) {
 465+ return isset( $this->sessionIndexSh[$session] )
 466+ || isset( $this->sessionIndexEx[$session] );
 467+ }
 468+
 469+ /**
 470+ * @param $session string
318471 * @param $type string
319472 * @param $keys Array
320473 * @return string

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r109802In LockServerDaemon:...aaron02:55, 23 January 2012

Status & tagging log