Libevent R3: Working with an event loop

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

Overview

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

Running the loop

event_base 에 이벤트를 등록시켰다면(등록과 관련해서는 다음 장을 참고하도록 하자), Libevent 통해서 등록한 이벤트의 대기/알람을 할 수 있다.

  • Interface

<source lang=c>

  1. define EVLOOP_ONCE 0x01
  2. define EVLOOP_NONBLOCK 0x02
  3. define EVLOOP_NO_EXIT_ON_EMPTY 0x04

int event_base_loop(struct event_base *base, int flags); </source> 기본적으로 event_base_loop() 는 event_base에 더이상 등록된 이벤트가 없을 때까지 동작한다. event_base_loop() 는 루프를 돌면서 event_base 에 등록된 이벤트 중, 현재 triggered 된 이벤트가 있는지를 계속해서 확인한다.(예를 들면 read 이벤트가 등록된 file descriptor 가 read 준비가 되었을 때, 혹은 timeout 으로 등록된 이벤트가 expire 되었을 때). triggered 된 이벤트가 있다면, 해당 이벤트를 "active" 상태로 표시하고 해당 이벤트를 실행한다.

event_base_loop() 의 동작방식은 인자값 flag 를 통해서 변경할 수 있다.

EVLOOP_ONCE를 설정하면 event_base 에 등록된 이벤트들이 "active" 될 때까지 대기를 한다. 이후 active 된 이벤트를 실행한다. 이 일을 반복하면서 등록된 이벤트가 하나도 없을때까지 계속한다. 등록된 이벤트가 더이상 없을 때 Loop 를 중단하고 리턴한다.

EVLOOP_NONBLOCK 으로 설정하게 되면, event_base 에 등록된 이벤트가 "trigger" 될 때까지 대기하다가, trigger 된 이벤트에 등록된 Callback 함수를 실행시킨다.

일반적으로 evnet_base 에 등록된 이벤트가 하나도 없을 경우, event_base_loop() 는 종료된다. 그런데 EVLOOP_NO_EXIT_ON_EMPTY 를 설정하면 이를 변경할 수 있다. 만약 다른 스레드로부터 이벤트를 등록 받는 경우, 등록된 이벤트가 하나도 없더라도 계속해서 루프를 돌아야 하는데, 이런경우에 EVLOOP_NO_EXIT_ON_EMPTY 를 설정하게 되면 event_base_loopbreak(), event_base_loopexit() 를 호출하거나 에러가 발생하지 않는 이상, 계속해서 루프를 돌게 된다.

루프가 종료되면 event_base_loop() 는 정상 종료일 경우 0, 에러가 발생했을 경우 -1을 리턴한다. 그리고 등록된 이벤트가 없어서 종료된 경우, 1을 리턴한다.

이해를 돕기 위해서 다음의 의사코드를 보도록 하자.

  • Pseudocode

<source lang=c> while(any events are registered with the loop,

       or EVLOOP_NO_EXIT_ON_EMPTY was set)

{

   if(EVLOOP_NONBLOCK was set, or any events are already active)
       If any registered events have triggered, mark them active.
   else
       Wait until at least one event has triggered, and mark it active.
   
   for(p = 0; p < n_priorities; ++p)
   {
       if(any event with priority of p is active)
       {
           Run all active events with priority of p.
           break;  /* Do not run any events of a less important priority */
       }
   }
   
   if(EVLOOP_ONCE was wet or EVLOOP_NONBLOCK was set)
   {
       break;
   }

} </source>

혹은 간편하게 다음과 같이 호출할 수도 있다.

  • Interface

<source lang=c> int event_base_dispatch(struct event_base *base) </source> event_base_dispatch() 함수는 event_base_loop() 를 아무 옵션없이 실행한 것과 같다. 더이상 등록된 이벤트가 없을 때까지, event_base_loopbreak() 혹은 event_base_loopexit() 가 호출될 때 까지 계속해서 루프를 돈다.

Stopping the loop

현재 동작중인 이벤트 루프를 중지시키고 싶다면 다음과 같은 함수를 사용할 수 있다. <source lang=c> int event_base_loopexit(struct event_base *base, const struct timeval *tv); int event_base_loopbreak(struct event_base *base); </source> event_base_loopexit() 함수는 인자로 넘겨진 시간 뒤에 종료하고 싶을 때 사용한다. 만약 tv 인자값으로 NULL 이 오게되면 딜레이 없이 바로 종료된다. 만약 현재 active 로 표시된 이벤트 들이 있다면 현재 표시된 active 이벤트 등리 모두 실행된 다음에 종료된다.

event_base_loopbreak() 함수는 루프를 즉시 종료하는 함수이다. event_base_loopexit() 와 다른 점은, 현재 active로 표시된 이벤트들이 있다고 하더라도, process 중인 이벤트 중료 후 바로 종료를 한다는 점이다.

그리고 등록된 이벤트가 하나도 없는 상태에서도 다르게 동작한다. event_base_loopexit() 는 다음번 루프 확인 스케쥴에서 종료가 되지만, event_base_loopbreak() 는 스케쥴링 없이 바로 종료가 된다.

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

  • Example: Shut down immediately

<source lang=c>

  1. include <event2/event.h>

/* Here's a callback function that calls loopbreak */ void cb(int sock, short what, void *arg) {

   struct event_base *base = arg;
   event_base_loopbreak(base);

}

