Last active
July 22, 2021 05:26
-
-
Save okmttdhr/b62f4f2aaba64ed42c86a7818f48251d to your computer and use it in GitHub Desktop.
libuv event loop source code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://github.com/okmttdhr/libuv/blob/8ea8f124386486af2380127c6885848f9d502a36/src/unix/epoll.c#L208-L420 | |
for (;;) { | |
/* Only need to set the provider_entry_time if timeout != 0. The function | |
* will return early if the loop isn't configured with UV_METRICS_IDLE_TIME. | |
*/ | |
if (timeout != 0) | |
uv__metrics_set_provider_entry_time(loop); | |
/* See the comment for max_safe_timeout for an explanation of why | |
* this is necessary. Executive summary: kernel bug workaround. | |
*/ | |
if (sizeof(int32_t) == sizeof(long) && timeout >= max_safe_timeout) | |
timeout = max_safe_timeout; | |
if (sigmask != 0 && no_epoll_pwait != 0) | |
if (pthread_sigmask(SIG_BLOCK, &sigset, NULL)) | |
abort(); | |
if (no_epoll_wait != 0 || (sigmask != 0 && no_epoll_pwait == 0)) { | |
nfds = epoll_pwait(loop->backend_fd, | |
events, | |
ARRAY_SIZE(events), | |
timeout, | |
&sigset); | |
if (nfds == -1 && errno == ENOSYS) { | |
uv__store_relaxed(&no_epoll_pwait_cached, 1); | |
no_epoll_pwait = 1; | |
} | |
} else { | |
nfds = epoll_wait(loop->backend_fd, | |
events, | |
ARRAY_SIZE(events), | |
timeout); | |
if (nfds == -1 && errno == ENOSYS) { | |
uv__store_relaxed(&no_epoll_wait_cached, 1); | |
no_epoll_wait = 1; | |
} | |
} | |
if (sigmask != 0 && no_epoll_pwait != 0) | |
if (pthread_sigmask(SIG_UNBLOCK, &sigset, NULL)) | |
abort(); | |
/* Update loop->time unconditionally. It's tempting to skip the update when | |
* timeout == 0 (i.e. non-blocking poll) but there is no guarantee that the | |
* operating system didn't reschedule our process while in the syscall. | |
*/ | |
SAVE_ERRNO(uv__update_time(loop)); | |
if (nfds == 0) { | |
assert(timeout != -1); | |
if (reset_timeout != 0) { | |
timeout = user_timeout; | |
reset_timeout = 0; | |
} | |
if (timeout == -1) | |
continue; | |
if (timeout == 0) | |
return; | |
/* We may have been inside the system call for longer than |timeout| | |
* milliseconds so we need to update the timestamp to avoid drift. | |
*/ | |
goto update_timeout; | |
} | |
if (nfds == -1) { | |
if (errno == ENOSYS) { | |
/* epoll_wait() or epoll_pwait() failed, try the other system call. */ | |
assert(no_epoll_wait == 0 || no_epoll_pwait == 0); | |
continue; | |
} | |
if (errno != EINTR) | |
abort(); | |
if (reset_timeout != 0) { | |
timeout = user_timeout; | |
reset_timeout = 0; | |
} | |
if (timeout == -1) | |
continue; | |
if (timeout == 0) | |
return; | |
/* Interrupted by a signal. Update timeout and poll again. */ | |
goto update_timeout; | |
} | |
have_signals = 0; | |
nevents = 0; | |
{ | |
/* Squelch a -Waddress-of-packed-member warning with gcc >= 9. */ | |
union { | |
struct epoll_event* events; | |
uv__io_t* watchers; | |
} x; | |
x.events = events; | |
assert(loop->watchers != NULL); | |
loop->watchers[loop->nwatchers] = x.watchers; | |
loop->watchers[loop->nwatchers + 1] = (void*) (uintptr_t) nfds; | |
} | |
for (i = 0; i < nfds; i++) { | |
pe = events + i; | |
fd = pe->data.fd; | |
/* Skip invalidated events, see uv__platform_invalidate_fd */ | |
if (fd == -1) | |
continue; | |
assert(fd >= 0); | |
assert((unsigned) fd < loop->nwatchers); | |
w = loop->watchers[fd]; | |
if (w == NULL) { | |
/* File descriptor that we've stopped watching, disarm it. | |
* | |
* Ignore all errors because we may be racing with another thread | |
* when the file descriptor is closed. | |
*/ | |
epoll_ctl(loop->backend_fd, EPOLL_CTL_DEL, fd, pe); | |
continue; | |
} | |
/* Give users only events they're interested in. Prevents spurious | |
* callbacks when previous callback invocation in this loop has stopped | |
* the current watcher. Also, filters out events that users has not | |
* requested us to watch. | |
*/ | |
pe->events &= w->pevents | POLLERR | POLLHUP; | |
/* Work around an epoll quirk where it sometimes reports just the | |
* EPOLLERR or EPOLLHUP event. In order to force the event loop to | |
* move forward, we merge in the read/write events that the watcher | |
* is interested in; uv__read() and uv__write() will then deal with | |
* the error or hangup in the usual fashion. | |
* | |
* Note to self: happens when epoll reports EPOLLIN|EPOLLHUP, the user | |
* reads the available data, calls uv_read_stop(), then sometime later | |
* calls uv_read_start() again. By then, libuv has forgotten about the | |
* hangup and the kernel won't report EPOLLIN again because there's | |
* nothing left to read. If anything, libuv is to blame here. The | |
* current hack is just a quick bandaid; to properly fix it, libuv | |
* needs to remember the error/hangup event. We should get that for | |
* free when we switch over to edge-triggered I/O. | |
*/ | |
if (pe->events == POLLERR || pe->events == POLLHUP) | |
pe->events |= | |
w->pevents & (POLLIN | POLLOUT | UV__POLLRDHUP | UV__POLLPRI); | |
if (pe->events != 0) { | |
/* Run signal watchers last. This also affects child process watchers | |
* because those are implemented in terms of signal watchers. | |
*/ | |
if (w == &loop->signal_io_watcher) { | |
have_signals = 1; | |
} else { | |
uv__metrics_update_idle_time(loop); | |
w->cb(loop, w, pe->events); | |
} | |
nevents++; | |
} | |
} | |
if (reset_timeout != 0) { | |
timeout = user_timeout; | |
reset_timeout = 0; | |
} | |
if (have_signals != 0) { | |
uv__metrics_update_idle_time(loop); | |
loop->signal_io_watcher.cb(loop, &loop->signal_io_watcher, POLLIN); | |
} | |
loop->watchers[loop->nwatchers] = NULL; | |
loop->watchers[loop->nwatchers + 1] = NULL; | |
if (have_signals != 0) | |
return; /* Event loop should cycle now so don't poll again. */ | |
if (nevents != 0) { | |
if (nfds == ARRAY_SIZE(events) && --count != 0) { | |
/* Poll for more events but don't block this time. */ | |
timeout = 0; | |
continue; | |
} | |
return; | |
} | |
if (timeout == 0) | |
return; | |
if (timeout == -1) | |
continue; | |
update_timeout: | |
assert(timeout > 0); | |
real_timeout -= (loop->time - base); | |
if (real_timeout <= 0) | |
return; | |
timeout = real_timeout; | |
} | |
// https://github.com/okmttdhr/libuv/blob/8ea8f124386486af2380127c6885848f9d502a36/src/unix/epoll.c#L143-L176 | |
while (!QUEUE_EMPTY(&loop->watcher_queue)) { | |
q = QUEUE_HEAD(&loop->watcher_queue); | |
QUEUE_REMOVE(q); | |
QUEUE_INIT(q); | |
w = QUEUE_DATA(q, uv__io_t, watcher_queue); | |
assert(w->pevents != 0); | |
assert(w->fd >= 0); | |
assert(w->fd < (int) loop->nwatchers); | |
e.events = w->pevents; | |
e.data.fd = w->fd; | |
if (w->events == 0) | |
op = EPOLL_CTL_ADD; | |
else | |
op = EPOLL_CTL_MOD; | |
/* XXX Future optimization: do EPOLL_CTL_MOD lazily if we stop watching | |
* events, skip the syscall and squelch the events after epoll_wait(). | |
*/ | |
if (epoll_ctl(loop->backend_fd, op, w->fd, &e)) { | |
if (errno != EEXIST) | |
abort(); | |
assert(op == EPOLL_CTL_ADD); | |
/* We've reactivated a file descriptor that's been watched before. */ | |
if (epoll_ctl(loop->backend_fd, EPOLL_CTL_MOD, w->fd, &e)) | |
abort(); | |
} | |
w->events = w->pevents; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment