Skip to content

do 모듈

uhm0311 edited this page Apr 19, 2022 · 2 revisions

memcached_do(), memcached_vdo()

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가 이뤄지지 않은 경우
}

에러 핸들링 일관성 이슈

실패시 memcached_io_reset() 호출하지 않고 에러 코드를 그대로 반환

auto.cc

memcached_return_t rc= memcached_vdo(instance, vector, 7, true);
if (ptr->flags.no_reply or memcached_failed(rc))
{
  return rc;
}

실패시 항상 memcached_io_reset() 호출

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;
}

실패시 조건부로 memcached_io_reset() 호출

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;
}

실패 조건의 판단 방법 memcached_failed(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);
}

실패 조건의 판단 방법 rc != MEMCACHED_SUCESS

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_vdo() 외부에 전달할 오류 타입 검토

외부 오류 타입 검토

  • memcached_set_error() 함수나 memcached_set_errno() 함수를 사용하면 에러 코드가 저장되므로 connection 실패 시에 MEMCACHED_CONNECTION_FAILURE로 통합하는 방안이 있습니다.

memcached_vdo()에서 미리 처리할 오류 핸들링 검토

  • MEMCACHED_WRITE_FAILURE를 반환하는 경우 미리 memcached_io_reset()을 호출하면 됩니다.
  • connection 오류의 경우 connection이 이뤄지지 않았으면 후속 조치가 필요하지 않습니다.
  • connection이 이뤄졌으나 memcached_version_instance() 함수에서 실패하는 경우 memcached_io_reset()이 호출됩니다.