blob: 8d75ce373cf43d9cd09c9276d7c802b4a529f417 [file] [log] [blame]
Mark Sleeade2c832006-09-08 03:41:50 +00001<?php
2
Mark Slee4902c052007-03-01 00:31:30 +00003/**
4 * Copyright (c) 2006- Facebook
5 * Distributed under the Thrift Software License
6 *
7 * See accompanying file LICENSE or visit the Thrift site at:
8 * http://developers.facebook.com/thrift/
9 *
10 * @package thrift.transport
Mark Slee4902c052007-03-01 00:31:30 +000011 */
12
Mark Sleeade2c832006-09-08 03:41:50 +000013/** Inherits from Socket */
Mark Slee1c4a5592006-09-25 21:32:05 +000014include_once $GLOBALS['THRIFT_ROOT'].'/transport/TSocket.php';
Mark Sleeade2c832006-09-08 03:41:50 +000015
16/**
17 * This library makes use of APC cache to make hosts as down in a web
18 * environment. If you are running from the CLI or on a system without APC
19 * installed, then these null functions will step in and act like cache
20 * misses.
21 */
22if (!function_exists('apc_fetch')) {
23 function apc_fetch($key) { return FALSE; }
24 function apc_store($key, $var, $ttl=0) { return FALSE; }
25}
26
27/**
28 * Sockets implementation of the TTransport interface that allows connection
29 * to a pool of servers.
30 *
31 * @package thrift.transport
Mark Sleeade2c832006-09-08 03:41:50 +000032 */
33class TSocketPool extends TSocket {
34
35 /**
Mark Slee3f11b7a2006-10-04 19:02:03 +000036 * Remote servers. Array of associative arrays with 'host' and 'port' keys
Mark Sleeade2c832006-09-08 03:41:50 +000037 */
Mark Slee3f11b7a2006-10-04 19:02:03 +000038 private $servers_ = array();
Mark Sleeade2c832006-09-08 03:41:50 +000039
40 /**
41 * How many times to retry each host in connect
42 *
43 * @var int
44 */
45 private $numRetries_ = 1;
46
47 /**
48 * Retry interval in seconds, how long to not try a host if it has been
49 * marked as down.
50 *
51 * @var int
52 */
53 private $retryInterval_ = 60;
54
55 /**
56 * Max consecutive failures before marking a host down.
57 *
58 * @var int
59 */
60 private $maxConsecutiveFailures_ = 1;
61
62 /**
63 * Try hosts in order? or Randomized?
64 *
65 * @var bool
66 */
67 private $randomize_ = TRUE;
68
69 /**
70 * Always try last host, even if marked down?
71 *
72 * @var bool
73 */
74 private $alwaysTryLast_ = TRUE;
75
76 /**
77 * Socket pool constructor
78 *
Mark Sleead58f952007-01-03 19:23:50 +000079 * @param array $hosts List of remote hostnames
80 * @param mixed $ports Array of remote ports, or a single common port
81 * @param bool $persist Whether to use a persistent socket
82 * @param mixed $debugHandler Function for error logging
Mark Sleeade2c832006-09-08 03:41:50 +000083 */
84 public function __construct($hosts=array('localhost'),
85 $ports=array(9090),
Mark Sleead58f952007-01-03 19:23:50 +000086 $persist=FALSE,
87 $debugHandler=null) {
88 parent::__construct(null, 0, $persist, $debugHandler);
89
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 /**
Mark Slee0cdc6c82007-11-13 10:19:08 +0000105 * Add a server to the pool
106 *
107 * This function does not prevent you from adding a duplicate server entry.
108 *
109 * @param string $host hostname or IP
110 * @param int $port port
111 */
112 public function addServer($host, $port) {
113 $this->servers_[] = array('host' => $host, 'port' => $port);
114 }
115
116 /**
Mark Sleeade2c832006-09-08 03:41:50 +0000117 * Sets how many time to keep retrying a host in the connect function.
118 *
119 * @param int $numRetries
120 */
121 public function setNumRetries($numRetries) {
122 $this->numRetries_ = $numRetries;
123 }
124
125 /**
126 * Sets how long to wait until retrying a host if it was marked down
127 *
128 * @param int $numRetries
129 */
130 public function setRetryInterval($retryInterval) {
131 $this->retryInterval_ = $retryInterval;
132 }
133
134 /**
135 * Sets how many time to keep retrying a host before marking it as down.
136 *
137 * @param int $numRetries
138 */
139 public function setMaxConsecutiveFailures($maxConsecutiveFailures) {
140 $this->maxConsecutiveFailures_ = $maxConsecutiveFailures;
141 }
142
143 /**
144 * Turns randomization in connect order on or off.
145 *
146 * @param bool $randomize
147 */
148 public function setRandomize($randomize) {
149 $this->randomize_ = $randomize;
150 }
151
152 /**
153 * Whether to always try the last server.
154 *
155 * @param bool $alwaysTryLast
156 */
157 public function setAlwaysTryLast($alwaysTryLast) {
158 $this->alwaysTryLast_ = $alwaysTryLast;
159 }
160
161
162 /**
163 * Connects the socket by iterating through all the servers in the pool
164 * and trying to find one that works.
165 */
166 public function open() {
Mark Slee3f11b7a2006-10-04 19:02:03 +0000167 // Check if we want order randomization
Mark Sleeade2c832006-09-08 03:41:50 +0000168 if ($this->randomize_) {
Mark Slee3f11b7a2006-10-04 19:02:03 +0000169 shuffle($this->servers_);
Mark Sleeade2c832006-09-08 03:41:50 +0000170 }
Mark Sleeade2c832006-09-08 03:41:50 +0000171
Mark Slee3f11b7a2006-10-04 19:02:03 +0000172 // Count servers to identify the "last" one
173 $numServers = count($this->servers_);
174
175 for ($i = 0; $i < $numServers; ++$i) {
176
177 // This extracts the $host and $port variables
178 extract($this->servers_[$i]);
Mark Sleeade2c832006-09-08 03:41:50 +0000179
180 // Check APC cache for a record of this server being down
Mark Slee3f11b7a2006-10-04 19:02:03 +0000181 $failtimeKey = 'thrift_failtime:'.$host.':'.$port.'~';
Mark Sleeade2c832006-09-08 03:41:50 +0000182
183 // Cache miss? Assume it's OK
184 $lastFailtime = apc_fetch($failtimeKey);
185 if ($lastFailtime === FALSE) {
186 $lastFailtime = 0;
187 }
188
189 $retryIntervalPassed = FALSE;
190
191 // Cache hit...make sure enough the retry interval has elapsed
192 if ($lastFailtime > 0) {
193 $elapsed = time() - $lastFailtime;
robertb0fac3e2007-01-15 23:53:25 +0000194 if ($elapsed > $this->retryInterval_) {
Mark Sleeade2c832006-09-08 03:41:50 +0000195 $retryIntervalPassed = TRUE;
196 if ($this->debug_) {
Mark Sleee7714a62007-01-11 01:26:00 +0000197 call_user_func($this->debugHandler_,
198 'TSocketPool: retryInterval '.
199 '('.$this->retryInterval_.') '.
200 'has passed for host '.$host.':'.$port);
Mark Sleeade2c832006-09-08 03:41:50 +0000201 }
202 }
203 }
204
205 // Only connect if not in the middle of a fail interval, OR if this
206 // is the LAST server we are trying, just hammer away on it
207 $isLastServer = FALSE;
Mark Sleea09e34e2007-01-03 18:45:04 +0000208 if ($this->alwaysTryLast_) {
Mark Slee3f11b7a2006-10-04 19:02:03 +0000209 $isLastServer = ($i == ($numServers - 1));
Mark Sleeade2c832006-09-08 03:41:50 +0000210 }
211
212 if (($lastFailtime === 0) ||
213 ($isLastServer) ||
214 ($lastFailtime > 0 && $retryIntervalPassed)) {
215
216 // Set underlying TSocket params to this one
217 $this->host_ = $host;
218 $this->port_ = $port;
Mark Slee0cdc6c82007-11-13 10:19:08 +0000219
Mark Slee3f11b7a2006-10-04 19:02:03 +0000220 // Try up to numRetries_ connections per server
Mark Sleeade2c832006-09-08 03:41:50 +0000221 for ($attempt = 0; $attempt < $this->numRetries_; $attempt++) {
222 try {
Mark Slee3f11b7a2006-10-04 19:02:03 +0000223 // Use the underlying TSocket open function
Mark Sleeade2c832006-09-08 03:41:50 +0000224 parent::open();
225
226 // Only clear the failure counts if required to do so
227 if ($lastFailtime > 0) {
228 apc_store($failtimeKey, 0);
229 }
Mark Slee3f11b7a2006-10-04 19:02:03 +0000230
Mark Sleeade2c832006-09-08 03:41:50 +0000231 // Successful connection, return now
232 return;
233
Mark Slee76791962007-03-14 02:47:35 +0000234 } catch (TException $tx) {
Mark Sleeade2c832006-09-08 03:41:50 +0000235 // Connection failed
236 }
237 }
238
239 // Mark failure of this host in the cache
240 $consecfailsKey = 'thrift_consecfails:'.$host.':'.$port.'~';
241
242 // Ignore cache misses
243 $consecfails = apc_fetch($consecfailsKey);
244 if ($consecfails === FALSE) {
245 $consecfails = 0;
246 }
247
248 // Increment by one
249 $consecfails++;
250
251 // Log and cache this failure
252 if ($consecfails >= $this->maxConsecutiveFailures_) {
253 if ($this->debug_) {
Mark Sleee7714a62007-01-11 01:26:00 +0000254 call_user_func($this->debugHandler_,
255 'TSocketPool: marking '.$host.':'.$port.
Karl Lehenbauer893ef722007-01-17 18:56:10 +0000256 ' as down for '.$this->retryInterval_.' secs '.
Mark Sleee7714a62007-01-11 01:26:00 +0000257 'after '.$consecfails.' failed attempts.');
Mark Sleeade2c832006-09-08 03:41:50 +0000258 }
259 // Store the failure time
260 apc_store($failtimeKey, time());
261
262 // Clear the count of consecutive failures
263 apc_store($consecfailsKey, 0);
264 } else {
265 apc_store($consecfailsKey, $consecfails);
266 }
267 }
Mark Slee3f11b7a2006-10-04 19:02:03 +0000268 }
Mark Sleeade2c832006-09-08 03:41:50 +0000269
270 // Holy shit we failed them all. The system is totally ill!
271 $error = 'TSocketPool: All hosts in pool are down. ';
Mark Slee588e4522006-11-15 22:23:06 +0000272 $hosts = array();
273 foreach ($this->servers_ as $server) {
274 $hosts []= $server['host'].':'.$server['port'];
275 }
276 $hostlist = implode(',', $hosts);
277 $error .= '('.$hostlist.')';
Mark Sleeade2c832006-09-08 03:41:50 +0000278 if ($this->debug_) {
Mark Sleee7714a62007-01-11 01:26:00 +0000279 call_user_func($this->debugHandler_, $error);
Mark Sleeade2c832006-09-08 03:41:50 +0000280 }
Mark Slee76791962007-03-14 02:47:35 +0000281 throw new TException($error);
Mark Sleeade2c832006-09-08 03:41:50 +0000282 }
283}
284
285?>