/* * Copyright (c) 2013-2014, Sony Mobile Communications Inc. * Copyright (c) 2014, Courtney Cavin * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of the organization nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include "list.h" #include "waiter.h" #include "util.h" struct pollset { int nfds; int cause; }; static struct pollset *pollset_create(int count) { struct pollset *ps; ps = calloc(1, sizeof(*ps) + sizeof(struct pollfd) * count); if (ps == NULL) return NULL; return ps; } static void pollset_destroy(struct pollset *ps) { free(ps); } static void pollset_reset(struct pollset *ps) { ps->nfds = 0; } static void pollset_add_fd(struct pollset *ps, int fd) { struct pollfd *pfd = (struct pollfd *)(ps + 1); pfd[ps->nfds].fd = fd; pfd[ps->nfds].events = POLLERR | POLLIN; ps->nfds++; } static int pollset_wait(struct pollset *ps, int ms) { struct pollfd *pfd = (struct pollfd *)(ps + 1); int rc; int i; rc = poll(pfd, ps->nfds, ms); if (rc <= 0) return rc; ps->cause = -1; for (i = 0; i < ps->nfds; ++i) { if (pfd[i].revents & (POLLERR | POLLIN)) { ps->cause = i; break; } } return rc; } static int pollset_cause_fd(struct pollset *ps, int fd) { struct pollfd *pfd = (struct pollfd *)(ps + 1); return (ps->cause >= 0 && pfd[ps->cause].fd == fd); } enum waiter_type { WATCH_TYPE_NULL, WATCH_TYPE_FD, WATCH_TYPE_TIMEOUT, }; struct waiter_ticket { enum waiter_type type; union { int filedes; unsigned int event; unsigned int interval; }; struct { void (* fn)(void *data, struct waiter_ticket *); void *data; } callback; uint64_t start; int updated; struct waiter *waiter; struct list_item list_item; }; struct waiter { struct list tickets; struct pollset *pollset; int count; }; struct waiter *waiter_create(void) { struct waiter *w; w = calloc(1, sizeof(*w)); if (w == NULL) return NULL; list_init(&w->tickets); return w; } void waiter_destroy(struct waiter *w) { struct waiter_ticket *ticket; struct list_item *safe; struct list_item *node; list_for_each_safe(&w->tickets, node, safe) { ticket = list_entry(node, struct waiter_ticket, list_item); free(ticket); } if (w->pollset) pollset_destroy(w->pollset); free(w); } void waiter_synchronize(struct waiter *w) { struct waiter_ticket *oticket; struct waiter_ticket *ticket; struct list_item *node; list_for_each(&w->tickets, node) { struct list_item *onode; ticket = list_entry(node, struct waiter_ticket, list_item); if (ticket->type != WATCH_TYPE_TIMEOUT) continue; list_for_each_after(node, onode) { oticket = list_entry(onode, struct waiter_ticket, list_item); if (oticket->type != WATCH_TYPE_TIMEOUT) continue; if (oticket->interval == ticket->interval) { oticket->start = ticket->start; break; } } } } void waiter_wait(struct waiter *w) { struct pollset *ps = w->pollset; struct waiter_ticket *ticket; struct list_item *node; uint64_t term_time; uint64_t now; int rc; pollset_reset(ps); term_time = (uint64_t)-1; list_for_each(&w->tickets, node) { ticket = list_entry(node, struct waiter_ticket, list_item); switch (ticket->type) { case WATCH_TYPE_TIMEOUT: if (ticket->start + ticket->interval < term_time) term_time = ticket->start + ticket->interval; break; case WATCH_TYPE_FD: pollset_add_fd(ps, ticket->filedes); break; case WATCH_TYPE_NULL: break; } } if (term_time == (uint64_t)-1) { /* wait forever */ rc = pollset_wait(ps, -1); } else { now = time_ms(); if (now >= term_time) { /* already past timeout, skip poll */ rc = 0; } else { uint64_t delta; delta = term_time - now; if (delta > ((1u << 31) - 1)) delta = ((1u << 31) - 1); rc = pollset_wait(ps, (int)delta); } } if (rc < 0) return; now = time_ms(); list_for_each(&w->tickets, node) { int fresh = 0; ticket = list_entry(node, struct waiter_ticket, list_item); switch (ticket->type) { case WATCH_TYPE_TIMEOUT: if (now >= ticket->start + ticket->interval) { ticket->start = now; fresh = !ticket->updated; } break; case WATCH_TYPE_FD: if (rc == 0) /* timed-out */ break; if (pollset_cause_fd(ps, ticket->filedes)) fresh = !ticket->updated; break; case WATCH_TYPE_NULL: break; } if (fresh) { ticket->updated = 1; if (ticket->callback.fn) (* ticket->callback.fn)( ticket->callback.data, ticket ); } } } int waiter_wait_timeout(struct waiter *w, unsigned int ms) { struct waiter_ticket ticket; int rc; memset(&ticket, 0, sizeof(ticket)); waiter_ticket_set_timeout(&ticket, ms); list_append(&w->tickets, &ticket.list_item); w->count++; waiter_wait(w); rc = waiter_ticket_check(&ticket); list_remove(&w->tickets, &ticket.list_item); w->count--; return -!rc; } void waiter_ticket_set_null(struct waiter_ticket *ticket) { ticket->type = WATCH_TYPE_NULL; } void waiter_ticket_set_fd(struct waiter_ticket *ticket, int fd) { ticket->type = WATCH_TYPE_FD; ticket->filedes = fd; } void waiter_ticket_set_timeout(struct waiter_ticket *ticket, unsigned int ms) { ticket->type = WATCH_TYPE_TIMEOUT; ticket->interval = ms; ticket->start = time_ms(); } struct waiter_ticket *waiter_add_null(struct waiter *w) { struct waiter_ticket *ticket; ticket = calloc(1, sizeof(*ticket)); if (ticket == NULL) return NULL; ticket->waiter = w; list_append(&w->tickets, &ticket->list_item); if ((w->count % 32) == 0) { if (w->pollset) pollset_destroy(w->pollset); w->pollset = pollset_create(w->count + 33); if (w->pollset == NULL) return NULL; } w->count++; waiter_ticket_set_null(ticket); return ticket; } struct waiter_ticket *waiter_add_fd(struct waiter *w, int fd) { struct waiter_ticket *ticket; ticket = waiter_add_null(w); if (ticket == NULL) return NULL; waiter_ticket_set_fd(ticket, fd); return ticket; } struct waiter_ticket *waiter_add_timeout(struct waiter *w, unsigned int ms) { struct waiter_ticket *ticket; ticket = waiter_add_null(w); if (ticket == NULL) return NULL; waiter_ticket_set_timeout(ticket, ms); return ticket; } void waiter_ticket_delete(struct waiter_ticket *ticket) { struct waiter *w = ticket->waiter; list_remove(&w->tickets, &ticket->list_item); w->count--; free(ticket); } void waiter_ticket_callback(struct waiter_ticket *ticket, waiter_ticket_cb_t cb_fn, void *data) { ticket->callback.fn = cb_fn; ticket->callback.data = data; } int waiter_ticket_check(const struct waiter_ticket *ticket) { return -(ticket->updated == 0); } int waiter_ticket_clear(struct waiter_ticket *ticket) { int ret; ret = waiter_ticket_check(ticket); ticket->updated = 0; return ret; }