blob: 8036bea178e3e24c1d7a2cd91ca7ecef047eaa6c [file] [log] [blame]
Mark Sleeade2c832006-09-08 03:41:50 +00001<?php
2
3/** Inherits from Socket */
Mark Slee1c4a5592006-09-25 21:32:05 +00004include_once $GLOBALS['THRIFT_ROOT'].'/transport/TSocket.php';
Mark Sleeade2c832006-09-08 03:41:50 +00005
6/**
7 * This library makes use of APC cache to make hosts as down in a web
8 * environment. If you are running from the CLI or on a system without APC
9 * installed, then these null functions will step in and act like cache
10 * misses.
11 */
12if (!function_exists('apc_fetch')) {
13 function apc_fetch($key) { return FALSE; }
14 function apc_store($key, $var, $ttl=0) { return FALSE; }
15}
16
17/**
18 * Sockets implementation of the TTransport interface that allows connection
19 * to a pool of servers.
20 *
21 * @package thrift.transport
22 * @author Mark Slee <mcslee@facebook.com>
23 */
24class TSocketPool extends TSocket {
25
26 /**
Mark Slee3f11b7a2006-10-04 19:02:03 +000027 * Remote servers. Array of associative arrays with 'host' and 'port' keys
Mark Sleeade2c832006-09-08 03:41:50 +000028 */
Mark Slee3f11b7a2006-10-04 19:02:03 +000029 private $servers_ = array();
Mark Sleeade2c832006-09-08 03:41:50 +000030
31 /**
32 * How many times to retry each host in connect
33 *
34 * @var int
35 */
36 private $numRetries_ = 1;
37
38 /**
39 * Retry interval in seconds, how long to not try a host if it has been
40 * marked as down.
41 *
42 * @var int
43 */
44 private $retryInterval_ = 60;
45
46 /**
47 * Max consecutive failures before marking a host down.
48 *
49 * @var int
50 */
51 private $maxConsecutiveFailures_ = 1;
52
53 /**
54 * Try hosts in order? or Randomized?
55 *
56 * @var bool
57 */
58 private $randomize_ = TRUE;
59
60 /**
61 * Always try last host, even if marked down?
62 *
63 * @var bool
64 */
65 private $alwaysTryLast_ = TRUE;
66
67 /**
Mark Sleead58f952007-01-03 19:23:50 +000068 * User can supply their own debug handler instead of error_log
69 *
70 * @var mixed
71 */
72 private $debugHandler_ = null;
73
74 /**
Mark Sleeade2c832006-09-08 03:41:50 +000075 * Socket pool constructor
76 *
Mark Sleead58f952007-01-03 19:23:50 +000077 * @param array $hosts List of remote hostnames
78 * @param mixed $ports Array of remote ports, or a single common port
79 * @param bool $persist Whether to use a persistent socket
80 * @param mixed $debugHandler Function for error logging
Mark Sleeade2c832006-09-08 03:41:50 +000081 */
82 public function __construct($hosts=array('localhost'),
83 $ports=array(9090),
Mark Sleead58f952007-01-03 19:23:50 +000084 $persist=FALSE,
85 $debugHandler=null) {
86 parent::__construct(null, 0, $persist, $debugHandler);
87
88 $this->debugHandler_ = $debugHandler ? $debugHandler : 'error_log';
Mark Sleeade2c832006-09-08 03:41:50 +000089
Mark Slee3f11b7a2006-10-04 19:02:03 +000090 if (!is_array($ports)) {
91 $port = $ports;
92 $ports = array();
93 foreach ($hosts as $key => $val) {
94 $ports[$key] = $port;
Mark Sleeade2c832006-09-08 03:41:50 +000095 }
96 }
Mark Slee3f11b7a2006-10-04 19:02:03 +000097
98 foreach ($hosts as $key => $host) {
99 $this->servers_ []= array('host' => $host,
100 'port' => $ports[$key]);
101 }
Mark Sleeade2c832006-09-08 03:41:50 +0000102 }
103
104 /**
105 * Sets how many time to keep retrying a host in the connect function.
106 *
107 * @param int $numRetries
108 */
109 public function setNumRetries($numRetries) {
110 $this->numRetries_ = $numRetries;
111 }
112
113 /**
114 * Sets how long to wait until retrying a host if it was marked down
115 *
116 * @param int $numRetries
117 */
118 public function setRetryInterval($retryInterval) {
119 $this->retryInterval_ = $retryInterval;
120 }
121
122 /**
123 * Sets how many time to keep retrying a host before marking it as down.
124 *
125 * @param int $numRetries
126 */
127 public function setMaxConsecutiveFailures($maxConsecutiveFailures) {
128 $this->maxConsecutiveFailures_ = $maxConsecutiveFailures;
129 }
130
131 /**
132 * Turns randomization in connect order on or off.
133 *
134 * @param bool $randomize
135 */
136 public function setRandomize($randomize) {
137 $this->randomize_ = $randomize;
138 }
139
140 /**
141 * Whether to always try the last server.
142 *
143 * @param bool $alwaysTryLast
144 */
145 public function setAlwaysTryLast($alwaysTryLast) {
146 $this->alwaysTryLast_ = $alwaysTryLast;
147 }
148
149
150 /**
151 * Connects the socket by iterating through all the servers in the pool
152 * and trying to find one that works.
153 */
154 public function open() {
Mark Slee3f11b7a2006-10-04 19:02:03 +0000155 // Check if we want order randomization
Mark Sleeade2c832006-09-08 03:41:50 +0000156 if ($this->randomize_) {
Mark Slee3f11b7a2006-10-04 19:02:03 +0000157 shuffle($this->servers_);
Mark Sleeade2c832006-09-08 03:41:50 +0000158 }
Mark Sleeade2c832006-09-08 03:41:50 +0000159
Mark Slee3f11b7a2006-10-04 19:02:03 +0000160 // Count servers to identify the "last" one
161 $numServers = count($this->servers_);
162
163 for ($i = 0; $i < $numServers; ++$i) {
164
165 // This extracts the $host and $port variables
166 extract($this->servers_[$i]);
Mark Sleeade2c832006-09-08 03:41:50 +0000167
168 // Check APC cache for a record of this server being down
Mark Slee3f11b7a2006-10-04 19:02:03 +0000169 $failtimeKey = 'thrift_failtime:'.$host.':'.$port.'~';
Mark Sleeade2c832006-09-08 03:41:50 +0000170
171 // Cache miss? Assume it's OK
172 $lastFailtime = apc_fetch($failtimeKey);
173 if ($lastFailtime === FALSE) {
174 $lastFailtime = 0;
175 }
176
177 $retryIntervalPassed = FALSE;
178
179 // Cache hit...make sure enough the retry interval has elapsed
180 if ($lastFailtime > 0) {
181 $elapsed = time() - $lastFailtime;
182 if ($elapsed > $retryInterval) {
183 $retryIntervalPassed = TRUE;
184 if ($this->debug_) {
Mark Sleee7714a62007-01-11 01:26:00 +0000185 call_user_func($this->debugHandler_,
186 'TSocketPool: retryInterval '.
187 '('.$this->retryInterval_.') '.
188 'has passed for host '.$host.':'.$port);
Mark Sleeade2c832006-09-08 03:41:50 +0000189 }
190 }
191 }
192
193 // Only connect if not in the middle of a fail interval, OR if this
194 // is the LAST server we are trying, just hammer away on it
195 $isLastServer = FALSE;
Mark Sleea09e34e2007-01-03 18:45:04 +0000196 if ($this->alwaysTryLast_) {
Mark Slee3f11b7a2006-10-04 19:02:03 +0000197 $isLastServer = ($i == ($numServers - 1));
Mark Sleeade2c832006-09-08 03:41:50 +0000198 }
199
200 if (($lastFailtime === 0) ||
201 ($isLastServer) ||
202 ($lastFailtime > 0 && $retryIntervalPassed)) {
203
204 // Set underlying TSocket params to this one
205 $this->host_ = $host;
206 $this->port_ = $port;
207
Mark Slee3f11b7a2006-10-04 19:02:03 +0000208 // Try up to numRetries_ connections per server
Mark Sleeade2c832006-09-08 03:41:50 +0000209 for ($attempt = 0; $attempt < $this->numRetries_; $attempt++) {
210 try {
Mark Slee3f11b7a2006-10-04 19:02:03 +0000211 // Use the underlying TSocket open function
Mark Sleeade2c832006-09-08 03:41:50 +0000212 parent::open();
213
214 // Only clear the failure counts if required to do so
215 if ($lastFailtime > 0) {
216 apc_store($failtimeKey, 0);
217 }
Mark Slee3f11b7a2006-10-04 19:02:03 +0000218
Mark Sleeade2c832006-09-08 03:41:50 +0000219 // Successful connection, return now
220 return;
221
222 } catch (Exception $x) {
223 // Connection failed
224 }
225 }
226
227 // Mark failure of this host in the cache
228 $consecfailsKey = 'thrift_consecfails:'.$host.':'.$port.'~';
229
230 // Ignore cache misses
231 $consecfails = apc_fetch($consecfailsKey);
232 if ($consecfails === FALSE) {
233 $consecfails = 0;
234 }
235
236 // Increment by one
237 $consecfails++;
238
239 // Log and cache this failure
240 if ($consecfails >= $this->maxConsecutiveFailures_) {
241 if ($this->debug_) {
Mark Sleee7714a62007-01-11 01:26:00 +0000242 call_user_func($this->debugHandler_,
243 'TSocketPool: marking '.$host.':'.$port.
244 ' as down for '.$this->retryInterval.' secs '.
245 'after '.$consecfails.' failed attempts.');
Mark Sleeade2c832006-09-08 03:41:50 +0000246 }
247 // Store the failure time
248 apc_store($failtimeKey, time());
249
250 // Clear the count of consecutive failures
251 apc_store($consecfailsKey, 0);
252 } else {
253 apc_store($consecfailsKey, $consecfails);
254 }
255 }
Mark Slee3f11b7a2006-10-04 19:02:03 +0000256 }
Mark Sleeade2c832006-09-08 03:41:50 +0000257
258 // Holy shit we failed them all. The system is totally ill!
259 $error = 'TSocketPool: All hosts in pool are down. ';
Mark Slee588e4522006-11-15 22:23:06 +0000260 $hosts = array();
261 foreach ($this->servers_ as $server) {
262 $hosts []= $server['host'].':'.$server['port'];
263 }
264 $hostlist = implode(',', $hosts);
265 $error .= '('.$hostlist.')';
Mark Sleeade2c832006-09-08 03:41:50 +0000266 if ($this->debug_) {
Mark Sleee7714a62007-01-11 01:26:00 +0000267 call_user_func($this->debugHandler_, $error);
Mark Sleeade2c832006-09-08 03:41:50 +0000268 }
269 throw new Exception($error);
270 }
271}
272
273?>