이벤트 루프(Event Loop)의 내부 들여다보기:
Clair.Event_Loop 사례 연구
안녕하세요, 오늘은 시스템 프로그래밍의 꽃이라 불리는 '이벤트 루프(Event Loop)'에 대해 이야기해보려 합니다.
많은 분이 고수준 프레임워크 뒤에 숨겨진 이벤트 루프를 마법처럼 느끼곤 하지만, 사실 그 밑바닥은 아주 냉정하고 치밀한 '기다림과 배달의 미학'으로 이루어져 있습니다. Ada로 구현된 시스템 라이브러리인 Clair.Event_Loop의 코드를 따라가며 그 설계를 파헤쳐 봅시다.
1. 이벤트 루프란 무엇인가? (식당의 웨이터)
이벤트 루프를 가장 쉽게 이해하는 방법은 '유능한 웨이터'를 떠올리는 것입니다.
* 웨이터는 주방에서 음식이 나오길 기다리거나(I/O),
* 손님이 벨을 누르길 기다리거나(Signal),
* 정해진 시간마다 테이블을 닦습니다(Timer).
중요한 건 웨이터가 한 명이라는 것입니다. 한 명이 이 모든 일을 효율적으로 처리하려면, "무언가 발생했을 때(Event)"만 움직이는 무한 루프가 필요합니다.
2. 커널의 귀를 빌리다: kqueue
Clair.Event_Loop는 BSD 계열(macOS 포함)의 강력한 커널 이벤트 통지 메커니즘인 kqueue를 사용합니다.(추후 epoll 지원 예정)
-- 시스템 콜 직접 호출
count := kevent_c (Interfaces.C.int(self.fd),
System.NULL_ADDRESS, 0,
events_ptr, MAX_EVENTS,
ts_access);
루프의 핵심은 kevent라는 시스템 콜입니다. 프로그램은 여기서 잠(Sleep)에 듭니다. CPU를 점유하지 않고 가만히 기다리다가, 커널이 "야, 데이터 왔어!" 혹은 "타이머 울렸어!"라고 깨워주면 그제야 일어납니다. 시스템 자원을 극도로 아끼는 설계죠.
3. 죽음의 굴레를 피하는 법: 가비지 컬렉션 설계
이벤트 루프를 설계할 때 가장 골치 아픈 문제는 "이벤트 처리 중에 나 자신을 삭제하면 어떡하지?"입니다. (자신이 속한 리스트에서 자신을 삭제하는 콜백 함수 등)
Clair는 이를 call_depth와 garbage_head로 우아하게 해결합니다.
procedure release (self : in out Context; src : Handle) is
begin
src.ref_count := src.ref_count - 1;
if src.ref_count = 0 then
if self.call_depth > 0 then
-- 지금 루프 도는 중이니까 일단 '쓰레기통'에 넣어둬!
src.next_garbage := self.garbage_head;
self.garbage_head := src;
else
free_source (src); -- 안전할 때 진짜 해제
end if;
end if;
end release;
루프가 실행 중일 때는 메모리를 즉시 해제하지 않고 쓰레기통에 모아두었다가, 루프가 한 바퀴 완전히 끝나 안전해졌을 때 비웁니다. 시스템 안정성을 확보하는 고전적이면서도 확실한 방법입니다.
4. 0.001초의 미학: 시간 관리
이벤트 루프는 시간을 엄격하게 다룹니다. Clair는 CLOCK_MONOTONIC을 사용하여 시스템 시간이 뒤로 가거나 갑자기 바뀌어도 흔들리지 않는 절대적인 마감 시간(Deadline)을 계산합니다.
특히 흥미로운 점은 'Spurious Wakeup(가짜 깨어남)' 방지 로직입니다. 커널이 아무 이유 없이 루프를 깨울 때가 있는데, 이때 Clair는 delay 0.001을 주어 아주 짧게 재정비한 뒤 다시 잠에 듭니다. 시스템 콜의 미묘한 오차까지 방어하는 치밀함이죠.
5. 틈새 시장: Idle 태스크
이벤트 루프에 할 일이 없을 때는 놀아야 할까요? 아니요, Clair는 Idle 태스크를 처리합니다.
-- 이벤트가 없으면 대기하지 말고 즉시 처리
if self.idle_count > 0 then
actual_timeout := IMMEDIATE;
end if;
I/O나 타이머 같은 긴급한 일이 없을 때, 우선순위가 낮은 작업들을 조금씩 처리합니다. 이때 MAX_IDLE_BATCH를 두어 특정 작업이 루프를 독점(Starvation 기아 방지)하는 것을 방지하는 센스도 잊지 않았습니다.
결론: 명시적 제어의 즐거움
Clair.Event_Loop를 공부하며 느끼는 점은, 시스템 프로그래밍의 본질은 '제어권'에 있다는 것입니다.
운영체제가 제공하는 로우레벨 API를 직접 다루며, 메모리의 해제 시점을 결정하고, 나노초 단위의 타임아웃을 계산하는 과정은 번거롭지만 그만큼 강력합니다. 블랙박스 같은 라이브러리에 의존하는 대신, 루프의 톱니바퀴 하나하나를 직접 깎아 만드는 즐거움. 그것이 바로 시스템 엔지니어의 참맛 아닐까요?
한 줄 요약:
이벤트 루프는 커널의 알림을 기다리는 '무한 대기조'이며, Clair처럼 정교한 자원 관리와 예외 처리가 결합될 때 비로소 멈추지 않는 시스템의 심장이 된다.
게이가 올렸던거 전부 하나도 몰랐는데 이건 좀 알듯하노 이기