blob: b3efb2d201885b34496e05fb306192d11bf70168 [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
11 * @author Mark Slee <mcslee@facebook.com>
12 */
13
Mark Sleeade2c832006-09-08 03:41:50 +000014/** Inherits from Socket */
Mark Slee1c4a5592006-09-25 21:32:05 +000015include_once $GLOBALS['THRIFT_ROOT'].'/transport/TSocket.php';
Mark Sleeade2c832006-09-08 03:41:50 +000016
17/**
18 * This library makes use of APC cache to make hosts as down in a web
19 * environment. If you are running from the CLI or on a system without APC
20 * installed, then these null functions will step in and act like cache
21 * misses.
22 */
23if (!function_exists('apc_fetch')) {
24 function apc_fetch($key) { return FALSE; }
25 function apc_store($key, $var, $ttl=0) { return FALSE; }
26}
27
28/**
29 * Sockets implementation of the TTransport interface that allows connection
30 * to a pool of servers.
31 *
32 * @package thrift.transport
33 * @author Mark Slee <mcslee@facebook.com>
34 */
35class TSocketPool extends TSocket {
36
37 /**
Mark Slee3f11b7a2006-10-04 19:02:03 +000038 * Remote servers. Array of associative arrays with 'host' and 'port' keys
Mark Sleeade2c832006-09-08 03:41:50 +000039 */
Mark Slee3f11b7a2006-10-04 19:02:03 +000040 private $servers_ = array();
Mark Sleeade2c832006-09-08 03:41:50 +000041
42 /**
43 * How many times to retry each host in connect
44 *
45 * @var int
46 */
47 private $numRetries_ = 1;
48
49 /**
50 * Retry interval in seconds, how long to not try a host if it has been
51 * marked as down.
52 *
53 * @var int
54 */
55 private $retryInterval_ = 60;
56
57 /**
58 * Max consecutive failures before marking a host down.
59 *
60 * @var int
61 */
62 private $maxConsecutiveFailures_ = 1;
63
64 /**
65 * Try hosts in order? or Randomized?
66 *
67 * @var bool
68 */
69 private $randomize_ = TRUE;
70
71 /**
72 * Always try last host, even if marked down?
73 *
74 * @var bool
75 */
76 private $alwaysTryLast_ = TRUE;
77
78 /**
79 * Socket pool constructor
80 *
Mark Sleead58f952007-01-03 19:23:50 +000081 * @param array $hosts List of remote hostnames
82 * @param mixed $ports Array of remote ports, or a single common port
83 * @param bool $persist Whether to use a persistent socket
84 * @param mixed $debugHandler Function for error logging
Mark Sleeade2c832006-09-08 03:41:50 +000085 */
86 public function __construct($hosts=array('localhost'),
87 $ports=array(9090),
Mark Sleead58f952007-01-03 19:23:50 +000088 $persist=FALSE,
89 $debugHandler=null) {
90 parent::__construct(null, 0, $persist, $debugHandler);
91
Mark Slee3f11b7a2006-10-04 19:02:03 +000092 if (!is_array($ports)) {
93 $port = $ports;
94 $ports = array();
95 foreach ($hosts as $key => $val) {
96 $ports[$key] = $port;
Mark Sleeade2c832006-09-08 03:41:50 +000097 }
98 }
Mark Slee3f11b7a2006-10-04 19:02:03 +000099
100 foreach ($hosts as $key => $host) {
101 $this->servers_ []= array('host' => $host,
102 'port' => $ports[$key]);
103 }
Mark Sleeade2c832006-09-08 03:41:50 +0000104 }
105
106 /**
107 * Sets how many time to keep retrying a host in the connect function.
108 *
109 * @param int $numRetries
110 */
111 public function setNumRetries($numRetries) {
112 $this->numRetries_ = $numRetries;
113 }
114
115 /**
116 * Sets how long to wait until retrying a host if it was marked down
117 *
118 * @param int $numRetries
119 */
120 public function setRetryInterval($retryInterval) {
121 $this->retryInterval_ = $retryInterval;
122 }
123
124 /**
125 * Sets how many time to keep retrying a host before marking it as down.
126 *
127 * @param int $numRetries
128 */
129 public function setMaxConsecutiveFailures($maxConsecutiveFailures) {
130 $this->maxConsecutiveFailures_ = $maxConsecutiveFailures;
131 }
132
133 /**
134 * Turns randomization in connect order on or off.
135 *
136 * @param bool $randomize
137 */
138 public function setRandomize($randomize) {
139 $this->randomize_ = $randomize;
140 }
141
142 /**
143 * Whether to always try the last server.
144 *
145 * @param bool $alwaysTryLast
146 */
147 public function setAlwaysTryLast($alwaysTryLast) {
148 $this->alwaysTryLast_ = $alwaysTryLast;
149 }
150
151
152 /**
153 * Connects the socket by iterating through all the servers in the pool
154 * and trying to find one that works.
155 */
156 public function open() {
Mark Slee3f11b7a2006-10-04 19:02:03 +0000157 // Check if we want order randomization
Mark Sleeade2c832006-09-08 03:41:50 +0000158 if ($this->randomize_) {
Mark Slee3f11b7a2006-10-04 19:02:03 +0000159 shuffle($this->servers_);
Mark Sleeade2c832006-09-08 03:41:50 +0000160 }
Mark Sleeade2c832006-09-08 03:41:50 +0000161
Mark Slee3f11b7a2006-10-04 19:02:03 +0000162 // Count servers to identify the "last" one
163 $numServers = count($this->servers_);
164
165 for ($i = 0; $i < $numServers; ++$i) {
166
167 // This extracts the $host and $port variables
168 extract($this->servers_[$i]);
Mark Sleeade2c832006-09-08 03:41:50 +0000169
170 // Check APC cache for a record of this server being down
Mark Slee3f11b7a2006-10-04 19:02:03 +0000171 $failtimeKey = 'thrift_failtime:'.$host.':'.$port.'~';
Mark Sleeade2c832006-09-08 03:41:50 +0000172
173 // Cache miss? Assume it's OK
174 $lastFailtime = apc_fetch($failtimeKey);
175 if ($lastFailtime === FALSE) {
176 $lastFailtime = 0;
177 }
178
179 $retryIntervalPassed = FALSE;
180
181 // Cache hit...make sure enough the retry interval has elapsed
182 if ($lastFailtime > 0) {
183 $elapsed = time() - $lastFailtime;
robertb0fac3e2007-01-15 23:53:25 +0000184 if ($elapsed > $this->retryInterval_) {
Mark Sleeade2c832006-09-08 03:41:50 +0000185 $retryIntervalPassed = TRUE;
186 if ($this->debug_) {
Mark Sleee7714a62007-01-11 01:26:00 +0000187 call_user_func($this->debugHandler_,
188 'TSocketPool: retryInterval '.
189 '('.$this->retryInterval_.') '.
190 'has passed for host '.$host.':'.$port);
Mark Sleeade2c832006-09-08 03:41:50 +0000191 }
192 }
193 }
194
195 // Only connect if not in the middle of a fail interval, OR if this
196 // is the LAST server we are trying, just hammer away on it
197 $isLastServer = FALSE;
Mark Sleea09e34e2007-01-03 18:45:04 +0000198 if ($this->alwaysTryLast_) {
Mark Slee3f11b7a2006-10-04 19:02:03 +0000199 $isLastServer = ($i == ($numServers - 1));
Mark Sleeade2c832006-09-08 03:41:50 +0000200 }
201
202 if (($lastFailtime === 0) ||
203 ($isLastServer) ||
204 ($lastFailtime > 0 && $retryIntervalPassed)) {
205
206 // Set underlying TSocket params to this one
207 $this->host_ = $host;
208 $this->port_ = $port;
209
Mark Slee3f11b7a2006-10-04 19:02:03 +0000210 // Try up to numRetries_ connections per server
Mark Sleeade2c832006-09-08 03:41:50 +0000211 for ($attempt = 0; $attempt < $this->numRetries_; $attempt++) {
212 try {
Mark Slee3f11b7a2006-10-04 19:02:03 +0000213 // Use the underlying TSocket open function
Mark Sleeade2c832006-09-08 03:41:50 +0000214 parent::open();
215
216 // Only clear the failure counts if required to do so
217 if ($lastFailtime > 0) {
218 apc_store($failtimeKey, 0);
219 }
Mark Slee3f11b7a2006-10-04 19:02:03 +0000220
Mark Sleeade2c832006-09-08 03:41:50 +0000221 // Successful connection, return now
222 return;
223
Mark Slee76791962007-03-14 02:47:35 +0000224 } catch (TException $tx) {
Mark Sleeade2c832006-09-08 03:41:50 +0000225 // Connection failed
226 }
227 }
228
229 // Mark failure of this host in the cache
230 $consecfailsKey = 'thrift_consecfails:'.$host.':'.$port.'~';
231
232 // Ignore cache misses
233 $consecfails = apc_fetch($consecfailsKey);
234 if ($consecfails === FALSE) {
235 $consecfails = 0;
236 }
237
238 // Increment by one
239 $consecfails++;
240
241 // Log and cache this failure
242 if ($consecfails >= $this->maxConsecutiveFailures_) {
243 if ($this->debug_) {
Mark Sleee7714a62007-01-11 01:26:00 +0000244 call_user_func($this->debugHandler_,
245 'TSocketPool: marking '.$host.':'.$port.
Karl Lehenbauer893ef722007-01-17 18:56:10 +0000246 ' as down for '.$this->retryInterval_.' secs '.
Mark Sleee7714a62007-01-11 01:26:00 +0000247 'after '.$consecfails.' failed attempts.');
Mark Sleeade2c832006-09-08 03:41:50 +0000248 }
249 // Store the failure time
250 apc_store($failtimeKey, time());
251
252 // Clear the count of consecutive failures
253 apc_store($consecfailsKey, 0);
254 } else {
255 apc_store($consecfailsKey, $consecfails);
256 }
257 }
Mark Slee3f11b7a2006-10-04 19:02:03 +0000258 }
Mark Sleeade2c832006-09-08 03:41:50 +0000259
260 // Holy shit we failed them all. The system is totally ill!
261 $error = 'TSocketPool: All hosts in pool are down. ';
Mark Slee588e4522006-11-15 22:23:06 +0000262 $hosts = array();
263 foreach ($this->servers_ as $server) {
264 $hosts []= $server['host'].':'.$server['port'];
265 }
266 $hostlist = implode(',', $hosts);
267 $error .= '('.$hostlist.')';
Mark Sleeade2c832006-09-08 03:41:50 +0000268 if ($this->debug_) {
Mark Sleee7714a62007-01-11 01:26:00 +0000269 call_user_func($this->debugHandler_, $error);
Mark Sleeade2c832006-09-08 03:41:50 +0000270 }
Mark Slee76791962007-03-14 02:47:35 +0000271 throw new TException($error);
Mark Sleeade2c832006-09-08 03:41:50 +0000272 }
273}
274
275?>