Libevent R1: Setting up the Libevent library

From 탱이의 잡동사니
Jump to navigation Jump to search

Overview

뭔본은 이곳<ref>http://www.wangafu.net/~nickm/libevent-book/Ref1_libsetup.html</ref>에서 볼 수 있다.

Libevent는 굉장히 적은 수의 전역변수들을 몇개 가지고 있는데, 라이브러리 전역에 영향을 미친다.

반드시 라이브러리 사용전에 이 변수들을 설정해야한다. 만약 이런 설정없이 라이브러리를 사용하게 되면 문제가 발생하게 된다.

Log messages in Libevent

Libevent는 에러/경고 로그를 내부적으로 관리할 수 있다. 게다가 로깅 함수와 같이 컴파일한다면 디버깅 메시지 역시 로그로 남길 수 있다. Default 값으로는 이런 메시지들은 stderr 로 출력된다. 별도의 로깅 함수 혹은 자신이 개발한 로깅 함수로 override 가능하다.

Interface

<source lang=c>

  1. define EVENT_LOG_DEBUG 0
  2. define EVENT_LOG_MSG 1
  3. define EVENT_LOG_WARN 2
  4. define EVENT_LOG_ERR 3

/* Deprecated; see note at the end of this section */

  1. define _EVENT_LOG_DEBUG EVENT_LOG_DEBUG
  2. define _EVENT_LOG_MSG EVENT_LOG_MSG
  3. define _EVENT_LOG_WARN EVENT_LOG_WARN
  4. define _EVENT_LOG_ERR EVENT_LOG_ERR

typedef void (*event_log_cb)(int severity, const char *msg);

void event_set_log_callback(event_log_cb cb); </source>

Libevent Default 로깅 함수를 override 하기위해서는 먼저 event_log_cb 와 같은 형식의 프로토타입을 선언하고, event_set_log_callback() 에 인자값으로 넘겨주면 된다. 그러면 Libevent가 로그를 기록할 때, 설정된 로그 함수를 호출할 것이다. 다시 Default 설정으로 돌아가고 싶을때면 event_set_log_callback(NULL) 을 호출하면 된다.

Examples

<source lang=c>

  1. include <event2/event.h>
  2. include <stdio.h>

static void discard_cb(int ceverity, const char *msg) { /* This callback does nothing */ }

static FILE *logfile = NULL; static void write_to_file_cb(int severity, const char *msg) { const char *s;

if(!logfile) { return }

switch (severity) { case _EVENT_LOG_DEBUG: s = "debug"; break; case _EVENT_LOG_MSG: s = "msg"; break; case _EVENT_LOG_WARN: s = "warn"; break; case _EVENT_LOG_ERR: s = "error"; break; default: s = "?"; break; /* never reached */ } fprintf(logfile, "[%s] %s\n", s, msg); }

/* Turn off all logging from Libevent. */ void suppress_logging(void) { event_set_log_callback(discard_cb); }

/* Redirect all Libevent log messages to the C stdio file 'f'. */ void set_logfile(FILE *f) { logfile = f; event_set_log_callback(write_to_file_cb); } </source>

NOTE

사용자가 작성한 event_log_cb callback() 함수 안에서 Libevent 함수를 호출하면 안된다. 예를 들어, bufferevents 를 사용해서 네트워크 소켓으로 경고 메시지를 보내는 로그 콜백 함수가 있다고 하면, 제대로 동작하지 않을뿐더러 어디가 문제인지 찾아내기도 쉽지 않을 것이다. 나중 버전의 Libevent 에서는 이런 제약이 사라질 수도 있겠지만, 지금은 호출하면 안된다.

대부분은 Debug 로그 옵션은 활성화 되어 있지 않다. 만약 Libevent 빌드 시 Debug 로그 관련 옵션들을 함께 빌드했다면 Debug logging 수동으로 활성화시킬 수 있다.

Interface

<source lang=c>

  1. define EVENT_DBG_NONE 0
  2. define EVENT_DBG_ALL 0xffffffffu

void event_enable_debug_logging(ev_uint32_t which); </source>

디버그 로그들은 굉장히 자세하지만, 대부분의 상황에서는 쓸 일이 없다. event_enable_debug_logging() 과 인자값으로 EVENT_DBG_NONE를 같이 호출하면 Default 값으로 돌아가게 된다. EVENT_DBG_ALL 과 같이 호출하게 되면 모든 Debug 메시지를 출력하게 된다.

