blog

libuv 이벤트 루프 디버그 로깅 - idle

데모 테스트\n#include <stdio.h\n#include\n#include <uv.h\nvoid idle_cb {\n pri...

Oct 18, 2025 · 8 min. read
シェア

데모 테스트

#include <stdio.h>
#include <stdlib.h>
#include <uv.h>
void idle_cb(uv_idle_t *handle) {
 printf("idle callback
");
}
int main() {
 uv_idle_t idle;
 //  
 uv_idle_init(uv_default_loop(), &idle);
 // 시작하면 이벤트 루프의 각 라운드가 유휴 상태로 실행됩니다._cb
 uv_idle_start(&idle, idle_cb);
 uv_run(uv_default_loop(), UV_RUN_DEFAULT);
 return 0;
}

uv_idle_t의 데이터 구조

uv_default_loop()로 이동합니다.

static uv_loop_t default_loop_struct;
static uv_loop_t* default_loop_ptr;
uv_loop_t* uv_default_loop(void) {
 if (default_loop_ptr != NULL)
 return default_loop_ptr;
 if (uv_loop_init(&default_loop_struct))
 return NULL;
 default_loop_ptr = &default_loop_struct;
 return default_loop_ptr;
}

uv_loop_init으로 이동하여 기본 루프로 돌아가거나 이미 실행된 경우 기본 루프로 돌아갑니다.

루프 구조

uv_loop_init으로 이동하여 루프를 초기화합니다.

int uv_loop_init(uv_loop_t* loop) {
 uv__loop_internal_fields_t* lfields;
 void* saved_data;
 int err;
 saved_data = loop->data;
 memset(loop, 0, sizeof(*loop));
 loop->data = saved_data;
 lfields = (uv__loop_internal_fields_t*) uv__calloc(1, sizeof(*lfields));
 if (lfields == NULL)
 return UV_ENOMEM;
 loop->internal_fields = lfields;
 err = uv_mutex_init(&lfields->loop_metrics.lock);
 if (err)
 goto fail_metrics_mutex_init;
 memset(&lfields->loop_metrics.metrics,
 0,
 sizeof(lfields->loop_metrics.metrics));
 heap_init((struct heap*) &loop->timer_heap);
 uv__queue_init(&loop->wq);
 uv__queue_init(&loop->idle_handles);
 uv__queue_init(&loop->async_handles);
 uv__queue_init(&loop->check_handles);
 uv__queue_init(&loop->prepare_handles);
 uv__queue_init(&loop->handle_queue);
 loop->active_handles = 0;
 loop->active_reqs.count = 0;
 loop->nfds = 0;
 loop->watchers = NULL;
 loop->nwatchers = 0;
 uv__queue_init(&loop->pending_queue);
 uv__queue_init(&loop->watcher_queue);
 loop->closing_handles = NULL;
 uv__update_time(loop);
 loop->async_io_watcher.fd = -1;
 loop->async_wfd = -1;
 loop->signal_pipefd[0] = -1;
 loop->signal_pipefd[1] = -1;
 loop->backend_fd = -1;
 loop->emfile_fd = -1;
 loop->timer_counter = 0;
 loop->stop_flag = 0;
 err = uv__platform_loop_init(loop);
 if (err)
 goto fail_platform_init;
 uv__signal_global_once_init();
 err = uv__process_init(loop);
 if (err)
 goto fail_signal_init;
 uv__queue_init(&loop->process_handles);
 err = uv_rwlock_init(&loop->cloexec_lock);
 if (err)
 goto fail_rwlock_init;
 err = uv_mutex_init(&loop->wq_mutex);
 if (err)
 goto fail_mutex_init;
 err = uv_async_init(loop, &loop->wq_async, uv__work_done);
 if (err)
 goto fail_async_init;
 uv__handle_unref(&loop->wq_async);
 loop->wq_async.flags |= UV_HANDLE_INTERNAL;
 return 0;
fail_async_init:
 uv_mutex_destroy(&loop->wq_mutex);
fail_mutex_init:
 uv_rwlock_destroy(&loop->cloexec_lock);
fail_rwlock_init:
 uv__signal_loop_cleanup(loop);
fail_signal_init:
 uv__platform_loop_delete(loop);
fail_platform_init:
 uv_mutex_destroy(&lfields->loop_metrics.lock);
fail_metrics_mutex_init:
 uv__free(lfields);
 loop->internal_fields = NULL;
 uv__free(loop->watchers);
 loop->nwatchers = 0;
 return err;
}

이번에는 주로 다음 사항에 중점을 두고 유휴 대기열을 분석합니다.

// ...
uv__queue_init(&loop->idle_handles);
// ...
uv__queue_init(&loop->handle_queue);
// ...
err = uv_async_init(loop, &loop->wq_async, uv__work_done);

큐는 큐에 대한 포인터이며, 큐의 첫 번째 요소는 큐에 대한 포인터를 가리킵니다.

static inline void uv__queue_init(struct uv__queue* q) {
 q->next = q;
 q->prev = q;
}

대기열 구조는 다음과 같습니다.

struct uv__queue {
 struct uv__queue* next;
 struct uv__queue* prev;
};

idle_handles 대기열에 대한 포인터 &loop->idle_handles = 0x00000001000794d8.

핸들_큐 큐에 대한 포인터 &loop->handle_queue = 0x00000001000792a0.

err = uv_async_init(loop, &loop->wq_async, uv__work_done);

이 문장은 비동기성을 포함하며, 자세한 내용은 지금은 생략하고 loop->handle_queue 미치는 영향은 wq_async->handle_queue 머리에 loop->handle_queue 삽입하는 것입니다.

wq_async->handle_queue 대기열에 대한 포인터 &loop->wq_async->handle_queue = 0x0000000100079378.

uv_default_loop가 초기화되면 uv_idle_init이 실행됩니다.

idle_handle 구조

int uv_##name##_init(uv_loop_t* loop, uv_##name##_t* handle) { \
 uv__handle_init(loop, (uv_handle_t*)handle, UV_##type); \
 handle->name##_cb = NULL; \
 return 0; \
 } 
#define uv__handle_init(loop_, h, type_) \
 do { \
 (h)->loop = (loop_); \
 (h)->type = (type_); \
 (h)->flags = UV_HANDLE_REF; /* Ref the loop when active. */ \
 uv__queue_insert_tail(&(loop_)->handle_queue, &(h)->handle_queue); \
 uv__handle_platform_init(h); \
 } \
 while (0)
static inline void uv__queue_insert_tail(struct uv__queue* h,
 struct uv__queue* q) {
 q->next = h;
 q->prev = h->prev;
 q->prev->next = q;
 h->prev = q;
}

이 단계는 uv_async_init처럼 작동하여 idle_handle->handle_queueloop->handle_queue 삽입합니다.

따라서 loop->handle_queue 루프 대기열에는

  • 루프 -> 핸들 큐에 대한 포인터
  • wq_async->handle_queue
  • idle_handle->handle_queue

로 이동

v_idle_start(&idle, idle_cb);
 int uv_##name##_start(uv_##name##_t* handle, uv_##name##_cb cb) { \
 if (uv__is_active(handle)) return 0; \
 if (cb == NULL) return UV_EINVAL; \
 uv__queue_insert_head(&handle->loop->name##_handles, &handle->queue); \
 handle->name##_cb = cb; \
 uv__handle_start(handle); \
 return 0; \
 } 

h는 loop->idle_handles, q는 idle_handle->queue이니 헷갈리지 마세요.

static inline void uv__queue_insert_head(struct uv__queue* h,
 struct uv__queue* q) {
 q->next = h->next;
 q->prev = h;
 q->next->prev = q;
 h->next = q;
}
  • 루프->유휴_핸들에 대한 포인터
  • idle_handle->queue

그 후

uv_run(uv_default_loop(), UV_RUN_DEFAULT);

루프가 이미 정의되어 있으므로 uv_default_loop()는 정의된 루프를 직접 반환합니다.

int uv_run(uv_loop_t* loop, uv_run_mode mode) {
 int timeout;
 int r;
 int can_sleep;
 r = uv__loop_alive(loop);
 if (!r)
 uv__update_time(loop);
 /* Maintain backwards compatibility by processing timers before entering the
 * while loop for UV_RUN_DEFAULT. Otherwise timers only need to be executed
 * once, which should be done after polling in order to maintain proper
 * execution order of the conceptual event loop. */
 if (mode == UV_RUN_DEFAULT && r != 0 && loop->stop_flag == 0) {
 uv__update_time(loop);
 uv__run_timers(loop);
 }
 while (r != 0 && loop->stop_flag == 0) {
 can_sleep =
 uv__queue_empty(&loop->pending_queue) &&
 uv__queue_empty(&loop->idle_handles);
 uv__run_pending(loop);
 uv__run_idle(loop);
 uv__run_prepare(loop);
 timeout = 0;
 if ((mode == UV_RUN_ONCE && can_sleep) || mode == UV_RUN_DEFAULT)
 timeout = uv__backend_timeout(loop);
 uv__metrics_inc_loop_count(loop);
 uv__io_poll(loop, timeout);
 /* Process immediate callbacks (e.g. write_cb) a small fixed number of
 * times to avoid loop starvation.*/
 for (r = 0; r < 8 && !uv__queue_empty(&loop->pending_queue); r++)
 uv__run_pending(loop);
 /* Run one final update on the provider_idle_time in case uv__io_poll
 * returned because the timeout expired, but no events were received. This
 * call will be ignored if the provider_entry_time was either never set (if
 * the timeout == 0) or was already updated b/c an event was received.
 */
 uv__metrics_update_idle_time(loop);
 uv__run_check(loop);
 uv__run_closing_handles(loop);
 uv__update_time(loop);
 uv__run_timers(loop);
 r = uv__loop_alive(loop);
 if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
 break;
 }
 /* The if statement lets gcc compile it to a conditional store. Avoids
 * dirtying a cache line.
 */
 if (loop->stop_flag != 0)
 loop->stop_flag = 0;
 return r;
}

이번에는 uv__run_idle(loop)를 살펴보세요;

void uv__run_##name(uv_loop_t* loop) { \
 uv_##name##_t* h; \
 struct uv__queue queue; \
 struct uv__queue* q; \
 uv__queue_move(&loop->name##_handles, &queue); \
 while (!uv__queue_empty(&queue)) { \
 q = uv__queue_head(&queue); \
 h = uv__queue_data(q, uv_##name##_t, queue); \
 uv__queue_remove(q); \
 uv__queue_insert_tail(&loop->name##_handles, q); \
 h->name##_cb(h); \
 } \
 }
static inline void uv__queue_move(struct uv__queue* h, struct uv__queue* n) {
 if (uv__queue_empty(h))
 uv__queue_init(n);
 else
 uv__queue_split(h, h->next, n);
}

loop->idle_handles가 비어 있지 않습니다.

h는 루프->유휴_핸들입니다.

Q는 루프->유휴_핸들->넥스트입니다.

n은 새로 정의된 빈 대기열입니다.

static inline void uv__queue_split(struct uv__queue* h,
 struct uv__queue* q,
 struct uv__queue* n) {
 n->prev = h->prev;
 n->prev->next = n;
 n->next = q;
 h->prev = q->prev;
 h->prev->next = h;
 q->prev = n;
}

loop->idle_handles는 이제 큐에서 제외되고 새 큐 큐가 loop->idle_handles를 대신합니다.

동안 루프 입력

while (!uv__queue_empty(&queue)) { \
 q = uv__queue_head(&queue); \
 h = uv__queue_data(q, uv_##name##_t, queue); \
 uv__queue_remove(q); \
 uv__queue_insert_tail(&loop->name##_handles, q); \
 h->name##_cb(h); \
 } 

q는 idle_handle->queue에 대한 포인터입니다.

static inline struct uv__queue* uv__queue_head(const struct uv__queue* q) {
 return q->next;
}

구조체 uv_idle_t의 큐 위치와 idle_handle에 대한 포인터를 기반으로, idle_handle에 대한 전체 정보를 유추할 수 있습니다.

h = uv__queue_data(q, uv_idle_t, queue); 
#define uv__queue_data(pointer, type, field) \
 ((type*) ((char*) (pointer) - offsetof(type, field)))

대기열에서 idle_handle 제외하기

static inline void uv__queue_remove(struct uv__queue* q) {
 q->prev->next = q->next;
 q->next->prev = q->prev;
}

그런 다음 idle_handle을 loop->idle_handles에 다시 넣습니다.

마지막 통화

h->name##_cb(h);

데모에서 cb 실행하기

void idle_cb(uv_idle_t *handle) {
 printf("idle callback
");
}

마지막 uv__queue_insert_tail은 idle_handle을 loop->idle_handles 큐에 다시 넣기 때문에, 이 콜백은 항상 현재 실행됩니다.

Read next

React 훅으로 카운트다운 타이머 컴포넌트 감싸기

React Hook은 카운트다운 타이머 컴포넌트를 캡슐화하며, React Hook을 사용하면 React의 기능을 사용할 수 있을 뿐만 아니라 공용 로직의 장점을 캡슐화할 수 있습니다. SMS 카운트다운 사용자 정의 래핑

Oct 18, 2025 · 1 min read