feda/lib/sue/sue_base.c
2026-03-19 06:21:19 +05:00

388 lines
11 KiB
C

#include <stddef.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include "sue_aloc.h"
#include "sue_sigs.h"
#include "sue_base.h"
enum { /* local constants, unused elsewhere */
fdhandlers_size_step = 16
};
struct sue_sel_timeout_item {
struct sue_timeout_handler *h;
struct sue_sel_timeout_item *next;
};
struct sue_sel_signal_item {
struct sue_signal_handler *h;
struct sue_sel_signal_item *next;
};
struct sue_sel_loophook_item {
struct sue_loop_hook *h;
struct sue_sel_loophook_item *next;
};
void sue_sel_init(struct sue_event_selector *s)
{
s->timeouts = NULL;
s->signalhandlers = NULL;
s->fdhandlers = NULL;
s->fdhandlerssize = 0;
s->max_fd = -1;
s->loophooks = NULL;
s->breakflag = 0;
}
void sue_sel_done(struct sue_event_selector *s)
{
while(s->timeouts) {
struct sue_sel_timeout_item *tmp = s->timeouts;
sue_free(s->timeouts);
s->timeouts = tmp;
}
while(s->signalhandlers) {
struct sue_sel_signal_item *tmp = s->signalhandlers;
sue_free(s->signalhandlers);
s->signalhandlers = tmp;
}
if(s->fdhandlers)
sue_free(s->fdhandlers);
while(s->loophooks) {
struct sue_sel_loophook_item *tmp = s->loophooks;
sue_free(s->loophooks);
s->loophooks = tmp;
}
}
void sue_sel_register_fd(struct sue_event_selector *s,
struct sue_fd_handler *h)
{
if(s->fdhandlerssize <= h->fd) { /* needs resize */
int oldsz = s->fdhandlerssize;
struct sue_fd_handler **tmp;
int i;
while(s->fdhandlerssize <= h->fd)
s->fdhandlerssize += fdhandlers_size_step;
tmp = sue_malloc(sizeof(*tmp) * s->fdhandlerssize);
for(i = 0; i < s->fdhandlerssize; i++)
tmp[i] = i < oldsz ? s->fdhandlers[i] : NULL;
if(s->fdhandlers)
sue_free(s->fdhandlers);
s->fdhandlers = tmp;
}
s->fdhandlers[h->fd] = h;
if(h->fd > s->max_fd)
s->max_fd = h->fd;
}
void sue_sel_remove_fd(struct sue_event_selector *s,
struct sue_fd_handler *h)
{
SUE_ASSERT(h->fd < s->fdhandlerssize && s->fdhandlers[h->fd] == h);
s->fdhandlers[h->fd] = NULL;
if(h->fd == s->max_fd) {
while(s->max_fd >= 0 && s->fdhandlers[s->max_fd])
s->max_fd--;
}
}
static int sth_earlier_than(const struct sue_timeout_handler *a,
int sec, int usec)
{
return (a->sec < sec) || (a->sec == sec && a->usec < usec);
}
void sue_sel_register_timeout(struct sue_event_selector *s,
struct sue_timeout_handler *h)
{
struct sue_sel_timeout_item **p = &(s->timeouts);
struct sue_sel_timeout_item *tmp;
while(*p && sth_earlier_than(h, (*p)->h->sec, (*p)->h->usec))
p = &((*p)->next);
tmp = sue_malloc(sizeof(*tmp));
tmp->h = h;
tmp->next = *p;
*p = tmp;
}
void sue_sel_remove_timeout(struct sue_event_selector *s,
struct sue_timeout_handler *h)
{
struct sue_sel_timeout_item **p = &(s->timeouts);
while(*p && (*p)->h != h) {
if(sth_earlier_than((*p)->h, h->sec, h->usec))
return;
p = &((*p)->next);
}
if(*p) {
struct sue_sel_timeout_item *tmp = *p;
*p = tmp->next;
sue_free(tmp);
}
}
void sue_sel_register_signal(struct sue_event_selector *s,
struct sue_signal_handler *h)
{
struct sue_sel_signal_item *tmp;
tmp = sue_malloc(sizeof(*tmp));
tmp->h = h;
tmp->next = s->signalhandlers;
s->signalhandlers = tmp;
sue_signals_add(h->signo);
}
void sue_sel_remove_signal(struct sue_event_selector *s,
struct sue_signal_handler *h)
{
struct sue_sel_signal_item **p = &(s->signalhandlers);
while(*p) {
if((*p)->h == h) {
struct sue_sel_signal_item *tmp = *p;
*p = tmp->next;
sue_signals_remove(tmp->h->signo);
sue_free(tmp);
return; /* yes, only one instance is removed */
}
p = &((*p)->next);
}
}
void sue_sel_register_loop_hook(struct sue_event_selector *s,
struct sue_loop_hook *h)
{
struct sue_sel_loophook_item *tmp;
tmp = sue_malloc(sizeof(*tmp));
tmp->h = h;
tmp->next = s->loophooks;
s->loophooks = tmp;
}
void sue_sel_remove_loop_hook(struct sue_event_selector *s,
struct sue_loop_hook *h)
{
struct sue_sel_loophook_item **p = &(s->loophooks);
while(*p) {
if((*p)->h == h) {
struct sue_sel_loophook_item *tmp = *p;
*p = tmp->next;
sue_free(tmp);
return; /* yes, only one instance is removed */
}
p = &((*p)->next);
}
}
struct select_sets {
fd_set rd, wr, ex;
unsigned has_rd:1, has_wr:1, has_ex:1;
};
static void clear_select_sets(struct select_sets *sets)
{
FD_ZERO(&(sets->rd));
FD_ZERO(&(sets->wr));
FD_ZERO(&(sets->ex));
sets->has_rd = 0;
sets->has_wr = 0;
sets->has_ex = 0;
}
static void fill_select_sets(struct sue_event_selector *s,
struct select_sets *sets)
{
int i;
clear_select_sets(sets);
for(i = 0; i <= s->max_fd; i++) {
if(s->fdhandlers[i]) {
if(s->fdhandlers[i]->want_read) {
sets->has_rd = 1;
FD_SET(i, &(sets->rd));
}
if(s->fdhandlers[i]->want_write) {
sets->has_wr = 1;
FD_SET(i, &(sets->wr));
}
if(s->fdhandlers[i]->want_except) {
sets->has_ex = 1;
FD_SET(i, &(sets->ex));
}
}
}
}
static void compute_timeout(const struct sue_timeout_handler *ts,
struct timespec *spec)
{
struct timeval current;
gettimeofday(&current, NULL /* timezone unused */);
int a_sec, a_usec;
if(ts->sec < current.tv_sec ||
(ts->sec == current.tv_sec && ts->usec <= current.tv_usec))
{
spec->tv_sec = 0;
spec->tv_nsec = 0;
return;
}
a_sec = ts->sec - current.tv_sec;
a_usec = ts->usec - current.tv_usec;
if(a_usec < 0) {
a_usec += 1000000;
a_sec -= 1;
}
spec->tv_sec = a_sec;
spec->tv_nsec = a_usec * 1000;
}
static void enter_signal_section(const struct sue_sel_signal_item *hs,
sigset_t *saved_mask)
{
sigset_t ps_mask;
sigemptyset(&ps_mask);
while(hs) {
sigaddset(&ps_mask, hs->h->signo);
hs = hs->next;
}
sigprocmask(SIG_BLOCK, &ps_mask, saved_mask);
}
static void leave_signal_section(const sigset_t *saved_mask)
{
sigprocmask(SIG_SETMASK, saved_mask, NULL);
}
static void handle_signal_events(const struct sue_sel_signal_item *hs)
{
const struct sue_sel_signal_item *p = hs;
while(p) {
const struct sue_sel_signal_item *nx = p->next;
int cnt = sue_signals_get_counter(p->h->signo);
if(cnt > 0)
p->h->handle_signal(p->h, cnt);
p = nx;
}
sue_signals_zero_counters();
}
void handle_timeouts(struct sue_sel_timeout_item **ts)
{
struct timeval ct;
gettimeofday(&ct, 0 /* timezone unused */);
while(*ts && sth_earlier_than((*ts)->h, ct.tv_sec, ct.tv_usec)) {
struct sue_sel_timeout_item *tmp = *ts;
*ts = tmp->next;
tmp->h->handle_timeout(tmp->h);
sue_free(tmp);
}
/* we only remove items from the start of the list; even if the handler
changes the list, nothing wrong will happen, as we don't store
any information here, we always look at the main object's pointer */
}
static void
handle_fds(struct sue_event_selector *s, struct select_sets *sets)
{
int i;
for(i = 0; i <= s->max_fd; i++) {
struct sue_fd_handler *curr_handler = s->fdhandlers[i];
if(curr_handler) {
int rd = FD_ISSET(i, &sets->rd);
int wr = FD_ISSET(i, &sets->wr);
int ex = FD_ISSET(i, &sets->ex);
if(rd || wr || ex)
curr_handler->handle_fd_event(curr_handler, rd, wr, ex);
}
}
}
static void handle_loophooks(struct sue_sel_loophook_item *lhs)
{
struct sue_sel_loophook_item *p;
p = lhs;
while(p) {
struct sue_sel_loophook_item *nx = p->next;
p->h->loop_hook(p->h);
p = nx;
}
/* such a construct with 'nx' pointer is needed for the case the hook
decides to remove itself from the list; should it decide to add
something, there's nothing wrong with it, as addition is always
done into the beginning of the list
*/
}
int sue_sel_go(struct sue_event_selector *s)
{
while(!s->breakflag) {
struct select_sets sets;
struct timespec tspec;
sigset_t ps_mask;
int res;
/* Within the following section, all signals handled with the
event selector are blocked (masked), except for the duration
of pselect call, when they're unblocked. Please note that
we actually check/handle signals twice, which allows to
leave the signals unmasked outside of the section.
Actually, the first call to handle_signal_events processes
the signals received during the event handling, and the
second call handles signals happened when we were blocked
in pselect.
*/
if(s->signalhandlers) {
enter_signal_section(s->signalhandlers, &ps_mask);
handle_signal_events(s->signalhandlers);
}
fill_select_sets(s, &sets);
if(s->timeouts)
compute_timeout(s->timeouts->h, &tspec);
res = pselect(s->max_fd+1,
sets.has_rd ? &sets.rd : NULL,
sets.has_wr ? &sets.wr : NULL,
sets.has_ex ? &sets.ex : NULL,
s->timeouts ? &tspec : NULL,
s->signalhandlers ? &ps_mask : NULL);
if(res == -1 && errno != EINTR)
return -1;
if(s->signalhandlers) {
if(res == -1 && errno == EINTR)
handle_signal_events(s->signalhandlers);
leave_signal_section(&ps_mask);
}
handle_timeouts(&(s->timeouts));
if(res > 0)
handle_fds(s, &sets);
handle_loophooks(s->loophooks);
}
return 0;
}
void sue_sel_break(struct sue_event_selector *s)
{
s->breakflag = 1;
}
void sue_timeout_set_from_now(struct sue_timeout_handler *th,
long a_sec, long a_usec)
{
enum { max_usec = 1000000 };
struct timeval current;
gettimeofday(&current, 0 /* timezone unused */);
th->sec = current.tv_sec + a_sec;
th->usec = current.tv_usec + a_usec;
if(th->usec >= max_usec) {
th->sec += th->usec / max_usec;
th->usec %= max_usec;
}
}