static int event_unregister_epoll_common (struct event_pool *event_pool, int fd, int idx, int do_close) { int ret = -1; struct event_slot_epoll *slot = NULL; GF_VALIDATE_OR_GOTO ("event", event_pool, out); slot = event_slot_get (event_pool, idx); assert (slot->fd == fd); LOCK (&slot->lock); { ret = epoll_ctl (event_pool->fd, EPOLL_CTL_DEL, fd, NULL); if (ret == -1) { gf_log ("epoll", GF_LOG_ERROR, "fail to del fd(=%d) from epoll fd(=%d) (%s)", fd, event_pool->fd, strerror (errno)); goto unlock; } slot->do_close = do_close; slot->gen++; /* detect unregister in dispatch_handler() */ } unlock: UNLOCK (&slot->lock); event_slot_unref (event_pool, slot, idx); /* one for event_register() */ event_slot_unref (event_pool, slot, idx); /* one for event_slot_get() */ out: return ret; }
static int event_select_on_epoll (struct event_pool *event_pool, int fd, int idx, int poll_in, int poll_out) { int ret = -1; struct event_slot_epoll *slot = NULL; struct epoll_event epoll_event = {0, }; struct event_data *ev_data = (void *)&epoll_event.data; GF_VALIDATE_OR_GOTO ("event", event_pool, out); slot = event_slot_get (event_pool, idx); assert (slot->fd == fd); LOCK (&slot->lock); { __slot_update_events (slot, poll_in, poll_out); epoll_event.events = slot->events; ev_data->idx = idx; ev_data->gen = slot->gen; if (slot->in_handler) /* in_handler indicates at least one thread executing event_dispatch_epoll_handler() which will perform epoll_ctl(EPOLL_CTL_MOD) anyways (because of EPOLLET) This not only saves a system call, but also avoids possibility of another epoll thread parallely picking up the next event while the ongoing handler is still in progress (and resulting in unnecessary contention on rpc_transport_t->mutex). */ goto unlock; ret = epoll_ctl (event_pool->fd, EPOLL_CTL_MOD, fd, &epoll_event); if (ret == -1) { gf_msg ("epoll", GF_LOG_ERROR, errno, LG_MSG_EPOLL_FD_MODIFY_FAILED, "failed to " "modify fd(=%d) events to %d", fd, epoll_event.events); } } unlock: UNLOCK (&slot->lock); event_slot_unref (event_pool, slot, idx); out: return idx; }
static int event_dispatch_epoll_handler (struct event_pool *event_pool, struct epoll_event *event) { struct event_data *ev_data = NULL; struct event_slot_epoll *slot = NULL; event_handler_t handler = NULL; void *data = NULL; int idx = -1; int gen = -1; int ret = -1; int fd = -1; ev_data = (void *)&event->data; handler = NULL; data = NULL; idx = ev_data->idx; gen = ev_data->gen; slot = event_slot_get (event_pool, idx); LOCK (&slot->lock); { fd = slot->fd; if (fd == -1) { gf_log ("epoll", GF_LOG_ERROR, "stale fd found on idx=%d, gen=%d, events=%d, " "slot->gen=%d", idx, gen, event->events, slot->gen); /* fd got unregistered in another thread */ goto pre_unlock; } if (gen != slot->gen) { gf_log ("epoll", GF_LOG_ERROR, "generation mismatch on idx=%d, gen=%d, " "slot->gen=%d, slot->fd=%d", idx, gen, slot->gen, slot->fd); /* slot was re-used and therefore is another fd! */ goto pre_unlock; } handler = slot->handler; data = slot->data; slot->in_handler++; } pre_unlock: UNLOCK (&slot->lock); if (!handler) goto out; ret = handler (fd, idx, data, (event->events & (EPOLLIN|EPOLLPRI)), (event->events & (EPOLLOUT)), (event->events & (EPOLLERR|EPOLLHUP))); LOCK (&slot->lock); { slot->in_handler--; if (gen != slot->gen) { /* event_unregister() happened while we were in handler() */ gf_log ("epoll", GF_LOG_DEBUG, "generation bumped on idx=%d from " "gen=%d to slot->gen=%d, fd=%d, " "slot->fd=%d", idx, gen, slot->gen, fd, slot->fd); goto post_unlock; } /* This call also picks up the changes made by another thread calling event_select_on_epoll() while this thread was busy in handler() */ if (slot->in_handler == 0) { event->events = slot->events; ret = epoll_ctl (event_pool->fd, EPOLL_CTL_MOD, fd, event); } } post_unlock: UNLOCK (&slot->lock); out: event_slot_unref (event_pool, slot, idx); return ret; }
int event_register_epoll (struct event_pool *event_pool, int fd, event_handler_t handler, void *data, int poll_in, int poll_out) { int idx = -1; int ret = -1; int destroy = 0; struct epoll_event epoll_event = {0, }; struct event_data *ev_data = (void *)&epoll_event.data; struct event_slot_epoll *slot = NULL; GF_VALIDATE_OR_GOTO ("event", event_pool, out); /* TODO: Even with the below check, there is a possiblity of race, * What if the destroy mode is set after the check is done. * Not sure of the best way to prevent this race, ref counting * is one possibility. * There is no harm in registering and unregistering the fd * even after destroy mode is set, just that such fds will remain * open until unregister is called, also the events on that fd will be * notified, until one of the poller thread is alive. */ pthread_mutex_lock (&event_pool->mutex); { destroy = event_pool->destroy; } pthread_mutex_unlock (&event_pool->mutex); if (destroy == 1) goto out; idx = event_slot_alloc (event_pool, fd); if (idx == -1) { gf_log ("epoll", GF_LOG_ERROR, "could not find slot for fd=%d", fd); return -1; } slot = event_slot_get (event_pool, idx); assert (slot->fd == fd); LOCK (&slot->lock); { /* make epoll 'singleshot', which means we need to re-add the fd with epoll_ctl(EPOLL_CTL_MOD) after delivery of every single event. This assures us that while a poller thread has picked up and is processing an event, another poller will not try to pick this at the same time as well. */ slot->events = EPOLLPRI | EPOLLONESHOT; slot->handler = handler; slot->data = data; __slot_update_events (slot, poll_in, poll_out); epoll_event.events = slot->events; ev_data->idx = idx; ev_data->gen = slot->gen; ret = epoll_ctl (event_pool->fd, EPOLL_CTL_ADD, fd, &epoll_event); /* check ret after UNLOCK() to avoid deadlock in event_slot_unref() */ } UNLOCK (&slot->lock); if (ret == -1) { gf_log ("epoll", GF_LOG_ERROR, "failed to add fd(=%d) to epoll fd(=%d) (%s)", fd, event_pool->fd, strerror (errno)); event_slot_unref (event_pool, slot, idx); idx = -1; } /* keep slot->ref (do not event_slot_unref) if successful */ out: return idx; }