Index: trunk/phase3/includes/PoolCounter.php |
— | — | @@ -1,11 +1,11 @@ |
2 | 2 | <?php |
3 | 3 | |
4 | 4 | /** |
5 | | - * When you have many workers (threads/servers) giving service, and a |
| 5 | + * When you have many workers (threads/servers) giving service, and a |
6 | 6 | * cached item expensive to produce expires, you may get several workers |
7 | 7 | * doing the job at the same time. |
8 | 8 | * |
9 | | - * Given enough requests and the item expiring fast (non-cacheable, |
| 9 | + * Given enough requests and the item expiring fast (non-cacheable, |
10 | 10 | * lots of edits...) that single work can end up unfairly using most (all) |
11 | 11 | * of the cpu of the pool. This is also known as 'Michael Jackson effect' |
12 | 12 | * since this effect triggered on the english wikipedia on the day Michael |
— | — | @@ -15,16 +15,16 @@ |
16 | 16 | * The PoolCounter provides semaphore semantics for restricting the number |
17 | 17 | * of workers that may be concurrently performing such single task. |
18 | 18 | * |
19 | | - * By default PoolCounter_Stub is used, which provides no locking. You |
| 19 | + * By default PoolCounter_Stub is used, which provides no locking. You |
20 | 20 | * can get a useful one in the PoolCounter extension. |
21 | 21 | */ |
22 | 22 | abstract class PoolCounter { |
23 | | - |
| 23 | + |
24 | 24 | /* Return codes */ |
25 | 25 | const LOCKED = 1; /* Lock acquired */ |
26 | 26 | const RELEASED = 2; /* Lock released */ |
27 | 27 | const DONE = 3; /* Another worker did the work for you */ |
28 | | - |
| 28 | + |
29 | 29 | const ERROR = -1; /* Indeterminate error */ |
30 | 30 | const NOT_LOCKED = -2; /* Called release() with no lock held */ |
31 | 31 | const QUEUE_FULL = -3; /* There are already maxqueue workers on this lock */ |
— | — | @@ -33,38 +33,38 @@ |
34 | 34 | |
35 | 35 | /** |
36 | 36 | * I want to do this task and I need to do it myself. |
37 | | - * |
| 37 | + * |
38 | 38 | * @return Locked/Error |
39 | 39 | */ |
40 | 40 | abstract function acquireForMe(); |
41 | 41 | |
42 | 42 | /** |
43 | | - * I want to do this task, but if anyone else does it |
| 43 | + * I want to do this task, but if anyone else does it |
44 | 44 | * instead, it's also fine for me. I will read its cached data. |
45 | | - * |
| 45 | + * |
46 | 46 | * @return Locked/Done/Error |
47 | 47 | */ |
48 | 48 | abstract function acquireForAnyone(); |
49 | 49 | |
50 | 50 | /** |
51 | 51 | * I have successfully finished my task. |
52 | | - * Lets another one grab the lock, and returns the workers |
| 52 | + * Lets another one grab the lock, and returns the workers |
53 | 53 | * waiting on acquireForAnyone() |
54 | | - * |
| 54 | + * |
55 | 55 | * @return Released/NotLocked/Error |
56 | 56 | */ |
57 | 57 | abstract function release(); |
58 | | - |
| 58 | + |
59 | 59 | /** |
60 | 60 | * $key: All workers with the same key share the lock. |
61 | | - * $workers: It wouldn't be a good idea to have more than this number of |
| 61 | + * $workers: It wouldn't be a good idea to have more than this number of |
62 | 62 | * workers doing the task simultaneously. |
63 | | - * $maxqueue: If this number of workers are already working/waiting, |
| 63 | + * $maxqueue: If this number of workers are already working/waiting, |
64 | 64 | * fail instead of wait. |
65 | 65 | * $timeout: Maximum time in seconds to wait for the lock. |
66 | 66 | */ |
67 | 67 | protected $key, $workers, $maxqueue, $timeout; |
68 | | - |
| 68 | + |
69 | 69 | /** |
70 | 70 | * Create a Pool counter. This should only be called from the PoolWorks. |
71 | 71 | * |
— | — | @@ -80,10 +80,10 @@ |
81 | 81 | } |
82 | 82 | $conf = $wgPoolCounterConf[$type]; |
83 | 83 | $class = $conf['class']; |
84 | | - |
| 84 | + |
85 | 85 | return new $class( $conf, $type, $key ); |
86 | 86 | } |
87 | | - |
| 87 | + |
88 | 88 | protected function __construct( $conf, $type, $key ) { |
89 | 89 | $this->key = $key; |
90 | 90 | $this->workers = $conf['workers']; |
— | — | @@ -125,7 +125,7 @@ |
126 | 126 | */ |
127 | 127 | abstract class PoolCounterWork { |
128 | 128 | protected $cacheable = false; //Does this override getCachedWork() ? |
129 | | - |
| 129 | + |
130 | 130 | /** |
131 | 131 | * Actually perform the work, caching it if needed. |
132 | 132 | */ |
— | — | @@ -140,30 +140,34 @@ |
141 | 141 | } |
142 | 142 | |
143 | 143 | /** |
144 | | - * A work not so good (eg. expired one) but better than an error |
| 144 | + * A work not so good (eg. expired one) but better than an error |
145 | 145 | * message. |
146 | 146 | * @return mixed work result or false |
147 | 147 | */ |
148 | 148 | function fallback() { |
149 | 149 | return false; |
150 | 150 | } |
151 | | - |
| 151 | + |
152 | 152 | /** |
153 | 153 | * Do something with the error, like showing it to the user. |
154 | 154 | */ |
155 | | - function error( $status ) { |
| 155 | + function error( $status ) { |
156 | 156 | return false; |
157 | 157 | } |
158 | 158 | |
159 | 159 | /** |
160 | 160 | * Log an error |
| 161 | + * |
| 162 | + * @param $status Status |
161 | 163 | */ |
162 | 164 | function logError( $status ) { |
163 | 165 | wfDebugLog( 'poolcounter', $status->getWikiText() ); |
164 | 166 | } |
165 | | - |
| 167 | + |
166 | 168 | /** |
167 | 169 | * Get the result of the work (whatever it is), or false. |
| 170 | + * @param $skipcache bool |
| 171 | + * @return bool|mixed |
168 | 172 | */ |
169 | 173 | function execute( $skipcache = false ) { |
170 | 174 | if ( $this->cacheable && !$skipcache ) { |
— | — | @@ -183,7 +187,7 @@ |
184 | 188 | $result = $this->doWork(); |
185 | 189 | $this->poolCounter->release(); |
186 | 190 | return $result; |
187 | | - |
| 191 | + |
188 | 192 | case PoolCounter::DONE: |
189 | 193 | $result = $this->getCachedWork(); |
190 | 194 | if ( $result === false ) { |
— | — | @@ -193,27 +197,27 @@ |
194 | 198 | return $this->execute( true ); |
195 | 199 | } |
196 | 200 | return $result; |
197 | | - |
| 201 | + |
198 | 202 | case PoolCounter::QUEUE_FULL: |
199 | 203 | case PoolCounter::TIMEOUT: |
200 | 204 | $result = $this->fallback(); |
201 | | - |
| 205 | + |
202 | 206 | if ( $result !== false ) { |
203 | 207 | return $result; |
204 | 208 | } |
205 | 209 | /* no break */ |
206 | | - |
| 210 | + |
207 | 211 | /* These two cases should never be hit... */ |
208 | 212 | case PoolCounter::ERROR: |
209 | 213 | default: |
210 | 214 | $errors = array( PoolCounter::QUEUE_FULL => 'pool-queuefull', PoolCounter::TIMEOUT => 'pool-timeout' ); |
211 | | - |
212 | | - $status = Status::newFatal( isset($errors[$status->value]) ? $errors[$status->value] : 'pool-errorunknown' ); |
| 215 | + |
| 216 | + $status = Status::newFatal( isset( $errors[$status->value] ) ? $errors[$status->value] : 'pool-errorunknown' ); |
213 | 217 | $this->logError( $status ); |
214 | 218 | return $this->error( $status ); |
215 | 219 | } |
216 | 220 | } |
217 | | - |
| 221 | + |
218 | 222 | function __construct( $type, $key ) { |
219 | 223 | $this->poolCounter = PoolCounter::factory( $type, $key ); |
220 | 224 | } |
Index: trunk/extensions/PoolCounter/PoolCounterClient_body.php |
— | — | @@ -13,6 +13,10 @@ |
14 | 14 | } |
15 | 15 | } |
16 | 16 | |
| 17 | + /** |
| 18 | + * @param $key |
| 19 | + * @return Status |
| 20 | + */ |
17 | 21 | function get( $key ) { |
18 | 22 | $hashes = array(); |
19 | 23 | foreach ( $this->hostNames as $hostName ) { |
— | — | @@ -47,6 +51,9 @@ |
48 | 52 | return Status::newGood( $conn ); |
49 | 53 | } |
50 | 54 | |
| 55 | + /** |
| 56 | + * @param $conn |
| 57 | + */ |
51 | 58 | function close( $conn ) { |
52 | 59 | foreach ( $this->conns as $hostName => $otherConn ) { |
53 | 60 | if ( $conn === $otherConn ) { |
— | — | @@ -65,6 +72,9 @@ |
66 | 73 | class PoolCounter_Client extends PoolCounter { |
67 | 74 | private $conn; |
68 | 75 | |
| 76 | + /** |
| 77 | + * @var PoolCounter_ConnectionManager |
| 78 | + */ |
69 | 79 | static private $manager; |
70 | 80 | |
71 | 81 | function __construct( $conf, $type, $key ) { |
— | — | @@ -75,6 +85,9 @@ |
76 | 86 | } |
77 | 87 | } |
78 | 88 | |
| 89 | + /** |
| 90 | + * @return Status |
| 91 | + */ |
79 | 92 | function getConn() { |
80 | 93 | if ( !isset( $this->conn ) ) { |
81 | 94 | $status = self::$manager->get( $this->key ); |
— | — | @@ -83,13 +96,16 @@ |
84 | 97 | } |
85 | 98 | $this->conn = $status->value; |
86 | 99 | |
87 | | - // Set the read timeout to be 1.5 times the pool timeout. |
| 100 | + // Set the read timeout to be 1.5 times the pool timeout. |
88 | 101 | // This allows the server to time out gracefully before we give up on it. |
89 | 102 | stream_set_timeout( $this->conn, 0, $this->timeout * 1e6 * 1.5 ); |
90 | 103 | } |
91 | 104 | return Status::newGood( $this->conn ); |
92 | 105 | } |
93 | 106 | |
| 107 | + /** |
| 108 | + * @return Status |
| 109 | + */ |
94 | 110 | function sendCommand( /*, ...*/ ) { |
95 | 111 | $args = func_get_args(); |
96 | 112 | $args = str_replace( ' ', '%20', $args ); |
— | — | @@ -127,6 +143,9 @@ |
128 | 144 | } |
129 | 145 | } |
130 | 146 | |
| 147 | + /** |
| 148 | + * @return Status |
| 149 | + */ |
131 | 150 | function acquireForMe() { |
132 | 151 | wfProfileIn( __METHOD__ ); |
133 | 152 | $status = $this->sendCommand( 'ACQ4ME', $this->key, $this->workers, $this->maxqueue, $this->timeout ); |
— | — | @@ -134,6 +153,9 @@ |
135 | 154 | return $status; |
136 | 155 | } |
137 | 156 | |
| 157 | + /** |
| 158 | + * @return Status |
| 159 | + */ |
138 | 160 | function acquireForAnyone() { |
139 | 161 | wfProfileIn( __METHOD__ ); |
140 | 162 | $status = $this->sendCommand( 'ACQ4ANY', $this->key, $this->workers, $this->maxqueue, $this->timeout ); |
— | — | @@ -141,6 +163,9 @@ |
142 | 164 | return $status; |
143 | 165 | } |
144 | 166 | |
| 167 | + /** |
| 168 | + * @return Status |
| 169 | + */ |
145 | 170 | function release() { |
146 | 171 | wfProfileIn( __METHOD__ ); |
147 | 172 | $status = $this->sendCommand( 'RELEASE', $this->key ); |