void main_loop(struct event_base *base, evutil_socket_t watchdog_fd) {

   struct event *watchdog_event;
   
   /* Construct a new event to trigger whenever there are any bytes to
    * read from a watchdog socket. When that happens, we'll call the
    * cb function, which will make the lop exit immediately without
    * running any other active events at all.
    */
    watchdog_event = event_new(base, watchdog_fd, EV_READ, cb, base);
    
    event_add(watchdog_event, NULL);
    
    event_base_dispatch(base);
}

</source>

  • Example: Run an event loop for 10 seconds, then exit.

<source lang=c>

  1. include <event2/event.h>

void run_base_with_ticks(struct event_base *base) {

   struct timeval ten_sec;
   
   ten_sec.tv_sec = 10;
   ten_sec.tv_usec = 0;
   
   /* Now we run the event_base for a series of 10-second intervals, printing
    * "Tick" after each. For a much better way to implement a 10-second
    * timer, see the section below about persistent timer evetns. */
   while(1) {
       /* This scehdules an exit ten secods from now. */
       event_base_loopexit(base, &ten_sec);
        
       event_base_dispatch(base);
       puts("Tick");
   }

} </source>

event_base_dispatch() 혹은 event_base_loop() 가 종료되었을 때, event_base_loopxexit() 혹은 event_base_break() 중 어느 것에 의해 종료되었는지 알고 싶을 때가 있다. 이런 경우, 다음의 함수를 사용하면 된다.

  • Interface

<source lang=c> int event_base_got_exit(struct event_base *base); int event_base_got_break(struct event_base *base); </source> 이 둘 함수 모두 함수의 이름대로 event_base_loopexit() 혹은 event_base_break() 에 의해 종료 되었을 경우, true 을 리턴한다. 반대의 경우 false 를 리턴한다. 결과 값은 다음번 이벤트 루프를 실행하게 되면 초기화된다.

Re-checking for events

쉽게 말하면, Libevent는 이벤트를 체크하고, active된 이벤트들을 우선수위 순서대로 실행시킨다. 그리고 다시 이벤트를 체크하고 실행하는 일을 계속한다. 그런데 만약 이벤트 스캔을 임의대로 하고 싶다면? 예를 들어 특정 active 된 이벤트 실행 후, 다시금 이벤트-scan 을 하고 싶다면? event_base_loopbreak() 와 event_base_loopcontinue() 를 조합하면 이와 유사한 효과를 낼 수 있다.

  • Interface

<source lang=c> int event_base_loopcontinue(struct event_base *); </source> event_base_loopcontinue() 사용시, Running event callback 에서 호출되지 않으면 아무 의미가 없다.

Checking the internal time cache

event callback 내에서 시간을 확인하고자할 때 Libevent cache time 을 활용할 수 있다.(대부분의 OS 에서 gettimeofday() 함수는 syscall 로 제공되는데, Libevent cahce time 을 사용하면 별도의 overhead 를 피할 수 있다)

Libevent는 이를 위해 현재의 callback 이 실행된 시간을 cache에 저장한다.

  • Interface

<source lang=c> int event_base_gettimeofday_cached(struct event_base *base, struct timeval *tv_out); </source> event_base_gettimeofday_cached() 함수는 tv_out 을 output 으로 돌려주는데, 현재 event_base 내에서 실행중인 callback의 실행 시작 시간, 그 외의 경우라면 내부적으로 evutil_gettimeofday()를 이용해 현재 시각을 리턴한다. 성공시 0, 실패시 음수값을 리턴한다.

기본적으로 현재 동작중인 callback 의 실행 시작을 리턴하기때문에 정말 근소한 차이는 있을 수 있다. 그리고, 만약 실행되고 있는 callback 함수가 오랜시간동안 동작하고 있었다면 그 차이는 점점 커질 것이다.

강제적으로 이 cache 를 업데이터하고 싶다면 다음의 함수를 이용하자.

  • Interface

<source lang=c> int event_base_update_cache_time(struct event_base *base); </source>

성공시 0, 실패시 -1을 리턴한다. 이벤트 루프내에 현재 동작중인 콜백이 없다면, 아무런 효과는 없다.

Dumping the event_base status

  • Interface

<source lang=c> void event_base_dump_events(struct event_base *base, FILE *f); </source> event_base_dump_events() 함수를 사용하면 디버깅을 쉽게 하기 위한 정보들(프로그램 혹은 Libevent 라이브러리) 현재 진행중인 event_base 의 모든 사항(등록된 모든 이벤트 리스트와 상태 등..)을 확인할 수 있다.

Running a function over every event in an event_base

  • Interface

<source lang=c> typedef int (*event_base_foreach_event_cb)(confst struct event_base *, const struct event *, void *); int event_base_foreach_event(struct event_base *base,

   event_base_foreach_event_cb fn,
   void *arg);

</source> event_base_foreach_event() 를 사용하면 event_base 에 등록된(현재 실행중인 이벤트 역시)이벤트마다 적용이 되는 Callback 함수를 등록할 수 있다. 실행되는 이벤트 하나당 한번씩 실행되며, 세번째 인자 arg는 호출되는 이벤트 콜백 함수에 인자값으로 넘겨지게 된다.

0을 리턴하면 실행을 계속하고, 다른 값을 리턴하면 실행을 멈춘다. event_base_foreach_event 에 등록하는 callback 에서는 절대로 event_base에 이벤트를 등록하거나 삭제하는 등의 일을 해서는 안된다. 만약 그렇게 되면 event_base 가 오작동을 일으킬 수 있다.

References

<references />