blob: e280d770d00770200801af12e13762308453d143 [file] [log] [blame]
/*
* Copyright (c) 2008-2011, Dave Benson.
*
* All rights reserved.
*
* 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 "protobuf-c" 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.
*/
/* NOTE: this may not work very well on windows, where i'm
not sure that "SOCKETs" are allocated nicely like
file-descriptors are */
/* TODO:
* * epoll() implementation
* * kqueue() implementation
* * windows port (yeah, right, volunteers are DEFINITELY needed for this one...)
*/
#if HAVE_PROTOBUF_C_CONFIG_H
#include "protobuf-c-config.h"
#endif
#include <assert.h>
#if HAVE_ALLOCA_H
# include <alloca.h>
#elif defined HAVE_MALLOC_H
# include <malloc.h>
#endif
#include <sys/time.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#if HAVE_SYS_POLL_H
# include <sys/poll.h>
# define USE_POLL 1
#elif HAVE_SYS_SELECT_H
# include <sys/select.h>
# define USE_POLL 0
#endif
/* windows annoyances: use select, use a full-fledges map for fds */
#ifdef WIN32
# include <winsock.h>
# define USE_POLL 0
# define HAVE_SMALL_FDS 0
#endif
#include <limits.h>
#include <errno.h>
#include <signal.h>
#include "protobuf-c-dispatch.h"
#include "gskrbtreemacros.h"
#include "gsklistmacros.h"
#define DEBUG_DISPATCH_INTERNALS 0
#define DEBUG_DISPATCH 0
#ifndef HAVE_SMALL_FDS
# define HAVE_SMALL_FDS 1
#endif
#define protobuf_c_assert(condition) assert(condition)
#define ALLOC_WITH_ALLOCATOR(allocator, size) ((allocator)->alloc ((allocator)->allocator_data, (size)))
#define FREE_WITH_ALLOCATOR(allocator, ptr) ((allocator)->free ((allocator)->allocator_data, (ptr)))
/* macros that assume you have a ProtobufCAllocator* named
allocator in scope */
#define ALLOC(size) ALLOC_WITH_ALLOCATOR((allocator), size)
#define FREE(ptr) FREE_WITH_ALLOCATOR((allocator), ptr)
typedef struct _Callback Callback;
struct _Callback
{
ProtobufCDispatchCallback func;
void *data;
};
typedef struct _FDMap FDMap;
struct _FDMap
{
int notify_desired_index; /* -1 if not an known fd */
int change_index; /* -1 if no prior change */
int closed_since_notify_started;
};
#if !HAVE_SMALL_FDS
typedef struct _FDMapNode FDMapNode;
struct _FDMapNode
{
ProtobufC_FD fd;
FDMapNode *left, *right, *parent;
protobuf_c_boolean is_red;
FDMap map;
};
#endif
typedef struct _RealDispatch RealDispatch;
struct _RealDispatch
{
ProtobufCDispatch base;
Callback *callbacks; /* parallels notifies_desired */
size_t notifies_desired_alloced;
size_t changes_alloced;
#if HAVE_SMALL_FDS
FDMap *fd_map; /* map indexed by fd */
size_t fd_map_size; /* number of elements of fd_map */
#else
FDMapNode *fd_map_tree; /* map indexed by fd */
#endif
protobuf_c_boolean is_dispatching;
ProtobufCDispatchTimer *timer_tree;
ProtobufCAllocator *allocator;
ProtobufCDispatchTimer *recycled_timeouts;
ProtobufCDispatchIdle *first_idle, *last_idle;
ProtobufCDispatchIdle *recycled_idles;
};
struct _ProtobufCDispatchTimer
{
RealDispatch *dispatch;
/* the actual timeout time */
unsigned long timeout_secs;
unsigned timeout_usecs;
/* red-black tree stuff */
ProtobufCDispatchTimer *left, *right, *parent;
protobuf_c_boolean is_red;
/* user callback */
ProtobufCDispatchTimerFunc func;
void *func_data;
};
struct _ProtobufCDispatchIdle
{
RealDispatch *dispatch;
ProtobufCDispatchIdle *prev, *next;
/* user callback */
ProtobufCDispatchIdleFunc func;
void *func_data;
};
/* Define the tree of timers, as per gskrbtreemacros.h */
#define TIMER_GET_IS_RED(n) ((n)->is_red)
#define TIMER_SET_IS_RED(n,v) ((n)->is_red = (v))
#define TIMERS_COMPARE(a,b, rv) \
if (a->timeout_secs < b->timeout_secs) rv = -1; \
else if (a->timeout_secs > b->timeout_secs) rv = 1; \
else if (a->timeout_usecs < b->timeout_usecs) rv = -1; \
else if (a->timeout_usecs > b->timeout_usecs) rv = 1; \
else if (a < b) rv = -1; \
else if (a > b) rv = 1; \
else rv = 0;
#define GET_TIMER_TREE(d) \
(d)->timer_tree, ProtobufCDispatchTimer *, \
TIMER_GET_IS_RED, TIMER_SET_IS_RED, \
parent, left, right, \
TIMERS_COMPARE
#if !HAVE_SMALL_FDS
#define FD_MAP_NODES_COMPARE(a,b, rv) \
if (a->fd < b->fd) rv = -1; \
else if (a->fd > b->fd) rv = 1; \
else rv = 0;
#define GET_FD_MAP_TREE(d) \
(d)->fd_map_tree, FDMapNode *, \
TIMER_GET_IS_RED, TIMER_SET_IS_RED, \
parent, left, right, \
FD_MAP_NODES_COMPARE
#define COMPARE_FD_TO_FD_MAP_NODE(a,b, rv) \
if (a < b->fd) rv = -1; \
else if (a > b->fd) rv = 1; \
else rv = 0;
#endif
/* declare the idle-handler list */
#define GET_IDLE_LIST(d) \
ProtobufCDispatchIdle *, d->first_idle, d->last_idle, prev, next
/* Create or destroy a Dispatch */
ProtobufCDispatch *protobuf_c_dispatch_new (ProtobufCAllocator *allocator)
{
RealDispatch *rv = ALLOC (sizeof (RealDispatch));
struct timeval tv;
rv->base.n_changes = 0;
rv->notifies_desired_alloced = 8;
rv->base.notifies_desired = ALLOC (sizeof (ProtobufC_FDNotify) * rv->notifies_desired_alloced);
rv->base.n_notifies_desired = 0;
rv->callbacks = ALLOC (sizeof (Callback) * rv->notifies_desired_alloced);
rv->changes_alloced = 8;
rv->base.changes = ALLOC (sizeof (ProtobufC_FDNotifyChange) * rv->changes_alloced);
#if HAVE_SMALL_FDS
rv->fd_map_size = 16;
rv->fd_map = ALLOC (sizeof (FDMap) * rv->fd_map_size);
memset (rv->fd_map, 255, sizeof (FDMap) * rv->fd_map_size);
#else
rv->fd_map_tree = NULL;
#endif
rv->allocator = allocator;
rv->timer_tree = NULL;
rv->first_idle = rv->last_idle = NULL;
rv->base.has_idle = 0;
rv->recycled_idles = NULL;
rv->recycled_timeouts = NULL;
rv->is_dispatching = 0;
/* need to handle SIGPIPE more gracefully than default */
signal (SIGPIPE, SIG_IGN);
gettimeofday (&tv, NULL);
rv->base.last_dispatch_secs = tv.tv_sec;
rv->base.last_dispatch_usecs = tv.tv_usec;
return &rv->base;
}
#if !HAVE_SMALL_FDS
void free_fd_tree_recursive (ProtobufCAllocator *allocator,
FDMapNode *node)
{
if (node)
{
free_fd_tree_recursive (allocator, node->left);
free_fd_tree_recursive (allocator, node->right);
FREE (node);
}
}
#endif
/* XXX: leaking timer_tree seemingly? */
void
protobuf_c_dispatch_free(ProtobufCDispatch *dispatch)
{
RealDispatch *d = (RealDispatch *) dispatch;
ProtobufCAllocator *allocator = d->allocator;
while (d->recycled_timeouts != NULL)
{
ProtobufCDispatchTimer *t = d->recycled_timeouts;
d->recycled_timeouts = t->right;
FREE (t);
}
while (d->recycled_idles != NULL)
{
ProtobufCDispatchIdle *i = d->recycled_idles;
d->recycled_idles = i->next;
FREE (i);
}
FREE (d->base.notifies_desired);
FREE (d->base.changes);
FREE (d->callbacks);
#if HAVE_SMALL_FDS
FREE (d->fd_map);
#else
free_fd_tree_recursive (allocator, d->fd_map_tree);
#endif
FREE (d);
}
ProtobufCAllocator *
protobuf_c_dispatch_peek_allocator (ProtobufCDispatch *dispatch)
{
RealDispatch *d = (RealDispatch *) dispatch;
return d->allocator;
}
/* TODO: perhaps thread-private dispatches make more sense? */
static ProtobufCDispatch *def = NULL;
ProtobufCDispatch *protobuf_c_dispatch_default (void)
{
if (def == NULL)
def = protobuf_c_dispatch_new (&protobuf_c_default_allocator);
return def;
}
#if HAVE_SMALL_FDS
static void
enlarge_fd_map (RealDispatch *d,
unsigned fd)
{
size_t new_size = d->fd_map_size * 2;
FDMap *new_map;
ProtobufCAllocator *allocator = d->allocator;
while (fd >= new_size)
new_size *= 2;
new_map = ALLOC (sizeof (FDMap) * new_size);
memcpy (new_map, d->fd_map, d->fd_map_size * sizeof (FDMap));
memset (new_map + d->fd_map_size,
255,
sizeof (FDMap) * (new_size - d->fd_map_size));
FREE (d->fd_map);
d->fd_map = new_map;
d->fd_map_size = new_size;
}
static inline void
ensure_fd_map_big_enough (RealDispatch *d,
unsigned fd)
{
if (fd >= d->fd_map_size)
enlarge_fd_map (d, fd);
}
#endif
static unsigned
allocate_notifies_desired_index (RealDispatch *d)
{
unsigned rv = d->base.n_notifies_desired++;
ProtobufCAllocator *allocator = d->allocator;
if (rv == d->notifies_desired_alloced)
{
unsigned new_size = d->notifies_desired_alloced * 2;
ProtobufC_FDNotify *n = ALLOC (new_size * sizeof (ProtobufC_FDNotify));
Callback *c = ALLOC (new_size * sizeof (Callback));
memcpy (n, d->base.notifies_desired, d->notifies_desired_alloced * sizeof (ProtobufC_FDNotify));
FREE (d->base.notifies_desired);
memcpy (c, d->callbacks, d->notifies_desired_alloced * sizeof (Callback));
FREE (d->callbacks);
d->base.notifies_desired = n;
d->callbacks = c;
d->notifies_desired_alloced = new_size;
}
#if DEBUG_DISPATCH_INTERNALS
fprintf (stderr, "allocate_notifies_desired_index: returning %u\n", rv);
#endif
return rv;
}
static unsigned
allocate_change_index (RealDispatch *d)
{
unsigned rv = d->base.n_changes++;
if (rv == d->changes_alloced)
{
ProtobufCAllocator *allocator = d->allocator;
unsigned new_size = d->changes_alloced * 2;
ProtobufC_FDNotifyChange *n = ALLOC (new_size * sizeof (ProtobufC_FDNotifyChange));
memcpy (n, d->base.changes, d->changes_alloced * sizeof (ProtobufC_FDNotifyChange));
FREE (d->base.changes);
d->base.changes = n;
d->changes_alloced = new_size;
}
return rv;
}
static inline FDMap *
get_fd_map (RealDispatch *d, ProtobufC_FD fd)
{
#if HAVE_SMALL_FDS
if ((unsigned)fd >= d->fd_map_size)
return NULL;
else
return d->fd_map + fd;
#else
FDMapNode *node;
GSK_RBTREE_LOOKUP_COMPARATOR (GET_FD_MAP_TREE (d), fd, COMPARE_FD_TO_FD_MAP_NODE, node);
return node ? &node->map : NULL;
#endif
}
static inline FDMap *
force_fd_map (RealDispatch *d, ProtobufC_FD fd)
{
#if HAVE_SMALL_FDS
ensure_fd_map_big_enough (d, fd);
return d->fd_map + fd;
#else
{
FDMap *fm = get_fd_map (d, fd);
ProtobufCAllocator *allocator = d->allocator;
if (fm == NULL)
{
FDMapNode *node = ALLOC (sizeof (FDMapNode));
FDMapNode *conflict;
node->fd = fd;
memset (&node->map, 255, sizeof (FDMap));
GSK_RBTREE_INSERT (GET_FD_MAP_TREE (d), node, conflict);
assert (conflict == NULL);
fm = &node->map;
}
return fm;
}
#endif
}
static void
deallocate_change_index (RealDispatch *d,
FDMap *fm)
{
unsigned ch_ind = fm->change_index;
unsigned from = d->base.n_changes - 1;
ProtobufC_FD from_fd;
fm->change_index = -1;
if (ch_ind == from)
{
d->base.n_changes--;
return;
}
from_fd = d->base.changes[ch_ind].fd;
get_fd_map (d, from_fd)->change_index = ch_ind;
d->base.changes[ch_ind] = d->base.changes[from];
d->base.n_changes--;
}
static void
deallocate_notify_desired_index (RealDispatch *d,
ProtobufC_FD fd,
FDMap *fm)
{
unsigned nd_ind = fm->notify_desired_index;
unsigned from = d->base.n_notifies_desired - 1;
ProtobufC_FD from_fd;
(void) fd;
#if DEBUG_DISPATCH_INTERNALS
fprintf (stderr, "deallocate_notify_desired_index: fd=%d, nd_ind=%u\n",fd,nd_ind);
#endif
fm->notify_desired_index = -1;
if (nd_ind == from)
{
d->base.n_notifies_desired--;
return;
}
from_fd = d->base.notifies_desired[from].fd;
get_fd_map (d, from_fd)->notify_desired_index = nd_ind;
d->base.notifies_desired[nd_ind] = d->base.notifies_desired[from];
d->callbacks[nd_ind] = d->callbacks[from];
d->base.n_notifies_desired--;
}
/* Registering file-descriptors to watch. */
void
protobuf_c_dispatch_watch_fd (ProtobufCDispatch *dispatch,
ProtobufC_FD fd,
unsigned events,
ProtobufCDispatchCallback callback,
void *callback_data)
{
RealDispatch *d = (RealDispatch *) dispatch;
unsigned f = fd; /* avoid tiring compiler warnings: "comparison of signed versus unsigned" */
unsigned nd_ind, change_ind;
unsigned old_events;
FDMap *fm;
#if DEBUG_DISPATCH
fprintf (stderr, "dispatch: watch_fd: %d, %s%s\n",
fd,
(events&PROTOBUF_C_EVENT_READABLE)?"r":"",
(events&PROTOBUF_C_EVENT_WRITABLE)?"w":"");
#endif
if (callback == NULL)
assert (events == 0);
else
assert (events != 0);
fm = force_fd_map (d, f);
/* XXX: should we set fm->map.closed_since_notify_started=0 ??? */
if (fm->notify_desired_index == -1)
{
if (callback != NULL)
nd_ind = fm->notify_desired_index = allocate_notifies_desired_index (d);
old_events = 0;
}
else
{
old_events = dispatch->notifies_desired[fm->notify_desired_index].events;
if (callback == NULL)
deallocate_notify_desired_index (d, fd, fm);
else
nd_ind = fm->notify_desired_index;
}
if (callback == NULL)
{
if (fm->change_index == -1)
{
change_ind = fm->change_index = allocate_change_index (d);
dispatch->changes[change_ind].old_events = old_events;
}
else
change_ind = fm->change_index;
d->base.changes[change_ind].fd = f;
d->base.changes[change_ind].events = 0;
return;
}
assert (callback != NULL && events != 0);
if (fm->change_index == -1)
{
change_ind = fm->change_index = allocate_change_index (d);
dispatch->changes[change_ind].old_events = old_events;
}
else
change_ind = fm->change_index;
d->base.changes[change_ind].fd = fd;
d->base.changes[change_ind].events = events;
d->base.notifies_desired[nd_ind].fd = fd;
d->base.notifies_desired[nd_ind].events = events;
d->callbacks[nd_ind].func = callback;
d->callbacks[nd_ind].data = callback_data;
}
void
protobuf_c_dispatch_close_fd (ProtobufCDispatch *dispatch,
ProtobufC_FD fd)
{
protobuf_c_dispatch_fd_closed (dispatch, fd);
close (fd);
}
void
protobuf_c_dispatch_fd_closed(ProtobufCDispatch *dispatch,
ProtobufC_FD fd)
{
RealDispatch *d = (RealDispatch *) dispatch;
FDMap *fm;
#if DEBUG_DISPATCH
fprintf (stderr, "dispatch: fd %d closed\n", fd);
#endif
fm = force_fd_map (d, fd);
fm->closed_since_notify_started = 1;
if (fm->change_index != -1)
deallocate_change_index (d, fm);
if (fm->notify_desired_index != -1)
deallocate_notify_desired_index (d, fd, fm);
}
static void
free_timer (ProtobufCDispatchTimer *timer)
{
RealDispatch *d = timer->dispatch;
timer->right = d->recycled_timeouts;
d->recycled_timeouts = timer;
}
void
protobuf_c_dispatch_dispatch (ProtobufCDispatch *dispatch,
size_t n_notifies,
ProtobufC_FDNotify *notifies)
{
RealDispatch *d = (RealDispatch *) dispatch;
unsigned fd_max;
unsigned i;
struct timeval tv;
/* Re-entrancy guard. If this is triggerred, then
you are calling protobuf_c_dispatch_dispatch (or _run)
from a callback function. That's not allowed. */
protobuf_c_assert (!d->is_dispatching);
d->is_dispatching = 1;
gettimeofday (&tv, NULL);
dispatch->last_dispatch_secs = tv.tv_sec;
dispatch->last_dispatch_usecs = tv.tv_usec;
fd_max = 0;
for (i = 0; i < n_notifies; i++)
if (fd_max < (unsigned) notifies[i].fd)
fd_max = notifies[i].fd;
ensure_fd_map_big_enough (d, fd_max);
for (i = 0; i < n_notifies; i++)
d->fd_map[notifies[i].fd].closed_since_notify_started = 0;
for (i = 0; i < n_notifies; i++)
{
unsigned fd = notifies[i].fd;
if (!d->fd_map[fd].closed_since_notify_started
&& d->fd_map[fd].notify_desired_index != -1)
{
unsigned nd_ind = d->fd_map[fd].notify_desired_index;
unsigned events = d->base.notifies_desired[nd_ind].events & notifies[i].events;
if (events != 0)
d->callbacks[nd_ind].func (fd, events, d->callbacks[nd_ind].data);
}
}
/* clear changes */
for (i = 0; i < dispatch->n_changes; i++)
d->fd_map[dispatch->changes[i].fd].change_index = -1;
dispatch->n_changes = 0;
/* handle idle functions */
while (d->first_idle != NULL)
{
ProtobufCDispatchIdle *idle = d->first_idle;
ProtobufCDispatchIdleFunc func = idle->func;
void *data = idle->func_data;
GSK_LIST_REMOVE_FIRST (GET_IDLE_LIST (d));
idle->func = NULL; /* set to NULL to render remove_idle a no-op */
func (dispatch, data);
idle->next = d->recycled_idles;
d->recycled_idles = idle;
}
dispatch->has_idle = 0;
/* handle timers */
while (d->timer_tree != NULL)
{
ProtobufCDispatchTimer *min_timer;
GSK_RBTREE_FIRST (GET_TIMER_TREE (d), min_timer);
if (min_timer->timeout_secs < (unsigned long) tv.tv_sec
|| (min_timer->timeout_secs == (unsigned long) tv.tv_sec
&& min_timer->timeout_usecs <= (unsigned) tv.tv_usec))
{
ProtobufCDispatchTimerFunc func = min_timer->func;
void *func_data = min_timer->func_data;
GSK_RBTREE_REMOVE (GET_TIMER_TREE (d), min_timer);
/* Set to NULL as a way to tell protobuf_c_dispatch_remove_timer()
that we are in the middle of notifying */
min_timer->func = NULL;
min_timer->func_data = NULL;
func (&d->base, func_data);
free_timer (min_timer);
}
else
{
d->base.has_timeout = 1;
d->base.timeout_secs = min_timer->timeout_secs;
d->base.timeout_usecs = min_timer->timeout_usecs;
break;
}
}
if (d->timer_tree == NULL)
d->base.has_timeout = 0;
/* Finish reentrance guard. */
d->is_dispatching = 0;
}
void
protobuf_c_dispatch_clear_changes (ProtobufCDispatch *dispatch)
{
RealDispatch *d = (RealDispatch *) dispatch;
unsigned i;
for (i = 0; i < dispatch->n_changes; i++)
{
FDMap *fm = get_fd_map (d, dispatch->changes[i].fd);
assert (fm->change_index == (int) i);
fm->change_index = -1;
}
dispatch->n_changes = 0;
}
static inline unsigned
events_to_pollfd_events (unsigned ev)
{
return ((ev & PROTOBUF_C_EVENT_READABLE) ? POLLIN : 0)
| ((ev & PROTOBUF_C_EVENT_WRITABLE) ? POLLOUT : 0)
;
}
static inline unsigned
pollfd_events_to_events (unsigned ev)
{
return ((ev & (POLLIN|POLLHUP)) ? PROTOBUF_C_EVENT_READABLE : 0)
| ((ev & POLLOUT) ? PROTOBUF_C_EVENT_WRITABLE : 0)
;
}
void
protobuf_c_dispatch_run (ProtobufCDispatch *dispatch)
{
struct pollfd *fds;
void *to_free = NULL, *to_free2 = NULL;
size_t n_events;
RealDispatch *d = (RealDispatch *) dispatch;
ProtobufCAllocator *allocator = d->allocator;
unsigned i;
int timeout;
ProtobufC_FDNotify *events;
if (dispatch->n_notifies_desired < 128)
fds = alloca (sizeof (struct pollfd) * dispatch->n_notifies_desired);
else
to_free = fds = ALLOC (sizeof (struct pollfd) * dispatch->n_notifies_desired);
for (i = 0; i < dispatch->n_notifies_desired; i++)
{
fds[i].fd = dispatch->notifies_desired[i].fd;
fds[i].events = events_to_pollfd_events (dispatch->notifies_desired[i].events);
fds[i].revents = 0;
}
/* compute timeout */
if (dispatch->has_idle)
timeout = 0;
else if (!dispatch->has_timeout)
timeout = -1;
else
{
struct timeval tv;
gettimeofday (&tv, NULL);
if (dispatch->timeout_secs < (unsigned long) tv.tv_sec
|| (dispatch->timeout_secs == (unsigned long) tv.tv_sec
&& dispatch->timeout_usecs <= (unsigned) tv.tv_usec))
timeout = 0;
else
{
int du = dispatch->timeout_usecs - tv.tv_usec;
int ds = dispatch->timeout_secs - tv.tv_sec;
if (du < 0)
{
du += 1000000;
ds -= 1;
}
if (ds > INT_MAX / 1000)
timeout = INT_MAX / 1000 * 1000;
else
/* Round up, so that we ensure that something can run
if they just wait the full duration */
timeout = ds * 1000 + (du + 999) / 1000;
}
}
if (poll (fds, dispatch->n_notifies_desired, timeout) < 0)
{
if (errno == EINTR)
return; /* probably a signal interrupted the poll-- let the user have control */
/* i don't really know what would plausibly cause this */
fprintf (stderr, "error polling: %s\n", strerror (errno));
return;
}
n_events = 0;
for (i = 0; i < dispatch->n_notifies_desired; i++)
if (fds[i].revents)
n_events++;
if (n_events < 128)
events = alloca (sizeof (ProtobufC_FDNotify) * n_events);
else
to_free2 = events = ALLOC (sizeof (ProtobufC_FDNotify) * n_events);
n_events = 0;
for (i = 0; i < dispatch->n_notifies_desired; i++)
if (fds[i].revents)
{
events[n_events].fd = fds[i].fd;
events[n_events].events = pollfd_events_to_events (fds[i].revents);
/* note that we may actually wind up with fewer events
now that we actually call pollfd_events_to_events() */
if (events[n_events].events != 0)
n_events++;
}
protobuf_c_dispatch_dispatch (dispatch, n_events, events);
if (to_free)
FREE (to_free);
if (to_free2)
FREE (to_free2);
}
ProtobufCDispatchTimer *
protobuf_c_dispatch_add_timer(ProtobufCDispatch *dispatch,
unsigned timeout_secs,
unsigned timeout_usecs,
ProtobufCDispatchTimerFunc func,
void *func_data)
{
RealDispatch *d = (RealDispatch *) dispatch;
ProtobufCDispatchTimer *rv;
ProtobufCDispatchTimer *at;
ProtobufCDispatchTimer *conflict;
protobuf_c_assert (func != NULL);
if (d->recycled_timeouts != NULL)
{
rv = d->recycled_timeouts;
d->recycled_timeouts = rv->right;
}
else
{
rv = d->allocator->alloc (d->allocator, sizeof (ProtobufCDispatchTimer));
}
rv->timeout_secs = timeout_secs;
rv->timeout_usecs = timeout_usecs;
rv->func = func;
rv->func_data = func_data;
rv->dispatch = d;
GSK_RBTREE_INSERT (GET_TIMER_TREE (d), rv, conflict);
/* is this the first element in the tree */
for (at = rv; at != NULL; at = at->parent)
if (at->parent && at->parent->right == at)
break;
if (at == NULL) /* yes, so set the public members */
{
dispatch->has_timeout = 1;
dispatch->timeout_secs = rv->timeout_secs;
dispatch->timeout_usecs = rv->timeout_usecs;
}
return rv;
}
ProtobufCDispatchTimer *
protobuf_c_dispatch_add_timer_millis
(ProtobufCDispatch *dispatch,
unsigned millis,
ProtobufCDispatchTimerFunc func,
void *func_data)
{
unsigned tsec = dispatch->last_dispatch_secs;
unsigned tusec = dispatch->last_dispatch_usecs;
tusec += 1000 * (millis % 1000);
tsec += millis / 1000;
if (tusec >= 1000*1000)
{
tusec -= 1000*1000;
tsec += 1;
}
return protobuf_c_dispatch_add_timer (dispatch, tsec, tusec, func, func_data);
}
void protobuf_c_dispatch_remove_timer (ProtobufCDispatchTimer *timer)
{
protobuf_c_boolean may_be_first;
RealDispatch *d = timer->dispatch;
/* ignore mid-notify removal */
if (timer->func == NULL)
return;
may_be_first = d->base.timeout_usecs == timer->timeout_usecs
&& d->base.timeout_secs == timer->timeout_secs;
GSK_RBTREE_REMOVE (GET_TIMER_TREE (d), timer);
if (may_be_first)
{
if (d->timer_tree == NULL)
d->base.has_timeout = 0;
else
{
ProtobufCDispatchTimer *min;
GSK_RBTREE_FIRST (GET_TIMER_TREE (d), min);
d->base.timeout_secs = min->timeout_secs;
d->base.timeout_usecs = min->timeout_usecs;
}
}
}
ProtobufCDispatchIdle *
protobuf_c_dispatch_add_idle (ProtobufCDispatch *dispatch,
ProtobufCDispatchIdleFunc func,
void *func_data)
{
RealDispatch *d = (RealDispatch *) dispatch;
ProtobufCDispatchIdle *rv;
if (d->recycled_idles != NULL)
{
rv = d->recycled_idles;
d->recycled_idles = rv->next;
}
else
{
ProtobufCAllocator *allocator = d->allocator;
rv = ALLOC (sizeof (ProtobufCDispatchIdle));
}
GSK_LIST_APPEND (GET_IDLE_LIST (d), rv);
rv->func = func;
rv->func_data = func_data;
rv->dispatch = d;
dispatch->has_idle = 1;
return rv;
}
void
protobuf_c_dispatch_remove_idle (ProtobufCDispatchIdle *idle)
{
if (idle->func != NULL)
{
RealDispatch *d = idle->dispatch;
GSK_LIST_REMOVE (GET_IDLE_LIST (d), idle);
idle->next = d->recycled_idles;
d->recycled_idles = idle;
}
}
void protobuf_c_dispatch_destroy_default (void)
{
if (def)
{
ProtobufCDispatch *to_kill = def;
def = NULL;
protobuf_c_dispatch_free (to_kill);
}
}