blob: f21896f4878600883f7cc32a75849ef9154aca02 [file] [log] [blame]
/*
* 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 <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#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;
}