Index: trunk/phase3/maintenance/locking/LockServerDaemon.php |
— | — | @@ -22,30 +22,22 @@ |
23 | 23 | /** @var resource */ |
24 | 24 | protected $sock; // socket to listen/accept on |
25 | 25 | /** @var Array */ |
26 | | - protected $shLocks = array(); // (key => session => 1) |
27 | | - /** @var Array */ |
28 | | - protected $exLocks = array(); // (key => session) |
29 | | - /** @var Array */ |
30 | 26 | protected $sessions = array(); // (session => resource) |
31 | 27 | /** @var Array */ |
32 | 28 | protected $deadSessions = array(); // (session => UNIX timestamp) |
33 | 29 | |
34 | | - /** @var Array */ |
35 | | - protected $sessionIndexSh = array(); // (session => key => 1) |
36 | | - /** @var Array */ |
37 | | - protected $sessionIndexEx = array(); // (session => key => 1) |
| 30 | + /** @var LockHolder */ |
| 31 | + protected $lockHolder; |
38 | 32 | |
39 | 33 | protected $address; // string (IP/hostname) |
40 | 34 | protected $port; // integer |
41 | 35 | protected $authKey; // string key |
42 | 36 | protected $connTimeout; // array ( 'sec' => integer, 'usec' => integer ) |
43 | 37 | protected $lockTimeout; // integer number of seconds |
44 | | - protected $maxLocks; // integer |
| 38 | + protected $maxBacklog; // integer |
45 | 39 | protected $maxClients; // integer |
46 | | - protected $maxBacklog; // integer |
47 | 40 | |
48 | 41 | protected $startTime; // integer UNIX timestamp |
49 | | - protected $lockCount = 0; // integer |
50 | 42 | protected $ticks = 0; // integer counter |
51 | 43 | |
52 | 44 | protected static $instance = null; |
— | — | @@ -88,17 +80,19 @@ |
89 | 81 | 'usec' => floor( ( $connTimeout - floor( $connTimeout ) ) * 1e6 ) |
90 | 82 | ); |
91 | 83 | $this->lockTimeout = isset( $config['lockTimeout'] ) |
92 | | - ? $config['lockTimeout'] |
| 84 | + ? (int)$config['lockTimeout'] |
93 | 85 | : 60; |
94 | | - $this->maxLocks = isset( $config['maxLocks'] ) |
95 | | - ? $config['maxLocks'] |
96 | | - : 5000; |
97 | 86 | $this->maxClients = isset( $config['maxClients'] ) |
98 | | - ? $config['maxClients'] |
| 87 | + ? (int)$config['maxClients'] |
99 | 88 | : 1000; // less than default FD_SETSIZE |
100 | 89 | $this->maxBacklog = isset( $config['maxBacklog'] ) |
101 | | - ? $config['maxBacklog'] |
102 | | - : 10; |
| 90 | + ? (int)$config['maxBacklog'] |
| 91 | + : 100; |
| 92 | + $maxLocks = isset( $config['maxLocks'] ) |
| 93 | + ? (int)$config['maxLocks'] |
| 94 | + : 5000; |
| 95 | + |
| 96 | + $this->lockHolder = new LockHolder( $maxLocks ); |
103 | 97 | } |
104 | 98 | |
105 | 99 | /** |
— | — | @@ -113,6 +107,7 @@ |
114 | 108 | throw new Exception( "socket_create(): " . socket_strerror( socket_last_error() ) ); |
115 | 109 | } |
116 | 110 | socket_set_option( $sock, SOL_SOCKET, SO_REUSEADDR, 1 ); // bypass 2MLS |
| 111 | + socket_set_nonblock( $sock ); // don't block on accept() |
117 | 112 | if ( socket_bind( $sock, $this->address, $this->port ) === false ) { |
118 | 113 | throw new Exception( "socket_bind(): " . |
119 | 114 | socket_strerror( socket_last_error( $sock ) ) ); |
— | — | @@ -135,7 +130,7 @@ |
136 | 131 | $clients = array( $this->sock ); // start off with listening socket |
137 | 132 | do { |
138 | 133 | // Create a copy, so $clients doesn't get modified by socket_select() |
139 | | - $read = $clients; // clients-with-data |
| 134 | + $read = $clients; // clients-with-data (plus listening socket) |
140 | 135 | // Get a list of all the clients that have data to be read from |
141 | 136 | $changed = socket_select( $read, $write = NULL, $except = NULL, NULL ); |
142 | 137 | if ( $changed === false ) { |
— | — | @@ -148,12 +143,15 @@ |
149 | 144 | if ( in_array( $this->sock, $read ) && count( $clients ) < $this->maxClients ) { |
150 | 145 | // Accept the new client... |
151 | 146 | $newsock = socket_accept( $this->sock ); |
152 | | - socket_set_option( $newsock, SOL_SOCKET, SO_RCVTIMEO, $this->connTimeout ); |
153 | | - socket_set_option( $newsock, SOL_SOCKET, SO_SNDTIMEO, $this->connTimeout ); |
154 | | - $clients[] = $newsock; |
155 | | - // Remove the listening socket from the clients-with-data array... |
156 | | - $key = array_search( $this->sock, $read ); |
157 | | - unset( $read[$key] ); |
| 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] ); |
| 155 | + } |
158 | 156 | } |
159 | 157 | // Loop through all the clients that have data to read... |
160 | 158 | foreach ( $read as $read_sock ) { |
— | — | @@ -210,11 +208,11 @@ |
211 | 209 | $this->sessions[$session] = $sourceSock; |
212 | 210 | } |
213 | 211 | if ( $function === 'ACQUIRE' ) { |
214 | | - return $this->lock( $session, $type, $resources ); |
| 212 | + return $this->lockHolder->lock( $session, $type, $resources ); |
215 | 213 | } elseif ( $function === 'RELEASE' ) { |
216 | | - return $this->unlock( $session, $type, $resources ); |
| 214 | + return $this->lockHolder->unlock( $session, $type, $resources ); |
217 | 215 | } elseif ( $function === 'RELEASE_ALL' ) { |
218 | | - return $this->release( $session ); |
| 216 | + return $this->lockHolder->release( $session ); |
219 | 217 | } elseif ( $function === 'STAT' ) { |
220 | 218 | return $this->stat(); |
221 | 219 | } |
— | — | @@ -262,12 +260,63 @@ |
263 | 261 | } |
264 | 262 | |
265 | 263 | /** |
| 264 | + * Clear locks for sessions that have been dead for a while |
| 265 | + * |
| 266 | + * @return integer Number of sessions purged |
| 267 | + */ |
| 268 | + protected function purgeExpiredLocks() { |
| 269 | + $now = time(); |
| 270 | + $count = 0; |
| 271 | + foreach ( $this->deadSessions as $session => $timestamp ) { |
| 272 | + if ( ( $now - $timestamp ) > $this->lockTimeout ) { |
| 273 | + $this->lockHolder->release( $session ); |
| 274 | + unset( $this->deadSessions[$session] ); |
| 275 | + ++$count; |
| 276 | + } |
| 277 | + } |
| 278 | + return $count; |
| 279 | + } |
| 280 | + |
| 281 | + /** |
| 282 | + * @return string |
| 283 | + */ |
| 284 | + protected function stat() { |
| 285 | + return ( time() - $this->startTime ) . ':' . memory_get_usage(); |
| 286 | + } |
| 287 | +} |
| 288 | + |
| 289 | +/** |
| 290 | + * LockServerDaemon helper class that keeps track of the locks. |
| 291 | + * This should not require MediaWiki setup or PHP files. |
| 292 | + */ |
| 293 | +class LockHolder { |
| 294 | + /** @var Array */ |
| 295 | + protected $shLocks = array(); // (key => session => 1) |
| 296 | + /** @var Array */ |
| 297 | + protected $exLocks = array(); // (key => session) |
| 298 | + |
| 299 | + /** @var Array */ |
| 300 | + protected $sessionIndexSh = array(); // (session => key => 1) |
| 301 | + /** @var Array */ |
| 302 | + protected $sessionIndexEx = array(); // (session => key => 1) |
| 303 | + protected $lockCount = 0; // integer |
| 304 | + |
| 305 | + protected $maxLocks; // integer |
| 306 | + |
| 307 | + /** |
| 308 | + * @params $maxLocks integer Maximum number of locks to allow |
| 309 | + */ |
| 310 | + public function __construct( $maxLocks ) { |
| 311 | + $this->maxLocks = $maxLocks; |
| 312 | + } |
| 313 | + |
| 314 | + /** |
266 | 315 | * @param $session string |
267 | 316 | * @param $type string |
268 | 317 | * @param $keys Array |
269 | 318 | * @return string |
270 | 319 | */ |
271 | | - protected function lock( $session, $type, $keys ) { |
| 320 | + public function lock( $session, $type, array $keys ) { |
272 | 321 | if ( $this->lockCount >= $this->maxLocks ) { |
273 | 322 | return 'TOO_MANY_LOCKS'; |
274 | 323 | } |
— | — | @@ -312,7 +361,7 @@ |
313 | 362 | * @param $keys Array |
314 | 363 | * @return string |
315 | 364 | */ |
316 | | - protected function unlock( $session, $type, $keys ) { |
| 365 | + public function unlock( $session, $type, array $keys ) { |
317 | 366 | if ( $type === 'SH' ) { |
318 | 367 | foreach ( $keys as $key ) { |
319 | 368 | $this->unset_sh_lock( $key, $session ); |
— | — | @@ -331,7 +380,7 @@ |
332 | 381 | * @param $session string |
333 | 382 | * @return string |
334 | 383 | */ |
335 | | - protected function release( $session ) { |
| 384 | + public function release( $session ) { |
336 | 385 | if ( isset( $this->sessionIndexSh[$session] ) ) { |
337 | 386 | foreach ( $this->sessionIndexSh[$session] as $key => $x ) { |
338 | 387 | $this->unset_sh_lock( $key, $session ); |
— | — | @@ -346,28 +395,6 @@ |
347 | 396 | } |
348 | 397 | |
349 | 398 | /** |
350 | | - * @return string |
351 | | - */ |
352 | | - protected function stat() { |
353 | | - return ( time() - $this->startTime ) . ':' . memory_get_usage(); |
354 | | - } |
355 | | - |
356 | | - /** |
357 | | - * Clear locks for sessions that have been dead for a while |
358 | | - * |
359 | | - * @return void |
360 | | - */ |
361 | | - protected function purgeExpiredLocks() { |
362 | | - $now = time(); |
363 | | - foreach ( $this->deadSessions as $session => $timestamp ) { |
364 | | - if ( ( $now - $timestamp ) > $this->lockTimeout ) { |
365 | | - $this->release( $session ); |
366 | | - unset( $this->deadSessions[$session] ); |
367 | | - } |
368 | | - } |
369 | | - } |
370 | | - |
371 | | - /** |
372 | 399 | * @param $key string |
373 | 400 | * @param $session string |
374 | 401 | * @return void |