관련 함수들은 <event2/event.h>에 정의되어 있으며 event_enable_debug_logging() 은 Libevent 2.1.1-alpha 버전 이후부터 사용가능하다. 그 외는 Libevent 1.0c 이후부터 사용가능하다.

COMPATIBILITY NOTE

Libevent 2.0.19-stable 이전에는 EVENT_LOG_* 매크로가 언더 스코어('_')로 시작했었다: EVENT_LOG_DEBUG, _EVENT_LOG_MSG, _EVENT_LOG_WARN, _EVENT_LOG_ERR. 하지만 현재는 잘 사용되지 않으며, 이후 버전에는 삭제될 예정이다.

Handling fatal errors

Libevent에서 치명적인 내부 에러가 발생했을때는, 기본적으로 exit()를 호출하거나 abort()를 호출하여 동작중인 프로세스를 정지시킨다. 이런 상황에서는 항상 어딘가에 버그가 있다는 것을 의미한다. 여러분의 코드 혹은 Libevent 자체에..

만약 원한다면 exit() 혹은 abort() 호출하는 것 대신에 정상적인 프로그램 종료를 하도록 재설정할 수도 있다.

Interface

<source lang=c> typedef void (*event_fatal_cb)(int err); void event_set_fatal_callback(event_fatal_cb cb); </source>

이 기능을 사용하기 위해서는 먼저 에러를 처리할 함수를 먼저 생성한 뒤에 event_set_fatal_callback() 함수에 인자값으로 넘겨주면 된다. 이후 Libevent 에서 fatal error 가 발생하게 된다면, 여기에 등록된 함수를 호출하게 된다.

새로운 함수 생성시, 절대로 Libevent 관련 핸들러를 리턴하면 안되다. 만약 그렇게 하면 이상한 동작을 일으키거나 프로그램이 자동 종료될 수가 있다.

<event2/event.h> 파일에 선언되어 있으며, Libevent 2.0.3-alpha 버전 이후 사용 가능하다.

Memory management

기본적으로 Libevent는 C 라이브러리를 사용하여 Heap 영역에 메모리를 할당한다. 하지만 만약 사용자가 원한다면 다른 메모리 관련 함수들로 malloc, realloc, free를 대신하게끔 할 수도 있다.

Interface

<source lang=c>


void event_set_mem_functions(void *(*malloc_fn)(size_t sz),

                            void *(*realloc_fn)(void *ptr, size_t sz),
                            void (*free_fn)(void *ptr));

</source>

다음 예제를 보자.

Example

<source lang=c>


  1. include <event2/event.h>
  2. include <sys/types.h>
  3. include <stdlib.h>

/* This union's purpose is to be as big as the largest of all the

* types it contains. */

union alignment {

   size_t sz;
   void *ptr;
   double dbl;

}; /* We need to make sure that everything we return is on the right

  alignment to hold anything, including a double. */
  1. define ALIGNMENT sizeof(union alignment)

/* We need to do this cast-to-char* trick on our pointers to adjust

  them; doing arithmetic on a void* is not standard. */
  1. define OUTPTR(ptr) (((char*)ptr)+ALIGNMENT)
  2. define INPTR(ptr) (((char*)ptr)-ALIGNMENT)

static size_t total_allocated = 0; static void *replacement_malloc(size_t sz) {

   void *chunk = malloc(sz + ALIGNMENT);
   if (!chunk) return chunk;
   total_allocated += sz;
   *(size_t*)chunk = sz;
   return OUTPTR(chunk);

} static void *replacement_realloc(void *ptr, size_t sz) {

   size_t old_size = 0;
   if (ptr) {
       ptr = INPTR(ptr);
       old_size = *(size_t*)ptr;
   }
   ptr = realloc(ptr, sz + ALIGNMENT);
   if (!ptr)
       return NULL;
   *(size_t*)ptr = sz;
   total_allocated = total_allocated - old_size + sz;
   return OUTPTR(ptr);

} static void replacement_free(void *ptr) {

   ptr = INPTR(ptr);
   total_allocated -= *(size_t*)ptr;
   free(ptr);

} void start_counting_bytes(void) {

   event_set_mem_functions(replacement_malloc,
                           replacement_realloc,
                           replacement_free);

} </source>

