콘텐츠로 이동

레퍼런스 목차

15. Polling and pollers

이 category는 raw socket, file descriptor, generic timer를 하나의 event loop에서 기다리는 진입점 — 일회성 zlink_poll과 재사용 가능한 poller family를 다룬다. Poller readiness는 Core의 세 event family 중 하나다(나머지 둘은 socket monitor 이벤트와 timer fire — Socket monitor·Timers category 참고). 정확한 signature는 Polling 스펙이 소유한다.


Caller가 제공한 poll item 배열을 한 번 기다린다.

zlink_pollitem_t items[2] = {
    { .socket = s1, .events = ZLINK_POLLIN },
    { .socket = s2, .events = ZLINK_POLLIN | ZLINK_POLLOUT },
};
zlink_config_result_t err;
int ready = zlink_poll(items, 2, /*timeout_ms=*/1000, &err);

Parameters. itemszlink_pollitem_t(socket/fd/events/revents) 배열이다. item_count는 배열 길이다. timeout_ms는 무한이면 -1, 즉시 반환이면 0이다. error_out은 실패 상세를 받는다.

Return과 errno. 준비된 item 수를 반환하고, timeout이면 0, 실패면 -1을 반환하며 error_outerrno 둘 다 설정된다.

선택 기준. 작고 고정된 item 집합에 대한 단일 대기에 쓴다 — 각 item의 revents는 평가 전에 지워지므로 반환 직후의 스냅샷일 뿐이다. Item 집합이 자주 바뀌거나 fd·timer source가 필요하면 대신 아래 재사용 가능한 poller family를 쓴다 — 매 호출마다 배열을 다시 훑는 것은 점진적으로 유지되는 poller만큼 확장성이 좋지 않다.


재사용 가능한 poller를 만들거나, 파괴하거나, 현재 보유한 source 개수를 읽는다.

void *poller = zlink_poller_new();
// ...
zlink_config_result_t err;
int n = zlink_poller_size(poller, &err);
// ...
zlink_poller_destroy(&poller);

Parameters. new는 인자가 없다. destroyvoid **poller_p를 받는다(handle을 비울 수 있다). size는 poller와 error_out 출력을 받는다.

Return과 errno. new는 poller handle 또는 NULL을 반환한다. destroyzlink_close_result_t를 반환한다 — wait가 진행 중일 때 파괴하면 ZLINK_CLOSE_BUSYEBUSY. size는 개수를, 실패하면 -1을 반환한다.

선택 기준. Poller 인스턴스 하나가 필요한 만큼의 source를 처리하며 여러 wait 호출에 재사용된다 — 서로 다른 poller는 동시에 쓸 수 있지만, caller는 하나의 poller에서 add/modify/remove/wait를 직렬화해야 한다.


Poller에 raw socket source를 등록·갱신·제거한다.

zlink_poller_add(poller, s, userdata, ZLINK_POLLIN);
zlink_poller_modify(poller, s, ZLINK_POLLIN | ZLINK_POLLOUT);
zlink_poller_remove(poller, s);

Parameters. source는 socket handle이다. user_data(add에만)는 일치하는 zlink_poller_event_t 항목으로 되돌려받는 borrowed pointer다. eventszlink_pollitem_t와 같은 zlink_poller_event_mask_t bit에 더해 ZLINK_POLLCOMPLETION(completion queue를 가진 raw PAIR·DEALER·ROUTER·STREAM을 추가할 때만 유효 — 아래 참고)을 받는다.

Return과 errno. 셋 다 zlink_config_result_t를 반환한다 — 성공하면 ZLINK_CONFIG_OK. 이미 등록된 source를 추가하면 ZLINK_CONFIG_CONFLICTEEXIST. 없는 source를 갱신·제거하면 ZLINK_CONFIG_NOT_FOUNDENOENT. 잘못된 event bit면 ZLINK_CONFIG_INVALID_ARGUMENTEINVAL. Source가 지원하지 않는 event면 ZLINK_CONFIG_NOT_SUPPORTEDENOTSUP.

선택 기준. Poller는 source handle을 빌릴 뿐이다 — 파괴하기 전에 source를 제거한다. ZLINK_POLLCOMPLETION을(단독으로, 또는 ZLINK_POLLIN/ZLINK_POLLOUT과 OR로) 설정하면 하나의 poller가 같은 socket의 DATA readiness와 completion readiness를 함께 소유한다. DEALERROUTER는 REQUEST 결과에 쓰고(DEALER/ROUTER category), PAIR·DEALER·ROUTER·STREAM은 pending-send completion record에도 쓴다. 이 등록은 readiness만 소유하며 zlink_poller_wait()가 queue record를 제거하지는 않는다. 다른 source, zlink_poll item, zlink_poller_modify에 쓰면 ZLINK_CONFIG_INVALID_ARGUMENT/EINVAL을 반환한다. 등록된 source가 닫히면 POLLERR를 한 번 만들며 명시적으로 제거될 때까지 등록 상태로 남는다.


