blob: 05fb35f673502514a57b4770716dcfb63177cff0 [file] [log] [blame]
Marc Slemkoe03da182006-07-21 21:32:36 +00001#include <config.h>
Mark Sleee8540632006-05-30 09:24:40 +00002#include <sys/socket.h>
3#include <arpa/inet.h>
4#include <netinet/in.h>
5#include <netinet/tcp.h>
6#include <netdb.h>
7#include <unistd.h>
8#include <errno.h>
Mark Slee29050782006-09-29 00:12:30 +00009#include <fcntl.h>
10#include <sys/select.h>
Mark Sleee8540632006-05-30 09:24:40 +000011
Mark Slee29050782006-09-29 00:12:30 +000012#include "concurrency/Monitor.h"
Marc Slemkod42a2c22006-08-10 03:30:18 +000013#include "TSocket.h"
14#include "TTransportException.h"
Mark Sleee8540632006-05-30 09:24:40 +000015
Marc Slemko6f038a72006-08-03 18:58:09 +000016namespace facebook { namespace thrift { namespace transport {
17
Mark Sleee8540632006-05-30 09:24:40 +000018using namespace std;
Mark Slee29050782006-09-29 00:12:30 +000019using namespace facebook::thrift::concurrency;
Mark Sleee8540632006-05-30 09:24:40 +000020
Mark Slee29050782006-09-29 00:12:30 +000021// Global var to track total socket sys calls
Mark Slee8d7e1f62006-06-07 06:48:56 +000022uint32_t g_socket_syscalls = 0;
23
24/**
25 * TSocket implementation.
26 *
27 * @author Mark Slee <mcslee@facebook.com>
28 */
29
Mark Sleee8540632006-05-30 09:24:40 +000030// Mutex to protect syscalls to netdb
Mark Slee29050782006-09-29 00:12:30 +000031static Monitor s_netdb_monitor;
Mark Sleee8540632006-05-30 09:24:40 +000032
33// TODO(mcslee): Make this an option to the socket class
34#define MAX_RECV_RETRIES 20
Mark Slee29050782006-09-29 00:12:30 +000035
36TSocket::TSocket(string host, int port) :
37 host_(host),
38 port_(port),
Martin Kraemeree341cb2007-02-05 21:40:38 +000039 socket_(-1),
Mark Slee29050782006-09-29 00:12:30 +000040 connTimeout_(0),
41 sendTimeout_(0),
42 recvTimeout_(0),
43 lingerOn_(1),
44 lingerVal_(0),
45 noDelay_(1) {
Mark Sleeb9ff32a2006-11-16 01:00:24 +000046 recvTimeval_.tv_sec = (int)(recvTimeout_/1000);
47 recvTimeval_.tv_usec = (int)((recvTimeout_%1000)*1000);
Mark Sleee8540632006-05-30 09:24:40 +000048}
49
Aditya Agarwalebc99e02007-01-15 23:14:58 +000050TSocket::TSocket() :
51 host_(""),
52 port_(0),
Martin Kraemeree341cb2007-02-05 21:40:38 +000053 socket_(-1),
Aditya Agarwalebc99e02007-01-15 23:14:58 +000054 connTimeout_(0),
55 sendTimeout_(0),
56 recvTimeout_(0),
57 lingerOn_(1),
58 lingerVal_(0),
59 noDelay_(1) {
60 recvTimeval_.tv_sec = (int)(recvTimeout_/1000);
61 recvTimeval_.tv_usec = (int)((recvTimeout_%1000)*1000);
62}
63
Mark Slee29050782006-09-29 00:12:30 +000064TSocket::TSocket(int socket) :
65 host_(""),
66 port_(0),
67 socket_(socket),
68 connTimeout_(0),
69 sendTimeout_(0),
70 recvTimeout_(0),
71 lingerOn_(1),
72 lingerVal_(0),
73 noDelay_(1) {
Mark Sleeb9ff32a2006-11-16 01:00:24 +000074 recvTimeval_.tv_sec = (int)(recvTimeout_/1000);
75 recvTimeval_.tv_usec = (int)((recvTimeout_%1000)*1000);
Mark Slee29050782006-09-29 00:12:30 +000076}
77
Mark Sleee8540632006-05-30 09:24:40 +000078TSocket::~TSocket() {
79 close();
80}
81
Mark Slee8d7e1f62006-06-07 06:48:56 +000082bool TSocket::isOpen() {
Martin Kraemeree341cb2007-02-05 21:40:38 +000083 return (socket_ >= 0);
Mark Slee8d7e1f62006-06-07 06:48:56 +000084}
85
Mark Sleeb9ff32a2006-11-16 01:00:24 +000086bool TSocket::peek() {
87 if (!isOpen()) {
88 return false;
89 }
90 uint8_t buf;
91 int r = recv(socket_, &buf, 1, MSG_PEEK);
92 if (r == -1) {
93 perror("TSocket::peek()");
94 close();
Mark Sleef9831082007-02-20 20:59:21 +000095 throw TTransportException(TTransportException::UNKNOWN, "recv() ERROR:" + errno);
Mark Sleeb9ff32a2006-11-16 01:00:24 +000096 }
97 return (r > 0);
98}
99
Mark Slee8d7e1f62006-06-07 06:48:56 +0000100void TSocket::open() {
Mark Sleea9848d72007-02-21 04:54:05 +0000101 if (isOpen()) {
102 throw TTransportException(TTransportException::ALREADY_OPEN);
103 }
104
Mark Sleee8540632006-05-30 09:24:40 +0000105 // Create socket
106 socket_ = socket(AF_INET, SOCK_STREAM, 0);
107 if (socket_ == -1) {
Mark Slee8d7e1f62006-06-07 06:48:56 +0000108 perror("TSocket::open() socket");
109 close();
Mark Sleef9831082007-02-20 20:59:21 +0000110 throw TTransportException(TTransportException::NOT_OPEN, "socket() ERROR:" + errno);
Mark Sleee8540632006-05-30 09:24:40 +0000111 }
Mark Slee29050782006-09-29 00:12:30 +0000112
113 // Send timeout
114 if (sendTimeout_ > 0) {
115 setSendTimeout(sendTimeout_);
116 }
117
118 // Recv timeout
119 if (recvTimeout_ > 0) {
120 setRecvTimeout(recvTimeout_);
121 }
122
123 // Linger
124 setLinger(lingerOn_, lingerVal_);
125
126 // No delay
127 setNoDelay(noDelay_);
128
Mark Slee8d7e1f62006-06-07 06:48:56 +0000129 // Lookup the hostname
Mark Sleee8540632006-05-30 09:24:40 +0000130 struct sockaddr_in addr;
131 addr.sin_family = AF_INET;
132 addr.sin_port = htons(port_);
133
Mark Sleee8540632006-05-30 09:24:40 +0000134 {
Mark Slee29050782006-09-29 00:12:30 +0000135 // Scope lock on host entry lookup
136 Synchronized s(s_netdb_monitor);
Mark Sleee8540632006-05-30 09:24:40 +0000137 struct hostent *host_entry = gethostbyname(host_.c_str());
138
139 if (host_entry == NULL) {
Mark Slee29050782006-09-29 00:12:30 +0000140 perror("TSocket: dns error: failed call to gethostbyname.");
Mark Sleee8540632006-05-30 09:24:40 +0000141 close();
Mark Sleef9831082007-02-20 20:59:21 +0000142 throw TTransportException(TTransportException::NOT_OPEN, "gethostbyname() failed");
Mark Sleee8540632006-05-30 09:24:40 +0000143 }
144
145 addr.sin_port = htons(port_);
146 memcpy(&addr.sin_addr.s_addr,
147 host_entry->h_addr_list[0],
148 host_entry->h_length);
149 }
Mark Slee29050782006-09-29 00:12:30 +0000150
151 // Set the socket to be non blocking for connect if a timeout exists
152 int flags = fcntl(socket_, F_GETFL, 0);
153 if (connTimeout_ > 0) {
154 fcntl(socket_, F_SETFL, flags | O_NONBLOCK);
155 } else {
156 fcntl(socket_, F_SETFL, flags | ~O_NONBLOCK);
157 }
158
159 // Conn timeout
160 struct timeval c = {(int)(connTimeout_/1000),
161 (int)((connTimeout_%1000)*1000)};
Mark Sleee8540632006-05-30 09:24:40 +0000162
163 // Connect the socket
164 int ret = connect(socket_, (struct sockaddr *)&addr, sizeof(addr));
165
Mark Slee29050782006-09-29 00:12:30 +0000166 if (ret == 0) {
167 goto done;
168 }
169
170 if (errno != EINPROGRESS) {
Mark Sleee8540632006-05-30 09:24:40 +0000171 close();
Mark Slee29050782006-09-29 00:12:30 +0000172 char buff[1024];
173 sprintf(buff, "TSocket::open() connect %s %d", host_.c_str(), port_);
174 perror(buff);
Mark Sleef9831082007-02-20 20:59:21 +0000175 throw TTransportException(TTransportException::NOT_OPEN, "open() ERROR: " + errno);
Mark Sleee8540632006-05-30 09:24:40 +0000176 }
177
Mark Slee29050782006-09-29 00:12:30 +0000178 fd_set fds;
179 FD_ZERO(&fds);
180 FD_SET(socket_, &fds);
181 ret = select(socket_+1, NULL, &fds, NULL, &c);
182
183 if (ret > 0) {
184 // Ensure connected
185 int val;
186 socklen_t lon;
187 lon = sizeof(int);
188 int ret2 = getsockopt(socket_, SOL_SOCKET, SO_ERROR, (void *)&val, &lon);
189 if (ret2 == -1) {
190 close();
191 perror("TSocket::open() getsockopt SO_ERROR");
Mark Sleef9831082007-02-20 20:59:21 +0000192 throw TTransportException(TTransportException::NOT_OPEN, "open() ERROR: " + errno);
Mark Slee29050782006-09-29 00:12:30 +0000193 }
194 if (val == 0) {
195 goto done;
196 }
197 close();
198 perror("TSocket::open() SO_ERROR was set");
Mark Sleef9831082007-02-20 20:59:21 +0000199 throw TTransportException(TTransportException::NOT_OPEN, "open() ERROR: " + errno);
Mark Slee29050782006-09-29 00:12:30 +0000200 } else if (ret == 0) {
201 close();
202 perror("TSocket::open() timeed out");
Mark Sleef9831082007-02-20 20:59:21 +0000203 throw TTransportException(TTransportException::NOT_OPEN, "open() ERROR: " + errno);
Mark Slee29050782006-09-29 00:12:30 +0000204 } else {
205 close();
206 perror("TSocket::open() select error");
Mark Sleef9831082007-02-20 20:59:21 +0000207 throw TTransportException(TTransportException::NOT_OPEN, "open() ERROR: " + errno);
Mark Slee29050782006-09-29 00:12:30 +0000208 }
209
210 done:
211 // Set socket back to normal mode (blocking)
212 fcntl(socket_, F_SETFL, flags);
Mark Sleee8540632006-05-30 09:24:40 +0000213}
214
215void TSocket::close() {
Martin Kraemeree341cb2007-02-05 21:40:38 +0000216 if (socket_ >= 0) {
Mark Sleee8540632006-05-30 09:24:40 +0000217 shutdown(socket_, SHUT_RDWR);
218 ::close(socket_);
219 }
Martin Kraemeree341cb2007-02-05 21:40:38 +0000220 socket_ = -1;
Mark Sleee8540632006-05-30 09:24:40 +0000221}
222
Mark Slee8d7e1f62006-06-07 06:48:56 +0000223uint32_t TSocket::read(uint8_t* buf, uint32_t len) {
Martin Kraemeree341cb2007-02-05 21:40:38 +0000224 if (socket_ < 0) {
Mark Sleef9831082007-02-20 20:59:21 +0000225 throw TTransportException(TTransportException::NOT_OPEN, "Called read on non-open socket");
Mark Slee8d7e1f62006-06-07 06:48:56 +0000226 }
Mark Sleee8540632006-05-30 09:24:40 +0000227
Mark Sleee8540632006-05-30 09:24:40 +0000228 uint32_t retries = 0;
Mark Slee8d7e1f62006-06-07 06:48:56 +0000229
230 try_again:
231 // Read from the socket
232 int got = recv(socket_, buf, len, 0);
233 ++g_socket_syscalls;
234
235 // Check for error on read
Mark Sleec4257802007-01-24 23:14:30 +0000236 if (got < 0) {
Mark Slee8d7e1f62006-06-07 06:48:56 +0000237 // If temporarily out of resources, sleep a bit and try again
238 if (errno == EAGAIN && retries++ < MAX_RECV_RETRIES) {
239 usleep(50);
240 goto try_again;
Mark Sleee8540632006-05-30 09:24:40 +0000241 }
242
Mark Slee8d7e1f62006-06-07 06:48:56 +0000243 // If interrupted, try again
244 if (errno == EINTR && retries++ < MAX_RECV_RETRIES) {
245 goto try_again;
Mark Sleee8540632006-05-30 09:24:40 +0000246 }
247
Mark Sleec4257802007-01-24 23:14:30 +0000248 // Now it's not a try again case, but a real probblez
249 perror("TSocket::read()");
250
Mark Slee8d7e1f62006-06-07 06:48:56 +0000251 // If we disconnect with no linger time
252 if (errno == ECONNRESET) {
Mark Sleef9831082007-02-20 20:59:21 +0000253 throw TTransportException(TTransportException::NOT_OPEN, "ECONNRESET");
Mark Slee8d7e1f62006-06-07 06:48:56 +0000254 }
255
256 // This ish isn't open
257 if (errno == ENOTCONN) {
Mark Sleef9831082007-02-20 20:59:21 +0000258 throw TTransportException(TTransportException::NOT_OPEN, "ENOTCONN");
Mark Slee8d7e1f62006-06-07 06:48:56 +0000259 }
260
261 // Timed out!
262 if (errno == ETIMEDOUT) {
Mark Sleef9831082007-02-20 20:59:21 +0000263 throw TTransportException(TTransportException::TIMED_OUT, "ETIMEDOUT");
Mark Slee8d7e1f62006-06-07 06:48:56 +0000264 }
265
266 // Some other error, whatevz
Mark Sleef9831082007-02-20 20:59:21 +0000267 throw TTransportException(TTransportException::UNKNOWN, "ERROR:" + errno);
Mark Slee8d7e1f62006-06-07 06:48:56 +0000268 }
269
270 // The remote host has closed the socket
271 if (got == 0) {
272 close();
273 return 0;
Mark Sleee8540632006-05-30 09:24:40 +0000274 }
275
276 // Pack data into string
Mark Slee8d7e1f62006-06-07 06:48:56 +0000277 return got;
Mark Sleee8540632006-05-30 09:24:40 +0000278}
279
Mark Slee8d7e1f62006-06-07 06:48:56 +0000280void TSocket::write(const uint8_t* buf, uint32_t len) {
Martin Kraemeree341cb2007-02-05 21:40:38 +0000281 if (socket_ < 0) {
Mark Sleef9831082007-02-20 20:59:21 +0000282 throw TTransportException(TTransportException::NOT_OPEN, "Called write on non-open socket");
Mark Slee8d7e1f62006-06-07 06:48:56 +0000283 }
284
Mark Sleee8540632006-05-30 09:24:40 +0000285 uint32_t sent = 0;
286
Mark Slee8d7e1f62006-06-07 06:48:56 +0000287 while (sent < len) {
Marc Slemko9d4a3e22006-07-21 19:53:48 +0000288
289 int flags = 0;
Mark Slee29050782006-09-29 00:12:30 +0000290 #ifdef MSG_NOSIGNAL
Mark Slee8d7e1f62006-06-07 06:48:56 +0000291 // Note the use of MSG_NOSIGNAL to suppress SIGPIPE errors, instead we
292 // check for the EPIPE return condition and close the socket in that case
Marc Slemko9d4a3e22006-07-21 19:53:48 +0000293 flags |= MSG_NOSIGNAL;
Mark Slee29050782006-09-29 00:12:30 +0000294 #endif // ifdef MSG_NOSIGNAL
Marc Slemko9d4a3e22006-07-21 19:53:48 +0000295
296 int b = send(socket_, buf + sent, len - sent, flags);
Mark Slee8d7e1f62006-06-07 06:48:56 +0000297 ++g_socket_syscalls;
298
Mark Sleee8540632006-05-30 09:24:40 +0000299 // Fail on a send error
300 if (b < 0) {
Mark Slee8d7e1f62006-06-07 06:48:56 +0000301 if (errno == EPIPE) {
302 close();
Mark Sleef9831082007-02-20 20:59:21 +0000303 throw TTransportException(TTransportException::NOT_OPEN, "EPIPE");
Mark Slee8d7e1f62006-06-07 06:48:56 +0000304 }
305
306 if (errno == ECONNRESET) {
307 close();
Mark Sleef9831082007-02-20 20:59:21 +0000308 throw TTransportException(TTransportException::NOT_OPEN, "ECONNRESET");
Mark Slee8d7e1f62006-06-07 06:48:56 +0000309 }
310
311 if (errno == ENOTCONN) {
312 close();
Mark Sleef9831082007-02-20 20:59:21 +0000313 throw TTransportException(TTransportException::NOT_OPEN, "ENOTCONN");
Mark Slee8d7e1f62006-06-07 06:48:56 +0000314 }
315
316 perror("TSocket::write() send < 0");
Mark Sleef9831082007-02-20 20:59:21 +0000317 throw TTransportException(TTransportException::UNKNOWN, "ERROR:" + errno);
Mark Sleee8540632006-05-30 09:24:40 +0000318 }
319
320 // Fail on blocked send
321 if (b == 0) {
Mark Sleef9831082007-02-20 20:59:21 +0000322 throw TTransportException(TTransportException::NOT_OPEN, "Socket send returned 0.");
Mark Sleee8540632006-05-30 09:24:40 +0000323 }
Mark Sleee8540632006-05-30 09:24:40 +0000324 sent += b;
325 }
326}
327
Aditya Agarwalebc99e02007-01-15 23:14:58 +0000328void TSocket::setHost(string host) {
329 host_ = host;
330}
331
332void TSocket::setPort(int port) {
333 port_ = port;
334}
335
Mark Slee8d7e1f62006-06-07 06:48:56 +0000336void TSocket::setLinger(bool on, int linger) {
Mark Slee29050782006-09-29 00:12:30 +0000337 lingerOn_ = on;
338 lingerVal_ = linger;
Martin Kraemeree341cb2007-02-05 21:40:38 +0000339 if (socket_ < 0) {
Mark Slee8d7e1f62006-06-07 06:48:56 +0000340 return;
341 }
342
Mark Slee29050782006-09-29 00:12:30 +0000343 struct linger l = {(lingerOn_ ? 1 : 0), lingerVal_};
344 int ret = setsockopt(socket_, SOL_SOCKET, SO_LINGER, &l, sizeof(l));
345 if (ret == -1) {
Mark Sleee8540632006-05-30 09:24:40 +0000346 perror("TSocket::setLinger()");
Mark Sleee8540632006-05-30 09:24:40 +0000347 }
Mark Sleee8540632006-05-30 09:24:40 +0000348}
349
Mark Slee8d7e1f62006-06-07 06:48:56 +0000350void TSocket::setNoDelay(bool noDelay) {
Mark Slee29050782006-09-29 00:12:30 +0000351 noDelay_ = noDelay;
Martin Kraemeree341cb2007-02-05 21:40:38 +0000352 if (socket_ < 0) {
Mark Slee8d7e1f62006-06-07 06:48:56 +0000353 return;
354 }
355
Mark Sleee8540632006-05-30 09:24:40 +0000356 // Set socket to NODELAY
Mark Slee29050782006-09-29 00:12:30 +0000357 int v = noDelay_ ? 1 : 0;
358 int ret = setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, &v, sizeof(v));
359 if (ret == -1) {
Mark Sleee8540632006-05-30 09:24:40 +0000360 perror("TSocket::setNoDelay()");
Mark Sleee8540632006-05-30 09:24:40 +0000361 }
Mark Sleee8540632006-05-30 09:24:40 +0000362}
Mark Slee29050782006-09-29 00:12:30 +0000363
364void TSocket::setConnTimeout(int ms) {
365 connTimeout_ = ms;
366}
367
368void TSocket::setRecvTimeout(int ms) {
369 recvTimeout_ = ms;
Mark Sleeb9ff32a2006-11-16 01:00:24 +0000370 recvTimeval_.tv_sec = (int)(recvTimeout_/1000);
371 recvTimeval_.tv_usec = (int)((recvTimeout_%1000)*1000);
Martin Kraemeree341cb2007-02-05 21:40:38 +0000372 if (socket_ < 0) {
Mark Slee29050782006-09-29 00:12:30 +0000373 return;
374 }
375
Mark Sleeb9ff32a2006-11-16 01:00:24 +0000376 // Copy because select may modify
377 struct timeval r = recvTimeval_;
Mark Slee29050782006-09-29 00:12:30 +0000378 int ret = setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, &r, sizeof(r));
379 if (ret == -1) {
380 perror("TSocket::setRecvTimeout()");
381 }
382}
383
384void TSocket::setSendTimeout(int ms) {
385 sendTimeout_ = ms;
Martin Kraemeree341cb2007-02-05 21:40:38 +0000386 if (socket_ < 0) {
Mark Slee29050782006-09-29 00:12:30 +0000387 return;
388 }
389
390 struct timeval s = {(int)(sendTimeout_/1000),
391 (int)((sendTimeout_%1000)*1000)};
392 int ret = setsockopt(socket_, SOL_SOCKET, SO_SNDTIMEO, &s, sizeof(s));
393 if (ret == -1) {
394 perror("TSocket::setSendTimeout()");
395 }
396}
397
Marc Slemko6f038a72006-08-03 18:58:09 +0000398}}} // facebook::thrift::transport