NOTE

  • 메모리 관리 함수들을 변경하게 되면, Libevent 내부적으로 사용하게되는 모든 allocate, resize, free 관련 함수에 영향을 끼치게 된다. 때문에 사용에 많은 주의가 필요하다.
  • malloc 함수 구현시, C 라이브리와 똑같은 메모리 alignment 를 지원해야 한다.
  • realloc 함수 구현시, realloc(NULL, sz) 를 구현해야 한다. (malloc(sz)와 같다)
  • realloc 함수 구현시, realloc(ptr, 0) 를 구현해야 한다. (free(ptr)와 같다)
  • free 함수 구현시, free(NULL) 을 구현할 필요는 없다.
  • malloc 함수 구현시, malloc(0) 을 구현할 필요는 없다.
  • 만약 Libevent 가 멀티 스레드 환경에서 작동해야 한다면, 메모리 관련 함수들은 반드시 thread-safe 해야 한다.
  • Libevent will use these functions to allocate memory that it returns to you. Thus, if you want to free memory that is allocated and returned by a Libevent function, and you have replaced the malloc and realloc functions, then you will probably have to use your replacement free function to free it.

Locks and threading

이미 알고 있드시, 멀티 스레드 환경에서의 데이터 접근은 그리 안전하지가 못하다.

Libevent에서는 다음의 세 가지 데이터 방식을 가진다.

  • 싱글 스레드 데이터 방식 : 멀티 스레드 환경에서는 절대로 안전하지 않다.
  • optionally-locked 데이터 방식 : 데이터에 접근하고 나올때마다 Lock 설정을 해주어야 한다.
  • always-locked : 항상 lock 방식으로 동작되며 멀티 스레드 환경에서 안전하다.

Libevent 에서 Locking 을 사용하고자 한다면, 어떤 방식의 Locking 을 사용하고자 하는지 설정해줘야 한다. 반드시 Libevent 최초 설정시 이를 지정해주어야 한다. 그래야만 지정된 방식으로 Libevent 데이터 방식으로 메모리가 관리될 수 있기 때문이다. 또한 pthread 라이브러리 혹은 Windows threading 방식을 사용한다면, 이를 위해 미리 지정되어 있는 방식을 사용할 수 있다.

Interface

<source lang=c>

  1. ifdef WIN32

int evthread_use_windows_threads(void);

  1. define EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED
  2. endif
  3. ifdef _EVENT_HAVE_PTHREADS

int evthread_use_pthreads(void);

  1. define EVTHREAD_USE_PTHREADS_IMPLEMENTED
  2. endif

</source>

위의 함수 두개 모두 성공시 0, 실패시 -1 을 리턴한다.

만약 다른 종류의 thread 라이브러리를 사용한다면 다음의 사항들을 확인해야 한다.

  • Locks
  • locking
  • unlocking
  • lock allocation
  • lock destruction
  • Conditions
  • condition variable creation
  • condition variable destruction
  • waiting on a condition variable
  • signaling/broadcasting to a condition variable
  • Threads
  • thread ID detection

그런 다음 evthread_set_lock_callbacks() 와 evthread_set_id_callback 인터페이스를 통해 설정을 해주어야 한다.

Interface

<source lang=c>

  1. define EVTHREAD_WRITE 0x04
  2. define EVTHREAD_READ 0x08
  3. define EVTHREAD_TRY 0x10
  1. define EVTHREAD_LOCKTYPE_RECURSIVE 1
  2. define EVTHREAD_LOCKTYPE_READWRITE 2
  1. define EVTHREAD_LOCK_API_VERSION 1

struct evthread_lock_callbacks {

      int lock_api_version;
      unsigned supported_locktypes;
      void *(*alloc)(unsigned locktype);
      void (*free)(void *lock, unsigned locktype);
      int (*lock)(unsigned mode, void *lock);
      int (*unlock)(unsigned mode, void *lock);

};

int evthread_set_lock_callbacks(const struct evthread_lock_callbacks *);

void evthread_set_id_callback(unsigned long (*id_fn)(void));

struct evthread_condition_callbacks {

       int condition_api_version;
       void *(*alloc_condition)(unsigned condtype);
       void (*free_condition)(void *cond);
       int (*signal_condition)(void *cond, int broadcast);
       int (*wait_condition)(void *cond, void *lock,
           const struct timeval *timeout);

};

int evthread_set_condition_callbacks(

       const struct evthread_condition_callbacks *);

</source>

Detecting the version of Libevent

새 버전의 Libevent 에는 기능이 추가될 수도 있고, 버그가 삭제될 수도 있다. 때로는 Libevent의 버전을 확인해야할 때가 있는데, 버전 정보를 확인하면 다음의 사항들을 알 수 있다.

  • 프로그램에 맞는 버전의 Libevent 버전을 알 수 있다.
  • 디버깅에 사용할 수 있다.
  • 버그, 작동 여부 등을 알 수 있다.

