]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/memcached-client.php
MediaWiki 1.11.0-scripts
[autoinstalls/mediawiki.git] / includes / memcached-client.php
1 <?php
2 //
3 // +---------------------------------------------------------------------------+
4 // | memcached client, PHP                                                     |
5 // +---------------------------------------------------------------------------+
6 // | Copyright (c) 2003 Ryan T. Dean <rtdean@cytherianage.net>                 |
7 // | All rights reserved.                                                      |
8 // |                                                                           |
9 // | Redistribution and use in source and binary forms, with or without        |
10 // | modification, are permitted provided that the following conditions        |
11 // | are met:                                                                  |
12 // |                                                                           |
13 // | 1. Redistributions of source code must retain the above copyright         |
14 // |    notice, this list of conditions and the following disclaimer.          |
15 // | 2. Redistributions in binary form must reproduce the above copyright      |
16 // |    notice, this list of conditions and the following disclaimer in the    |
17 // |    documentation and/or other materials provided with the distribution.   |
18 // |                                                                           |
19 // | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR      |
20 // | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
21 // | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.   |
22 // | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,          |
23 // | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT  |
24 // | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
25 // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY     |
26 // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT       |
27 // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF  |
28 // | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.         |
29 // +---------------------------------------------------------------------------+
30 // | Author: Ryan T. Dean <rtdean@cytherianage.net>                            |
31 // | Heavily influenced by the Perl memcached client by Brad Fitzpatrick.      |
32 // |   Permission granted by Brad Fitzpatrick for relicense of ported Perl     |
33 // |   client logic under 2-clause BSD license.                                |
34 // +---------------------------------------------------------------------------+
35 //
36 // $TCAnet$
37 //
38
39 /**
40  * This is the PHP client for memcached - a distributed memory cache daemon.
41  * More information is available at http://www.danga.com/memcached/
42  *
43  * Usage example:
44  *
45  * require_once 'memcached.php';
46  *
47  * $mc = new memcached(array(
48  *              'servers' => array('127.0.0.1:10000',
49  *                                 array('192.0.0.1:10010', 2),
50  *                                 '127.0.0.1:10020'),
51  *              'debug'   => false,
52  *              'compress_threshold' => 10240,
53  *              'persistant' => true));
54  *
55  * $mc->add('key', array('some', 'array'));
56  * $mc->replace('key', 'some random string');
57  * $val = $mc->get('key');
58  *
59  * @author  Ryan T. Dean <rtdean@cytherianage.net>
60  * @package memcached-client
61  * @version 0.1.2
62  */
63
64 // {{{ requirements
65 // }}}
66
67 // {{{ constants
68 // {{{ flags
69
70 /**
71  * Flag: indicates data is serialized
72  */
73 define("MEMCACHE_SERIALIZED", 1<<0);
74
75 /**
76  * Flag: indicates data is compressed
77  */
78 define("MEMCACHE_COMPRESSED", 1<<1);
79
80 // }}}
81
82 /**
83  * Minimum savings to store data compressed
84  */
85 define("COMPRESSION_SAVINGS", 0.20);
86
87 // }}}
88
89 // {{{ class memcached
90 /**
91  * memcached client class implemented using (p)fsockopen()
92  *
93  * @author  Ryan T. Dean <rtdean@cytherianage.net>
94  * @addtogroup Cache
95  */
96 class memcached
97 {
98    // {{{ properties
99    // {{{ public
100
101    /**
102     * Command statistics
103     *
104     * @var     array
105     * @access  public
106     */
107    var $stats;
108
109    // }}}
110    // {{{ private
111
112    /**
113     * Cached Sockets that are connected
114     *
115     * @var     array
116     * @access  private
117     */
118    var $_cache_sock;
119
120    /**
121     * Current debug status; 0 - none to 9 - profiling
122     *
123     * @var     boolean
124     * @access  private
125     */
126    var $_debug;
127
128    /**
129     * Dead hosts, assoc array, 'host'=>'unixtime when ok to check again'
130     *
131     * @var     array
132     * @access  private
133     */
134    var $_host_dead;
135
136    /**
137     * Is compression available?
138     *
139     * @var     boolean
140     * @access  private
141     */
142    var $_have_zlib;
143
144    /**
145     * Do we want to use compression?
146     *
147     * @var     boolean
148     * @access  private
149     */
150    var $_compress_enable;
151
152    /**
153     * At how many bytes should we compress?
154     *
155     * @var     integer 
156     * @access  private
157     */
158    var $_compress_threshold;
159
160    /**
161     * Are we using persistant links?
162     *
163     * @var     boolean
164     * @access  private
165     */
166    var $_persistant;
167
168    /**
169     * If only using one server; contains ip:port to connect to
170     *
171     * @var     string
172     * @access  private
173     */
174    var $_single_sock;
175
176    /**
177     * Array containing ip:port or array(ip:port, weight)
178     *
179     * @var     array
180     * @access  private
181     */
182    var $_servers;
183
184    /**
185     * Our bit buckets
186     *
187     * @var     array
188     * @access  private
189     */
190    var $_buckets;
191
192    /**
193     * Total # of bit buckets we have
194     *
195     * @var     integer 
196     * @access  private
197     */
198    var $_bucketcount;
199
200    /**
201     * # of total servers we have
202     *
203     * @var     integer 
204     * @access  private
205     */
206    var $_active;
207
208    /**
209     * Stream timeout in seconds. Applies for example to fread()
210     *
211     * @var     integer
212     * @access  private
213     */
214    var $_timeout_seconds;
215
216    /**
217     * Stream timeout in microseconds
218     *
219     * @var     integer
220     * @access  private
221     */
222    var $_timeout_microseconds;
223
224    /**
225     * Connect timeout in seconds
226     */
227    var $_connect_timeout;
228
229    /**
230     * Number of connection attempts for each server
231     */
232    var $_connect_attempts;
233
234    // }}}
235    // }}}
236    // {{{ methods
237    // {{{ public functions
238    // {{{ memcached()
239
240    /**
241     * Memcache initializer
242     *
243     * @param   array    $args    Associative array of settings
244     *
245     * @return  mixed
246     * @access  public
247     */
248    function memcached ($args)
249    {
250       $this->set_servers(@$args['servers']);
251       $this->_debug = @$args['debug'];
252       $this->stats = array();
253       $this->_compress_threshold = @$args['compress_threshold'];
254       $this->_persistant = array_key_exists('persistant', $args) ? (@$args['persistant']) : false;
255       $this->_compress_enable = true;
256       $this->_have_zlib = function_exists("gzcompress");
257
258       $this->_cache_sock = array();
259       $this->_host_dead = array();
260
261       $this->_timeout_seconds = 1;
262       $this->_timeout_microseconds = 0;
263
264       $this->_connect_timeout = 0.01;
265       $this->_connect_attempts = 3;
266    }
267
268    // }}}
269    // {{{ add()
270
271    /**
272     * Adds a key/value to the memcache server if one isn't already set with
273     * that key
274     *
275     * @param   string  $key     Key to set with data
276     * @param   mixed   $val     Value to store
277     * @param   integer $exp     (optional) Time to expire data at
278     *
279     * @return  boolean
280     * @access  public
281     */
282    function add ($key, $val, $exp = 0)
283    {
284       return $this->_set('add', $key, $val, $exp);
285    }
286
287    // }}}
288    // {{{ decr()
289
290    /**
291     * Decriment a value stored on the memcache server
292     *
293     * @param   string   $key     Key to decriment
294     * @param   integer  $amt     (optional) Amount to decriment
295     *
296     * @return  mixed    FALSE on failure, value on success
297     * @access  public
298     */
299    function decr ($key, $amt=1)
300    {
301       return $this->_incrdecr('decr', $key, $amt);
302    }
303
304    // }}}
305    // {{{ delete()
306
307    /**
308     * Deletes a key from the server, optionally after $time
309     *
310     * @param   string   $key     Key to delete
311     * @param   integer  $time    (optional) How long to wait before deleting
312     *
313     * @return  boolean  TRUE on success, FALSE on failure
314     * @access  public
315     */
316    function delete ($key, $time = 0)
317    {
318       if (!$this->_active)
319          return false;
320
321       $sock = $this->get_sock($key);
322       if (!is_resource($sock))
323          return false;
324
325       $key = is_array($key) ? $key[1] : $key;
326
327       @$this->stats['delete']++;
328       $cmd = "delete $key $time\r\n";
329       if(!$this->_safe_fwrite($sock, $cmd, strlen($cmd)))
330       {
331          $this->_dead_sock($sock);
332          return false;
333       }
334       $res = trim(fgets($sock));
335
336       if ($this->_debug)
337          $this->_debugprint(sprintf("MemCache: delete %s (%s)\n", $key, $res));
338
339       if ($res == "DELETED")
340          return true;
341       return false;
342    }
343
344    // }}}
345    // {{{ disconnect_all()
346
347    /**
348     * Disconnects all connected sockets
349     *
350     * @access  public
351     */
352    function disconnect_all ()
353    {
354       foreach ($this->_cache_sock as $sock)
355          fclose($sock);
356
357       $this->_cache_sock = array();
358    }
359
360    // }}}
361    // {{{ enable_compress()
362
363    /**
364     * Enable / Disable compression
365     *
366     * @param   boolean  $enable  TRUE to enable, FALSE to disable
367     *
368     * @access  public
369     */
370    function enable_compress ($enable)
371    {
372       $this->_compress_enable = $enable;
373    }
374
375    // }}}
376    // {{{ forget_dead_hosts()
377
378    /**
379     * Forget about all of the dead hosts
380     *
381     * @access  public
382     */
383    function forget_dead_hosts ()
384    {
385       $this->_host_dead = array();
386    }
387
388    // }}}
389    // {{{ get()
390
391    /**
392     * Retrieves the value associated with the key from the memcache server
393     *
394     * @param  string   $key     Key to retrieve
395     *
396     * @return  mixed
397     * @access  public
398     */
399    function get ($key)
400    {
401       $fname = 'memcached::get';
402       wfProfileIn( $fname );
403
404       if (!$this->_active) {
405          wfProfileOut( $fname );
406          return false;
407       }
408
409       $sock = $this->get_sock($key);
410
411       if (!is_resource($sock)) {
412          wfProfileOut( $fname );
413          return false;
414       }
415
416       @$this->stats['get']++;
417
418       $cmd = "get $key\r\n";
419       if (!$this->_safe_fwrite($sock, $cmd, strlen($cmd)))
420       {
421          $this->_dead_sock($sock);
422          wfProfileOut( $fname );
423          return false;
424       }
425
426       $val = array();
427       $this->_load_items($sock, $val);
428
429       if ($this->_debug)
430          foreach ($val as $k => $v)
431             $this->_debugprint(@sprintf("MemCache: sock %s got %s => %s\r\n", serialize($sock), $k, $v));
432
433       wfProfileOut( $fname );
434       return @$val[$key];
435    }
436
437    // }}}
438    // {{{ get_multi()
439
440    /**
441     * Get multiple keys from the server(s)
442     *
443     * @param   array    $keys    Keys to retrieve
444     *
445     * @return  array
446     * @access  public
447     */
448    function get_multi ($keys)
449    {
450       if (!$this->_active)
451          return false;
452
453       $this->stats['get_multi']++;
454       $sock_keys = array();
455       
456       foreach ($keys as $key)
457       {
458          $sock = $this->get_sock($key);
459          if (!is_resource($sock)) continue;
460          $key = is_array($key) ? $key[1] : $key;
461          if (!isset($sock_keys[$sock]))
462          {
463             $sock_keys[$sock] = array();
464             $socks[] = $sock;
465          }
466          $sock_keys[$sock][] = $key;
467       }
468
469       // Send out the requests
470       foreach ($socks as $sock)
471       {
472          $cmd = "get";
473          foreach ($sock_keys[$sock] as $key)
474          {
475             $cmd .= " ". $key;
476          }
477          $cmd .= "\r\n";
478
479          if ($this->_safe_fwrite($sock, $cmd, strlen($cmd)))
480          {
481             $gather[] = $sock;
482          } else
483          {
484             $this->_dead_sock($sock);
485          }
486       }
487
488       // Parse responses
489       $val = array();
490       foreach ($gather as $sock)
491       {
492          $this->_load_items($sock, $val);
493       }
494
495       if ($this->_debug)
496          foreach ($val as $k => $v)
497             $this->_debugprint(sprintf("MemCache: got %s => %s\r\n", $k, $v));
498
499       return $val;
500    }
501
502    // }}}
503    // {{{ incr()
504
505    /**
506     * Increments $key (optionally) by $amt
507     *
508     * @param   string   $key     Key to increment
509     * @param   integer  $amt     (optional) amount to increment
510     *
511     * @return  integer  New key value?
512     * @access  public
513     */
514    function incr ($key, $amt=1)
515    {
516       return $this->_incrdecr('incr', $key, $amt);
517    }
518
519    // }}}
520    // {{{ replace()
521
522    /**
523     * Overwrites an existing value for key; only works if key is already set
524     *
525     * @param   string   $key     Key to set value as
526     * @param   mixed    $value   Value to store
527     * @param   integer  $exp     (optional) Experiation time
528     *
529     * @return  boolean
530     * @access  public
531     */
532    function replace ($key, $value, $exp=0)
533    {
534       return $this->_set('replace', $key, $value, $exp);
535    }
536
537    // }}}
538    // {{{ run_command()
539
540    /**
541     * Passes through $cmd to the memcache server connected by $sock; returns
542     * output as an array (null array if no output)
543     *
544     * NOTE: due to a possible bug in how PHP reads while using fgets(), each
545     *       line may not be terminated by a \r\n.  More specifically, my testing
546     *       has shown that, on FreeBSD at least, each line is terminated only
547     *       with a \n.  This is with the PHP flag auto_detect_line_endings set
548     *       to falase (the default).
549     *
550     * @param   resource $sock    Socket to send command on
551     * @param   string   $cmd     Command to run
552     *
553     * @return  array    Output array
554     * @access  public
555     */
556    function run_command ($sock, $cmd)
557    {
558       if (!is_resource($sock))
559          return array();
560
561       if (!$this->_safe_fwrite($sock, $cmd, strlen($cmd)))
562          return array();
563
564       while (true)
565       {
566          $res = fgets($sock);
567          $ret[] = $res;
568          if (preg_match('/^END/', $res))
569             break;
570          if (strlen($res) == 0)
571             break;
572       }
573       return $ret;
574    }
575
576    // }}}
577    // {{{ set()
578
579    /**
580     * Unconditionally sets a key to a given value in the memcache.  Returns true
581     * if set successfully.
582     *
583     * @param   string   $key     Key to set value as
584     * @param   mixed    $value   Value to set
585     * @param   integer  $exp     (optional) Experiation time
586     *
587     * @return  boolean  TRUE on success
588     * @access  public
589     */
590    function set ($key, $value, $exp=0)
591    {
592       return $this->_set('set', $key, $value, $exp);
593    }
594
595    // }}}
596    // {{{ set_compress_threshold()
597
598    /**
599     * Sets the compression threshold
600     *
601     * @param   integer  $thresh  Threshold to compress if larger than
602     *
603     * @access  public
604     */
605    function set_compress_threshold ($thresh)
606    {
607       $this->_compress_threshold = $thresh;
608    }
609
610    // }}}
611    // {{{ set_debug()
612
613    /**
614     * Sets the debug flag
615     *
616     * @param   boolean  $dbg     TRUE for debugging, FALSE otherwise
617     *
618     * @access  public
619     *
620     * @see     memcahced::memcached
621     */
622    function set_debug ($dbg)
623    {
624       $this->_debug = $dbg;
625    }
626
627    // }}}
628    // {{{ set_servers()
629
630    /**
631     * Sets the server list to distribute key gets and puts between
632     *
633     * @param   array    $list    Array of servers to connect to
634     *
635     * @access  public
636     *
637     * @see     memcached::memcached()
638     */
639    function set_servers ($list)
640    {
641       $this->_servers = $list;
642       $this->_active = count($list);
643       $this->_buckets = null;
644       $this->_bucketcount = 0;
645
646       $this->_single_sock = null;
647       if ($this->_active == 1)
648          $this->_single_sock = $this->_servers[0];
649    }
650
651    /**
652     * Sets the timeout for new connections
653     *
654     * @param   integer  $seconds Number of seconds
655     * @param   integer  $microseconds  Number of microseconds
656     *
657     * @access  public
658     */
659    function set_timeout ($seconds, $microseconds)
660    {
661       $this->_timeout_seconds = $seconds;
662       $this->_timeout_microseconds = $microseconds;
663    }
664
665    // }}}
666    // }}}
667    // {{{ private methods
668    // {{{ _close_sock()
669
670    /**
671     * Close the specified socket
672     *
673     * @param   string   $sock    Socket to close
674     *
675     * @access  private
676     */
677    function _close_sock ($sock)
678    {
679       $host = array_search($sock, $this->_cache_sock);
680       fclose($this->_cache_sock[$host]);
681       unset($this->_cache_sock[$host]);
682    }
683
684    // }}}
685    // {{{ _connect_sock()
686
687    /**
688     * Connects $sock to $host, timing out after $timeout
689     *
690     * @param   integer  $sock    Socket to connect
691     * @param   string   $host    Host:IP to connect to
692     *
693     * @return  boolean
694     * @access  private
695     */
696    function _connect_sock (&$sock, $host)
697    {
698       list ($ip, $port) = explode(":", $host);
699       $sock = false;
700       $timeout = $this->_connect_timeout;
701       $errno = $errstr = null;
702       for ($i = 0; !$sock && $i < $this->_connect_attempts; $i++) {
703          if ($i > 0) {
704             # Sleep until the timeout, in case it failed fast
705             $elapsed = microtime(true) - $t;
706             if ( $elapsed < $timeout ) {
707                usleep(($timeout - $elapsed) * 1e6);
708             }
709             $timeout *= 2;
710          }
711          $t = microtime(true);
712          if ($this->_persistant == 1)
713          {
714             $sock = @pfsockopen($ip, $port, $errno, $errstr, $timeout);
715          } else
716          {
717             $sock = @fsockopen($ip, $port, $errno, $errstr, $timeout);
718          }
719       }
720       if (!$sock) {
721          if ($this->_debug)
722             $this->_debugprint( "Error connecting to $host: $errstr\n" );
723          return false;
724       }
725
726       // Initialise timeout
727       stream_set_timeout($sock, $this->_timeout_seconds, $this->_timeout_microseconds);
728
729       return true;
730    }
731
732    // }}}
733    // {{{ _dead_sock()
734
735    /**
736     * Marks a host as dead until 30-40 seconds in the future
737     *
738     * @param   string   $sock    Socket to mark as dead
739     *
740     * @access  private
741     */
742    function _dead_sock ($sock)
743    {
744       $host = array_search($sock, $this->_cache_sock);
745       @list ($ip, /* $port */) = explode(":", $host);
746       $this->_host_dead[$ip] = time() + 30 + intval(rand(0, 10));
747       $this->_host_dead[$host] = $this->_host_dead[$ip];
748       unset($this->_cache_sock[$host]);
749    }
750
751    // }}}
752    // {{{ get_sock()
753
754    /**
755     * get_sock
756     *
757     * @param   string   $key     Key to retrieve value for;
758     *
759     * @return  mixed    resource on success, false on failure
760     * @access  private
761     */
762    function get_sock ($key)
763    {
764       if (!$this->_active)
765          return false;
766
767       if ($this->_single_sock !== null) {
768          $this->_flush_read_buffer($this->_single_sock);
769          return $this->sock_to_host($this->_single_sock);
770       }
771
772       $hv = is_array($key) ? intval($key[0]) : $this->_hashfunc($key);
773
774       if ($this->_buckets === null)
775       {
776          foreach ($this->_servers as $v)
777          {
778             if (is_array($v))
779             {
780                for ($i=0; $i<$v[1]; $i++)
781                   $bu[] = $v[0];
782             } else
783             {
784                $bu[] = $v;
785             }
786          }
787          $this->_buckets = $bu;
788          $this->_bucketcount = count($bu);
789       }
790
791       $realkey = is_array($key) ? $key[1] : $key;
792       for ($tries = 0; $tries<20; $tries++)
793       {
794          $host = $this->_buckets[$hv % $this->_bucketcount];
795          $sock = $this->sock_to_host($host);
796          if (is_resource($sock)) {
797             $this->_flush_read_buffer($sock);
798             return $sock;
799          }
800          $hv += $this->_hashfunc($tries . $realkey);
801       }
802
803       return false;
804    }
805
806    // }}}
807    // {{{ _hashfunc()
808
809    /**
810     * Creates a hash integer  based on the $key
811     *
812     * @param   string   $key     Key to hash
813     *
814     * @return  integer  Hash value
815     * @access  private
816     */
817    function _hashfunc ($key)
818    {
819       # Hash function must on [0,0x7ffffff]
820       # We take the first 31 bits of the MD5 hash, which unlike the hash
821       # function used in a previous version of this client, works
822       return hexdec(substr(md5($key),0,8)) & 0x7fffffff;
823    }
824
825    // }}}
826    // {{{ _incrdecr()
827
828    /**
829     * Perform increment/decriment on $key
830     *
831     * @param   string   $cmd     Command to perform
832     * @param   string   $key     Key to perform it on
833     * @param   integer  $amt     Amount to adjust
834     *
835     * @return  integer     New value of $key
836     * @access  private
837     */
838    function _incrdecr ($cmd, $key, $amt=1)
839    {
840       if (!$this->_active)
841          return null;
842
843       $sock = $this->get_sock($key);
844       if (!is_resource($sock))
845          return null;
846
847       $key = is_array($key) ? $key[1] : $key;
848       @$this->stats[$cmd]++;
849       if (!$this->_safe_fwrite($sock, "$cmd $key $amt\r\n"))
850          return $this->_dead_sock($sock);
851
852       stream_set_timeout($sock, 1, 0);
853       $line = fgets($sock);
854       $match = array();
855       if (!preg_match('/^(\d+)/', $line, $match))
856          return null;
857       return $match[1];
858    }
859
860    // }}}
861    // {{{ _load_items()
862
863    /**
864     * Load items into $ret from $sock
865     *
866     * @param   resource $sock    Socket to read from
867     * @param   array    $ret     Returned values
868     *
869     * @access  private
870     */
871    function _load_items ($sock, &$ret)
872    {
873       while (1)
874       {
875          $decl = fgets($sock);
876          if ($decl == "END\r\n")
877          {
878             return true;
879          } elseif (preg_match('/^VALUE (\S+) (\d+) (\d+)\r\n$/', $decl, $match))
880          {
881             list($rkey, $flags, $len) = array($match[1], $match[2], $match[3]);
882             $bneed = $len+2;
883             $offset = 0;
884
885             while ($bneed > 0)
886             {
887                $data = fread($sock, $bneed);
888                $n = strlen($data);
889                if ($n == 0)
890                   break;
891                $offset += $n;
892                $bneed -= $n;
893                @$ret[$rkey] .= $data;
894             }
895
896             if ($offset != $len+2)
897             {
898                // Something is borked!
899                if ($this->_debug)
900                   $this->_debugprint(sprintf("Something is borked!  key %s expecting %d got %d length\n", $rkey, $len+2, $offset));
901
902                unset($ret[$rkey]);
903                $this->_close_sock($sock);
904                return false;
905             }
906
907             if ($this->_have_zlib && $flags & MEMCACHE_COMPRESSED)
908                $ret[$rkey] = gzuncompress($ret[$rkey]);
909
910             $ret[$rkey] = rtrim($ret[$rkey]);
911
912             if ($flags & MEMCACHE_SERIALIZED)
913                $ret[$rkey] = unserialize($ret[$rkey]);
914
915          } else
916          {
917             $this->_debugprint("Error parsing memcached response\n");
918             return 0;
919          }
920       }
921    }
922
923    // }}}
924    // {{{ _set()
925
926    /**
927     * Performs the requested storage operation to the memcache server
928     *
929     * @param   string   $cmd     Command to perform
930     * @param   string   $key     Key to act on
931     * @param   mixed    $val     What we need to store
932     * @param   integer  $exp     When it should expire
933     *
934     * @return  boolean
935     * @access  private
936     */
937    function _set ($cmd, $key, $val, $exp)
938    {
939       if (!$this->_active)
940          return false;
941
942       $sock = $this->get_sock($key);
943       if (!is_resource($sock))
944          return false;
945
946       @$this->stats[$cmd]++;
947
948       $flags = 0;
949
950       if (!is_scalar($val))
951       {
952          $val = serialize($val);
953          $flags |= MEMCACHE_SERIALIZED;
954          if ($this->_debug)
955             $this->_debugprint(sprintf("client: serializing data as it is not scalar\n"));
956       }
957
958       $len = strlen($val);
959
960       if ($this->_have_zlib && $this->_compress_enable &&
961           $this->_compress_threshold && $len >= $this->_compress_threshold)
962       {
963          $c_val = gzcompress($val, 9);
964          $c_len = strlen($c_val);
965
966          if ($c_len < $len*(1 - COMPRESSION_SAVINGS))
967          {
968             if ($this->_debug)
969                $this->_debugprint(sprintf("client: compressing data; was %d bytes is now %d bytes\n", $len, $c_len));
970             $val = $c_val;
971             $len = $c_len;
972             $flags |= MEMCACHE_COMPRESSED;
973          }
974       }
975       if (!$this->_safe_fwrite($sock, "$cmd $key $flags $exp $len\r\n$val\r\n"))
976          return $this->_dead_sock($sock);
977
978       $line = trim(fgets($sock));
979
980       if ($this->_debug)
981       {
982          if ($flags & MEMCACHE_COMPRESSED)
983             $val = 'compressed data';
984          $this->_debugprint(sprintf("MemCache: %s %s => %s (%s)\n", $cmd, $key, $val, $line));
985       }
986       if ($line == "STORED")
987          return true;
988       return false;
989    }
990
991    // }}}
992    // {{{ sock_to_host()
993
994    /**
995     * Returns the socket for the host
996     *
997     * @param   string   $host    Host:IP to get socket for
998     *
999     * @return  mixed    IO Stream or false
1000     * @access  private
1001     */
1002    function sock_to_host ($host)
1003    {
1004       if (isset($this->_cache_sock[$host]))
1005          return $this->_cache_sock[$host];
1006
1007       $sock = null;
1008       $now = time();
1009       list ($ip, /* $port */) = explode (":", $host);
1010       if (isset($this->_host_dead[$host]) && $this->_host_dead[$host] > $now ||
1011           isset($this->_host_dead[$ip]) && $this->_host_dead[$ip] > $now)
1012          return null;
1013
1014       if (!$this->_connect_sock($sock, $host))
1015          return $this->_dead_sock($host);
1016
1017       // Do not buffer writes
1018       stream_set_write_buffer($sock, 0);
1019
1020       $this->_cache_sock[$host] = $sock;
1021
1022       return $this->_cache_sock[$host];
1023    }
1024
1025    function _debugprint($str){
1026       print($str);
1027    }
1028
1029    /**
1030     * Write to a stream, timing out after the correct amount of time
1031     *
1032     * @return bool false on failure, true on success
1033     */
1034     /*
1035    function _safe_fwrite($f, $buf, $len = false) {
1036       stream_set_blocking($f, 0);
1037
1038       if ($len === false) {
1039          wfDebug("Writing " . strlen( $buf ) . " bytes\n");
1040          $bytesWritten = fwrite($f, $buf);
1041       } else {
1042          wfDebug("Writing $len bytes\n");
1043          $bytesWritten = fwrite($f, $buf, $len);
1044       }
1045       $n = stream_select($r=NULL, $w = array($f), $e = NULL, 10, 0);
1046       #   $this->_timeout_seconds, $this->_timeout_microseconds);
1047
1048       wfDebug("stream_select returned $n\n");
1049       stream_set_blocking($f, 1);
1050       return $n == 1;
1051       return $bytesWritten;
1052    }*/
1053
1054    /**
1055     * Original behaviour
1056     */
1057    function _safe_fwrite($f, $buf, $len = false) {
1058       if ($len === false) {
1059          $bytesWritten = fwrite($f, $buf);
1060       } else {
1061          $bytesWritten = fwrite($f, $buf, $len);
1062       }
1063       return $bytesWritten;
1064    }
1065
1066    /**
1067     * Flush the read buffer of a stream
1068     */
1069    function _flush_read_buffer($f) {
1070       if (!is_resource($f)) {
1071          return;
1072       }
1073       $n = stream_select($r=array($f), $w = NULL, $e = NULL, 0, 0);
1074       while ($n == 1 && !feof($f)) {
1075          fread($f, 1024);
1076          $n = stream_select($r=array($f), $w = NULL, $e = NULL, 0, 0);
1077       }
1078    }
1079
1080    // }}}
1081    // }}}
1082    // }}}
1083 }
1084
1085 // vim: sts=3 sw=3 et
1086
1087 // }}}
1088