{"id":13597900,"url":"https://github.com/tidwall/neco","last_synced_at":"2025-10-08T14:35:04.003Z","repository":{"id":232433151,"uuid":"783844253","full_name":"tidwall/neco","owner":"tidwall","description":"Concurrency library for C (coroutines)","archived":false,"fork":false,"pushed_at":"2024-04-14T13:02:35.000Z","size":397,"stargazers_count":865,"open_issues_count":4,"forks_count":69,"subscribers_count":12,"default_branch":"main","last_synced_at":"2024-04-14T16:16:55.224Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tidwall.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2024-04-08T17:23:33.000Z","updated_at":"2024-04-15T18:09:54.778Z","dependencies_parsed_at":"2024-04-15T18:09:51.217Z","dependency_job_id":"70dadd49-1b11-414f-9c3b-9baa86f25644","html_url":"https://github.com/tidwall/neco","commit_stats":null,"previous_names":["tidwall/neco"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tidwall%2Fneco","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tidwall%2Fneco/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tidwall%2Fneco/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tidwall%2Fneco/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tidwall","download_url":"https://codeload.github.com/tidwall/neco/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248609644,"owners_count":21132916,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T17:00:43.093Z","updated_at":"2025-10-08T14:34:58.949Z","avatar_url":"https://github.com/tidwall.png","language":"C","readme":"\n\n\u003cp align=\"center\"\u003e\n\u003cpicture\u003e\n  \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/assets/logo-dark.png\"\u003e\n  \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/assets/logo-light.png\"\u003e\n  \u003cimg alt=\"Neco\" src=\"docs/assets/logo-light.png\" width=\"260\"\u003e\n\u003c/picture\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n\u003ca href=\"docs/API.md\"\u003e\u003cimg src=\"https://img.shields.io/badge/api-reference-blue.svg?style=flat-square\" alt=\"API Reference\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nNeco is a C library that provides concurrency using coroutines.\nIt's small \u0026 fast, and intended to make concurrent I/O \u0026 network programming \neasy.\n\n## Features\n\n- [Coroutines](docs/API.md#basic-operations): starting, sleeping, suspending, resuming, yielding, and joining.\n- [Synchronization](docs/API.md#channels): channels, generators, mutexes, condition variables, and waitgroups.\n- Support for [deadlines and cancelation](docs/API.md#deadlines-and-cancelation).\n- [Posix friendly](docs/API.md#posix-wrappers) interface using file descriptors.\n- Additional APIs for [networking](docs/API.md#networking-utilities),\n[signals](docs/API.md#signals), [random data](docs/API.md#random-number-generator), [streams](docs/API.md#streams-and-buffered-io), and [buffered I/O](docs/API.md#streams-and-buffered-io).\n- Lightweight runtime with a fair and deterministic [scheduler](#the-scheduler).\n- [Fast](#fast-context-switching) user-space context switching. Uses assembly in most cases.\n- Stackful coroutines that are nestable, with their life times fully managed by the scheduler.\n- Cross-platform. Linux, Mac, FreeBSD. _(Also WebAssembly and Windows with [some limitations](#platform-notes))_.\n- Single file amalgamation. No dependencies.\n- [Test suite](tests/README.md) with 100% coverage using sanitizers and [Valgrind](https://valgrind.org).\n\nFor a deeper dive, check out the [API reference](docs/API.md).\n\nIt may also be worthwhile to see the [Bluebox](https://github.com/tidwall/bluebox) project for a\nmore complete example of using Neco, including benchmarks.\n\n## Goals\n\n- Give C programs fast single-threaded concurrency.\n- To use a concurrency model that resembles the simplicity of pthreads or Go.\n- Provide an API for concurrent networking and I/O.\n- Make it easy to interop with existing Posix functions.\n\nIt's a non-goal for Neco to provide a scalable multithreaded runtime, where the\ncoroutine scheduler is shared among multiple cpu cores. Or to use other \nconcurrency models like async/await.\n\n## Using\n\nJust drop the \"neco.c\" and \"neco.h\" files into your project. Most modern C compilers should work.\n\n```sh\ncc -c neco.c\n```\n\n## Example 1 (Start a coroutine)\n\nA coroutine is started with the [`neco_start()`](docs/API.md#neco_start) function.\n\nWhen `neco_start()` is called for the first time it will initialize a Neco runtime and scheduler for the current thread, and then blocks until the coroutine and all child coroutines have terminated.\n\n```c\n#include \u003cstdio.h\u003e\n#include \"neco.h\"\n\nvoid coroutine(int argc, void *argv[]) {\n    printf(\"main coroutine started\\n\");\n}\n\nint main(int argc, char *argv[]) {\n    neco_start(coroutine, 0);\n    return 0;\n}\n```\n\n## Example 2 (Use neco_main instead of main)\n\nOptionally, [`neco_main()`](docs/API.md#neco_main) can be used in place of the standard `main()`.\nThis is for when the entirety of your program is intended to be run from only coroutines.\nIt [adjusts the behavior](docs/API.md#neco_main) of the program slightly to make development and error checking easier.\n\n```c\n#include \u003cstdio.h\u003e\n#include \"neco.h\"\n\nint neco_main(int argc, char *argv[]) {\n    printf(\"main coroutine started\\n\");\n    return 0;\n}\n```\n\n## Example 3 (Multiple coroutines)\n\nHere we'll start two coroutines that continuously prints \"tick\" every one second and \"tock\" every two.\n\n```c\n#include \u003cstdio.h\u003e\n#include \"neco.h\"\n\nvoid ticker(int argc, void *argv[]) {\n    while (1) {\n        neco_sleep(NECO_SECOND);\n        printf(\"tick\\n\");\n    }\n}\n\nvoid tocker(int argc, void *argv[]) {\n    while (1) {\n        neco_sleep(NECO_SECOND*2);\n        printf(\"tock\\n\");\n    }\n}\n\nint neco_main(int argc, char *argv[]) {\n    neco_start(ticker, 0);\n    neco_start(tocker, 0);\n    \n    // Keep the program alive for an hour.\n    neco_sleep(NECO_HOUR);\n    return 0;\n}\n```\n\n## Example 4 (Coroutine arguments)\n\nA coroutine is like its own little program that accepts any number of arguments.\n\n```c\nvoid coroutine(int argc, void *argv[])\n```\n\nThe arguments are a series of pointers passed to the coroutine. \nAll arguments are guaranteed to be in scope when the coroutine starts and until the first `neco_` function is called. This allows you an opportunity to validate and/or copy them.\n\n```c\n#include \u003cstdlib.h\u003e\n#include \u003cassert.h\u003e\n#include \u003cunistd.h\u003e\n#include \"neco.h\"\n\nvoid coroutine(int argc, void *argv[]) {\n\n    // All arguments are currently in scope and should be copied before first\n    // neco_*() function is called in this coroutine.\n\n    int arg0 = *(int*)argv[0];\n    int arg1 = *(int*)argv[1];\n    int arg2 = *(int*)argv[2];\n    char *arg3 = argv[3];\n    char *arg4 = argv[4];\n\n    printf(\"arg0=%d, arg1=%d, arg2=%d, arg3=%s, arg4=%s\\n\", \n        arg0, arg1, arg2, arg3, arg4);\n\n    neco_sleep(NECO_SECOND/2);\n\n    // The arguments are no longer in scope and it's unsafe to use the argv\n    // variable any further.\n\n    printf(\"second done\\n\");\n    \n}\n\nint neco_main(int argc, char *argv[]) {\n\n    int arg0 = 0;\n    int *arg1 = malloc(sizeof(int));\n    *arg1 = 1;\n\n    neco_start(coroutine, 5, \u0026arg0, arg1, \u0026(int){2}, NULL, \"hello world\");\n    free(arg1);\n\n    neco_sleep(NECO_SECOND);\n    printf(\"first done\\n\");\n\n    return 0;\n}\n```\n\n## Example 5 (Channels)\n\nA [channel](docs/API.md#channels) is a mechanism for communicating between two or more coroutines.\n\nHere we'll create a second coroutine that sends the message 'ping' to the first coroutine.\n\n```c\n#include \u003cstdlib.h\u003e\n#include \u003cunistd.h\u003e\n#include \"neco.h\"\n\nvoid coroutine(int argc, void *argv[]) {\n    neco_chan *messages = argv[0];\n    \n    // Send a message of the 'messages' channel. \n    char *msg = \"ping\";\n    neco_chan_send(messages, \u0026msg);\n\n    // This coroutine no longer needs the channel.\n    neco_chan_release(messages);\n}\n\nint neco_main(int argc, char *argv[]) {\n\n    // Create a new channel that is used to send 'char*' string messages.\n    neco_chan *messages;\n    neco_chan_make(\u0026messages, sizeof(char*), 0);\n\n    // Start a coroutine that sends messages over the channel. \n    // It's a good idea to use neco_chan_retain on a channel before using it\n    // in a new coroutine. This will avoid potential use-after-free bugs.\n    neco_chan_retain(messages);\n    neco_start(coroutine, 1, messages);\n\n    // Receive the next incoming message. Here we’ll receive the \"ping\"\n    // message we sent above and print it out.\n    char *msg = NULL;\n    neco_chan_recv(messages, \u0026msg);\n    printf(\"%s\\n\", msg);\n    \n    // This coroutine no longer needs the channel.\n    neco_chan_release(messages);\n\n    return 0;\n}\n```\n\n## Example 6 (Generators)\n\nA [generator](docs/API.md#generators) is like channel but is stricly bound to a coroutine and is intended to treat the coroutine like an iterator.\n\n```c\n#include \u003cstdio.h\u003e\n#include \u003cunistd.h\u003e\n#include \"neco.h\"\n\nvoid coroutine(int argc, void *argv[]) {\n    // Yield each int to the caller, one at a time.\n    for (int i = 0; i \u003c 10; i++) {\n        neco_gen_yield(\u0026i);\n    }\n}\n\nint neco_main(int argc, char *argv[]) {\n    \n    // Create a new generator coroutine that is used to send ints.\n    neco_gen *gen;\n    neco_gen_start(\u0026gen, sizeof(int), coroutine, 0);\n\n    // Iterate over each int until the generator is closed.\n    int i;\n    while (neco_gen_next(gen, \u0026i) != NECO_CLOSED) {\n        printf(\"%d\\n\", i); \n    }\n\n    // This coroutine no longer needs the generator.\n    neco_gen_release(gen);\n    return 0;\n}\n```\n\n## Example 7 (Connect to server)\n\nNeco provides [`neco_dial()`](docs/API.md#neco_dial) for easily connecting\nto server.\n\nHere we'll performing a (very simple) HTTP request which prints the homepage of\nthe http://example.com website.\n\n```c\n#include \u003cstdio.h\u003e\n#include \u003cunistd.h\u003e\n#include \"neco.h\"\n\nint neco_main(int argc, char *argv[]) {\n    int fd = neco_dial(\"tcp\", \"example.com:80\");\n    if (fd \u003c 0) {\n        printf(\"neco_dial: %s\\n\", neco_strerror(fd));\n        return 0;\n    }\n    char req[] = \"GET / HTTP/1.1\\r\\n\"\n                 \"Host: example.com\\r\\n\"\n                 \"Connection: close\\r\\n\"\n                 \"\\r\\n\";\n    neco_write(fd, req, sizeof(req));\n    while (1) {\n        char buf[256];\n        int n = neco_read(fd, buf, sizeof(buf));\n        if (n \u003c= 0) {\n            break;\n        }\n        printf(\"%.*s\", n, buf);\n    }\n    close(fd);\n    return 0;\n}\n```\n\n## Example 8 (Create a server)\n\nUse [`neco_serve()`](docs/API.md) to quickly bind and listen on an address. \n\nHere we'll run a tiny webserver at http://127.0.0.1:8080\n\n```c\n#include \u003cstdio.h\u003e\n#include \u003cunistd.h\u003e\n#include \"../neco.h\"\n\nvoid request(int argc, void *argv[]) {\n    int fd = *(int*)argv[0];\n    char req[256];\n    int n = neco_read(fd, req, sizeof(req));\n    if (n \u003e 0) {\n        char res[] = \"HTTP/1.0 200 OK\\r\\n\"\n                     \"Content-Type: text/html\\r\\n\"\n                     \"Content-Length: 21\\r\\n\"\n                     \"\\r\\n\"\n                     \"\u003ch1\u003eHello Neco!\u003c/h1\u003e\\n\";\n        neco_write(fd, res, sizeof(res));\n    }\n    close(fd);\n}\n\nint neco_main(int argc, char *argv[]) {\n    int servfd = neco_serve(\"tcp\", \"127.0.0.1:8080\");\n    if (servfd \u003c 0) {\n        printf(\"neco_serve: %s\\n\", neco_strerror(servfd));\n        return 0;\n    }\n    printf(\"Serving at http://127.0.0.1:8080\\n\");\n    while (1) {\n        int fd = neco_accept(servfd, 0, 0);\n        if (servfd \u003c 0) {\n            printf(\"neco_accept: %s\\n\", neco_strerror(fd));\n            continue;\n        }\n        neco_start(request, 1, \u0026fd);\n    }\n    return 0;\n}\n```\n\n## Example 9 (Echo server and client)\n\nRun server with:\n\n```sh\ncc neco.c echo-server.c \u0026\u0026 ./a.out\n```\n\nRun client with:\n\n```sh\ncc neco.c echo-client.c \u0026\u0026 ./a.out\n```\n\n**echo-server.c**\n\n```c\n#include \u003cstdlib.h\u003e\n#include \u003cunistd.h\u003e\n#include \"neco.h\"\n\nvoid client(int argc, void *argv[]) {\n    int conn = *(int*)argv[0];\n    printf(\"client connected\\n\");\n    char buf[64];\n    while (1) {\n        ssize_t n = neco_read(conn, buf, sizeof(buf));\n        if (n \u003c= 0) {\n            break;\n        }\n        printf(\"%.*s\", (int)n, buf);\n    }\n    printf(\"client disconnected\\n\");\n    close(conn);\n}\n\nint neco_main(int argc, char *argv[]) {\n    int ln = neco_serve(\"tcp\", \"localhost:19203\");\n    if (ln == -1) {\n        perror(\"neco_serve\");\n        exit(1);\n    }\n    printf(\"listening at localhost:19203\\n\");\n    while (1) {\n        int conn = neco_accept(ln, 0, 0);\n        if (conn \u003e 0) {\n            neco_start(client, 1, \u0026conn);\n        }\n    }\n    close(ln);\n    return 0;\n}\n```\n\n**echo-client.c**\n\n```c\n#include \u003cstdlib.h\u003e\n#include \u003cunistd.h\u003e\n#include \"neco.h\"\n\nint neco_main(int argc, char *argv[]) {\n    int fd = neco_dial(\"tcp\", \"localhost:19203\");\n    if (fd == -1) {\n        perror(\"neco_listen\");\n        exit(1);\n    }\n    printf(\"connected\\n\");\n    char buf[64];\n    while (1) {\n        printf(\"\u003e \");\n        fflush(stdout);\n        ssize_t nbytes = neco_read(STDIN_FILENO, buf, sizeof(buf));\n        if (nbytes \u003c 0) {\n            break;\n        }\n        ssize_t ret = neco_write(fd, buf, nbytes);\n        if (ret \u003c 0) {\n            break;\n        }\n    }\n    printf(\"disconnected\\n\");\n    close(fd);\n    return 0;\n}\n```\n\n## Example 10 (Suspending and resuming a coroutine)\n\nAny coroutines can suspended itself indefinetly and then be resumed by other\ncoroutines by using [`neco_suspend()`](docs/API.md#neco_suspend) and \n[`neco_resume()`](docs/API.md#neco_resume).\n\n```c\n#include \u003cstdio.h\u003e\n#include \u003cunistd.h\u003e\n#include \"neco.h\"\n\nvoid coroutine(int argc, void *argv[]) {\n    printf(\"Suspending coroutine\\n\");\n    neco_suspend();\n    printf(\"Coroutine resumed\\n\");\n}\n\nint neco_main(int argc, char *argv[]) {\n    neco_start(coroutine, 0);\n    \n    for (int i = 0; i \u003c 3; i++) {\n        printf(\"%d\\n\", i+1);\n        neco_sleep(NECO_SECOND);\n    }\n\n    // Resume the suspended. The neco_lastid() returns the identifier for the\n    // last coroutine started by the current coroutine.\n    neco_resume(neco_lastid());\n    return 0;\n}\n// Output:\n// Suspending coroutine\n// 1\n// 2\n// 3\n// Coroutine resumed\n```\n\n### More examples\n\nYou can find more [examples here](examples).\n\n## Platform notes\n\nLinux, Mac, and FreeBSD supports all features.\n\nWindows and WebAssembly support the core coroutine features, but have some key\nlimitiations, mostly with working with file descriptors and networking.\nThis is primarly because the Neco event queue works with epoll and kqueue,\nwhich are only available on Linux and Mac/BSD respectively. This means that the\n`neco_wait()` (which allows for a coroutine to wait for a file descriptor to be\nreadable or writeable) is not currently available on those platforms.\n\nOther limitations include:\n\n- Windows only supports amd64.\n- Windows and WebAssembly use smaller default stacks of 1MB.\n- Windows and WebAssembly do not support guards or gaps.\n- Windows and WebAssembly do not support NECO_CSPRNG (Cryptographically secure\n  pseudorandom number generator)\n- Windows does not support stack unwinding.\n\nOther than that, Neco works great on those platforms.\n\nAny contributions towards making Windows and WebAssembly feature complete are\nwelcome. \n\n## The scheduler\n\nNeco uses [sco](https://github.com/tidwall/sco), which is a fair and\ndeterministic scheduler. This means that no coroutine takes priority over\nanother and that all concurrent operations will reproduce in an expected order.\n\n### Fast context switching\n\nThe coroutine context switching is powered by \n[llco](https://github.com/tidwall/llco) and uses assembly code in most\ncases. On my lab machine (AMD Ryzen 9 5950X) a context switch takes about 11\nnanoseconds.\n\n### Thread local runtime\n\nThere can be no more than one scheduler per thread.\n\nWhen the first coroutine is started using `neco_start()`, a new Neco\nruntime is initialized in the current thread, and each runtime has its own\nscheduler. \n\nCommunicating between coroutines that are running in different threads will\nrequire I/O mechanisms that do not block the current schedulers, such as\n`pipe()`, `eventfd()` or atomics. \n\n_Pthread utilties such as `pthread_mutex_t` and `pthread_cond_t` do not work very well in coroutines._\n\nFor example, here we'll create two threads, running their own Neco schedulers.\nEach using pipes to communicate with the other.\n\n```c\n#include \u003cstdio.h\u003e\n#include \u003cunistd.h\u003e\n#include \u003cpthread.h\u003e\n#include \"neco.h\"\n\nvoid coro1(int argc, void *argv[]) {\n    // This coroutine is running in a different scheduler than coro2.\n    int rd = *(int*)argv[0];\n    int wr = *(int*)argv[1];\n    int val;\n    neco_read(rd, \u0026val, sizeof(int));\n    printf(\"coro1: %d\\n\", val);\n    neco_write(wr, \u0026(int){ 2 }, sizeof(int));\n}\n\nvoid coro2(int argc, void *argv[]) {\n    // This coroutine is running in a different scheduler than coro1.\n    int rd = *(int*)argv[0];\n    int wr = *(int*)argv[1];\n    int val;\n    neco_write(wr, \u0026(int){ 1 }, sizeof(int));\n    neco_read(rd, \u0026val, sizeof(int));\n    printf(\"coro2: %d\\n\", val);\n}\n\nvoid *runtime1(void *arg) {\n    int *pipefds = arg;\n    neco_start(coro1, 2, \u0026pipefds[0], \u0026pipefds[3]);\n    return 0;\n}\n\nvoid *runtime2(void *arg) {\n    int *pipefds = arg;\n    neco_start(coro2, 2, \u0026pipefds[2], \u0026pipefds[1]);\n    return 0;\n}\n\nint main() {\n    int pipefds[4];\n    pipe(\u0026pipefds[0]);\n    pipe(\u0026pipefds[2]);\n    pthread_t th1, th2;\n    pthread_create(\u0026th1, 0, runtime1, pipefds);\n    pthread_create(\u0026th2, 0, runtime2, pipefds);\n    pthread_join(th1, 0);\n    pthread_join(th2, 0);\n    return 0;\n}\n```\n\n## License\n\nSource code is available under the MIT [License](LICENSE).\n","funding_links":[],"categories":["Concurrency","C"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftidwall%2Fneco","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftidwall%2Fneco","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftidwall%2Fneco/lists"}