├── .gitignore ├── README.md ├── demo ├── echoserver.c └── ircbot.c ├── include └── co.h ├── src ├── co.c ├── e_select.c ├── event.h ├── t_ucontext.c └── thread.h └── test ├── test_accept.c ├── test_co.c ├── test_connect_tcp.c ├── test_e_select.c ├── test_read_buffer.c ├── test_read_line.c ├── test_sleep.c └── test_t_ucontext.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | a.out 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `libco` — coroutines for C 2 | 3 | *A fun project for doing fun things!* I know that coroutine libraries already 4 | exist for C, but I want to try my hand at writing one to see if I can pull it 5 | off. 6 | 7 | ## When to use `libco` 8 | 9 | `libco` is a **good fit** for the following: 10 | 11 | * A document server (e.g. HTTP), shuffling things between disk and the network. 12 | * A graphics rendering master server, sending tasks to multiple slave nodes. 13 | * A middleware API server, responding to requests and possibly making further 14 | requests. 15 | * A chat server, forwarding messages between multiple connections. 16 | * Shell-like software, managing multiple subprocesses. 17 | * **Anything that waits on data from programs elsewhere and does comparatively 18 | little processing in between.** 19 | 20 | `libco` is a **bad fit** for the following: 21 | 22 | * A game server, with discrete time slots for stepping game logic. 23 | * A graphics rendering slave server, receiving tasks and completing them. 24 | * A backend API server, responding to requests by performing some complex 25 | operation. 26 | * Your CSV parser, which reads from a file and generates a bitmap 27 | * **Anything focused on computation over IO.** 28 | * **Anything focused on operations with no `libco` equivalent.** 29 | * **Anything simple. Use some common sense, friend.** 30 | 31 | It may seem like the first "good fit" list covers fewer cases than the "bad 32 | fit" list, and while this may be true in an absolute sense, it's increasingly 33 | the case that solutions are needed to shuffle data between multiple sources 34 | with minimal processing in between, and why shouldn't such an apparently simple 35 | task be actually simple in practice? 36 | 37 | ## Operations 38 | 39 | `libco` provides the following functions. If you can write your blocking code 40 | in terms of these functions, then `libco` is for you: 41 | 42 | * Thread management 43 | * `co_spawn` 44 | * Logging 45 | * `co_log` and per-level macros 46 | * `co_log_level` 47 | * `co_logger` 48 | * IO 49 | * `co_read` 50 | * `co_read_line` 51 | * `co_write` 52 | * Filesystem 53 | * `co_open` 54 | * `co_close` 55 | * Sockets 56 | * `co_connect_tcp` 57 | 58 | #### — Everything below this line is speculation and subject to change — 59 | 60 | ## Building/installing 61 | 62 | `libco`, in its attempt to remain lightweight, takes the "amalgamation" 63 | approach used by SQLite, here called a "bundle". You can download `libco` as a 64 | header file and a single C source file which you include in your project as you 65 | would any other header or source file. Compiler flags (`-DCO_UCONTEXT`, etc) 66 | determine which backend to use. 67 | 68 | ## Operations 69 | 70 | `libco` provides the following functions: 71 | 72 | * Thread management 73 | * `co_spawn` 74 | * Generators 75 | * `co_generator` 76 | * `co_fetch` 77 | * `co_yield` 78 | * IO 79 | * `co_read` 80 | * `co_write` 81 | * Filesystem 82 | * `co_open` 83 | * `co_close` 84 | * Network 85 | * `co_connect` 86 | * `co_bind` 87 | * `co_accept` 88 | * Subprocesses 89 | * `co_fork` 90 | * `co_exec` 91 | * Timers 92 | * `co_sleep` 93 | * Multiple producer, single consumer queues 94 | * `co_mpsc_new` 95 | * `co_mpsc_put` 96 | * `co_mpsc_get` 97 | 98 | If you can write your software in terms of these primitives and not spend too 99 | much time between calling them, `libco` is an option. Where an operation would 100 | block, `libco` defers the calling thread and puts it in the wait queue. When 101 | the event is ready, `libco` wakes the thread and allows the operation to 102 | continue. 103 | 104 | ## Backend 105 | 106 | `libco` currently only supports `select` and `ucontext` as event and threading 107 | backends, respectively. However, `epoll`, `poll`, and POSIX threads support are 108 | planned for the near future. 109 | 110 | ## Concepts 111 | 112 | With kernel threads/processes, when the process ends up waiting on some 113 | blocking operation such as reading from a file or the network, the kernel marks 114 | the process as waiting and goes to run something else. When the result of that 115 | operation is ready, the kernel takes the next opportunity to wake your process 116 | up. From the view of the process, it seems as if the operation just takes a 117 | long time to run. 118 | 119 | The idea in `libco` is similar, but everything is implemented in userspace. A 120 | list of outstanding operations and corresponding waiting threads is kept in a 121 | central event loop. When an event becomes ready, `libco` wakes up the thread so 122 | it can perform the operation unblocked. 123 | 124 | IO-driven programs such as servers are often designed using a much more 125 | explicit variant of this same architecture, the difference being that control 126 | for responding to an event passes in and out of a specific handler function. 127 | In this architecture, to read from the network, for example, you register the 128 | socket and a handler function with the event loop and the handler will be 129 | called whenever data is ready. In `libco`, however, you simply use the `libco` 130 | variant `co_read` in a loop. Control is passed to the event loop when blocked, 131 | and passed back to your code when data is ready. 132 | 133 | This makes writing complex IO-driven software very ergonomic. Indeed, this 134 | paradigm has been adopted as a built-in language feature in newer languages 135 | like Go. 136 | 137 | The drawback, in the case of `libco`, is that the code between blocking 138 | operations cannot run for too long, as `libco` has no preemption mechanism. 139 | However, in most backends `libco` threads are very cheap. 140 | -------------------------------------------------------------------------------- /demo/echoserver.c: -------------------------------------------------------------------------------- 1 | /* echoserver.c -- libco TCP echo server demo */ 2 | /* Copyright (C) 2015 Alex Iadicicco */ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | static void echo_thread(co_context_t *co, void *_peer) { 11 | co_logger_t *log = co_logger(co, NULL); 12 | co_file_t *peer = _peer; 13 | char byte; 14 | size_t rsize; 15 | 16 | for (;;) { 17 | if (co_read(co, peer, &byte, 1, &rsize) < 0) 18 | break; 19 | if (rsize == 0) 20 | break; 21 | if (co_write(co, peer, &byte, 1, NULL) < 0) 22 | break; 23 | } 24 | 25 | co_info(log, "echo thread exiting!"); 26 | co_close(co, peer); 27 | co_logger_close(co, log); 28 | } 29 | 30 | static void main_thread(co_context_t *co, void *unused) { 31 | co_logger_t *log = co_logger(co, NULL); 32 | co_file_t *listener; 33 | 34 | co_info(log, "creating echo server on port 4321"); 35 | listener = co_bind_tcp6(co, "::", 4321, 5); 36 | 37 | if (listener == NULL) { 38 | co_error(log, "could not create listener; stopping"); 39 | return; 40 | } 41 | 42 | co_info(log, "now accepting connections"); 43 | for (;;) { 44 | char buf[512]; 45 | unsigned short port; 46 | co_file_t *peer = co_accept(co, listener, buf, 512, &port); 47 | 48 | co_info(log, "connection on %s:%d", buf, port); 49 | co_spawn(co, echo_thread, peer); 50 | } 51 | } 52 | 53 | int main(int argc, char *argv[]) { 54 | co_context_t *co; 55 | 56 | co = co_init(); 57 | co_log_level(co, NULL, CO_LOG_DEBUG); 58 | 59 | co_run(co, main_thread, NULL); 60 | } 61 | -------------------------------------------------------------------------------- /demo/ircbot.c: -------------------------------------------------------------------------------- 1 | /* ircbot.c -- libco IRC bot demo */ 2 | /* Copyright (C) 2015 Alex Iadicicco */ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define MAX_PARAMS 32 11 | 12 | struct parsed { 13 | const char *source; 14 | const char *source_extra; 15 | 16 | const char *verb; 17 | 18 | const char *parv[MAX_PARAMS]; 19 | int parc; 20 | }; 21 | 22 | static void p_space(char **s) { 23 | while (**s && isspace(**s)) (*s)++; 24 | } 25 | 26 | static char *p_nonspace(char **s) { 27 | char *result = *s; 28 | while (**s && !isspace(**s)) (*s)++; 29 | if (**s) *(*s)++ = '\0'; 30 | return result; 31 | } 32 | 33 | static void p_source(char **s, struct parsed *m) { 34 | m->source = *s; 35 | while (**s && !isspace(**s)) { 36 | if (**s != '!') { (*s)++; continue; } 37 | *(*s)++ = '\0'; 38 | m->source_extra = *s; 39 | p_nonspace(s); 40 | return; 41 | } 42 | if (**s) *(*s)++ = '\0'; 43 | } 44 | 45 | static int parse_message(char *s, struct parsed *m) { 46 | memset(m, 0, sizeof(*m)); 47 | p_space(&s); 48 | if (*s == ':') { s++; p_source(&s, m); p_space(&s); } 49 | m->verb = p_nonspace(&s); 50 | p_space(&s); 51 | while (*s) { 52 | if (*s == ':') { s++; m->parv[m->parc++] = s; break; } 53 | m->parv[m->parc++] = p_nonspace(&s); 54 | p_space(&s); 55 | } 56 | return 0; 57 | } 58 | 59 | struct bot { 60 | /* configuration: */ 61 | co_logger_t *root_logger; 62 | const char *host; 63 | unsigned short port; 64 | const char *channels; 65 | 66 | /* state: */ 67 | bool running; 68 | co_logger_t *log; 69 | co_file_t *conn; 70 | }; 71 | 72 | static void bot_privmsg_handler( 73 | co_context_t *co, 74 | struct bot *b, 75 | struct parsed *msg 76 | ) { 77 | co_info(b->log, "%s: <%s> %s", msg->parv[0], msg->source, msg->parv[1]); 78 | if (!strcmp(msg->parv[1], ".quit")) 79 | b->running = false; 80 | } 81 | 82 | static void bot_irc_handler( 83 | co_context_t *co, 84 | struct bot *b, 85 | struct parsed *msg 86 | ) { 87 | if (!strcmp(msg->verb, "PING")) { 88 | co_fprintf(co, b->conn, "PONG %s\r\n", msg->parv[0]); 89 | } else if (!strcmp(msg->verb, "001")) { 90 | co_fprintf(co, b->conn, "JOIN %s\r\n", b->channels); 91 | } else if (!strcmp(msg->verb, "NOTICE")) { 92 | co_info(b->log, "%s: -%s- %s", msg->parv[0], 93 | msg->source, msg->parv[1]); 94 | } else if (!strcmp(msg->verb, "PRIVMSG")) { 95 | bot_privmsg_handler(co, b, msg); 96 | } 97 | } 98 | 99 | static void bot_thread(co_context_t *co, void *_bot) { 100 | struct bot *b = _bot; 101 | struct parsed msg; 102 | char line[1024]; 103 | int i; 104 | 105 | b->log = co_logger(co, b->root_logger); 106 | 107 | co_info(b->log, "connecting to %s:%d...", b->host, b->port); 108 | b->conn = co_connect_tcp(co, b->host, b->port); 109 | 110 | if (!b->conn) { 111 | co_error(b->log, "connection failed! exiting"); 112 | return; 113 | } 114 | 115 | co_info(b->log, "connected! registering..."); 116 | co_fprintf(co, b->conn, "NICK cobot\r\n"); 117 | co_fprintf(co, b->conn, "USER co * 0 :cobot\r\n"); 118 | 119 | b->running = true; 120 | 121 | while (b->running && co_read_line(co, b->conn, line, 1024)) { 122 | co_debug(b->log, " <- %s", line); 123 | if (parse_message(line, &msg) < 0) 124 | continue; 125 | 126 | co_trace(b->log, "source=%s$%s$", msg.source, msg.source_extra); 127 | co_trace(b->log, "verb=%s$ parc=%d parv:", msg.verb, msg.parc); 128 | for (i=0; ilog, " %s$", msg.parv[i]); 130 | 131 | bot_irc_handler(co, b, &msg); 132 | } 133 | 134 | co_info(b->log, "bot finished!"); 135 | co_close(co, b->conn); 136 | } 137 | 138 | static void main_thread(co_context_t *co, void *unused) { 139 | co_logger_t *root_logger = co_logger(co, NULL); 140 | 141 | struct bot *bot1 = calloc(1, sizeof(*bot1)); 142 | struct bot *bot2 = calloc(1, sizeof(*bot2)); 143 | 144 | bot1->root_logger = root_logger; 145 | bot1->host = "irc.interlinked.me"; 146 | bot1->port = 6667; 147 | bot1->channels = "#test"; 148 | 149 | bot2->root_logger = root_logger; 150 | bot2->host = "irc.ponychat.net"; 151 | bot2->port = 6667; 152 | bot2->channels = "#test"; 153 | 154 | co_spawn(co, bot_thread, bot1); 155 | co_spawn(co, bot_thread, bot2); 156 | } 157 | 158 | int main(int argc, char *argv[]) { 159 | co_context_t *co; 160 | 161 | co = co_init(); 162 | co_log_level(co, NULL, CO_LOG_DEBUG); 163 | 164 | co_run(co, main_thread, NULL); 165 | } 166 | -------------------------------------------------------------------------------- /include/co.h: -------------------------------------------------------------------------------- 1 | /* co.h -- libco header */ 2 | /* Copyright (C) 2015 Alex Iadicicco */ 3 | 4 | #ifndef __INC_CO_H__ 5 | #define __INC_CO_H__ 6 | 7 | #include 8 | #include 9 | 10 | typedef int co_err_t; 11 | 12 | typedef enum co_open_type co_open_type_t; 13 | typedef enum co_log_level co_log_level_t; 14 | 15 | typedef struct co_context co_context_t; 16 | typedef struct co_file co_file_t; 17 | typedef struct co_logger co_logger_t; 18 | 19 | enum co_open_type { 20 | CO_RDONLY, 21 | CO_WRONLY, 22 | CO_RDWR, 23 | CO_APPEND 24 | }; 25 | 26 | enum co_log_level { 27 | CO_LOG_TRACE = 0, 28 | CO_LOG_DEBUG = 1, 29 | CO_LOG_INFO = 2, 30 | CO_LOG_NOTICE = 3, 31 | CO_LOG_WARN = 4, 32 | CO_LOG_ERROR = 5, 33 | CO_LOG_FATAL = 6 34 | }; 35 | 36 | typedef void co_thread_fn( 37 | co_context_t *ctx, 38 | void *user 39 | ); 40 | 41 | 42 | /* THREAD OPERATIONS 43 | ========================================================================= */ 44 | 45 | extern co_err_t co_spawn( 46 | co_context_t *ctx, 47 | co_thread_fn *start, 48 | void *user 49 | ); 50 | 51 | 52 | /* IO OPERATIONS 53 | ========================================================================= */ 54 | 55 | extern co_err_t co_read( 56 | co_context_t *ctx, 57 | co_file_t *file, 58 | void *buf, 59 | size_t nbyte, 60 | ssize_t *rsize 61 | ); 62 | 63 | extern bool co_read_line( 64 | co_context_t *ctx, 65 | co_file_t *file, 66 | void *buf, 67 | size_t nbyte 68 | ); 69 | 70 | extern co_err_t co_write( 71 | co_context_t *ctx, 72 | co_file_t *file, 73 | const void *buf, 74 | size_t nbyte, 75 | ssize_t *wsize 76 | ); 77 | 78 | /* -- filesystem -- */ 79 | 80 | extern co_file_t *co_open( 81 | co_context_t *ctx, 82 | const char *path, 83 | co_open_type_t typ, 84 | unsigned mode 85 | ); 86 | 87 | extern void co_close( 88 | co_context_t *ctx, 89 | co_file_t *file 90 | ); 91 | 92 | /* -- sockets -- */ 93 | 94 | extern co_file_t *co_connect_tcp( 95 | co_context_t *ctx, 96 | const char *host, 97 | unsigned short port 98 | ); 99 | 100 | extern co_file_t *co_bind_tcp6( 101 | co_context_t *ctx, 102 | const char *host, 103 | unsigned short port, 104 | int backlog 105 | ); 106 | 107 | co_file_t *co_accept( 108 | co_context_t *ctx, 109 | co_file_t *file, 110 | char *addrbuf, 111 | size_t addrbufsize, 112 | unsigned short *port 113 | ); 114 | 115 | 116 | /* MISCELLANEOUS 117 | ========================================================================= */ 118 | 119 | extern void co_usleep( 120 | co_context_t *ctx, 121 | unsigned long usecs 122 | ); 123 | 124 | extern void co_sleep( 125 | co_context_t *ctx, 126 | unsigned long secs 127 | ); 128 | 129 | 130 | /* CONVENIENCE AND HELPERS 131 | ========================================================================= */ 132 | 133 | extern void __co_log( 134 | co_logger_t *logger, 135 | const char *func, 136 | int line, 137 | co_log_level_t level, 138 | const char *fmt, 139 | ... 140 | ); 141 | 142 | #define co_log(LOG, LEVEL, M...) \ 143 | __co_log(LOG, __FUNCTION__, __LINE__, LEVEL, M) 144 | 145 | #define co_trace( LOG, M...) co_log(LOG, CO_LOG_TRACE, M) 146 | #define co_debug( LOG, M...) co_log(LOG, CO_LOG_DEBUG, M) 147 | #define co_info( LOG, M...) co_log(LOG, CO_LOG_INFO, M) 148 | #define co_notice( LOG, M...) co_log(LOG, CO_LOG_NOTICE, M) 149 | #define co_warn( LOG, M...) co_log(LOG, CO_LOG_WARN, M) 150 | #define co_error( LOG, M...) co_log(LOG, CO_LOG_ERROR, M) 151 | #define co_fatal( LOG, M...) co_log(LOG, CO_LOG_FATAL, M) 152 | 153 | extern void co_log_level( 154 | co_context_t *ctx, 155 | co_logger_t *logger, 156 | co_log_level_t level 157 | ); 158 | 159 | extern co_logger_t *co_logger( 160 | co_context_t *ctx, 161 | co_logger_t *inherit 162 | ); 163 | 164 | extern void co_logger_close( 165 | co_context_t *ctx, 166 | co_logger_t *log 167 | ); 168 | 169 | extern ssize_t co_fprintf( 170 | co_context_t *ctx, 171 | co_file_t *file, 172 | const char *fmt, 173 | ... 174 | ); 175 | 176 | /* CONTEXT MANAGEMENT 177 | ========================================================================= */ 178 | 179 | extern co_context_t *co_init(void); 180 | 181 | extern void co_run( 182 | co_context_t *ctx, 183 | co_thread_fn *start, 184 | void *user 185 | ); 186 | 187 | #endif 188 | -------------------------------------------------------------------------------- /src/co.c: -------------------------------------------------------------------------------- 1 | /* co.c -- libco */ 2 | /* Copyright (C) 2015 Alex Iadicicco */ 3 | 4 | #include 5 | 6 | #include "event.h" 7 | #include "thread.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #define READBUF_SIZE 2048 25 | 26 | #define error(x) abort() 27 | 28 | typedef struct co_timer co_timer_t; 29 | 30 | struct co_file { 31 | thread_t *waiting; 32 | int fd; 33 | char readbuf[READBUF_SIZE]; 34 | int readbuflen; 35 | co_file_t *next; 36 | co_file_t *prev; 37 | }; 38 | 39 | struct co_logger { 40 | co_logger_t *inherit; 41 | co_log_level_t log_level; 42 | int refcount; 43 | }; 44 | 45 | struct co_timer { 46 | thread_t *waiting; 47 | struct timeval fire; 48 | co_timer_t *next; 49 | }; 50 | 51 | struct co_context { 52 | thread_context_t *threads; 53 | int num_threads; 54 | struct new_thread *new_thread; 55 | co_file_t *files; 56 | co_logger_t log; 57 | co_timer_t *timers; 58 | }; 59 | 60 | struct new_thread { 61 | struct new_thread *next; 62 | co_context_t *ctx; 63 | co_thread_fn *start; 64 | void *user; 65 | thread_t *t; 66 | }; 67 | 68 | static void start_thread(thread_context_t *ctx, void *priv) { 69 | struct new_thread *nt = priv; 70 | nt->start(nt->ctx, nt->user); 71 | nt->ctx->num_threads --; 72 | free(nt); 73 | } 74 | 75 | static co_file_t *new_file(int fd) { 76 | co_file_t *f = calloc(1, sizeof(*f)); 77 | f->fd = fd; 78 | return f; 79 | } 80 | 81 | static void add_file(co_context_t *ctx, co_file_t *f) { 82 | if (ctx->files) ctx->files->prev = f; 83 | 84 | f->next = ctx->files; 85 | f->prev = NULL; 86 | 87 | ctx->files = f; 88 | } 89 | 90 | static void remove_file(co_context_t *ctx, co_file_t *f) { 91 | if (ctx->files == f) ctx->files = f->next; 92 | 93 | if (f->next) f->next->prev = f->prev; 94 | if (f->prev) f->prev->next = f->next; 95 | } 96 | 97 | co_err_t co_spawn( 98 | co_context_t *ctx, 99 | co_thread_fn *start, 100 | void *user 101 | ) { 102 | struct new_thread *next; 103 | 104 | next = calloc(1, sizeof(*next)); 105 | next->ctx = ctx; 106 | next->start = start; 107 | next->user = user; 108 | next->t = thread_create(ctx->threads, start_thread, next); 109 | next->next = ctx->new_thread; 110 | ctx->new_thread = next; 111 | ctx->num_threads ++; 112 | 113 | co_debug(&ctx->log, "spawning a new thread: start=%p user=%p", 114 | start, user); 115 | 116 | return 0; 117 | } 118 | 119 | co_err_t co_read( 120 | co_context_t *ctx, 121 | co_file_t *file, 122 | void *_buf, 123 | size_t nbyte, 124 | ssize_t *rsize 125 | ) { 126 | unsigned char *buf = _buf; 127 | ssize_t rsz, total; 128 | 129 | if (file->waiting != NULL) { 130 | error("another context is already waiting on this file"); 131 | return -1; 132 | } 133 | 134 | if (nbyte > 1) { 135 | co_trace(&ctx->log, "begin read: file=%p buf=%p nbyte=%d", 136 | file, buf, nbyte); 137 | } 138 | 139 | total = 0; 140 | 141 | /* first fulfill as much of the request with the read buffer as we can */ 142 | if (file->readbuflen > 0) { 143 | rsz = file->readbuflen; 144 | if (rsz > nbyte) rsz = nbyte; 145 | memcpy(buf, file->readbuf, rsz); 146 | buf += rsz; 147 | total += rsz; 148 | nbyte -= rsz; 149 | file->readbuflen -= rsz; 150 | memmove(file->readbuf, file->readbuf + rsz, file->readbuflen); 151 | } 152 | 153 | /* either the read buffer satisfied the entire request, or the user 154 | * actually asked for 0 bytes... */ 155 | if (nbyte == 0) { 156 | if (rsize) *rsize = total; 157 | return 0; 158 | } 159 | 160 | /* if the read buffer couldn't satisfy the whole request, then do an 161 | * actual read. reads for more bytes than the read buffer can hold pass 162 | * directly through to the user. otherwise, we attempt to read into the 163 | * entire read buffer and do one last copy out of it for the call. */ 164 | file->waiting = thread_self(ctx->threads); 165 | event_fd_want_read(file->fd); 166 | thread_defer_self(ctx->threads); 167 | 168 | if (nbyte > READBUF_SIZE) { 169 | rsz = read(file->fd, buf, nbyte); 170 | if (rsz < 0) { // TODO: what do we do.. 171 | if (rsize) *rsize = total; 172 | return total > 0 ? 0 : -1; 173 | } else { 174 | total += rsz; 175 | } 176 | } else { 177 | rsz = read( 178 | file->fd, 179 | file->readbuf, 180 | READBUF_SIZE - file->readbuflen 181 | ); 182 | if (rsz < 0) { // TODO: what do we do.. 183 | if (rsize) *rsize = total; 184 | return total > 0 ? 0 : -1; 185 | } 186 | file->readbuflen = rsz; 187 | if (rsz > nbyte) rsz = nbyte; 188 | memcpy(buf, file->readbuf, rsz); 189 | buf += rsz; 190 | total += rsz; 191 | nbyte -= rsz; 192 | file->readbuflen -= rsz; 193 | memmove(file->readbuf, file->readbuf + rsz, file->readbuflen); 194 | } 195 | 196 | if (rsize) *rsize = total; 197 | return 0; 198 | } 199 | 200 | bool co_read_line( 201 | co_context_t *ctx, 202 | co_file_t *file, 203 | void *_buf, 204 | size_t nbyte 205 | ) { 206 | int total; 207 | unsigned char byte, *buf = _buf; 208 | ssize_t rsz; 209 | 210 | total = 0; 211 | 212 | co_trace(&ctx->log, "begin read line: file=%p buf=%p nbyte=%d", 213 | file, buf, nbyte); 214 | 215 | while (nbyte > 1) { 216 | co_read(ctx, file, &byte, 1, &rsz); 217 | if (rsz == 0) 218 | break; 219 | total++; 220 | if (byte == '\r' && nbyte > 2) { 221 | co_read(ctx, file, &byte, 1, &rsz); 222 | total++; 223 | if (rsz == 0) { 224 | *buf++ = '\r'; 225 | nbyte--; 226 | break; 227 | } else if (byte != '\n') { 228 | *buf++ = '\r'; 229 | *buf++ = byte; 230 | nbyte -= 2; 231 | break; 232 | } 233 | 234 | if (rsz == 0 || byte != '\n') { 235 | *buf++ = '\r'; 236 | *buf++ = byte; 237 | nbyte -= 2; 238 | if (rsz == 0) 239 | break; 240 | } else if (byte == '\n') { 241 | break; 242 | } 243 | } 244 | if (byte == '\n') 245 | break; 246 | *buf++ = byte; 247 | nbyte--; 248 | } 249 | 250 | *buf = '\0'; 251 | return total > 0; 252 | } 253 | 254 | co_err_t co_write( 255 | co_context_t *ctx, 256 | co_file_t *file, 257 | const void *buf, 258 | size_t nbyte, 259 | ssize_t *wsize 260 | ) { 261 | ssize_t wsz; 262 | 263 | co_trace(&ctx->log, "begin write: file=%p buf=%p nbyte=%d", 264 | file, buf, nbyte); 265 | 266 | if (file->waiting != NULL) { 267 | error("another context is already waiting on this file"); 268 | return -1; 269 | } 270 | 271 | file->waiting = thread_self(ctx->threads); 272 | event_fd_want_write(file->fd); 273 | 274 | thread_defer_self(ctx->threads); 275 | 276 | wsz = write(file->fd, buf, nbyte); 277 | if (wsz < 0) { 278 | if (wsize) *wsize = 0; 279 | return -1; 280 | } else { 281 | if (wsize) *wsize = wsz; 282 | return 0; 283 | } 284 | } 285 | 286 | co_file_t *co_open( 287 | co_context_t *ctx, 288 | const char *path, 289 | co_open_type_t typ, 290 | unsigned mode 291 | ) { 292 | int fd; 293 | co_file_t *f; 294 | 295 | co_trace(&ctx->log, "opening file: %s", path); 296 | 297 | if (!(fd = open(path, O_RDONLY))) 298 | return NULL; 299 | 300 | f = new_file(fd); 301 | add_file(ctx, f); 302 | 303 | return f; 304 | } 305 | 306 | void co_close( 307 | co_context_t *ctx, 308 | co_file_t *f 309 | ) { 310 | if (f == NULL) 311 | return; 312 | 313 | co_trace(&ctx->log, "closing file"); 314 | 315 | remove_file(ctx, f); 316 | 317 | close(f->fd); 318 | free(f); 319 | } 320 | 321 | co_file_t *co_connect_tcp( 322 | co_context_t *ctx, 323 | const char *host, 324 | unsigned short port 325 | ) { 326 | struct addrinfo gai_hints; 327 | struct addrinfo *gai; 328 | struct sockaddr_in *sa4; 329 | struct sockaddr_in6 *sa6; 330 | void *addr; 331 | int sock, err; 332 | co_file_t *f; 333 | char buf[512]; 334 | 335 | co_info(&ctx->log, "connect to %s:%d", host, port); 336 | 337 | /* TODO: replace this with an async lookup mechanism */ 338 | memset(&gai_hints, 0, sizeof(gai_hints)); 339 | gai_hints.ai_family = AF_INET; 340 | gai_hints.ai_socktype = SOCK_STREAM; 341 | gai_hints.ai_protocol = 0; 342 | if ((err = getaddrinfo(host, NULL, &gai_hints, &gai)) != 0) { 343 | co_error( 344 | &ctx->log, 345 | "co_connect_tcp failed: %s", 346 | gai_strerror(err) 347 | ); 348 | return NULL; 349 | } 350 | if (gai->ai_addr == NULL) 351 | goto fail_addr; 352 | 353 | switch (gai->ai_family) { 354 | case AF_INET: 355 | sa4 = (void*)gai->ai_addr; 356 | sa4->sin_port = htons(port); 357 | addr = &sa4->sin_addr; 358 | break; 359 | case AF_INET6: 360 | sa6 = (void*)gai->ai_addr; 361 | sa6->sin6_port = htons(port); 362 | addr = &sa6->sin6_addr; 363 | break; 364 | default: 365 | goto fail_addr; 366 | } 367 | 368 | if (inet_ntop(gai->ai_family, addr, buf, 512)) { 369 | co_debug(&ctx->log, "address resolves to %s", buf); 370 | } else { 371 | snprintf(buf, 512, ""); 372 | co_notice(&ctx->log, "inet_ntop failed?"); 373 | } 374 | 375 | if ((sock = socket(gai->ai_family, SOCK_STREAM, 0)) < 0) 376 | goto fail_addr; 377 | fcntl(sock, F_SETFL, O_NONBLOCK); 378 | 379 | f = new_file(sock); 380 | add_file(ctx, f); 381 | 382 | co_debug(&ctx->log, "starting connection attempt..."); 383 | for (;;) { 384 | err = connect(sock, gai->ai_addr, gai->ai_addrlen); 385 | if (err == 0) 386 | break; 387 | 388 | switch (errno) { 389 | case EINPROGRESS: 390 | case EINTR: 391 | case EALREADY: 392 | co_trace(&ctx->log, "connection not ready; deferring"); 393 | f->waiting = thread_self(ctx->threads); 394 | event_fd_want_write(f->fd); 395 | thread_defer_self(ctx->threads); 396 | co_debug(&ctx->log, "attempting to complete connection"); 397 | continue; 398 | 399 | default: 400 | goto fail_connect; 401 | } 402 | } 403 | 404 | co_debug(&ctx->log, "connection to %s:%d ready", host, port); 405 | freeaddrinfo(gai); 406 | return f; 407 | 408 | fail_connect: 409 | co_error(&ctx->log, "connection failed: %s", strerror(errno)); 410 | remove_file(ctx, f); 411 | free(f); 412 | close(sock); 413 | fail_addr: 414 | freeaddrinfo(gai); 415 | return NULL; 416 | } 417 | 418 | co_file_t *co_bind_tcp6( 419 | co_context_t *ctx, 420 | const char *host, 421 | unsigned short port, 422 | int backlog 423 | ) { 424 | int sock, one; 425 | struct sockaddr_in6 sa6; 426 | co_file_t *f; 427 | 428 | memset(&sa6, 0, sizeof(sa6)); 429 | if (inet_pton(AF_INET6, host, &sa6.sin6_addr) < 0) 430 | return NULL; 431 | sa6.sin6_family = AF_INET6; 432 | sa6.sin6_port = htons(port); 433 | 434 | if ((sock = socket(AF_INET6, SOCK_STREAM, 0)) < 0) 435 | return NULL; 436 | fcntl(sock, F_SETFL, O_NONBLOCK); 437 | 438 | one = 1; 439 | if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) 440 | goto fail_bind; 441 | 442 | if (bind(sock, (void*)&sa6, sizeof(sa6)) < 0) 443 | goto fail_bind; 444 | 445 | if (listen(sock, backlog) < 0) 446 | goto fail_bind; 447 | 448 | f = new_file(sock); 449 | add_file(ctx, f); 450 | 451 | return f; 452 | 453 | fail_bind: 454 | co_error(&ctx->log, "bind failed: %s", strerror(errno)); 455 | close(sock); 456 | return NULL; 457 | } 458 | 459 | co_file_t *co_accept( 460 | co_context_t *ctx, 461 | co_file_t *file, 462 | char *addrbuf, 463 | size_t addrbufsize, 464 | unsigned short *port 465 | ) { 466 | co_file_t *peer; 467 | struct sockaddr_storage ss; 468 | struct sockaddr_in *sa4; 469 | struct sockaddr_in6 *sa6; 470 | socklen_t ssize; 471 | int sock; 472 | int af = -1; 473 | void *addr; 474 | 475 | co_debug(&ctx->log, "waiting for a connection..."); 476 | for (;;) { 477 | ssize = sizeof(ss); 478 | sock = accept(file->fd, (void*)&ss, &ssize); 479 | if (sock >= 0) 480 | break; 481 | 482 | switch (errno) { 483 | case EAGAIN: 484 | co_trace(&ctx->log, "no connection ready; deferring"); 485 | file->waiting = thread_self(ctx->threads); 486 | event_fd_want_read(file->fd); 487 | thread_defer_self(ctx->threads); 488 | continue; 489 | case EINTR: 490 | co_trace(&ctx->log, "interrupted by signal; trying again"); 491 | continue; 492 | default: 493 | co_error(&ctx->log, "accept failed: %s", strerror(errno)); 494 | return NULL; 495 | } 496 | } 497 | 498 | fcntl(sock, F_SETFL, O_NONBLOCK); 499 | 500 | peer = new_file(sock); 501 | add_file(ctx, peer); 502 | 503 | if (port) *port = 0; 504 | 505 | switch (ssize) { 506 | case sizeof(struct sockaddr_in): 507 | af = AF_INET; 508 | sa4 = (void*)&ss; 509 | addr = &sa4->sin_addr; 510 | if (port) *port = ntohs(sa4->sin_port); 511 | break; 512 | case sizeof(struct sockaddr_in6): 513 | af = AF_INET6; 514 | sa6 = (void*)&ss; 515 | addr = &sa6->sin6_addr; 516 | if (port) *port = ntohs(sa6->sin6_port); 517 | break; 518 | } 519 | 520 | if (addrbuf != NULL) { 521 | if (af < 0 || !inet_ntop(af, addr, addrbuf, addrbufsize)) { 522 | snprintf(addrbuf, addrbufsize, ""); 523 | } 524 | } 525 | 526 | return peer; 527 | } 528 | 529 | static void make_timer( 530 | co_context_t *ctx, 531 | thread_t *waiting, 532 | struct timeval *fire 533 | ) { 534 | co_timer_t *timer, *cur; 535 | 536 | timer = calloc(1, sizeof(*timer)); 537 | timer->waiting = waiting; 538 | timer->fire.tv_sec = fire->tv_sec; 539 | timer->fire.tv_usec = fire->tv_usec; 540 | 541 | /* sorted insert into ctx->timers, sorted by fire time */ 542 | 543 | /* if list empty, easy, just put it as the whole list */ 544 | if (ctx->timers == NULL) { 545 | ctx->timers = timer; 546 | return; 547 | } 548 | 549 | /* if before first element, easy, just put as start of list */ 550 | if (timercmp(&timer->fire, &ctx->timers->fire, <)) { 551 | timer->next = ctx->timers; 552 | ctx->timers = timer; 553 | return; 554 | } 555 | 556 | /* otherwise, insert after last timer before it */ 557 | for (cur = ctx->timers; cur->next; cur = cur->next) { 558 | if (timercmp(&timer->fire, &cur->next->fire, <)) { 559 | timer->next = cur->next; 560 | cur->next = timer; 561 | return; 562 | } 563 | } 564 | 565 | /* otherwise, goes at the very end! cur is last timer with a ->next */ 566 | cur->next = timer; 567 | } 568 | 569 | void co_usleep( 570 | co_context_t *ctx, 571 | unsigned long usecs 572 | ) { 573 | struct timeval now; 574 | 575 | gettimeofday(&now, NULL); 576 | 577 | now.tv_sec += usecs / 1000000; 578 | now.tv_usec += usecs % 1000000; 579 | 580 | now.tv_sec += now.tv_usec / 1000000; 581 | now.tv_usec = now.tv_usec % 1000000; 582 | 583 | make_timer(ctx, thread_self(ctx->threads), &now); 584 | thread_defer_self(ctx->threads); 585 | } 586 | 587 | void co_sleep( 588 | co_context_t *ctx, 589 | unsigned long secs 590 | ) { 591 | struct timeval now; 592 | 593 | gettimeofday(&now, NULL); 594 | now.tv_sec += secs; 595 | 596 | make_timer(ctx, thread_self(ctx->threads), &now); 597 | thread_defer_self(ctx->threads); 598 | } 599 | 600 | static const char *log_level_names[] = { 601 | [CO_LOG_TRACE] = "TRACE", 602 | [CO_LOG_DEBUG] = "DEBUG", 603 | [CO_LOG_INFO] = "INFO", 604 | [CO_LOG_NOTICE] = "NOTICE", 605 | [CO_LOG_WARN] = "WARN", 606 | [CO_LOG_ERROR] = "ERROR", 607 | [CO_LOG_FATAL] = "FATAL", 608 | }; 609 | 610 | void __co_log( 611 | co_logger_t *logger, 612 | const char *func, 613 | int line, 614 | co_log_level_t level, 615 | const char *fmt, 616 | ... 617 | ) { 618 | char date[128]; 619 | char buf[2048]; 620 | time_t t; 621 | struct tm gmt; 622 | va_list va; 623 | 624 | if (level < logger->log_level) 625 | return; 626 | 627 | va_start(va, fmt); 628 | vsnprintf(buf, 2048, fmt, va); 629 | va_end(va); 630 | 631 | time(&t); 632 | gmtime_r(&t, &gmt); 633 | strftime(date, 128, "%Y-%m-%dT%H:%M:%SZ", &gmt); 634 | 635 | printf("[%s] %s:%d: [%s] %s\n", 636 | date, func, line, log_level_names[level], buf); 637 | } 638 | 639 | void co_log_level( 640 | co_context_t *ctx, 641 | co_logger_t *logger, 642 | co_log_level_t level 643 | ) { 644 | if (logger == NULL) 645 | logger = &ctx->log; 646 | logger->log_level = level; 647 | } 648 | 649 | co_logger_t *co_logger( 650 | co_context_t *ctx, 651 | co_logger_t *inherit 652 | ) { 653 | co_logger_t *logger = calloc(1, sizeof(*logger)); 654 | 655 | if (inherit == NULL) 656 | inherit = &ctx->log; 657 | logger->inherit = inherit; 658 | logger->inherit->refcount++; 659 | logger->log_level = inherit->log_level; 660 | logger->refcount = 1; 661 | 662 | return logger; 663 | } 664 | 665 | void co_logger_close( 666 | co_context_t *ctx, 667 | co_logger_t *logger 668 | ) { 669 | logger->inherit->refcount--; 670 | if (logger->inherit->refcount <= 0) 671 | co_logger_close(ctx, logger); 672 | logger->refcount--; 673 | if (logger->refcount <= 0) 674 | free(logger); 675 | } 676 | 677 | ssize_t co_fprintf( 678 | co_context_t *ctx, 679 | co_file_t *file, 680 | const char *fmt, 681 | ... 682 | ) { 683 | char buf[8192]; 684 | va_list va; 685 | int sz; 686 | ssize_t wsize; 687 | 688 | va_start(va, fmt); 689 | sz = vsnprintf(buf, 8192, fmt, va); 690 | va_end(va); 691 | 692 | if (co_write(ctx, file, buf, sz, &wsize) < 0) 693 | return -1; 694 | 695 | return wsize; 696 | } 697 | 698 | co_context_t *co_init(void) { 699 | co_context_t *ctx; 700 | 701 | ctx = calloc(1, sizeof(*ctx)); 702 | ctx->threads = thread_context_new(); 703 | ctx->new_thread = NULL; 704 | ctx->files = NULL; 705 | ctx->log.inherit = NULL; 706 | ctx->log.log_level = CO_LOG_NOTICE; 707 | ctx->log.refcount = 1; 708 | 709 | event_init(); 710 | 711 | co_trace(&ctx->log, "co context initialized"); 712 | 713 | return ctx; 714 | } 715 | 716 | static thread_t *unwait(co_context_t *ctx, int fd) { 717 | co_file_t *f; 718 | 719 | for (f=ctx->files; f; f=f->next) { 720 | if (f->fd == fd) { 721 | thread_t *t = f->waiting; 722 | co_trace(&ctx->log, " fd=%d -> t=%p", fd, t); 723 | f->waiting = NULL; 724 | return t; 725 | } 726 | } 727 | 728 | return NULL; 729 | } 730 | 731 | static thread_t *thread_poller(thread_context_t *threads, void *_ctx) { 732 | thread_t *res; 733 | co_context_t *ctx = _ctx; 734 | event_polled_t evt; 735 | co_file_t *f; 736 | struct timeval now, fire, timeout; 737 | co_timer_t *timer; 738 | 739 | co_trace(&ctx->log, "poll..."); 740 | 741 | if (ctx->num_threads == 0) { 742 | co_debug(&ctx->log, "no threads left; stopping"); 743 | thread_context_stop(threads); 744 | return NULL; 745 | } 746 | 747 | if (ctx->new_thread) { 748 | struct new_thread *next = ctx->new_thread->next; 749 | res = ctx->new_thread->t; 750 | ctx->new_thread = next; 751 | co_debug(&ctx->log, "starting newly spawned thread"); 752 | return res; 753 | } 754 | 755 | gettimeofday(&now, NULL); 756 | 757 | if (ctx->timers && !timercmp(&ctx->timers->fire, &now, >)) { 758 | /* fire timer */ 759 | timer = ctx->timers; 760 | res = timer->waiting; 761 | ctx->timers = timer->next; 762 | free(timer); 763 | co_debug(&ctx->log, "firing timer for thread=%p", res); 764 | return res; 765 | } 766 | 767 | /* limit loop delay to 1 second */ 768 | fire.tv_sec = now.tv_sec + 1; 769 | fire.tv_usec = now.tv_usec; 770 | 771 | if (ctx->timers && timercmp(&ctx->timers->fire, &fire, <)) { 772 | fire.tv_sec = ctx->timers->fire.tv_sec; 773 | fire.tv_usec = ctx->timers->fire.tv_usec; 774 | } 775 | 776 | timersub(&fire, &now, &timeout); 777 | 778 | if (event_poll(&evt, &timeout)) { 779 | switch (evt.tag) { 780 | case EVENT_NOTHING: 781 | co_trace(&ctx->log, "no events ready"); 782 | break; 783 | case EVENT_FD_CAN_READ: 784 | case EVENT_FD_CAN_WRITE: 785 | co_trace(&ctx->log, "fd=%d ready for IO", evt.v.fd); 786 | return unwait(ctx, evt.v.fd); 787 | } 788 | } 789 | 790 | /* don't check timers. we'll get the fired timer on the next loop */ 791 | 792 | return NULL; 793 | } 794 | 795 | static void do_nothing(int unused) { } 796 | 797 | void co_run( 798 | co_context_t *ctx, 799 | co_thread_fn *start, 800 | void *user 801 | ) { 802 | co_trace(&ctx->log, "installing dummy SIGPIPE handler"); 803 | signal(SIGPIPE, do_nothing); 804 | 805 | co_trace(&ctx->log, "spawning start thread"); 806 | co_spawn(ctx, start, user); 807 | 808 | co_debug(&ctx->log, "entering thread run loop"); 809 | thread_context_run(ctx->threads, thread_poller, ctx); 810 | } 811 | -------------------------------------------------------------------------------- /src/e_select.c: -------------------------------------------------------------------------------- 1 | /* e_select.c -- select-based event backend */ 2 | /* Copyright (C) 2015 Alex Iadicicco */ 3 | 4 | #include "event.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | /* maybe these can be replaced with big ring buffers for FASTFASTFAST? */ 11 | struct waiting { 12 | int fd; 13 | struct waiting *next, *prev; 14 | }; 15 | 16 | static int fd_max; 17 | static fd_set fds_waiting_read; 18 | static fd_set fds_waiting_write; 19 | 20 | static struct waiting waiting_queue; 21 | 22 | void event_init(void) { 23 | fd_max = 0; 24 | FD_ZERO(&fds_waiting_read); 25 | FD_ZERO(&fds_waiting_write); 26 | waiting_queue.next = &waiting_queue; 27 | waiting_queue.prev = &waiting_queue; 28 | } 29 | 30 | static void add_to_queue(int fd) { 31 | struct waiting *w = calloc(1, sizeof(*w)); 32 | w->fd = fd; 33 | w->next = &waiting_queue; 34 | w->prev = waiting_queue.prev; 35 | w->next->prev = w; 36 | w->prev->next = w; 37 | } 38 | 39 | void event_fd_want_read(int fd) { 40 | if (fd > fd_max) fd_max = fd; 41 | FD_SET(fd, &fds_waiting_read); 42 | add_to_queue(fd); 43 | } 44 | 45 | void event_fd_want_write(int fd) { 46 | if (fd > fd_max) fd_max = fd; 47 | FD_SET(fd, &fds_waiting_write); 48 | add_to_queue(fd); 49 | } 50 | 51 | bool event_poll(event_polled_t *result, struct timeval *timeout) { 52 | fd_set r, w, x; 53 | struct waiting *cur; 54 | 55 | r = fds_waiting_read; 56 | w = fds_waiting_write; 57 | FD_ZERO(&x); 58 | 59 | result->tag = EVENT_NOTHING; 60 | 61 | select(fd_max+1, &r, &w, &x, timeout); 62 | 63 | for ( 64 | cur = waiting_queue.next; 65 | cur != &waiting_queue; 66 | cur = cur->next 67 | ) { 68 | if (FD_ISSET(cur->fd, &r)) { 69 | result->tag = EVENT_FD_CAN_READ; 70 | result->v.fd = cur->fd; 71 | FD_CLR(cur->fd, &fds_waiting_read); 72 | cur->next->prev = cur->prev; 73 | cur->prev->next = cur->next; 74 | free(cur); 75 | return true; 76 | } 77 | if (FD_ISSET(cur->fd, &w)) { 78 | result->tag = EVENT_FD_CAN_WRITE; 79 | result->v.fd = cur->fd; 80 | FD_CLR(cur->fd, &fds_waiting_write); 81 | cur->next->prev = cur->prev; 82 | cur->prev->next = cur->next; 83 | free(cur); 84 | return true; 85 | } 86 | } 87 | 88 | return false; 89 | } 90 | -------------------------------------------------------------------------------- /src/event.h: -------------------------------------------------------------------------------- 1 | /* event.h -- event handling prototypes */ 2 | /* Copyright (C) 2015 Alex Iadicicco */ 3 | 4 | #ifndef __INC_CO_EVENT_H__ 5 | #define __INC_CO_EVENT_H__ 6 | 7 | #include 8 | #include 9 | 10 | typedef struct event_polled event_polled_t; 11 | 12 | struct event_polled { 13 | enum { 14 | EVENT_NOTHING, /* (none) */ 15 | EVENT_FD_CAN_READ, /* fd */ 16 | EVENT_FD_CAN_WRITE, /* fd */ 17 | EVENT_FD_ERROR, /* fd */ 18 | } tag; 19 | 20 | union { 21 | int fd; 22 | } v; 23 | }; 24 | 25 | extern void event_init(void); 26 | 27 | extern void event_fd_want_read(int fd); 28 | extern void event_fd_want_write(int fd); 29 | 30 | extern bool event_poll(event_polled_t *result, struct timeval *timeout); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /src/t_ucontext.c: -------------------------------------------------------------------------------- 1 | /* t_ucontext.c -- ucontext threading implementation */ 2 | /* Copyright (C) 2015 Alex Iadicicco */ 3 | 4 | #include "thread.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #define STACK_SIZE (1<<20) 11 | 12 | #if __SIZEOF_POINTER__ > __SIZEOF_INT__ 13 | # if __SIZEOF_INT__ < 4 14 | # error your integers are too small 15 | # elif __SIZEOF_POINTER__ > 8 16 | # error your pointers are too big 17 | # else 18 | # define _SPLIT_PTR 19 | # endif 20 | #endif 21 | 22 | /* I have only tested this on 32-bit and 64-bit x86 setups! */ 23 | 24 | #ifdef _SPLIT_PTR 25 | #define DEPTR(p0,p1) ((void*)( \ 26 | (((unsigned long)(p0)) << 32) | \ 27 | (((unsigned long)(p1)) << 0))) 28 | #define PTR(x) \ 29 | (((unsigned long)(x) >> 32) & 0xffffffff), \ 30 | (((unsigned long)(x) >> 0) & 0xffffffff) 31 | #else 32 | #define DEPTR(p0,p1) ((void*)((unsigned)(p0))) 33 | #define PTR(x) ((unsigned)(x)), 0 34 | #endif 35 | 36 | struct thread_context { 37 | ucontext_t poller; 38 | thread_t *self; 39 | bool running; 40 | }; 41 | 42 | struct thread { 43 | ucontext_t context; 44 | thread_fn *start; 45 | void *user; 46 | void *stack; 47 | }; 48 | 49 | static void start_thread(int c0, int c1, int t0, int t1) { 50 | thread_context_t *ctx = DEPTR(c0,c1); 51 | thread_t *t = DEPTR(t0,t1); 52 | t->start(ctx, t->user); 53 | free(t->context.uc_stack.ss_sp); 54 | free(t); 55 | } 56 | 57 | thread_context_t *thread_context_new(void) { 58 | return calloc(1, sizeof(thread_context_t)); 59 | } 60 | 61 | void thread_context_run(thread_context_t *ctx, thread_poll_fn *fn, void *user) { 62 | thread_t *next; 63 | 64 | ctx->running = true; 65 | 66 | for (;;) { 67 | for (next = NULL; !next; ) { 68 | if (!ctx->running) { 69 | return; 70 | } 71 | 72 | next = fn(ctx, user); 73 | } 74 | 75 | ctx->self = next; 76 | swapcontext(&ctx->poller, &next->context); 77 | } 78 | } 79 | 80 | void thread_context_stop(thread_context_t *ctx) { 81 | ctx->running = false; 82 | } 83 | 84 | thread_t *thread_create(thread_context_t *ctx, thread_fn *start, void *user) { 85 | thread_t *t; 86 | 87 | t = calloc(1, sizeof(*t)); 88 | t->start = start; 89 | t->user = user; 90 | getcontext(&t->context); 91 | t->context.uc_stack.ss_sp = malloc(STACK_SIZE); 92 | t->context.uc_stack.ss_size = STACK_SIZE; 93 | t->context.uc_link = &ctx->poller; 94 | makecontext(&t->context, start_thread, 4, PTR(ctx), PTR(t)); 95 | 96 | return t; 97 | } 98 | 99 | thread_t *thread_self(thread_context_t *ctx) { 100 | return ctx->self; 101 | } 102 | 103 | void thread_defer_self(thread_context_t *ctx) { 104 | swapcontext(&ctx->self->context, &ctx->poller); 105 | } 106 | -------------------------------------------------------------------------------- /src/thread.h: -------------------------------------------------------------------------------- 1 | /* thread.h -- threading prototypes */ 2 | /* Copyright (C) 2015 Alex Iadicicco */ 3 | 4 | #ifndef __INC_CO_THREAD_H__ 5 | #define __INC_CO_THREAD_H__ 6 | 7 | typedef struct thread_context thread_context_t; 8 | typedef struct thread thread_t; 9 | typedef void thread_fn(thread_context_t*, void*); 10 | typedef thread_t *thread_poll_fn(thread_context_t*, void*); 11 | 12 | extern thread_context_t *thread_context_new(void); 13 | extern void thread_context_run(thread_context_t*, thread_poll_fn*, void*); 14 | extern void thread_context_stop(thread_context_t*); 15 | 16 | /* the newly created thread will not be run unless it is returned from the 17 | poll function */ 18 | extern thread_t *thread_create(thread_context_t*, thread_fn*, void*); 19 | 20 | extern thread_t *thread_self(thread_context_t*); 21 | extern void thread_defer_self(thread_context_t*); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /test/test_accept.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | static void main_thread(co_context_t *co, void *user) { 7 | co_file_t *listener; 8 | int i; 9 | 10 | printf("attempting to bind to port 4321\n"); 11 | listener = co_bind_tcp6(co, "::", 4321, 5); 12 | 13 | if (!listener) { 14 | printf("bind failed! :(\n"); 15 | return; 16 | } 17 | 18 | printf("will accept *5* clients\n"); 19 | for (i=0; i<5; i++) { 20 | co_file_t *peer = co_accept(co, listener, NULL, 0, NULL); 21 | 22 | printf("got client #%d!\n", i+1); 23 | co_write(co, peer, "greetings\n", 10, NULL); 24 | co_close(co, peer); 25 | } 26 | 27 | printf("that's 5, bye!\n"); 28 | } 29 | 30 | int main(int argc, char *argv[]) { 31 | co_context_t *co; 32 | 33 | co = co_init(); 34 | co_log_level(co, NULL, CO_LOG_DEBUG); 35 | co_run(co, main_thread, NULL); 36 | } 37 | -------------------------------------------------------------------------------- /test/test_co.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | static void my_thread(co_context_t *co, void *user) { 5 | co_file_t *in = co_open(co, "/dev/stdin", CO_RDONLY, 0666); 6 | char buf[4]; 7 | ssize_t rsize; 8 | 9 | printf("hello from my thread!\n"); 10 | 11 | do { 12 | memset(buf, 0, sizeof(buf)); 13 | co_read(co, in, &buf, 4, &rsize); 14 | printf("got %d bytes: %.4s\n", rsize, buf); 15 | } while (rsize); 16 | } 17 | 18 | int main(int argc, char *argv[]) { 19 | co_context_t *co; 20 | 21 | co = co_init(); 22 | co_run(co, my_thread, NULL); 23 | } 24 | -------------------------------------------------------------------------------- /test/test_connect_tcp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define BUFSIZE 2048 7 | 8 | struct connector { 9 | const char *host; 10 | const char *name; 11 | }; 12 | 13 | static void connector_thread(co_context_t *co, void *_conn) { 14 | co_file_t *peer; 15 | struct connector *conn = _conn; 16 | const char *request_fmt = 17 | "GET / HTTP/1.0\r\n" 18 | "Host: %s\r\n" 19 | "User-Agent: libco/0.0\r\n" 20 | "Accept: */*\r\n" 21 | "Connection: close\r\n" 22 | "\r\n"; 23 | char buf[BUFSIZE]; 24 | ssize_t rsize; 25 | 26 | snprintf(buf, BUFSIZE, request_fmt, conn->host); 27 | 28 | printf("%s: connecting to %s\n", conn->name, conn->host); 29 | peer = co_connect_tcp(co, conn->host, 80); 30 | 31 | if (peer == NULL) { 32 | printf("%s: connect failed :(\n", conn->name); 33 | return; 34 | } 35 | 36 | printf("%s: connected! sending request...\n", conn->name); 37 | co_write(co, peer, buf, strlen(buf), NULL); 38 | printf("%s: request sent!\n", conn->name); 39 | printf("%s: reading response...\n", conn->name); 40 | 41 | do { 42 | memset(buf, 0, sizeof(buf)); 43 | co_read(co, peer, buf, BUFSIZE, &rsize); 44 | printf("%s: got %d bytes\n", conn->name, rsize); 45 | } while (rsize); 46 | 47 | printf("%s: bye!\n", conn->name); 48 | 49 | free(conn); 50 | } 51 | 52 | static void main_thread(co_context_t *co, void *user) { 53 | struct connector *google, *interlinked, *nowhere, *wiki, *amazon; 54 | 55 | google = calloc(1, sizeof(*google)); 56 | google->host = "www.google.com"; 57 | google->name = "google"; 58 | 59 | interlinked = calloc(1, sizeof(*interlinked)); 60 | interlinked->host = "interlinked.me"; 61 | interlinked->name = "interlinked"; 62 | 63 | nowhere = calloc(1, sizeof(*nowhere)); 64 | nowhere->host = "nonexistent.badtld"; 65 | nowhere->name = "nowhere"; 66 | 67 | wiki = calloc(1, sizeof(*wiki)); 68 | wiki->host = "en.wikipedia.org"; 69 | wiki->name = "wikipedia"; 70 | 71 | amazon = calloc(1, sizeof(*amazon)); 72 | amazon->host = "www.amazon.com"; 73 | amazon->name = "amazon"; 74 | 75 | printf("spawning threads...\n"); 76 | co_spawn(co, connector_thread, google); 77 | co_spawn(co, connector_thread, interlinked); 78 | co_spawn(co, connector_thread, nowhere); 79 | co_spawn(co, connector_thread, wiki); 80 | co_spawn(co, connector_thread, amazon); 81 | printf("done\n"); 82 | } 83 | 84 | int main(int argc, char *argv[]) { 85 | co_context_t *co; 86 | 87 | co = co_init(); 88 | co_run(co, main_thread, NULL); 89 | } 90 | -------------------------------------------------------------------------------- /test/test_e_select.c: -------------------------------------------------------------------------------- 1 | /* test_e_select -- tests select backend specifically */ 2 | /* Copyright (C) 2015 Alex Iadicicco */ 3 | 4 | #include "../src/event.h" 5 | 6 | #include 7 | #include 8 | 9 | int main(int argc, char *argv[]) { 10 | event_polled_t result; 11 | char buf[4]; 12 | 13 | event_init(); 14 | event_fd_want_read(0); 15 | 16 | for (;;) { 17 | if (!event_poll(&result)) { 18 | printf("sorry nothing!\n"); 19 | continue; 20 | } 21 | 22 | switch (result.tag) { 23 | case EVENT_FD_CAN_READ: 24 | printf("fd=%d can read! ", result.v.fd); 25 | printf("asked for 4 bytes, got %d\n", 26 | read(result.v.fd, &buf, 4)); 27 | event_fd_want_read(result.v.fd); 28 | break; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/test_read_buffer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static void main_thread(co_context_t *co, void *user) { 6 | co_file_t *peer; 7 | const char *request = 8 | "GET / HTTP/1.0\r\n" 9 | "Host: google.com\r\n" 10 | "Connection: close\r\n" 11 | "User-Agent: libco/0.1\r\n" 12 | "\r\n"; 13 | char buf; 14 | ssize_t rsize; 15 | 16 | printf("connecting...\n"); 17 | peer = co_connect_tcp(co, "google.com", 80); 18 | 19 | if (peer == NULL) { 20 | printf("connect failed :(\n"); 21 | return; 22 | } 23 | 24 | printf("connected! sending request...\n"); 25 | co_write(co, peer, request, strlen(request), NULL); 26 | printf("request sent! reading response, a byte at a time...\n"); 27 | 28 | do { 29 | buf = 0; 30 | co_read(co, peer, &buf, 1, &rsize); 31 | putchar(buf); 32 | } while (rsize); 33 | 34 | printf("end of response. bye!\n"); 35 | } 36 | 37 | int main(int argc, char *argv[]) { 38 | co_context_t *co; 39 | 40 | co = co_init(); 41 | co_run(co, main_thread, NULL); 42 | } 43 | -------------------------------------------------------------------------------- /test/test_read_line.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static void main_thread(co_context_t *co, void *user) { 6 | co_file_t *peer; 7 | co_logger_t *log; 8 | const char *request = 9 | "GET / HTTP/1.0\r\n" 10 | "Host: google.com\r\n" 11 | "Connection: close\r\n" 12 | "User-Agent: libco/0.1\r\n" 13 | "\r\n"; 14 | char buf[2048]; 15 | ssize_t rsize; 16 | 17 | log = co_logger(co, NULL); 18 | co_log_level(co, log, CO_LOG_INFO); 19 | 20 | co_info(log, "connecting..."); 21 | peer = co_connect_tcp(co, "google.com", 80); 22 | 23 | if (peer == NULL) { 24 | co_error(log, "connect failed :("); 25 | return; 26 | } 27 | 28 | co_info(log, "connected! sending request..."); 29 | co_write(co, peer, request, strlen(request), NULL); 30 | co_info(log, "request sent! reading response, a line at a time..."); 31 | 32 | while (co_read_line(co, peer, buf, 2048)) { 33 | co_info(log, "line: %s$", buf); 34 | } 35 | 36 | co_info(log, "end of response. bye!"); 37 | } 38 | 39 | int main(int argc, char *argv[]) { 40 | co_context_t *co; 41 | 42 | co = co_init(); 43 | co_log_level(co, NULL, CO_LOG_DEBUG); 44 | co_run(co, main_thread, NULL); 45 | } 46 | -------------------------------------------------------------------------------- /test/test_sleep.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct delay { 7 | const char *name; 8 | int iters; 9 | int usecs; 10 | }; 11 | 12 | static void sleeper_thread(co_context_t *co, void *_delay) { 13 | struct delay *delay = _delay; 14 | int i; 15 | 16 | for (i=0; iiters; i++) { 17 | printf("%s sleep...\n", delay->name); 18 | co_usleep(co, delay->usecs); 19 | } 20 | 21 | printf("%s done!\n", delay->name); 22 | } 23 | 24 | static void main_thread(co_context_t *co, void *user) { 25 | struct delay *one, *two; 26 | 27 | one = calloc(1, sizeof(struct delay)); 28 | one->name = "one"; 29 | one->iters = 15; 30 | one->usecs = 200000; 31 | 32 | two = calloc(1, sizeof(struct delay)); 33 | two->name = "two"; 34 | two->iters = 5; 35 | two->usecs = 500000; 36 | 37 | printf("spawning threads...\n"); 38 | co_spawn(co, sleeper_thread, one); 39 | co_spawn(co, sleeper_thread, two); 40 | printf("done\n"); 41 | } 42 | 43 | int main(int argc, char *argv[]) { 44 | co_context_t *co; 45 | 46 | co = co_init(); 47 | co_run(co, main_thread, NULL); 48 | } 49 | -------------------------------------------------------------------------------- /test/test_t_ucontext.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../src/thread.h" 4 | 5 | static thread_t *inside_thread; 6 | static volatile int counter = 0; 7 | 8 | static void inside(thread_context_t *ctx, void *_unused) { 9 | int i; 10 | for (i=0; ; i++) { 11 | printf("iteration %d!\n", i+1); 12 | thread_defer_self(ctx); 13 | } 14 | } 15 | 16 | static thread_t *poller(thread_context_t *ctx, void *_unused) { 17 | if (counter < 3) { 18 | counter ++; 19 | return inside_thread; 20 | } else { 21 | thread_context_stop(ctx); 22 | return NULL; 23 | } 24 | } 25 | 26 | int main(int argc, char *argv[]) { 27 | thread_context_t *ctx = thread_context_new(); 28 | inside_thread = thread_create(ctx, inside, NULL); 29 | printf("starting thread runtime!\n"); 30 | thread_context_run(ctx, poller, NULL); 31 | printf("thread runtime finished!\n"); 32 | } 33 | --------------------------------------------------------------------------------