/* * Copyright (c) 2009-2011, Salvatore Sanfilippo * * SPDX-FileCopyrightText: 2024 Hiredict Contributors * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo * * SPDX-License-Identifier: BSD-3-Clause * SPDX-License-Identifier: LGPL-3.0-or-later * */ #ifndef HIREDICT_POLL_H #define HIREDICT_POLL_H #include "../async.h" #include "../sockcompat.h" #include // for memset #include /* Values to return from redictPollTick */ #define REDICT_POLL_HANDLED_READ 1 #define REDICT_POLL_HANDLED_WRITE 2 #define REDICT_POLL_HANDLED_TIMEOUT 4 /* An adapter to allow manual polling of the async context by checking the state * of the underlying file descriptor. Useful in cases where there is no formal * IO event loop but regular ticking can be used, such as in game engines. */ typedef struct redictPollEvents { redictAsyncContext *context; redictFD fd; char reading, writing; char in_tick; char deleted; double deadline; } redictPollEvents; static double redictPollTimevalToDouble(struct timeval *tv) { if (tv == NULL) return 0.0; return tv->tv_sec + tv->tv_usec / 1000000.00; } static double redictPollGetNow(void) { #ifndef _MSC_VER struct timeval tv; gettimeofday(&tv,NULL); return redictPollTimevalToDouble(&tv); #else FILETIME ft; ULARGE_INTEGER li; GetSystemTimeAsFileTime(&ft); li.HighPart = ft.dwHighDateTime; li.LowPart = ft.dwLowDateTime; return (double)li.QuadPart * 1e-7; #endif } /* Poll for io, handling any pending callbacks. The timeout argument can be * positive to wait for a maximum given time for IO, zero to poll, or negative * to wait forever */ static int redictPollTick(redictAsyncContext *ac, double timeout) { int reading, writing; struct pollfd pfd; int handled; int ns; int itimeout; redictPollEvents *e = (redictPollEvents*)ac->ev.data; if (!e) return 0; /* local flags, won't get changed during callbacks */ reading = e->reading; writing = e->writing; if (!reading && !writing) return 0; pfd.fd = e->fd; pfd.events = 0; if (reading) pfd.events = POLLIN; if (writing) pfd.events |= POLLOUT; if (timeout >= 0.0) { itimeout = (int)(timeout * 1000.0); } else { itimeout = -1; } ns = poll(&pfd, 1, itimeout); if (ns < 0) { /* ignore the EINTR error */ if (errno != EINTR) return ns; ns = 0; } handled = 0; e->in_tick = 1; if (ns) { if (reading && (pfd.revents & POLLIN)) { redictAsyncHandleRead(ac); handled |= REDICT_POLL_HANDLED_READ; } /* on Windows, connection failure is indicated with the Exception fdset. * handle it the same as writable. */ if (writing && (pfd.revents & (POLLOUT | POLLERR))) { /* context Read callback may have caused context to be deleted, e.g. by doing an redictAsyncDisconnect() */ if (!e->deleted) { redictAsyncHandleWrite(ac); handled |= REDICT_POLL_HANDLED_WRITE; } } } /* perform timeouts */ if (!e->deleted && e->deadline != 0.0) { double now = redictPollGetNow(); if (now >= e->deadline) { /* deadline has passed. disable timeout and perform callback */ e->deadline = 0.0; redictAsyncHandleTimeout(ac); handled |= REDICT_POLL_HANDLED_TIMEOUT; } } /* do a delayed cleanup if required */ if (e->deleted) hi_free(e); else e->in_tick = 0; return handled; } static void redictPollAddRead(void *data) { redictPollEvents *e = (redictPollEvents*)data; e->reading = 1; } static void redictPollDelRead(void *data) { redictPollEvents *e = (redictPollEvents*)data; e->reading = 0; } static void redictPollAddWrite(void *data) { redictPollEvents *e = (redictPollEvents*)data; e->writing = 1; } static void redictPollDelWrite(void *data) { redictPollEvents *e = (redictPollEvents*)data; e->writing = 0; } static void redictPollCleanup(void *data) { redictPollEvents *e = (redictPollEvents*)data; /* if we are currently processing a tick, postpone deletion */ if (e->in_tick) e->deleted = 1; else hi_free(e); } static void redictPollScheduleTimer(void *data, struct timeval tv) { redictPollEvents *e = (redictPollEvents*)data; double now = redictPollGetNow(); e->deadline = now + redictPollTimevalToDouble(&tv); } static int redictPollAttach(redictAsyncContext *ac) { redictContext *c = &(ac->c); redictPollEvents *e; /* Nothing should be attached when something is already attached */ if (ac->ev.data != NULL) return REDICT_ERR; /* Create container for context and r/w events */ e = (redictPollEvents*)hi_malloc(sizeof(*e)); if (e == NULL) return REDICT_ERR; memset(e, 0, sizeof(*e)); e->context = ac; e->fd = c->fd; e->reading = e->writing = 0; e->in_tick = e->deleted = 0; e->deadline = 0.0; /* Register functions to start/stop listening for events */ ac->ev.addRead = redictPollAddRead; ac->ev.delRead = redictPollDelRead; ac->ev.addWrite = redictPollAddWrite; ac->ev.delWrite = redictPollDelWrite; ac->ev.scheduleTimer = redictPollScheduleTimer; ac->ev.cleanup = redictPollCleanup; ac->ev.data = e; return REDICT_OK; } #endif /* HIREDICT_POLL_H */