2 +--------------------------------------------------------------------+
3 | libmemcached - C/C++ Client Library for memcached |
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted under the terms of the BSD license. |
7 | You should have received a copy of the license in a bundled file |
8 | named LICENSE; in case you did not receive a copy you can review |
9 | the terms online at: https://opensource.org/licenses/BSD-3-Clause |
10 +--------------------------------------------------------------------+
11 | Copyright (c) 2006-2014 Brian Aker https://datadifferential.com/ |
12 | Copyright (c) 2020 Michael Wallner <mike@php.net> |
13 +--------------------------------------------------------------------+
16 #include "libmemcached/common.h"
17 #include "p9y/poll.hpp"
22 static memcached_return_t
set_hostinfo(memcached_instance_st
*server
) {
23 assert(server
->type
!= MEMCACHED_CONNECTION_UNIX_SOCKET
);
24 server
->clear_addrinfo();
26 char str_port
[MEMCACHED_NI_MAXSERV
] = {0};
28 int length
= snprintf(str_port
, MEMCACHED_NI_MAXSERV
, "%u", uint32_t(server
->port()));
29 if (length
>= MEMCACHED_NI_MAXSERV
or length
<= 0 or errno
) {
30 return memcached_set_error(*server
, MEMCACHED_MEMORY_ALLOCATION_FAILURE
, MEMCACHED_AT
,
31 memcached_literal_param("snprintf(NI_MAXSERV)"));
34 struct addrinfo hints
;
35 memset(&hints
, 0, sizeof(struct addrinfo
));
37 hints
.ai_family
= AF_UNSPEC
;
38 if (memcached_is_udp(server
->root
)) {
39 hints
.ai_protocol
= IPPROTO_UDP
;
40 hints
.ai_socktype
= SOCK_DGRAM
;
42 hints
.ai_socktype
= SOCK_STREAM
;
43 hints
.ai_protocol
= IPPROTO_TCP
;
46 assert(server
->address_info
== NULL
);
47 assert(server
->address_info_next
== NULL
);
49 assert(server
->hostname());
50 switch (errcode
= getaddrinfo(server
->hostname(), str_port
, &hints
, &server
->address_info
)) {
52 server
->address_info_next
= server
->address_info
;
53 server
->state
= MEMCACHED_SERVER_STATE_ADDRINFO
;
57 return memcached_set_error(*server
, MEMCACHED_TIMEOUT
, MEMCACHED_AT
,
58 memcached_string_make_from_cstr(gai_strerror(errcode
)));
61 server
->clear_addrinfo();
62 return memcached_set_errno(*server
, errno
, MEMCACHED_AT
,
63 memcached_literal_param("getaddrinfo(EAI_SYSTEM)"));
66 server
->clear_addrinfo();
67 return memcached_set_error(*server
, MEMCACHED_INVALID_ARGUMENTS
, MEMCACHED_AT
,
68 memcached_literal_param("getaddrinfo(EAI_BADFLAGS)"));
71 server
->clear_addrinfo();
72 return memcached_set_error(*server
, MEMCACHED_MEMORY_ALLOCATION_FAILURE
, MEMCACHED_AT
,
73 memcached_literal_param("getaddrinfo(EAI_MEMORY)"));
76 server
->clear_addrinfo();
77 return memcached_set_error(*server
, MEMCACHED_HOST_LOOKUP_FAILURE
, MEMCACHED_AT
,
78 memcached_string_make_from_cstr(gai_strerror(errcode
)));
82 return MEMCACHED_SUCCESS
;
85 static inline void set_socket_nonblocking(memcached_instance_st
*server
) {
88 if (ioctlsocket(server
->fd
, FIONBIO
, &arg
) == SOCKET_ERROR
) {
89 memcached_set_errno(*server
, get_socket_errno(), NULL
);
94 if (SOCK_NONBLOCK
== 0) {
96 flags
= fcntl(server
->fd
, F_GETFL
, 0);
97 } while (flags
== -1 && (errno
== EINTR
|| errno
== EAGAIN
));
100 memcached_set_errno(*server
, errno
, NULL
);
101 } else if ((flags
& O_NONBLOCK
) == 0) {
105 rval
= fcntl(server
->fd
, F_SETFL
, flags
| O_NONBLOCK
);
106 } while (rval
== -1 && (errno
== EINTR
or errno
== EAGAIN
));
109 memcached_set_errno(*server
, errno
, NULL
);
116 static bool set_socket_options(memcached_instance_st
*server
) {
117 assert_msg(server
->fd
!= INVALID_SOCKET
, "invalid socket was passed to set_socket_options()");
120 // If SOCK_CLOEXEC exists then we don't need to call the following
121 if (SOCK_CLOEXEC
== 0) {
125 flags
= fcntl(server
->fd
, F_GETFD
, 0);
126 } while (flags
== -1 and (errno
== EINTR
or errno
== EAGAIN
));
131 rval
= fcntl(server
->fd
, F_SETFD
, flags
| FD_CLOEXEC
);
132 } while (rval
== -1 && (errno
== EINTR
or errno
== EAGAIN
));
133 // we currently ignore the case where rval is -1
139 if (memcached_is_udp(server
->root
)) {
143 #ifdef HAVE_SO_SNDTIMEO
144 if (server
->root
->snd_timeout
> 0) {
145 struct timeval waittime
;
147 waittime
.tv_sec
= server
->root
->snd_timeout
/ 1000000;
148 waittime
.tv_usec
= server
->root
->snd_timeout
% 1000000;
150 int error
= setsockopt(server
->fd
, SOL_SOCKET
, SO_SNDTIMEO
, (char *) &waittime
,
151 (socklen_t
) sizeof(struct timeval
));
157 #ifdef HAVE_SO_RCVTIMEO
158 if (server
->root
->rcv_timeout
> 0) {
159 struct timeval waittime
;
161 waittime
.tv_sec
= server
->root
->rcv_timeout
/ 1000000;
162 waittime
.tv_usec
= server
->root
->rcv_timeout
% 1000000;
164 int error
= setsockopt(server
->fd
, SOL_SOCKET
, SO_RCVTIMEO
, (char *) &waittime
,
165 (socklen_t
) sizeof(struct timeval
));
173 # if defined(SO_NOSIGPIPE)
176 int error
= setsockopt(server
->fd
, SOL_SOCKET
, SO_NOSIGPIPE
, (void *) &set
, sizeof(int));
180 // This is not considered a fatal error
183 perror("setsockopt(SO_NOSIGPIPE)");
187 # endif // SO_NOSIGPIPE
190 if (server
->root
->flags
.no_block
) {
191 struct linger linger
;
194 linger
.l_linger
= 0; /* By default on close() just drop the socket */
195 int error
= setsockopt(server
->fd
, SOL_SOCKET
, SO_LINGER
, (char *) &linger
,
196 (socklen_t
) sizeof(struct linger
));
202 if (server
->root
->flags
.tcp_nodelay
) {
206 setsockopt(server
->fd
, IPPROTO_TCP
, TCP_NODELAY
, (char *) &flag
, (socklen_t
) sizeof(int));
212 if (server
->root
->flags
.tcp_keepalive
) {
216 setsockopt(server
->fd
, SOL_SOCKET
, SO_KEEPALIVE
, (char *) &flag
, (socklen_t
) sizeof(int));
222 if (server
->root
->tcp_keepidle
> 0) {
223 int error
= setsockopt(server
->fd
, IPPROTO_TCP
, TCP_KEEPIDLE
,
224 (char *) &server
->root
->tcp_keepidle
, (socklen_t
) sizeof(int));
230 if (server
->root
->send_size
> 0) {
231 int error
= setsockopt(server
->fd
, SOL_SOCKET
, SO_SNDBUF
, (char *) &server
->root
->send_size
,
232 (socklen_t
) sizeof(int));
237 if (server
->root
->recv_size
> 0) {
238 int error
= setsockopt(server
->fd
, SOL_SOCKET
, SO_RCVBUF
, (char *) &server
->root
->recv_size
,
239 (socklen_t
) sizeof(int));
244 /* libmemcached will always use nonblocking IO to avoid write deadlocks */
245 set_socket_nonblocking(server
);
250 static memcached_return_t
unix_socket_connect(memcached_instance_st
*server
) {
252 WATCHPOINT_ASSERT(server
->fd
== INVALID_SOCKET
);
255 int type
= SOCK_STREAM
;
257 type
|= SOCK_CLOEXEC
;
261 type
|= SOCK_NONBLOCK
;
264 if ((server
->fd
= socket(AF_UNIX
, type
, 0)) == -1) {
265 return memcached_set_errno(*server
, errno
, NULL
);
268 struct sockaddr_un servAddr
;
270 memset(&servAddr
, 0, sizeof(struct sockaddr_un
));
271 servAddr
.sun_family
= AF_UNIX
;
272 if (strlen(server
->hostname()) >= sizeof(servAddr
.sun_path
)) {
273 return memcached_set_error(*server
, MEMCACHED_FAIL_UNIX_SOCKET
, MEMCACHED_AT
);
275 strncpy(servAddr
.sun_path
, server
->hostname(),
276 sizeof(servAddr
.sun_path
) - 1); /* Copy filename */
278 if (connect(server
->fd
, (struct sockaddr
*) &servAddr
, sizeof(servAddr
)) == -1) {
283 server
->events(POLLOUT
);
287 server
->reset_socket();
290 case EISCONN
: /* We were spinning waiting on connect */
292 assert(0); // Programmer error
293 server
->reset_socket();
298 WATCHPOINT_ERRNO(errno
);
299 server
->reset_socket();
300 return memcached_set_errno(*server
, errno
, MEMCACHED_AT
);
304 server
->state
= MEMCACHED_SERVER_STATE_CONNECTED
;
306 WATCHPOINT_ASSERT(server
->fd
!= INVALID_SOCKET
);
308 return MEMCACHED_SUCCESS
;
311 return MEMCACHED_NOT_SUPPORTED
;
315 static memcached_return_t
network_connect(memcached_instance_st
*server
) {
316 bool timeout_error_occured
= false;
318 WATCHPOINT_ASSERT(server
->fd
== INVALID_SOCKET
);
319 WATCHPOINT_ASSERT(server
->cursor_active_
== 0);
322 We want to check both of these because if address_info_next has been fully tried, we want to do
323 a new lookup to make sure we have picked up on any new DNS information.
325 if (server
->address_info
== NULL
or server
->address_info_next
== NULL
) {
326 WATCHPOINT_ASSERT(server
->state
== MEMCACHED_SERVER_STATE_NEW
);
327 server
->address_info_next
= NULL
;
328 memcached_return_t rc
= set_hostinfo(server
);
330 if (memcached_failed(rc
)) {
335 assert(server
->address_info_next
);
336 assert(server
->address_info
);
338 /* Create the socket */
339 while (server
->address_info_next
and server
->fd
== INVALID_SOCKET
) {
340 int type
= server
->address_info_next
->ai_socktype
;
342 type
|= SOCK_CLOEXEC
;
346 type
|= SOCK_NONBLOCK
;
350 socket(server
->address_info_next
->ai_family
, type
, server
->address_info_next
->ai_protocol
);
352 if (int(server
->fd
) == SOCKET_ERROR
) {
353 return memcached_set_errno(*server
, get_socket_errno(), NULL
);
356 if (set_socket_options(server
) == false) {
357 server
->reset_socket();
358 return MEMCACHED_CONNECTION_FAILURE
;
361 /* connect to server */
362 if ((connect(server
->fd
, server
->address_info_next
->ai_addr
,
363 server
->address_info_next
->ai_addrlen
)
366 server
->state
= MEMCACHED_SERVER_STATE_CONNECTED
;
367 return MEMCACHED_SUCCESS
;
370 /* An error occurred */
371 int local_error
= get_socket_errno();
372 switch (local_error
) {
374 timeout_error_occured
= true;
377 #if EWOULDBLOCK != EAGAIN
381 case EINPROGRESS
: // nonblocking mode - first return
382 case EALREADY
: // nonblocking mode - subsequent returns
384 server
->events(POLLOUT
);
385 server
->state
= MEMCACHED_SERVER_STATE_IN_PROGRESS
;
386 memcached_return_t rc
= memcached_io_poll(server
, IO_POLL_CONNECT
, local_error
);
388 if (memcached_success(rc
)) {
389 server
->state
= MEMCACHED_SERVER_STATE_CONNECTED
;
390 return MEMCACHED_SUCCESS
;
393 // A timeout here is treated as an error, we will not retry
394 if (rc
== MEMCACHED_TIMEOUT
) {
395 timeout_error_occured
= true;
399 case EISCONN
: // we are connected :-)
400 WATCHPOINT_ASSERT(0); // This is a programmer's error
403 case EINTR
: // Special case, we retry ai_addr
404 WATCHPOINT_ASSERT(server
->fd
!= INVALID_SOCKET
);
405 server
->reset_socket();
409 // Probably not running service
412 memcached_set_errno(*server
, local_error
, MEMCACHED_AT
);
416 WATCHPOINT_ASSERT(server
->fd
!= INVALID_SOCKET
);
417 server
->reset_socket();
418 server
->address_info_next
= server
->address_info_next
->ai_next
;
421 WATCHPOINT_ASSERT(server
->fd
== INVALID_SOCKET
);
423 if (timeout_error_occured
) {
424 server
->reset_socket();
427 WATCHPOINT_STRING("Never got a good file descriptor");
429 if (memcached_has_current_error(*server
)) {
430 return memcached_instance_error_return(server
);
433 if (timeout_error_occured
and server
->state
< MEMCACHED_SERVER_STATE_IN_PROGRESS
) {
434 return memcached_set_error(
435 *server
, MEMCACHED_TIMEOUT
, MEMCACHED_AT
,
436 memcached_literal_param(
437 "if (timeout_error_occured and server->state < MEMCACHED_SERVER_STATE_IN_PROGRESS)"));
440 return memcached_set_error(*server
, MEMCACHED_CONNECTION_FAILURE
,
441 MEMCACHED_AT
); /* The last error should be from connect() */
447 Based on time/failure count fail the connect without trying. This prevents waiting in a state
448 where we get caught spending cycles just waiting.
450 static memcached_return_t
backoff_handling(memcached_instance_st
*server
, bool &in_timeout
) {
451 struct timeval curr_time
;
452 bool _gettime_success
= (gettimeofday(&curr_time
, NULL
) == 0);
455 If we hit server_failure_limit then something is completely wrong about the server.
457 1) If autoeject is enabled we do that.
458 2) If not? We go into timeout again, there is much else to do :(
460 if (server
->server_failure_counter
>= server
->root
->server_failure_limit
) {
462 We just auto_eject if we hit this point
464 if (_is_auto_eject_host(server
->root
)) {
465 set_last_disconnected_host(server
);
467 // Retry dead servers if requested
468 if (_gettime_success
and server
->root
->dead_timeout
> 0) {
469 server
->next_retry
= curr_time
.tv_sec
+ server
->root
->dead_timeout
;
471 // We only retry dead servers once before assuming failure again
472 server
->server_failure_counter
= server
->root
->server_failure_limit
- 1;
475 memcached_return_t rc
;
476 if (memcached_failed(rc
= run_distribution((memcached_st
*) server
->root
))) {
477 return memcached_set_error(
478 *server
, rc
, MEMCACHED_AT
,
479 memcached_literal_param("Backoff handling failed during run_distribution"));
482 return memcached_set_error(*server
, MEMCACHED_SERVER_MARKED_DEAD
, MEMCACHED_AT
);
485 server
->state
= MEMCACHED_SERVER_STATE_IN_TIMEOUT
;
487 // Sanity check/setting
488 if (server
->next_retry
== 0) {
489 server
->next_retry
= 1;
493 if (server
->state
== MEMCACHED_SERVER_STATE_IN_TIMEOUT
) {
495 If next_retry is less then our current time, then we reset and try everything again.
497 if (_gettime_success
and server
->next_retry
< curr_time
.tv_sec
) {
498 server
->state
= MEMCACHED_SERVER_STATE_NEW
;
499 server
->server_timeout_counter
= 0;
501 return memcached_set_error(*server
, MEMCACHED_SERVER_TEMPORARILY_DISABLED
, MEMCACHED_AT
);
507 return MEMCACHED_SUCCESS
;
510 static memcached_return_t
_memcached_connect(memcached_instance_st
*server
,
511 const bool set_last_disconnected
) {
513 if (server
->fd
!= INVALID_SOCKET
) {
514 return MEMCACHED_SUCCESS
;
517 LIBMEMCACHED_MEMCACHED_CONNECT_START();
519 bool in_timeout
= false;
520 memcached_return_t rc
;
521 if (memcached_failed(rc
= backoff_handling(server
, in_timeout
))) {
522 set_last_disconnected_host(server
);
526 if (LIBMEMCACHED_WITH_SASL_SUPPORT
and server
->root
->sasl
.callbacks
527 and memcached_is_udp(server
->root
))
529 return memcached_set_error(
530 *server
, MEMCACHED_INVALID_HOST_PROTOCOL
, MEMCACHED_AT
,
531 memcached_literal_param("SASL is not supported for UDP connections"));
534 if (server
->hostname()[0] == '/') {
535 server
->type
= MEMCACHED_CONNECTION_UNIX_SOCKET
;
538 /* We need to clean up the multi startup piece */
539 switch (server
->type
) {
540 case MEMCACHED_CONNECTION_UDP
:
541 case MEMCACHED_CONNECTION_TCP
:
542 rc
= network_connect(server
);
544 #if defined(LIBMEMCACHED_WITH_SASL_SUPPORT)
545 if (LIBMEMCACHED_WITH_SASL_SUPPORT
) {
546 if (server
->fd
!= INVALID_SOCKET
and server
->root
->sasl
.callbacks
) {
547 rc
= memcached_sasl_authenticate_connection(server
);
548 if (memcached_failed(rc
) and server
->fd
!= INVALID_SOCKET
) {
549 WATCHPOINT_ASSERT(server
->fd
!= INVALID_SOCKET
);
550 server
->reset_socket();
557 case MEMCACHED_CONNECTION_UNIX_SOCKET
:
558 rc
= unix_socket_connect(server
);
562 if (memcached_success(rc
)) {
563 server
->mark_server_as_clean();
564 memcached_version_instance(server
);
566 } else if (set_last_disconnected
) {
567 set_last_disconnected_host(server
);
568 if (memcached_has_current_error(*server
)) {
569 memcached_mark_server_for_timeout(server
);
570 assert(memcached_failed(memcached_instance_error_return(server
)));
572 memcached_set_error(*server
, rc
, MEMCACHED_AT
);
573 memcached_mark_server_for_timeout(server
);
576 LIBMEMCACHED_MEMCACHED_CONNECT_END();
580 int snprintf_length
=
581 snprintf(buffer
, sizeof(buffer
), "%s:%d", server
->hostname(), int(server
->port()));
582 return memcached_set_error(*server
, MEMCACHED_SERVER_TEMPORARILY_DISABLED
, MEMCACHED_AT
,
583 buffer
, snprintf_length
);
590 memcached_return_t
memcached_connect(memcached_instance_st
*server
) {
591 return _memcached_connect(server
, true);