-
Notifications
You must be signed in to change notification settings - Fork 16
do 모듈
uhm0311 edited this page Apr 19, 2022
·
2 revisions
do 모듈의 진입점은 memcached_do() 함수와 memcached_vdo() 함수입니다.
두 함수는 매개변수로 byte array를 받는지, vector 객체 array를 받는지 차이가 있고 내부 동작은 같습니다.
내부 동작은 다음과 같습니다.
우선 connect 모듈의 memcached_connect() 함수를 호출하여 연결이 되어 있지 않으면 연결을 먼저 수립합니다. 이미 연결이 되어 있는 경우 memcached_connect() 함수에서는 연결 시도를 다시 하지 않고 바로 성공한 것으로 간주합니다.
연결 시도 도중 실패한 경우 해당 에러 코드를 그대로 반환합니다.
연결이 수립됐다면 io 모듈의 함수를 호출합니다. memcached_do() 함수는 memcached_io_write() 함수를, memcached_vdo() 함수는 memcached_io_writev() 함수를 호출합니다.
io 모듈의 함수가 실패했다면 MEMCACHED_WRITE_FAILURE 에러 코드를 반환합니다.
memcached_vdo() 상세 분석
- memcached_do() 함수와 memcached_vdo() 함수가 있습니다.
- memcached_do() 함수는 하나의 buffer에 request 정보를 넣고 server에 write하는 함수입니다.
- memcached_vdo()는 libmemcached_io_vector_st 타입의 array에 request 정보를 나누어 넣고 server에 write하는 함수입니다.
struct libmemcached_io_vector_st
{
size_t length;
const void *buffer;
};
- 예를 들어, setattr operation을 수행할 때 두 함수의 호출은 다음과 같습니다.
char *buffer = "setattr list:test1 maxcount=1000 expiretime=30\r\n";
memcached_do(mc, buffer, strlen(buffer), true);
struct libmemcached_io_vector_st vector[]=
{
{ 8, "setattr " },
{ 11, "list:test1 " },
{ 14, "maxcount=1000 " },
{ 13, "expiretime=30" },
{ 2, "\r\n" }
};
memcached_vdo(mc, vector, 5, true);
- memcached_do() 함수와 memcached_vdo() 함수는 이렇게 매개변수로 하나의 buffer에 request의 정보를 담아서 주는지, array에 request의 정보를 나눠 담아서 주는지에 따라 나뉘는 함수입니다.
- 함수의 인터페이스만 다를 뿐 내부 동작은 서로 같습니다.
- 이번 문서에서는 memcached_vdo() 함수를 기준으로 설명하겠습니다.
memcached_vdo() 함수 소스 코드
memcached_return_t memcached_vdo(memcached_server_write_instance_st ptr,
const struct libmemcached_io_vector_st *vector, size_t count,
bool with_flush)
{
memcached_return_t rc;
ssize_t sent_length;
WATCHPOINT_ASSERT(count);
WATCHPOINT_ASSERT(vector);
if (memcached_failed(rc= memcached_connect(ptr)))
{
WATCHPOINT_ERROR(rc);
assert_msg(ptr->error_messages, "memcached_connect() returned an error but the memcached_server_write_instance_st showed none.");
return rc;
}
/*
** Since non buffering ops in UDP mode dont check to make sure they will fit
** before they start writing, if there is any data in buffer, clear it out,
** otherwise we might get a partial write.
**/
if (ptr->type == MEMCACHED_CONNECTION_UDP && with_flush && ptr->write_buffer_offset > UDP_DATAGRAM_HEADER_LENGTH)
{
memcached_io_write(ptr, NULL, 0, true);
}
sent_length= memcached_io_writev(ptr, vector, count, with_flush);
size_t command_length= 0;
for (uint32_t x= 0; x < count; ++x, vector++)
{
command_length+= vector->length;
}
if (sent_length == -1 or size_t(sent_length) != command_length)
{
rc= MEMCACHED_WRITE_FAILURE;
WATCHPOINT_ERROR(rc);
WATCHPOINT_ERRNO(errno);
}
else if ((ptr->root->flags.no_reply) == 0 and (ptr->root->flags.piped == false))
{
memcached_server_response_increment(ptr);
}
return rc;
}
- memcached_vdo() 함수의 내부 동작 순서는 다음과 같습니다.
- connection이 되어 있지 않으면 connection을 먼저 한다.
- vector에 나누어 담아져 있는 request 정보를 buffer에 write 한다.
- flush를 한다.
- 위 과정 중 connection이 실패할 경우 connection 실패 에러를, write 혹은 flush가 실패할 경우 MEMCACHED_WRITE_FAILURE 에러를 반환합니다.
- ascii protocol을 사용할 때 memcached_vdo() 함수에서 반환할 수 있는 모든 에러 코드는 다음과 같습니다.
memcached_vdo() {
memcached_connect() {
backoff_handling() {
return MEMCACHED_SERVER_MARKED_DEAD // server->server_failure_counter >= server->root->server_failure_limit이고 mc->flags.auto_eject_hosts == true인 경우
return MEMCACHED_SERVER_TEMPORARILY_DISABLED // server->state == MEMCACHED_SERVER_STATE_IN_TIMEOUT이고 retry time 조건을 만족하지 않는 경우
return MEMCACHED_SUCCESS // 성공
}
network_connect() {
set_hostinfo() {
return MEMCACHED_FAILURE // server->port를 string으로 변환하지 못한 경우. hash collision 이슈 PR로 str_port를 미리 들고 있게 되어 고려하지 않아도 됨
return MEMCACHED_TIMEOUT // getaddrinfo() 함수의 return 값이 EAI_AGAIN인 경우
return MEMCACHED_ERRNO // getaddrinfo() 함수의 return 값이 EAI_SYSTEM인 경우
return MEMCACHED_INVALID_ARGUMENTS // getaddrinfo() 함수의 return 값이 EAI_BADFLAGS인 경우
return MEMCACHED_MEMORY_ALLOCATION_FAILURE // getaddrinfo() 함수의 return 값이 EAI_MEMORY인 경우
return MEMCACHED_HOST_LOOKUP_FAILURE // getaddrinfo() 함수의 return 값이 위의 4가지가 아닌 경우
return MEMCACHED_SUCCESS // 성공
}
connect_poll() {
return MEMCACHED_ERRNO // poll() 함수의 return 값이 1이고 getsockopt() 함수의 errcode가 0이 아닌 경우, 혹은 poll() 함수의 return 값이 0이나 1이 아니고 errno가 ERESTART, EINTR, EFAULT, ENOMEM, EINVAL이 아닌 경우
return MEMCACHED_TIMEOUT // mc->poll_timeout == 0인 경우, 혹은 poll() 함수의 return 값이 0인 경우
return MEMCACHED_MEMORY_ALLOCATION_FAILURE // poll() 함수의 return 값이 0, 1이 아니고 errno가 EFAULT, ENOMEM, EINVAL인 경우
return MEMCACHED_SUCCESS // 성공
}
return MEMCACHED_TIMEOUT // connect() 함수의 return 값이 SOCKET_ERROR이고 errno가 ETIMEDOUT인 경우, 혹은 connect_poll() 함수의 return 값이 MEMCACHED_TIMEOUT이고 server->state 값이 MEMCACHED_SERVER_STATE_NEW, MEMCACHED_SERVER_STATE_ADDRINFO인 경우
return MEMCACHED_CONNECTION_FAILURE // connect() 함수의 return 값이 SOCKET_ERROR이고 errno가 ETIMEDOUT, EWOULDBLOCK, EINPROGRESS, EALREADY, EINTR가 아닌 경우
return MEMCACHED_ERRNO // socket() 함수의 return 값이 음수인 경우
return MEMCACHED_SUCCESS // 성공
}
unix_socket_connect() {
return MEMCACHED_CONNECTION_FAILURE // socket() 함수의 return 값이 음수인 경우, 혹은 socket() 함수의 return 값이 음수가 아니고 connect() 함수의 return 값이 음수이면서 errno가 EINPROGRESS, EALREADY, EINTR, EISCONN이 아닌 경우
return MEMCACHED_SUCCESS // 성공
}
memcached_version_instance() {
version_ascii_instance() {
memcached_vdo()
memcached_response() {
memcached_read_one_response() {
textual_read_one_response() {
memcached_io_readline() {
textual_value_fetch() {
memcached_string_check() {
return MEMCACHED_MEMORY_ALLOCATION_FAILURE // textual_value_fetch() 함수에서 response 문자열을 제대로 읽지 못한 경우
}
memcached_io_read() {
_io_fill() {
return MEMCACHED_IN_PROGRESS // recv 함수의 reutrn 값이 SOCKET_ERROR이며 errno가 ETIMEDOUT, EWOULDBLOCK, EAGAIN, ERESTART이면서 io_wait() 함수가 실패한 경우
return MEMCACHED_ERRNO // recv 함수의 reutrn 값이 SOCKET_ERROR이며 errno가 ETIMEDOUT, EWOULDBLOCK, EAGAIN, ERESTART가 아닌 경우
return MEMCACHED_CONNECTION_FAILURE // recv 함수의 return 값이 0인 경우
return MEMCACHED_SUCCESS // 성공
}
return MEMCACHED_CONNECTION_FAILURE // server->fd 값이 INVALID_SOCKET인 경우
return MEMCACHED_SUCCESS // 성공
}
return MEMCACHED_NOT_SUPPORTED // mc->flags.use_udp == true인 경우
return MEMCACHED_PARTIAL_READ // response 문자열을 제대로 읽지 못했거나 memcached_io_read() 함수에서 일부만 read한 경우
return MEMCACHED_SUCCESS // 성공
}
textual_version_fetch() {
return MEMCACHED_UNKNOWN_READ_FAILURE // strtol() 함수가 실패한 경우
return MEMCACHED_SUCCESS // 성공
}
return MEMCACHED_STAT // response가 STAT인 경우
return MEMCACHED_SERVER_ERROR // response가 SERVER_ERROR인 경우
return MEMCACHED_E2BIG // response가 SERVER_ERROR object too large for cache이거나 CLIENT_ERROR object too large for cache인 경우
return MEMCACHED_STORED // response가 STORED인 경우
return MEMCACHED_SWITCHOVER // response가 SWITCHOVER인 경우
return MEMCACHED_DELETED // response가 DELETED인 경우
return MEMCACHED_NOTFOUND // response가 NOTFOUND인 경우
return MEMCACHED_NOTSTORED // response가 NOTSTORED인 경우
return MEMCACHED_NOT_SUPPORTED // response가 NOT_SUPPORTED인 경우
return MEMCACHED_REPL_SLAVE // response가 REPL_SLAVE인 경우
return MEMCACHED_UNREADABLE // response가 UNREADABLE인 경우
return MEMCACHED_END // response가 END인 경우
return MEMCACHED_PROTOCOL_ERROR // response가 ERROR인 경우
return MEMCACHED_DATA_EXISTS // rseponse가 EXISTS인 경우
return MEMCACHED_ITEM // response가 ITEM인 경우
return MEMCACHED_TYPE_MISMATCH // response가 TYPE_MISMATCH인 경우
return MEMCACHED_CLIENT_ERROR // response가 CLIENT_ERROR인 경우
return MEMCACHED_UNKNOWN_READ_FAILURE // response가 위의 모든 경우가 아닌 경우
return MEMCACHED_SUCCESS // 성공
}
}
}
}
return MEMCACHED_SUCCESS // 성공
}
return MEMCACHED_NOT_SUPPORTED // 연결이 정상적으로 완료되었으나 mc->flags.use_udp == true인 경우
return MEMCACHED_INVALID_ARGUMENTS // mc == NULL 혹은 server == NULL인 경우
}
return MEMCACHED_SERVER_TEMPORARILY_DISABLED // backoff_handling() 함수에서 server->state == MEMCACHED_SERVER_STATE_IN_TIMEOUT이고 retry time 조건을 만족하여 timeout 상태이지만 backoff_handling() 함수의 reutrn 값이 MEMCACHED_SUCCESS인 경우
}
return MEMCACHED_WRITE_FAILURE // memcached_io_writev() 함수가 실패하여 return 값이 -1이거나 write를 했으나 일부는 write가 이뤄지지 않은 경우
}
auto.cc
memcached_return_t rc= memcached_vdo(instance, vector, 7, true);
if (ptr->flags.no_reply or memcached_failed(rc))
{
return rc;
}
delete.cc
memcached_return_t rc= MEMCACHED_SUCCESS;
if ((rc= memcached_vdo(instance, vector, 3, to_write)) != MEMCACHED_SUCCESS)
{
memcached_io_reset(instance);
return rc;
}
collection.cc
rc= memcached_vdo(instance, vector, 4, to_write);
if (rc == MEMCACHED_SUCCESS)
{
...
}
if (rc == MEMCACHED_WRITE_FAILURE)
{
memcached_io_reset(instance);
}
exist.cc
memcached_return_t rc;
if ((rc= memcached_vdo(instance, vector, 3, true)) != MEMCACHED_SUCCESS)
{
memcached_io_reset(instance);
return (rc == MEMCACHED_SUCCESS) ? MEMCACHED_WRITE_FAILURE : rc;
}
get.cc
rc= memcached_vdo(instance, vector, 4, true);
if (memcached_failed(rc))
{
memcached_io_reset(instance);
memcached_set_error(*ptr, rc, MEMCACHED_AT);
}
return rc;
memcached_failed() 소스 코드
static inline bool memcached_failed(memcached_return_t rc)
{
return (rc != MEMCACHED_SUCCESS &&
rc != MEMCACHED_END &&
rc != MEMCACHED_STORED &&
rc != MEMCACHED_STAT &&
rc != MEMCACHED_DELETED &&
rc != MEMCACHED_BUFFERED &&
rc != MEMCACHED_VALUE);
}
stats.cc
if (memcached_vdo(instance, vector, 2, true) != MEMCACHED_SUCCESS)
{
memcached_io_reset(instance);
return MEMCACHED_WRITE_FAILURE;
}
- return 하는 모든 에러 코드를 조사해 보았는데, 에러 코드 타입 자체는 적절해 보입니다.
- 오류 관리 부분에서 적절하지 않을 수 있는 부분이 memcached_set_error() 함수나 memcached_set_errno() 함수를 사용하지 않는 경우입니다.
- 두 함수를 사용하더라도 에러 코드가 중복되기 때문에 어느 함수에서 발생한 것인지에 대한 정보를 추가해주어야 합니다.
- memcached_set_error() 함수나 memcached_set_errno() 함수를 사용하면 에러 코드가 저장되므로 connection 실패 시에 MEMCACHED_CONNECTION_FAILURE로 통합하는 방안이 있습니다.
- MEMCACHED_WRITE_FAILURE를 반환하는 경우 미리 memcached_io_reset()을 호출하면 됩니다.
- connection 오류의 경우 connection이 이뤄지지 않았으면 후속 조치가 필요하지 않습니다.
- connection이 이뤄졌으나 memcached_version_instance() 함수에서 실패하는 경우 memcached_io_reset()이 호출됩니다.