]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/PoolCounter.php
MediaWiki 1.17.0
[autoinstallsdev/mediawiki.git] / includes / PoolCounter.php
1 <?php
2
3 /**
4  *  When you have many workers (threads/servers) giving service, and a 
5  * cached item expensive to produce expires, you may get several workers
6  * doing the job at the same time.
7  *  Given enough requests and the item expiring fast (non-cacheable, 
8  * lots of edits...) that single work can end up unfairly using most (all)
9  * of the cpu of the pool. This is also known as 'Michael Jackson effect'.
10  *  The PoolCounter provides semaphore semantics for restricting the number
11  * of workers that may be concurrently performing such single task.
12  *
13  *  By default PoolCounter_Stub is used, which provides no locking. You 
14  * can get a useful one in the PoolCounter extension.
15  */
16 abstract class PoolCounter {
17         
18         /* Return codes */
19         const LOCKED = 1;       /* Lock acquired */
20         const RELEASED = 2; /* Lock released */
21         const DONE = 3;         /* Another one did the work for you */
22         
23         const ERROR = -1;               /* Indeterminate error */
24         const NOT_LOCKED = -2;  /* Called release() with no lock held */
25         const QUEUE_FULL = -3;  /* There are already maxqueue workers on this lock */
26         const TIMEOUT = -4;             /* Timeout exceeded */
27         const LOCK_HELD = -5;   /* Cannot acquire another lock while you have one lock held */
28
29         /**
30          * I want to do this task and I need to do it myself.
31          * 
32          * @return Locked/Error
33          */
34         abstract function acquireForMe();
35
36         /**
37          * I want to do this task, but if anyone else does it 
38          * instead, it's also fine for me. I will read its cached data.
39          * 
40          * @return Locked/Done/Error
41          */
42         abstract function acquireForAnyone();
43
44         /**
45          * I have successfully finished my task.
46          * Lets another one grab the lock, and returns the workers 
47          * waiting on acquireForAnyone()
48          * 
49          * @return Released/NotLocked/Error
50          */
51         abstract function release();
52         
53         /**
54          *  $key: All workers with the same key share the lock.
55          *  $workers: It wouldn't be a good idea to have more than this number of 
56          * workers doing the task simultaneously.
57          *  $maxqueue: If this number of workers are already working/waiting, 
58          * fail instead of wait.
59          *  $timeout: Maximum time in seconds to wait for the lock.
60          */
61         protected $key, $workers, $maxqueue, $timeout;
62         
63         /**
64          * Create a Pool counter. This should only be called from the PoolWorks.
65          */
66         public static function factory( $type, $key ) {
67                 global $wgPoolCounterConf;
68                 if ( !isset( $wgPoolCounterConf[$type] ) ) {
69                         return new PoolCounter_Stub;
70                 }
71                 $conf = $wgPoolCounterConf[$type];
72                 $class = $conf['class'];
73                 
74                 return new $class( $conf, $type, $key );
75         }
76         
77         protected function __construct( $conf, $type, $key ) {
78                 $this->key = $key;
79                 $this->workers  = $conf['workers'];
80                 $this->maxqueue = $conf['maxqueue'];
81                 $this->timeout  = $conf['timeout'];
82         }
83 }
84
85 class PoolCounter_Stub extends PoolCounter {
86         function acquireForMe() {
87                 return Status::newGood( PoolCounter::LOCKED );
88         }
89
90         function acquireForAnyone() {
91                 return Status::newGood( PoolCounter::LOCKED );
92         }
93
94         function release() {
95                 return Status::newGood( PoolCounter::RELEASED );
96         }
97         
98         public function __construct() {
99                 /* No parameters needed */
100         }
101 }
102
103 /**
104  * Handy class for dealing with PoolCounters using class members instead of callbacks.
105  */
106 abstract class PoolCounterWork {
107         protected $cacheable = false; //Does this override getCachedWork() ?
108         
109         /**
110          * Actually perform the work, caching it if needed.
111          */
112         abstract function doWork();
113
114         /**
115          * Retrieve the work from cache
116          * @return mixed work result or false
117          */
118         function getCachedWork() {
119                 return false;
120         }
121
122         /**
123          * A work not so good (eg. expired one) but better than an error 
124          * message.
125          * @return mixed work result or false
126          */
127         function fallback() {
128                 return false;
129         }
130         
131         /**
132          * Do something with the error, like showing it to the user.
133          */
134         function error( $status ) {     
135                 return false;
136         }
137         
138         /**
139          * Get the result of the work (whatever it is), or false.
140          */
141         function execute( $skipcache = false ) {
142                 if ( $this->cacheable && !$skipcache ) {
143                         $status = $this->poolCounter->acquireForAnyone();
144                 } else {
145                         $status = $this->poolCounter->acquireForMe();
146                 }
147                 
148                 if ( $status->isOK() ) {
149                         switch ( $status->value ) {
150                                 case PoolCounter::LOCKED:
151                                         $result = $this->doWork();
152                                         $this->poolCounter->release();
153                                         return $result;
154                                 
155                                 case PoolCounter::DONE:
156                                         $result = $this->getCachedWork();
157                                         if ( $result === false ) {
158                                                 /* That someone else work didn't serve us.
159                                                  * Acquire the lock for me
160                                                  */
161                                                 return $this->execute( true );
162                                         }
163                                         return $result;
164                                         
165                                 case PoolCounter::QUEUE_FULL:
166                                 case PoolCounter::TIMEOUT:
167                                         $result = $this->fallback();
168                                         
169                                         if ( $result !== false ) {
170                                                 return $result;
171                                         }
172                                         /* no break */
173                                 
174                                 /* These two cases should never be hit... */
175                                 case PoolCounter::ERROR:
176                                 default:
177                                         $errors = array( PoolCounter::QUEUE_FULL => 'pool-queuefull', PoolCounter::TIMEOUT => 'pool-timeout' );
178                                         
179                                         $status = Status::newFatal( isset($errors[$status->value]) ? $errors[$status->value] : 'pool-errorunknown' );
180                                         /* continue to the error */
181                         }
182                 }
183                 return $this->error( $status );
184         }
185         
186         function __construct( $type, $key ) {
187                 $this->poolCounter = PoolCounter::factory( $type, $key );
188         }
189 }