Interface

<source lang=c>

  1. define LIBEVENT_VERSION_NUMBER 0x02000300
  2. define LIBEVENT_VERSION "2.0.3-alpha"

const char *event_get_version(void); ev_uint32_t event_get_version_number(void); </source>

매크로는 컴파일 당시의 Libevent 버전 정보를 보여주며, 함수는 run-time 버전 정보를 알려준다. 만약 동적 라이브러리로 구성된 환경이라면, 둘의 버전정보는 서로 다를 수 있다.

라이브러리 버전 정보 확인시, 2가지 포멧으로 출력이 가능하다. String format, 4-byte integer 타입이다. integer 타입시, 첫 바이트는 메이저 버전, 두번째 바이트는 마이너 버전, 세번째 바이트는 패치 버전, 마지막 바이트는 릴리즈 상태를 나타낸다.(0 release, nonzero 개발 버전)

따라서, Libevent 2.0.1-alpha 버전이라면, [02 00 01 00] 혹은 0x02000100 이 될 것이다. 그리고 2.0.1-alpha ~ 2.0.2-alpha 사이의 개발버전은 [02 00 01 08] 혹은 0x02000108 정도가 될 것이다.

Example: Compile-time checks

<source lang=c>

  1. include <event2/event.h>
  1. if !defined(LIBEVENT_VERSION_NUMBER) || LIBEVENT_VERSION_NUMBER < 0x02000100
  2. error "This version of Libevent is not supported; Get 2.0.1-alpha or later."
  3. endif

int make_sandwich(void) {

       /* Let's suppose that Libevent 6.0.5 introduces a make-me-a
          sandwich function. */
  1. if LIBEVENT_VERSION_NUMBER >= 0x06000500
       evutil_make_me_a_sandwich();
       return 0;
  1. else
       return -1;
  1. endif

} </source>

Example: Run-time checks

<source lang=c>

  1. include <event2/event.h>
  2. include <string.h>

int check_for_old_version(void) {

   const char *v = event_get_version();
   /* This is a dumb way to do it, but it is the only thing that works
      before Libevent 2.0. */
   if (!strncmp(v, "0.", 2) ||
       !strncmp(v, "1.1", 3) ||
       !strncmp(v, "1.2", 3) ||
       !strncmp(v, "1.3", 3)) {
       printf("Your version of Libevent is very old.  If you run into bugs,"
              " consider upgrading.\n");
       return -1;
   } else {
       printf("Running with Libevent version %s\n", v);
       return 0;
   }

}

int check_version_match(void) {

   ev_uint32_t v_compile, v_run;
   v_compile = LIBEVENT_VERSION_NUMBER;
   v_run = event_get_version_number();
   if ((v_compile & 0xffff0000) != (v_run & 0xffff0000)) {
       printf("Running with a Libevent version (%s) very different from the "
              "one we were built with (%s).\n", event_get_version(),
              LIBEVENT_VERSION);
       return -1;
   }
   return 0;

} </source>

Freeing global Lievent structures

Libevent 와 관련된 모든 Object 들을 free 했다고 하더라도, 여전히 global 변수 몇개는 남아있게 된다. 이는 별로 문제 될 건 없는데(프로그램 종료시, 자동으로 모두 clean 된다), 다만 프로그램 디버깅시 아직 메모리가 남아있는 것에 혼란할 수 있다. 이를 해결하고 싶다면, 다음의 함수를 호출하면 된다.

Interface

<source lang=c> void libevent_global_shutdown(void); </source> 이 함수는 Libevent 에서 리턴된 Object 들의 메모리를 해제하지는 않는다. 만약 이들 오브젝트에 할당된 메모리를 해제하고 싶다면 하나씩 직접 해제를 해야 한다. 단지 내부적으로 사용하는 global 변수만을 해제할 뿐이다.

libevent_global_shutdown() 함수를 호출하게 되면 남아있는 다른 Libevent 함수들에게 모두 영향을 주게 된다. 따라서 제일 마지막에 호출하도록 해야 한다. 그리고 이 함수는 멱등이다. 몇 번을 호출하더라도 괜찮다.

<event2/event.h> 파일에 정의되어 있으며, Libevent 2.1.1-alpha 버전 이후부터 사용 가능하다.

References

<references />