천재태지의 세상 돌려보기

seoz.egloos.com

- About Me... - Enlightenment, EFL - 타이젠 Tizen



[EFL] Ecore 콜백 (timer, idler, animator, job 등) 사용 시 주의 점 ├ Enlightenment, EFL

[ EFL 게시물 목차 : http://seoz.egloos.com/3458699 ]


안녕하세요? 천재태지 서주영입니다.

이번 포스팅에서는 Ecore의 다양한 콜백(callback)을 사용할 때 주의할 점을 설명드리겠습니다.
Ecore에는 timer, idler, idle_enterer, idle_exiter, animator, job, event 등 다양한 콜백이 존재합니다.
이들을 사용할 때 공통적으로 주의해야 할 점이 있습니다.

이전에도 timer를 사용할 때 주의할 부분에 대해서 포스팅한 적([EFL] ecore_timer 사용 시 흔히 하기 쉬운 실수 및 올바른 사용법 [1])이 있었는데요, 많은 분들이 실수를 하시는 부분이라 다시 별도로 포스팅을 합니다.
이 글에서는 Ecore Idler를 이용하여 예제를 설명드리겠습니다. Timer, animator, job 등 다른 콜백도 마찬가지이니 활용해서 사용하시기 바랍니다.

우선 ecore_idler는 아래와 같이 생성했다고 가정합니다.
Ecore_Idler *myidler = ecore_idler_add(_idler_cb, data);
Ecore_Idler *myidler는 idler를 가리키는 변수(핸들, 포인터)입니다. 이 변수를 이용하여 idler를 삭제할 수도 있습니다. 물론 이 변수는 애플리케이션에서 생성한 변수입니다.
_idler_cb는 해당하는 콜백이 불러주는 콜백 함수입니다.
data는 void *형인 사용자 데이터입니다. _idler_cb 콜백 함수가 호출될 때 파라미터로 이 사용자 데이터가 넘어오게 되어 있습니다.
즉, ecore main loop[2]이 돌다가 처리해야할 일이 없어서 idle 상태에 들어가면 _idler_cb()라는 콜백 함수가 불리고 함수의 인자로 void *형 data가 넘어오게 됩니다.

그럼, 위와 같이 ecore_idler를 생성했다고 가정하고, ecore 콜백 사용 시 주의해야할 점에 대해서 알아보겠습니다.


1. ecore_idler_del() 호출 시 처리해야 할 부분

Ecore의 콜백을 사용하다보면 원하는 시점에 이 콜백을 삭제하고 싶을 때가 있습니다.
이 때 고려해야 할 점이 두 가지가 있습니다.

if (myidler) <---------------- (1)
{
   ecore_idler_del(myidler);
   myidler = NULL;  <------------ (2)
}

(1) NULL 체크

물론 ecore_idler_del()과 같은 Ecore의 API에서는 NULL 체크를 해주기 때문에 ecore_idler_del()의 인자로 NULL을 넘겨주어도 무방합니다만, 애플리케이션에서 미리 NULL 체크를 함으로써 불필요한 함수 호출 및 연산을 줄일 수 있습니다.

(2) 변수 초기화

ecore_idler_del()을 호출했다고 하더라도 Ecore는 애플리케이션에서 사용하고 있는 변수에 대해서 알지 못하기 때문에 idler를 가리켰던 변수 myidler는 자동으로 초기화 되지 않습니다.
만약 myidler를 NULL로 초기화 하지 않는다면 myidler는 계속해서 이전에 가리키고 있던 메모리 주소를 가리키게 됩니다. 흔히 이런 변수를 dangling pointer라고 하는데요, 여기서는 idler를 삭제했기 때문에 myidler를 반드시 NULL로 초기화 해줘야 합니다.

물론 ecore_idler_del()은 내부적으로 magic 값을 체크하기 때문에 올바르지 않은 값을 가진 myidler를 인자로 넘겨주면 이를 예외로 처리해줍니다. 하지만 magic 값을 체크하는 과정에서 메모리 접근이 필요한데 만약 메모리가 운영체제로 반환이 되어 애플리케이션에서 더이상 접근할 수 없는 영역이 되었다면 크래시가 발생할 수 있습니다.
그러므로 이런 초기화는 습관으로 만들어도 좋을 것 같습니다.


2. idler 콜백에서 고려해야할 부분

static Eina_Bool
_idler_cb(void *data)
{
   myidler = NULL; <------------------ (1)

   return ECORE_CALLBACK_CANCEL;
}

(1) ECORE_CALLBACK_CANCEL로 콜백 리턴 시 변수 초기화

콜백에서 ECORE_CALLBACK_CANCEL이라는 값을 리턴하면 해당 콜백 종료 후 자동으로 idler가 삭제됩니다.
그러므로 앞서 살펴보았던 것과 같이 이 때에도 idler를 가리키고 있던 변수 myidler를 NULL로 초기화 해줘야 합니다.
만약 ECORE_CALLBACK_RENEW를 리턴하게 되면 idler가 계속 살아 있으므로 myidler를 초기화할 필요가 없습니다.

static Eina_Bool
_idler_cb(void *data)
{
   ecore_idler_del(myidler); <--------------------- (2) 이렇게 하면 안 됩니다.
   myidler = NULL;

   return ECORE_CALLBACK_CANCEL;
}

(2) ECORE_CALLBACK_CANCEL로 콜백 리턴 시 콜백 함수 안에서 콜백 삭제 금지

방금 전에 설명드린 것 처럼 콜백에서 ECORE_CALLBACK_CANCEL이라는 값을 리턴하면 자동으로 idler가 삭제되기 때문에 ECORE_CALLBACK_CANCEL로 콜백 함수를 리턴하는 경우 콜백 함수 안에서 ecore_idler_del()과 같은 API로 콜백을 삭제하면 안 됩니다.
물론 EFL에서는 이런 경우에 예외 처리를 해주고 있긴 하지만, 그렇다고 하더라도 이런식으로 사용하는 건 올바른 사용법이 아닙니다.


3. 관련 이슈 예방 방법

이런 이슈를 사전에 예방하시려면 개발을 할 때 터미널에 보이는 에러 메시지를 모두 수정해야 합니다.
예를 들어 애플리케이션 실행 도중에 터미널에 ERR과 같은 에러 메시지가 발생했다면 해당 부분을 찾아가 문제의 원인을 해결해야 합니다.
굉장히 자극적인 문구임에도 불구하고 많은 분들이 이런 에러를 무시하곤 합니다. 반드시 이런 에러를 모두 찾아서 문제를 해결해야 합니다. 반드시!
참고로 EFL(ecore 포함) 버전에 따라서 에러 메시지가 다르게 출력되는 경우가 있으니 숙지 바랍니다.

(1) 이미 지워진 콜백을 삭제하려 하는 경우

(ecore < 1.7)
ERR<4268>:ecore ecore.c:558 _ecore_magic_fail()
*** ECORE ERROR: Ecore Magic Check Failed!!!
*** IN FUNCTION: ecore_idler_del()
ERR<4268>:ecore ecore.c:562 _ecore_magic_fail()   Input handle has already been freed!
ERR<4268>:ecore ecore.c:571 _ecore_magic_fail() *** NAUGHTY PROGRAMMER!!!
*** SPANK SPANK SPANK!!!
*** Now go fix your code. Tut tut tut!

(ecore >= 1.7) (Tizen 2.1)
ERR<15588>: lib/ecore/ecore_idler.c:115 _ecore_idler_del() safety check failed: idler->delete_me is true

이런 에러 메시지는
ecore_idler_del(myidler);
...
ecore_idler_del(myidler); <- 에러 발생

와 같이 ecore_idler_del()로 삭제하려는 ecore idler가 이미 삭제된 경우에 불리는 에러 메시지입니다.
이는 위에서 설명드린 두 가지 방법을 이용하면 자연스럽게 해결됩니다.

(2) 올바르지 않은 포인터를 이용하는 경우

(ecore <= 1.7) (Tizen 2.1)
ERR<4266>:ecore ecore.c:558 _ecore_magic_fail()
*** ECORE ERROR: Ecore Magic Check Failed!!!
*** IN FUNCTION: ecore_idler_del()
ERR<4266>:ecore ecore.c:568 _ecore_magic_fail()   Input handle is wrong type
    Expected: f7c614f3 - Ecore_Idler (Idler)
    Supplied: 71737723 - <UNKNOWN>

ERR<4266>:ecore ecore.c:571 _ecore_magic_fail() *** NAUGHTY PROGRAMMER!!!
*** SPANK SPANK SPANK!!!
*** Now go fix your code. Tut tut tut!

(ecore >= 1.8)
ERR<15786>: lib/ecore/ecore_idler.c:115 _ecore_idler_del() safety check failed: idler->delete_me is true

이런 에러 메시지는

Evas_Object *btn = elm_button_add(win);
ecore_idler_del(btn);

혹은

char *type;
ecore_idler_del(type);

과 같이 ecore_idler_del()에 인자로 넘겨준 값이 ecore idler가 아닌 다른 값이거나 쓰레기 값일 때 발생합니다.
ecore idler 변수를 올바르게 사용하고 있는지 확인이 필요합니다.
올바르지 않은 포인터를 사용할 때 최악의 경우 나중에 크래시가 발생하기도 합니다. 이런 경우에는 디버깅이 매우 어려워지므로 처음부터 올바르게 ecore API를 사용해야 합니다.

(3) NULL

(ecore <= 1.7) (Tizen 2.1)
ERR<4288>:ecore ecore.c:558 _ecore_magic_fail()
*** ECORE ERROR: Ecore Magic Check Failed!!!
*** IN FUNCTION: ecore_idler_del()
ERR<4288>:ecore ecore.c:560 _ecore_magic_fail()   Input handle pointer is NULL!
ERR<4288>:ecore ecore.c:571 _ecore_magic_fail() *** NAUGHTY PROGRAMMER!!!
*** SPANK SPANK SPANK!!!
*** Now go fix your code. Tut tut tut!

(ecore >= 1.8)
아무 error 안 남

이런 에러는

Ecore_Idler *my_idler = NULL;
ecore_idler_del(my_idler);

와 같이 NULL을 사용하는 경우에 발생합니다.
그런데 EFL 1.7 버전까지는 에러 메시지가 발생하나 EFL 1.8 부터는 에러 메시지가 발생하지 않습니다.
애초에 이런 상황을 만들지 않도록 해주는 것이 좋겠습니다.

지금까지 Ecore의 콜백을 사용할 때 주의해야 하는 몇 가지 부분에 대해서 알아봤습니다.
이는 극히 일부분일 것이고, 실제로는 더 많은 상황이 있을 수 있습니다.
앞으로는 위 사항을 꼭 숙지하시어 문제가 발생하지 않도록 유의바랍니다.

감사합니다.

[1] http://seoz.egloos.com/3775503

[ EFL 게시물 목차 : http://seoz.egloos.com/3458699 ]



덧글

댓글 입력 영역