Poller에 raw platform file-descriptor source를 등록·갱신·제거한다.

zlink_poller_add_fd(poller, fd, userdata, ZLINK_POLLIN);

Parameters. fdzlink_fd_t(플랫폼 file descriptor/handle)다. 나머지는 zlink_poller_add/_modify/_remove와 모양이 같다.

Return과 errno. 위 socket-source 삼총사와 같다 — 중복 add면 ZLINK_CONFIG_CONFLICT/EEXIST, 없는 source면 ZLINK_CONFIG_NOT_FOUND/ENOENT.

선택 기준. 순수 OS file descriptor를 socket·timer와 같은 event loop에 접어 넣을 때 쓴다 — readiness는 raw-socket readiness 규칙이 아니라 플랫폼 poll semantics(readable/ writable)를 따른다.


Poller에 generic timer source(Timers category)를 등록·제거한다.

zlink_poller_add_timer(poller, timer, userdata);
zlink_poller_remove_timer(poller, timer);

Parameters. timerzlink_timer_new(Timers category)의 handle이다. events mask는 없다 — timer source는 항상 POLLIN만 신호한다(fire count를 받을 수 있음).

Return과 errno. 둘 다 zlink_config_result_t를 반환한다 — 다른 두 source family와 같은 conflict/not-found 매핑이다.

선택 기준. Socket·FD와 같은 wait loop로 timer fire를 받고, wait가 알리면 zlink_timer_recv(Timers category)로 누적된 count를 소진할 때 쓴다.


Poller에 현재 등록된 모든 source에 대한 readiness를 기다린다.

zlink_poller_event_t events[16];
zlink_config_result_t err;
int ready = zlink_poller_wait(poller, events, 16, /*timeout_ms=*/1000, &err);

for (int i = 0; i < ready; ++i) {
    if ((events[i].events & ZLINK_POLLCOMPLETION) == 0)
        continue;
    for (;;) {
        zlink_completion_t completion = {0};
        completion.struct_size = sizeof(completion);
        zlink_recv_result_t rc = zlink_completion_recv(
            events[i].socket, &completion, ZLINK_RECV_FLAGS_DONTWAIT);
        if (rc == ZLINK_RECV_NO_DATA)
            break;
        if (rc != ZLINK_RECV_OK)
            break; /* typed receive 실패 처리 */
        /* completion_id 또는 user_context를 대조하고 결과를 처리한다. */
        zlink_completion_close(&completion);
    }
}

Parameters. events/event_capacity는 caller 소유 출력 배열과 그 크기다. timeout_mserror_outzlink_poll과 같은 관례를 따른다.

Return과 errno. 쓰인 준비된 이벤트 수를 반환하고, timeout이면 0, 실패면 -1을 반환하며 error_out/errno가 설정된다. 각 zlink_poller_event_tsource_kind(SOCKET/FD/TIMER — 일치하는 socket/fd/timer 중 하나만 유효), user_data(등록 시의 borrowed pointer), events를 보고한다.

선택 기준. 반환된 배열은 caller 소유이며 Core storage에 대한 pointer를 담지 않는다. ZLINK_POLLCOMPLETION은 level-triggered다. Wait는 readiness를 보고하지만 record를 소비하지 않는다. 준비된 socket마다 DONTWAIT zlink_completion_recv()ZLINK_RECV_NO_DATA까지 반복하고 성공한 record를 모두 close한다. Socket의 completion bit는 poller registration 하나만 소유할 수 있고 다른 thread가 동시에 drain하는 것은 지원하지 않는다. recv_part family는 이 queue를 스스로 소진하지 않는다.


Source별 readiness

Source POLLIN POLLOUT 추가 규칙
raw socket 완결된 record를 받을 수 있음 submit 재시도가 가치 있음 Socket별 수신 모드가 적용됨
timer fire count를 받을 수 있음 지원 안 함 zlink_timer_recv()(Timers category)로 소진
FD 플랫폼에서 읽기 가능 플랫폼에서 쓰기 가능 플랫폼 poll semantics

ZLINK_POLLITEMS_DFLT는 stack buffer용 권장 초기 item 개수 힌트일 뿐 readiness bit가 아니다. ZLINK_HAVE_POLLER == 1은 이 공개 poller API가 빌드에 포함되어 있다는 뜻이다.


전체 근거는 Polling 스펙을 참고한다.