diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e87f17c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.7) +project(coio) + +add_library(${PROJECT_NAME} + coro.c + coio.c + coio_glib.c) + +add_custom_target(${PROJECT_NAME}_files SOURCES coioimpl.h) + +set_property(TARGET ${PROJECT_NAME} PROPERTY C_STANDARD 99) +target_compile_options(${PROJECT_NAME} PRIVATE -W -Wall -Wextra -Werror) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(GLIB2 REQUIRED glib-2.0) + +target_include_directories(${PROJECT_NAME} PUBLIC ${GLIB2_INCLUDE_DIRS}) +target_link_libraries(${PROJECT_NAME} ${GLIB2_LIBRARIES}) + +target_include_directories(${PROJECT_NAME} PUBLIC .) diff --git a/Makefile b/Makefile index 0e957d5..7322466 100644 --- a/Makefile +++ b/Makefile @@ -2,14 +2,15 @@ LIB = libcoio.a OBJS = \ coro.o \ - coio.o + coio.o \ + coio_glib.o all: $(LIB) $(OBJS): coio.h coro.h .c.o: - $(CC) $(CFLAGS) -W -Wall -Wextra -Werror -c $*.c + $(CC) $(shell pkg-config --cflags glib-2.0) $(CFLAGS) -W -Wall -Wextra -Werror -c $*.c $(LIB): $(OBJS) $(AR) rvc $(LIB) $? diff --git a/coio.c b/coio.c index d936e0c..a33bb33 100644 --- a/coio.c +++ b/coio.c @@ -13,8 +13,12 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#define _POSIX_C_SOURCE 200809L + #include #include +#include +#include #include "coioimpl.h" #include "coro.h" @@ -24,19 +28,33 @@ #include #endif -static coro_context _sched_ctx; -static unsigned long _taskcount = 0; - -CoioTaskList coio_ready = {0, 0}; +CoioTaskList coio_ready_list = {0, 0}; CoioTaskList coio_sleeping = {0, 0}; -CoioTask *coio_current; +coro_context coio_sched_ctx; +CoioTask* coio_current; +unsigned long coio_taskcount = 0; + +static int msleep(uvlong ms) +{ + struct timespec req, rem; + + if (ms > 999) { + req.tv_sec = (int)(ms / 1000); + req.tv_nsec = (ms - ((long)req.tv_sec * 1000)) * 1000000; + } else { + req.tv_sec = 0; + req.tv_nsec = ms * 1000000; + } + + return nanosleep(&req, &rem); +} static void _process_events() { uvlong now; int ms = 5; - CoioTask *t; + CoioTask* t; if ((t = coio_sleeping.head) != NULL && t->timeout != 0) { now = coio_now(); @@ -47,14 +65,14 @@ _process_events() } } /* TODO:do I/O polling instead of usleep */ - usleep(ms * 1000); + msleep(ms); /* handle CLOCK_MONOTONIC bugs (VirtualBox anyone?) */ - while (!coio_ready.head) { + while (!coio_ready_list.head) { /* wake up timed out tasks */ now = coio_now(); - while ((t = coio_sleeping.head) && now >= t->timeout) { - coio_rdy(t); + while ((t = coio_sleeping.head) && t->timeout && now >= t->timeout) { + coio_ready(t); } } } @@ -63,78 +81,83 @@ int coio_main() { /* initialize empty ctx for scheduler */ - coro_create(&_sched_ctx, NULL, NULL, NULL, 0); + coro_create(&coio_sched_ctx, NULL, NULL, NULL, 0); /* scheduler mainloop */ for (;;) { - if (!coio_ready.head && coio_sleeping.head) + if (!coio_ready_list.head && coio_sleeping.head) _process_events(); - if (!coio_ready.head) + if (!coio_ready_list.head) break; - coio_current = coio_ready.head; - coio_del(&coio_ready, coio_current); - coro_transfer(&_sched_ctx, &coio_current->ctx); + coio_current = coio_ready_list.head; + coio_current->ready = 0; + coio_del(&coio_ready_list, coio_current); + coro_transfer(&coio_sched_ctx, &coio_current->ctx); if (coio_current->done) { - _taskcount--; + coio_taskcount--; coro_stack_free(&coio_current->stk); free(coio_current); } coio_current = NULL; } - if (_taskcount) { + if (coio_taskcount) { return -1; } return 0; } static void -_coio_entry(void *arg) +_coio_entry(void* arg) { - CoioTask *task = (CoioTask *) arg; + CoioTask* task = (CoioTask*) arg; task->func(task->arg); task->done = 1; - coro_transfer(&coio_current->ctx, &_sched_ctx); + coro_transfer(&coio_current->ctx, &coio_sched_ctx); } int -coio_create(coio_func f, void *arg, unsigned int stacksize) +coio_create(const char* name, coio_func f, void* arg, unsigned int stacksize) { - CoioTask *task; + CoioTask* task; task = calloc(1, sizeof(*task)); if (!task) return -1; - if (!coro_stack_alloc(&task->stk, stacksize / sizeof(void *))) { + if (!coro_stack_alloc(&task->stk, stacksize / sizeof(void*))) { free(task); return -1; } + task->name = name; task->func = f; task->arg = arg; coro_create(&task->ctx, _coio_entry, task, task->stk.sptr, task->stk.ssze); - coio_add(&coio_ready, task); - _taskcount++; + coio_add(&coio_ready_list, task); + coio_taskcount++; return 0; } uvlong -coio_timeout(CoioTask * task, int ms) +coio_timeout(CoioTask* task, int ms) { - CoioTask *t; + CoioTask* t; - if (ms > 0) + if (ms >= 0) { task->timeout = coio_now() + (ms * 1000000); - - for (t = coio_sleeping.head; t != NULL && t->timeout && t->timeout < task->timeout; t = t->next); + for (t = coio_sleeping.head; t != NULL && t->timeout && t->timeout < task->timeout; t = t->next); + } else { + task->timeout = 0; + t = NULL; + } if (t) { task->prev = t->prev; @@ -173,12 +196,12 @@ coio_delay(int ms) void coio_yield() { - coio_rdy(coio_current); + coio_ready(coio_current); coio_transfer(); } void -coio_add(CoioTaskList * lst, CoioTask * task) +coio_add(CoioTaskList* lst, CoioTask* task) { if (lst->tail) { lst->tail->next = task; @@ -192,33 +215,36 @@ coio_add(CoioTaskList * lst, CoioTask * task) } void -coio_del(CoioTaskList * lst, CoioTask * task) +coio_del(CoioTaskList* lst, CoioTask* task) { if (task->prev) { task->prev->next = task->next; - } else { + } else if (lst->head == task) { lst->head = task->next; } if (task->next) { task->next->prev = task->prev; - } else { + } else if (lst->tail == task) { lst->tail = task->prev; } } void -coio_rdy(CoioTask * task) +coio_ready(CoioTask* task) { task->timeout = 0; - coio_del(&coio_sleeping, task); - coio_add(&coio_ready, task); + if (!task->ready) { + task->ready = 1; + coio_del(&coio_sleeping, task); + coio_add(&coio_ready_list, task); + } } void coio_transfer() { - coro_transfer(&coio_current->ctx, &_sched_ctx); + coro_transfer(&coio_current->ctx, &coio_sched_ctx); } uvlong @@ -242,3 +268,22 @@ coio_now() return (uvlong) ts.tv_sec * 1000 * 1000 * 1000 + ts.tv_nsec; #endif } + +void +coio_debug() +{ + CoioTask* t; + + fprintf(stderr, ">>>\nCurrent tasks: %s\n", coio_current->name); + + fprintf(stderr, "Sleeping tasks: "); + for (t = coio_sleeping.head; t != NULL; t = t->next) { + fprintf(stderr, "%s ", t->name); + } + + fprintf(stderr, "\nReady tasks: "); + for (t = coio_ready_list.head; t != NULL; t = t->next) { + fprintf(stderr, "%s ", t->name); + } + fprintf(stderr, "\n"); +} diff --git a/coio.h b/coio.h index 20a7e6c..de236b9 100644 --- a/coio.h +++ b/coio.h @@ -20,15 +20,18 @@ extern "C" { #endif - typedef struct CoioTask CoioTask; - typedef void (*coio_func) (void *arg); - typedef unsigned long long uvlong; +typedef struct CoioTask CoioTask; +typedef void (*coio_func)(void* arg); +typedef unsigned long long uvlong; - int coio_main (); - int coio_create(coio_func f, void *arg, unsigned int stacksize); - void coio_yield(); - uvlong coio_now(); - int coio_delay(int ms); +extern CoioTask* coio_current; + +int coio_main(); +int coio_create(const char* name, coio_func f, void* arg, unsigned int stacksize); +void coio_yield(); +uvlong coio_now(); +int coio_delay(int ms); +void coio_ready(CoioTask* task); #ifdef __cplusplus } diff --git a/coio_glib.c b/coio_glib.c new file mode 100644 index 0000000..e152fef --- /dev/null +++ b/coio_glib.c @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2018 Moritz Bitsch + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "coio_glib.h" +#include "coio.h" +#include "coioimpl.h" + +#include + +struct coio_source { + GSource base; +}; + +static gboolean coio_source_prepare(GSource* source, gint* timeout_) +{ + (void)source; + uvlong now; + int ms = 5; + CoioTask* t; + + if (coio_ready_list.head) { + *timeout_ = 0; + + /* if we return true here our check functions do not get called */ + if (coio_sleeping.head) { + return FALSE; + } + + return TRUE; + } + + if ((t = coio_sleeping.head) != NULL && t->timeout != 0) { + now = coio_now(); + if (now >= t->timeout) { + ms = 0; + } else { + ms = (t->timeout - now) / 1000000; + } + } + + *timeout_ = ms; + return FALSE; +} + +static gboolean coio_source_check(GSource* source) +{ + (void)source; + CoioTask* t; + + if (coio_sleeping.head) { + /* wake up timed out tasks */ + uvlong now = coio_now(); + while ((t = coio_sleeping.head) && t->timeout && now >= t->timeout) { + coio_ready(t); + } + } + + return TRUE; +} + +static gboolean coio_source_dispatch(GSource* source, GSourceFunc callback, gpointer user_data) +{ + (void)source; + CoioTask* last; + gboolean result = G_SOURCE_CONTINUE; + + /* error condition */ + if (!coio_ready_list.head && !coio_sleeping.head) + return G_SOURCE_REMOVE; + + if (!coio_ready_list.head) + return G_SOURCE_CONTINUE; + + last = coio_ready_list.tail; + + do { + coio_current = coio_ready_list.head; + coio_current->ready = 0; + coio_del(&coio_ready_list, coio_current); + coro_transfer(&coio_sched_ctx, &coio_current->ctx); + + if (coio_current->done) { + coio_taskcount--; + coro_stack_free(&coio_current->stk); + free(coio_current); + } + } while (coio_current != last); + + if (callback) { + result = callback(user_data); + } + + return result; +} + +static void coio_source_finalize(GSource* source) +{ + (void)source; +} + +GSource* coio_gsource_create() +{ + coro_create(&coio_sched_ctx, NULL, NULL, NULL, 0); + + static GSourceFuncs funcs = { + coio_source_prepare, + coio_source_check, + coio_source_dispatch, + coio_source_finalize, + NULL, + NULL + }; + + return g_source_new(&funcs, sizeof(struct coio_source)); +} + +gboolean coio_task_wakeup_helper(gpointer task) +{ + coio_ready((CoioTask*)task); + return G_SOURCE_REMOVE; +} diff --git a/coio_glib.h b/coio_glib.h new file mode 100644 index 0000000..7844470 --- /dev/null +++ b/coio_glib.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Moritz Bitsch + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef COIO_GLIB_H +#define COIO_GLIB_H + +#include + +GSource* coio_gsource_create(); +gboolean coio_task_wakeup_helper(gpointer task); + +#endif diff --git a/coioimpl.h b/coioimpl.h index 0acd967..2f7320b 100644 --- a/coioimpl.h +++ b/coioimpl.h @@ -25,29 +25,32 @@ struct CoioTask { coro_context ctx; struct coro_stack stk; + const char* name; coio_func func; - void *arg; + void* arg; uvlong timeout; + int ready; int done; /* linked list support */ - CoioTask *next; - CoioTask *prev; + CoioTask* next; + CoioTask* prev; }; struct CoioTaskList { - CoioTask *head; - CoioTask *tail; + CoioTask* head; + CoioTask* tail; }; -extern CoioTaskList coio_ready; +extern CoioTaskList coio_ready_list; extern CoioTaskList coio_sleeping; -extern CoioTask *coio_current; +extern coro_context coio_sched_ctx; +extern unsigned long coio_taskcount; -void coio_add (CoioTaskList * lst, CoioTask * task); -void coio_del (CoioTaskList * lst, CoioTask * task); -void coio_rdy (CoioTask * task); +void coio_add(CoioTaskList* lst, CoioTask* task); +void coio_del(CoioTaskList* lst, CoioTask* task); void coio_transfer(); +void coio_debug(); #endif diff --git a/testdelay.c b/testdelay.c index eb42587..d25b491 100644 --- a/testdelay.c +++ b/testdelay.c @@ -17,7 +17,7 @@ #include "coio.h" void -_t1(void *arg) +_t1(void* arg) { printf("going to sleep 1000ms (1s)\n"); coio_delay(1000); @@ -25,7 +25,7 @@ _t1(void *arg) } int -main(int argc, char **argv) +main(int argc, char** argv) { (void) argc; (void) argv; diff --git a/testyield.c b/testyield.c index 776b357..ac1b4a2 100644 --- a/testyield.c +++ b/testyield.c @@ -17,7 +17,7 @@ #include "coio.h" void -_t1(void *arg) +_t1(void* arg) { printf("Hello 1 from _t1\n"); coio_yield(); @@ -25,7 +25,7 @@ _t1(void *arg) } void -_t2(void *arg) +_t2(void* arg) { printf("Hello 1 from _t2\n"); coio_yield(); @@ -33,7 +33,7 @@ _t2(void *arg) } int -main(int argc, char **argv) +main(int argc, char** argv) { (void) argc; (void) argv;