Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e8e19e4bc | ||
|
|
a15af5b486 | ||
|
|
5a22d6f322 | ||
|
|
86a9a26fde | ||
|
|
b53d0a29fe | ||
|
|
1bc011c83a | ||
|
|
9967411f02 | ||
|
|
6827ebaf18 | ||
|
|
4788e0eef7 | ||
|
|
43725ee34a | ||
|
|
7d03e805d8 | ||
|
|
052e4c5e5d | ||
|
|
d808a81cd9 | ||
|
|
5f48ee01b6 | ||
|
|
3d4da9f841 | ||
|
|
d829d94d8d | ||
|
|
d5d9d4fe82 | ||
|
|
2c3852d9e6 | ||
|
|
e7a1b30928 | ||
|
|
a11fd9993f | ||
|
|
3a6550fcab | ||
|
|
66c497c7d3 | ||
|
|
fdfe46b008 | ||
|
|
f7728975d6 | ||
|
|
a49a93d411 | ||
|
|
5364a33b50 | ||
|
|
7a96dfc202 | ||
|
|
a9af544d34 | ||
|
|
6947b2939a | ||
|
|
ec26837192 | ||
|
|
ff9a69c6a4 | ||
|
|
8258e56e5e |
@@ -14,3 +14,5 @@ tmp.*
|
||||
*.testdata
|
||||
*.sqlite3
|
||||
*.db
|
||||
*.elf
|
||||
*.dbg
|
||||
@@ -21,10 +21,11 @@ easy.
|
||||
- [Synchronization](docs/API.md#channels): channels, generators, mutexes, condition variables, and waitgroups.
|
||||
- Support for [deadlines and cancelation](docs/API.md#deadlines-and-cancelation).
|
||||
- [Posix friendly](docs/API.md#posix-wrappers) interface using file descriptors.
|
||||
- Addtional APIs for [networking](docs/API.md#networking-utilities),
|
||||
- Additional APIs for [networking](docs/API.md#networking-utilities),
|
||||
[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).
|
||||
- Lightweight runtime with a fair and deterministic [scheduler](#the-scheduler).
|
||||
- [Fast](#fast-context-switching) user-space context switching. Uses assembly in most cases.
|
||||
- Stackful coroutines that are nestable, with their life times fully managed by the scheduler.
|
||||
- Cross-platform. Linux, Mac, FreeBSD. _(Also WebAssembly and Windows with [some limitations](#platform-notes))_.
|
||||
- Single file amalgamation. No dependencies.
|
||||
- [Test suite](tests/README.md) with 100% coverage using sanitizers and [Valgrind](https://valgrind.org).
|
||||
@@ -47,7 +48,7 @@ concurrency models like async/await.
|
||||
|
||||
## Using
|
||||
|
||||
Just drop the "neco.c" and "neco.h" files into your project. Uses standard C11 so most modern C compilers should work.
|
||||
Just drop the "neco.c" and "neco.h" files into your project. Most modern C compilers should work.
|
||||
|
||||
```sh
|
||||
cc -c neco.c
|
||||
@@ -168,7 +169,7 @@ int neco_main(int argc, char *argv[]) {
|
||||
*arg1 = 1;
|
||||
|
||||
neco_start(coroutine, 5, &arg0, arg1, &(int){2}, NULL, "hello world");
|
||||
free(arg2);
|
||||
free(arg1);
|
||||
|
||||
neco_sleep(NECO_SECOND);
|
||||
printf("first done\n");
|
||||
|
||||
Vendored
+4
-2
@@ -1,2 +1,4 @@
|
||||
import github.com/tidwall/sco v0.1.0
|
||||
import github.com/tidwall/stack v0.1.0
|
||||
import github.com/tidwall/sco v0.2.1
|
||||
|
||||
sum 9610203b4ce6f6e3cac862a3deb99cb418acac7c sco.c
|
||||
sum 92baa4119d14dbb2b1effcd2eaeb4adcba8befc1 sco.h
|
||||
|
||||
Vendored
+12
-7
@@ -135,6 +135,11 @@ static void llco_exit(void) {
|
||||
#error LLCO_ASM must not be defined
|
||||
#endif
|
||||
|
||||
#if defined(__COSMOCC__) && !defined(LLCO_NOASM)
|
||||
// Cosmopolitan has issues with asm code
|
||||
#define LLCO_NOASM
|
||||
#endif
|
||||
|
||||
// Passing the entry function into assembly requires casting the function
|
||||
// pointer to an object pointer, which is forbidden in the ISO C spec but
|
||||
// allowed in posix. Ignore the warning attributed to this requirement when
|
||||
@@ -1169,7 +1174,7 @@ const char *llco_method(void *caps) {
|
||||
}
|
||||
|
||||
#if defined(__GNUC__) && !defined(__EMSCRIPTEN__) && !defined(_WIN32) && \
|
||||
!defined(LLCO_NOUNWIND)
|
||||
!defined(LLCO_NOUNWIND) && !defined(__COSMOCC__)
|
||||
|
||||
#include <unwind.h>
|
||||
#include <string.h>
|
||||
@@ -1182,7 +1187,7 @@ struct llco_dlinfo {
|
||||
void *dli_saddr; /* Address of nearest symbol */
|
||||
};
|
||||
|
||||
#ifdef __linux__
|
||||
#if defined(__linux__) && !defined(_GNU_SOURCE)
|
||||
int dladdr(const void *, void *);
|
||||
#endif
|
||||
|
||||
@@ -1853,11 +1858,11 @@ static void sco_entry(void *udata) {
|
||||
co->prev = co;
|
||||
co->next = co;
|
||||
if (sco_cur) {
|
||||
// Reschedule the coroutine that started this one
|
||||
sco_list_push_back(&sco_yielders, co);
|
||||
sco_list_push_back(&sco_yielders, sco_cur);
|
||||
sco_nyielders += 2;
|
||||
sco_switch(false, false);
|
||||
// Reschedule the coroutine that started this one immediately after
|
||||
// all running coroutines, but before any yielding coroutines, and
|
||||
// continue running the started coroutine.
|
||||
sco_list_push_back(&sco_runners, sco_cur);
|
||||
sco_nrunners++;
|
||||
}
|
||||
sco_cur = co;
|
||||
if (sco_user_entry) {
|
||||
|
||||
Vendored
+10
-1
@@ -12,6 +12,12 @@
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#ifdef WORKER_STATIC
|
||||
#define WORKER_API static
|
||||
#else
|
||||
#define WORKER_API
|
||||
#endif
|
||||
|
||||
#define WORKER_DEF_TIMEOUT INT64_C(1000000000) // one second
|
||||
#define WORKER_DEF_MAX_THREADS 2
|
||||
#define WORKER_DEF_MAX_THREAD_ENTRIES 32
|
||||
@@ -46,6 +52,7 @@ struct worker {
|
||||
void (*free)(void*);
|
||||
};
|
||||
|
||||
WORKER_API
|
||||
void worker_free(struct worker *worker) {
|
||||
if (worker) {
|
||||
if (worker->threads) {
|
||||
@@ -70,6 +77,7 @@ void worker_free(struct worker *worker) {
|
||||
}
|
||||
}
|
||||
|
||||
WORKER_API
|
||||
struct worker *worker_new(struct worker_opts *opts) {
|
||||
// Load options
|
||||
int nthreads = opts ? opts->max_threads : 0;
|
||||
@@ -144,10 +152,10 @@ static void *worker_entry(void *arg) {
|
||||
ts.tv_sec += 1;
|
||||
pthread_cond_timedwait(&thread->cond, &thread->mu, &ts);
|
||||
if (thread->len == 0) {
|
||||
thread->th = 0;
|
||||
if (!thread->end) {
|
||||
pthread_detach(thread->th);
|
||||
}
|
||||
thread->th = 0;
|
||||
thread->end = false;
|
||||
break;
|
||||
}
|
||||
@@ -164,6 +172,7 @@ static void *worker_entry(void *arg) {
|
||||
/// @param udata any user data
|
||||
/// @return true for success or false if no worker is available.
|
||||
/// @return false for invalid arguments. Worker and work must no be null.
|
||||
WORKER_API
|
||||
bool worker_submit(struct worker *worker, int64_t pin, void(*work)(void *udata),
|
||||
void *udata)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// Port scanning
|
||||
//
|
||||
// Thanks to @sigidagi for recommending this example.
|
||||
// See https://github.com/tidwall/neco/issues/14
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include "../neco.h"
|
||||
|
||||
void co_dial_port(int argc, void** argv) {
|
||||
const char *host = argv[0]; // host
|
||||
int port = *(int*)argv[1]; // port
|
||||
neco_waitgroup *wg = argv[2]; // waitgroup
|
||||
|
||||
// Dial the port with a one second deadline
|
||||
char addr[256];
|
||||
snprintf(addr, sizeof(addr), "%s:%d", host, port);
|
||||
int fd = neco_dial_dl("tcp", addr, neco_now() + NECO_SECOND);
|
||||
if (fd < 0) {
|
||||
printf("%-5d FAIL\t%s\n", port, neco_strerror(neco_lasterr()));
|
||||
} else {
|
||||
printf("%-5d OK\n", port);
|
||||
close(fd);
|
||||
}
|
||||
// Notify the waitgroup that we are done.
|
||||
neco_waitgroup_done(wg);
|
||||
}
|
||||
|
||||
int neco_main(int argc, char** argv) {
|
||||
const char *host = "scanme.nmap.org";
|
||||
int ports[] = { 22, 80, 8080, 443 };
|
||||
|
||||
|
||||
// Use a waitgroup to synchronize the coroutines
|
||||
neco_waitgroup wg;
|
||||
neco_waitgroup_init(&wg);
|
||||
|
||||
// Dial all the ports
|
||||
printf("%s\n", host);
|
||||
for (int i = 0; i < sizeof(ports)/sizeof(int); i++) {
|
||||
neco_waitgroup_add(&wg, 1);
|
||||
neco_start(co_dial_port, 3, host, &ports[i], &wg);
|
||||
}
|
||||
|
||||
// Wait for all coroutines to finish
|
||||
neco_waitgroup_wait(&wg);
|
||||
return 0;
|
||||
}
|
||||
@@ -17,7 +17,8 @@ NECO_MAXCAP // Max stack_group capacity
|
||||
NECO_GAPSIZE // Size of gap (guard) pages
|
||||
NECO_SIGSTKSZ // Size of signal stack on main thread
|
||||
NECO_BURST // Number of read attempts before waiting, def: disabled
|
||||
NECO_MAXWORKER // Max number of worker threads, def: 2
|
||||
NECO_MAXWORKERS // Max number of worker threads, def: 64
|
||||
NECO_MAXIOWORKERS // Max number of io threads, def: 2
|
||||
|
||||
// Additional options that activate features
|
||||
|
||||
@@ -27,8 +28,11 @@ NECO_NOSTACKFREELIST // Do not use a stack free list
|
||||
NECO_NOPOOL // Do not use a coroutine and channel pools
|
||||
NECO_USEHEAPSTACK // Allocate stacks on the heap using malloc
|
||||
NECO_NOSIGNALS // Disable signal handling
|
||||
NECO_USEROUNDROBINPIN // Use Round-robin when pinning background work
|
||||
NECO_NOWORKERS // Disable all worker threads, work will run in coroutine
|
||||
NECO_USEREADWORKERS // Use read workers, disabled by default
|
||||
NECO_USEWRITEWORKERS // Use write workers, enabled by default on Linux
|
||||
NECO_NOREADWORKERS // Disable all read workers
|
||||
NECO_NOWRITEWORKERS // Disable all write workers
|
||||
*/
|
||||
|
||||
// Windows and Webassembly have limited features.
|
||||
@@ -41,7 +45,7 @@ NECO_NOWORKERS // Disable all worker threads, work will run in coroutine
|
||||
#define DEF_BURST -1
|
||||
#define NECO_USEHEAPSTACK
|
||||
#define NECO_NOSIGNALS
|
||||
#define NECO_NOWORKER
|
||||
#define NECO_NOWORKERS
|
||||
#else
|
||||
#define DEF_STACKSIZE 8388608
|
||||
#define DEF_DEFCAP 4
|
||||
@@ -49,8 +53,15 @@ NECO_NOWORKERS // Disable all worker threads, work will run in coroutine
|
||||
#define DEF_GAPSIZE 1048576
|
||||
#define DEF_SIGSTKSZ 1048576
|
||||
#define DEF_BURST -1
|
||||
#define DEF_MAXWORKER 2
|
||||
#define DEF_MAXWORKERS 64
|
||||
#define DEF_MAXRINGSIZE 32
|
||||
#define DEF_MAXIOWORKERS 2
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#ifndef NECO_USEWRITEWORKERS
|
||||
#define NECO_USEWRITEWORKERS
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef NECO_STACKSIZE
|
||||
@@ -71,18 +82,29 @@ NECO_NOWORKERS // Disable all worker threads, work will run in coroutine
|
||||
#ifndef NECO_BURST
|
||||
#define NECO_BURST DEF_BURST
|
||||
#endif
|
||||
#ifndef NECO_MAXWORKER
|
||||
#define NECO_MAXWORKER DEF_MAXWORKER
|
||||
#ifndef NECO_MAXWORKERS
|
||||
#define NECO_MAXWORKERS DEF_MAXWORKERS
|
||||
#endif
|
||||
#ifndef NECO_MAXRINGSIZE
|
||||
#define NECO_MAXRINGSIZE DEF_MAXRINGSIZE
|
||||
#endif
|
||||
#ifndef NECO_MAXIOWORKERS
|
||||
#define NECO_MAXIOWORKERS DEF_MAXIOWORKERS
|
||||
#endif
|
||||
|
||||
#ifdef NECO_TESTING
|
||||
#if NECO_BURST == -1
|
||||
#if NECO_BURST <= 0
|
||||
#undef NECO_BURST
|
||||
#define NECO_BURST 1
|
||||
#endif
|
||||
#if NECO_MAXWORKERS > 8
|
||||
#undef NECO_MAXWORKERS
|
||||
#define NECO_MAXWORKERS 8
|
||||
#endif
|
||||
#if NECO_MAXRINGSIZE > 4
|
||||
#undef NECO_MAXRINGSIZE
|
||||
#define NECO_MAXRINGSIZE 4
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// The following is only needed when LLCO_NOASM or LLCO_STACKJMP is defined.
|
||||
@@ -114,7 +136,7 @@ NECO_NOWORKERS // Disable all worker threads, work will run in coroutine
|
||||
#include "deps/aat.h"
|
||||
#include "deps/stack.h"
|
||||
|
||||
#ifndef NECO_NOWORKER
|
||||
#ifndef NECO_NOWORKERS
|
||||
#include "deps/worker.h"
|
||||
#endif
|
||||
|
||||
@@ -122,6 +144,7 @@ NECO_NOWORKERS // Disable all worker threads, work will run in coroutine
|
||||
|
||||
#define SCO_STATIC
|
||||
#define STACK_STATIC
|
||||
#define WORKER_STATIC
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic push
|
||||
@@ -266,6 +289,11 @@ static void llco_exit(void) {
|
||||
#error LLCO_ASM must not be defined
|
||||
#endif
|
||||
|
||||
#if defined(__COSMOCC__) && !defined(LLCO_NOASM)
|
||||
// Cosmopolitan has issues with asm code
|
||||
#define LLCO_NOASM
|
||||
#endif
|
||||
|
||||
// Passing the entry function into assembly requires casting the function
|
||||
// pointer to an object pointer, which is forbidden in the ISO C spec but
|
||||
// allowed in posix. Ignore the warning attributed to this requirement when
|
||||
@@ -1300,7 +1328,7 @@ const char *llco_method(void *caps) {
|
||||
}
|
||||
|
||||
#if defined(__GNUC__) && !defined(__EMSCRIPTEN__) && !defined(_WIN32) && \
|
||||
!defined(LLCO_NOUNWIND)
|
||||
!defined(LLCO_NOUNWIND) && !defined(__COSMOCC__)
|
||||
|
||||
#include <unwind.h>
|
||||
#include <string.h>
|
||||
@@ -1313,7 +1341,7 @@ struct llco_dlinfo {
|
||||
void *dli_saddr; /* Address of nearest symbol */
|
||||
};
|
||||
|
||||
#ifdef __linux__
|
||||
#if defined(__linux__) && !defined(_GNU_SOURCE)
|
||||
int dladdr(const void *, void *);
|
||||
#endif
|
||||
|
||||
@@ -1984,11 +2012,11 @@ static void sco_entry(void *udata) {
|
||||
co->prev = co;
|
||||
co->next = co;
|
||||
if (sco_cur) {
|
||||
// Reschedule the coroutine that started this one
|
||||
sco_list_push_back(&sco_yielders, co);
|
||||
sco_list_push_back(&sco_yielders, sco_cur);
|
||||
sco_nyielders += 2;
|
||||
sco_switch(false, false);
|
||||
// Reschedule the coroutine that started this one immediately after
|
||||
// all running coroutines, but before any yielding coroutines, and
|
||||
// continue running the started coroutine.
|
||||
sco_list_push_back(&sco_runners, sco_cur);
|
||||
sco_nrunners++;
|
||||
}
|
||||
sco_cur = co;
|
||||
if (sco_user_entry) {
|
||||
@@ -2591,7 +2619,7 @@ void *stack_addr(struct stack *stack) {
|
||||
}
|
||||
// END stack.c
|
||||
|
||||
#ifndef NECO_NOWORKER
|
||||
#ifndef NECO_NOWORKERS
|
||||
// BEGIN worker.c
|
||||
// https://github.com/tidwall/worker.c
|
||||
//
|
||||
@@ -2607,6 +2635,12 @@ void *stack_addr(struct stack *stack) {
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#ifdef WORKER_STATIC
|
||||
#define WORKER_API static
|
||||
#else
|
||||
#define WORKER_API
|
||||
#endif
|
||||
|
||||
#define WORKER_DEF_TIMEOUT INT64_C(1000000000) // one second
|
||||
#define WORKER_DEF_MAX_THREADS 2
|
||||
#define WORKER_DEF_MAX_THREAD_ENTRIES 32
|
||||
@@ -2641,6 +2675,7 @@ struct worker {
|
||||
void (*free)(void*);
|
||||
};
|
||||
|
||||
WORKER_API
|
||||
void worker_free(struct worker *worker) {
|
||||
if (worker) {
|
||||
if (worker->threads) {
|
||||
@@ -2665,6 +2700,7 @@ void worker_free(struct worker *worker) {
|
||||
}
|
||||
}
|
||||
|
||||
WORKER_API
|
||||
struct worker *worker_new(struct worker_opts *opts) {
|
||||
// Load options
|
||||
int nthreads = opts ? opts->max_threads : 0;
|
||||
@@ -2739,10 +2775,10 @@ static void *worker_entry(void *arg) {
|
||||
ts.tv_sec += 1;
|
||||
pthread_cond_timedwait(&thread->cond, &thread->mu, &ts);
|
||||
if (thread->len == 0) {
|
||||
thread->th = 0;
|
||||
if (!thread->end) {
|
||||
pthread_detach(thread->th);
|
||||
}
|
||||
thread->th = 0;
|
||||
thread->end = false;
|
||||
break;
|
||||
}
|
||||
@@ -2759,6 +2795,7 @@ static void *worker_entry(void *arg) {
|
||||
/// @param udata any user data
|
||||
/// @return true for success or false if no worker is available.
|
||||
/// @return false for invalid arguments. Worker and work must no be null.
|
||||
WORKER_API
|
||||
bool worker_submit(struct worker *worker, int64_t pin, void(*work)(void *udata),
|
||||
void *udata)
|
||||
{
|
||||
@@ -2795,7 +2832,7 @@ bool worker_submit(struct worker *worker, int64_t pin, void(*work)(void *udata),
|
||||
return submitted;
|
||||
}
|
||||
// END worker.c
|
||||
#endif // NECO_NOWORKER
|
||||
#endif // NECO_NOWORKERS
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic pop
|
||||
@@ -2827,9 +2864,11 @@ bool worker_submit(struct worker *worker, int64_t pin, void(*work)(void *udata),
|
||||
#include <netinet/in.h>
|
||||
#include <sys/un.h>
|
||||
#include <dlfcn.h>
|
||||
#include <sys/syscall.h>
|
||||
#endif
|
||||
#include <pthread.h>
|
||||
|
||||
|
||||
#include "neco.h"
|
||||
|
||||
#if defined(__FreeBSD__) || defined(__APPLE__)
|
||||
@@ -2839,7 +2878,7 @@ bool worker_submit(struct worker *worker, int64_t pin, void(*work)(void *udata),
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/eventfd.h>
|
||||
#define NECO_POLL_EPOLL
|
||||
#elif defined(__EMSCRIPTEN__) || defined(_WIN32)
|
||||
#elif defined(__EMSCRIPTEN__) || defined(_WIN32) || defined(__COSMOCC__)
|
||||
// #warning Webassembly has no polling
|
||||
#define NECO_POLL_DISABLED
|
||||
#else
|
||||
@@ -3218,7 +3257,7 @@ struct cleanup {
|
||||
void (*routine)(void *);
|
||||
void *arg;
|
||||
struct cleanup *next;
|
||||
} aligned16;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// colist - The standard queue type that is just a doubly linked list storing
|
||||
@@ -3230,7 +3269,7 @@ struct coroutine;
|
||||
struct colink {
|
||||
struct coroutine *prev;
|
||||
struct coroutine *next;
|
||||
} aligned16;
|
||||
};
|
||||
|
||||
struct colist {
|
||||
struct colink head;
|
||||
@@ -3583,7 +3622,7 @@ struct runtime {
|
||||
void (*arc4random_buf)(void *, size_t);
|
||||
#endif
|
||||
|
||||
#ifndef NECO_NOWORKER
|
||||
#ifndef NECO_NOWORKERS
|
||||
struct worker *worker;
|
||||
pthread_mutex_t iomu;
|
||||
struct colist iolist;
|
||||
@@ -3652,7 +3691,11 @@ static struct coroutine *evexists(int fd, enum evkind kind) {
|
||||
static int is_main_thread(void) {
|
||||
return IsGUIThread(false);
|
||||
}
|
||||
#elif defined(__linux__) || defined(__EMSCRIPTEN__)
|
||||
#elif defined(__linux__)
|
||||
static int is_main_thread(void) {
|
||||
return getpid() == (pid_t)syscall(SYS_gettid);
|
||||
}
|
||||
#elif defined(__EMSCRIPTEN__) || defined(__COSMOCC__)
|
||||
int gettid(void);
|
||||
static int is_main_thread(void) {
|
||||
return getpid() == gettid();
|
||||
@@ -4146,7 +4189,7 @@ static void rt_sched_paused_step(void) {
|
||||
}
|
||||
timeout = CLAMP(timeout, 0, MAX_TIMEOUT);
|
||||
|
||||
#ifndef NECO_NOWORKER
|
||||
#ifndef NECO_NOWORKERS
|
||||
if (rt->niowaiters > 0) {
|
||||
timeout = 0;
|
||||
while (1) {
|
||||
@@ -4319,9 +4362,9 @@ static int run(void(*coroutine)(int, void**), int nargs, va_list *args,
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#ifndef NECO_NOWORKER
|
||||
#ifndef NECO_NOWORKERS
|
||||
struct worker_opts wopts = {
|
||||
.max_threads = NECO_MAXWORKER,
|
||||
.max_threads = NECO_MAXWORKERS,
|
||||
.max_thread_entries = NECO_MAXRINGSIZE,
|
||||
.malloc = malloc0,
|
||||
.free = free0,
|
||||
@@ -4335,7 +4378,6 @@ static int run(void(*coroutine)(int, void**), int nargs, va_list *args,
|
||||
colist_init(&rt->iolist);
|
||||
#endif
|
||||
|
||||
|
||||
// Start the main coroutine. Actually, it's just queued to run first.
|
||||
ret = start(coroutine, nargs, args, argv, 0, 0);
|
||||
if (ret != NECO_OK) {
|
||||
@@ -4350,7 +4392,7 @@ fail:
|
||||
rt_freezchanpool();
|
||||
rt_restore_signal_handlers();
|
||||
rt_release_dlhandles();
|
||||
#ifndef NECO_NOWORKER
|
||||
#ifndef NECO_NOWORKERS
|
||||
worker_free(rt->worker);
|
||||
#endif
|
||||
rt_release();
|
||||
@@ -4981,7 +5023,8 @@ static void cowait(int fd, enum evkind kind, int64_t deadline) {
|
||||
// Networking code
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef NECO_NOWORKER
|
||||
#if !defined(NECO_NOWORKERS) && defined(NECO_USEREADWORKERS) && \
|
||||
!defined(NECO_NOREADWORKERS)
|
||||
|
||||
struct ioread {
|
||||
int fd;
|
||||
@@ -5005,11 +5048,14 @@ static void ioread(void *udata) {
|
||||
|
||||
static ssize_t read1(int fd, void *data, size_t nbytes) {
|
||||
ssize_t n;
|
||||
bool nowork = false;
|
||||
bool nowork = true;
|
||||
#ifdef NECO_TESTING
|
||||
if (neco_fail_read_counter > 0) {
|
||||
nowork = true;
|
||||
}
|
||||
#endif
|
||||
#if NECO_MAXIOWORKERS <= 0
|
||||
nowork = true;
|
||||
#endif
|
||||
if (!nowork) {
|
||||
nowork = true;
|
||||
@@ -5021,11 +5067,7 @@ static ssize_t read1(int fd, void *data, size_t nbytes) {
|
||||
.co = co,
|
||||
.rt = rt,
|
||||
};
|
||||
#ifdef NECO_USEROUNDROBINPIN
|
||||
int64_t pin = -1;
|
||||
#else
|
||||
int64_t pin = co->id;
|
||||
#endif
|
||||
int64_t pin = co->id % NECO_MAXIOWORKERS;
|
||||
if (worker_submit(rt->worker, pin, ioread, &info)) {
|
||||
rt->niowaiters++;
|
||||
sco_pause();
|
||||
@@ -5144,7 +5186,8 @@ static ssize_t write1(int fd, const void *data, size_t nbytes) {
|
||||
#define write2 write1
|
||||
#endif
|
||||
|
||||
#ifndef NECO_NOWORKER
|
||||
#if !defined(NECO_NOWORKERS) && defined(NECO_USEWRITEWORKERS) && \
|
||||
!defined(NECO_NOWRITEWORKERS)
|
||||
struct iowrite {
|
||||
int fd;
|
||||
const void *data;
|
||||
@@ -5172,6 +5215,9 @@ static ssize_t write3(int fd, const void *data, size_t nbytes) {
|
||||
if (neco_fail_write_counter > 0 || neco_partial_write > 0) {
|
||||
nowork = true;
|
||||
}
|
||||
#endif
|
||||
#if NECO_MAXIOWORKERS <= 0
|
||||
nowork = true;
|
||||
#endif
|
||||
if (!nowork) {
|
||||
nowork = true;
|
||||
@@ -5183,11 +5229,7 @@ static ssize_t write3(int fd, const void *data, size_t nbytes) {
|
||||
.co = co,
|
||||
.rt = rt,
|
||||
};
|
||||
#ifdef NECO_USEROUNDROBINPIN
|
||||
int64_t pin = -1;
|
||||
#else
|
||||
int64_t pin = co->id;
|
||||
#endif
|
||||
int64_t pin = co->id % NECO_MAXIOWORKERS;
|
||||
if (worker_submit(rt->worker, pin, iowrite, &info)) {
|
||||
rt->niowaiters++;
|
||||
sco_pause();
|
||||
@@ -7039,13 +7081,13 @@ int neco_serve(const char *network, const char *address) {
|
||||
|
||||
struct neco_mutex {
|
||||
int64_t rtid; // runtime id
|
||||
bool locked; // mutex is locked (read or write)
|
||||
int rlocked; // read lock counter
|
||||
struct colist queue; // coroutine doubly linked list
|
||||
int rlocked; // read lock counter
|
||||
bool locked; // mutex is locked (read or write)
|
||||
};
|
||||
|
||||
|
||||
static_assert(sizeof(neco_mutex) >= sizeof(struct neco_mutex), "");
|
||||
static_assert(_Alignof(neco_mutex) == _Alignof(struct neco_mutex), "");
|
||||
|
||||
static int mutex_init(neco_mutex *mutex) {
|
||||
struct neco_mutex *mu = (void*)mutex;
|
||||
@@ -7296,12 +7338,13 @@ int neco_mutex_destroy(neco_mutex *mutex) {
|
||||
}
|
||||
|
||||
struct neco_waitgroup {
|
||||
int64_t rtid;
|
||||
int count;
|
||||
struct colist queue;
|
||||
int64_t rtid; // runtime id
|
||||
struct colist queue; // coroutine doubly linked list
|
||||
int count; // current wait count
|
||||
};
|
||||
|
||||
static_assert(sizeof(neco_waitgroup) >= sizeof(struct neco_waitgroup), "");
|
||||
static_assert(_Alignof(neco_waitgroup) == _Alignof(struct neco_waitgroup), "");
|
||||
|
||||
inline
|
||||
static int check_waitgroup(struct neco_waitgroup *wg) {
|
||||
@@ -7444,11 +7487,12 @@ int neco_waitgroup_destroy(neco_waitgroup *waitgroup) {
|
||||
}
|
||||
|
||||
struct neco_cond {
|
||||
int64_t rtid; // runtime id
|
||||
struct colist queue; // coroutine doubly linked list
|
||||
int64_t rtid; // runtime id
|
||||
struct colist queue; // coroutine doubly linked list
|
||||
};
|
||||
|
||||
static_assert(sizeof(neco_cond) >= sizeof(struct neco_cond), "");
|
||||
static_assert(_Alignof(neco_cond) == _Alignof(struct neco_cond), "");
|
||||
|
||||
static int cond_init0(struct neco_cond *cv) {
|
||||
memset(cv, 0, sizeof(struct neco_cond));
|
||||
@@ -7749,11 +7793,13 @@ static int _pipe_(int pipefd[2]) {
|
||||
char path[PATH_MAX];
|
||||
snprintf(path, sizeof(path), "/tmp/neco.%" PRIu64 ".sock", tmpkey);
|
||||
int ln = neco_serve("unix", path);
|
||||
int64_t childid = 0;
|
||||
if (ln > 0) {
|
||||
// State a coroutine to connect to the listener.
|
||||
int nret = neco_start(co_pipe, 3, path, &secret, &fd0);
|
||||
neco_errconv_to_sys(nret);
|
||||
if (nret == NECO_OK) {
|
||||
childid = neco_lastid();
|
||||
int64_t dl = neco_now() + NECO_SECOND * 5;
|
||||
fd1 = neco_accept_dl(ln, 0, 0, dl);
|
||||
neco_errconv_to_sys(fd1);
|
||||
@@ -7773,8 +7819,8 @@ static int _pipe_(int pipefd[2]) {
|
||||
unlink(path);
|
||||
}
|
||||
int perrno = errno;
|
||||
neco_yield();
|
||||
if (ret == 0) {
|
||||
neco_join(childid);
|
||||
pipefd[0] = fd0;
|
||||
pipefd[1] = fd1;
|
||||
fd0 = -1;
|
||||
@@ -8649,7 +8695,7 @@ int neco_resume(int64_t id) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifndef NECO_NOWORKER
|
||||
#ifndef NECO_NOWORKERS
|
||||
struct iowork {
|
||||
void(*work)(void *udata);
|
||||
void *udata;
|
||||
@@ -8667,6 +8713,7 @@ static void iowork(void *udata) {
|
||||
#endif
|
||||
|
||||
static int workfn(int64_t pin, void(*work)(void *udata), void *udata) {
|
||||
(void)pin;
|
||||
struct coroutine *co = coself();
|
||||
if (!work) {
|
||||
return NECO_INVAL;
|
||||
@@ -8674,7 +8721,7 @@ static int workfn(int64_t pin, void(*work)(void *udata), void *udata) {
|
||||
if (!co) {
|
||||
return NECO_PERM;
|
||||
}
|
||||
#ifdef NECO_NOWORKER
|
||||
#ifdef NECO_NOWORKERS
|
||||
// Run in foreground
|
||||
work(udata);
|
||||
#else
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// basic operations
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -97,7 +101,7 @@ int neco_gen_close(neco_gen *gen);
|
||||
/// @}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// synchonization mechanisms
|
||||
// synchronization mechanisms
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// @defgroup Mutexes Mutexes
|
||||
@@ -106,7 +110,7 @@ int neco_gen_close(neco_gen *gen);
|
||||
/// to a variable or set of variables and helps to avoid data inconsistencies
|
||||
/// due to race conditions.
|
||||
/// @{
|
||||
typedef struct { char _[48]; } neco_mutex;
|
||||
typedef struct { int64_t _0; intptr_t _1[5]; } neco_mutex;
|
||||
|
||||
#define NECO_MUTEX_INITIALIZER { 0 }
|
||||
|
||||
@@ -128,8 +132,7 @@ int neco_mutex_tryrdlock(neco_mutex *mutex);
|
||||
/// At the same time, neco_waitgroup_wait() can be used to block until all
|
||||
/// coroutines are completed.
|
||||
/// @{
|
||||
typedef struct { char _[48]; } neco_waitgroup;
|
||||
|
||||
typedef struct { int64_t _0; intptr_t _1[5]; } neco_waitgroup;
|
||||
#define NECO_WAITGROUP_INITIALIZER { 0 }
|
||||
|
||||
int neco_waitgroup_init(neco_waitgroup *waitgroup);
|
||||
@@ -143,7 +146,7 @@ int neco_waitgroup_wait_dl(neco_waitgroup *waitgroup, int64_t deadline);
|
||||
/// A condition variable is a synchronization mechanism that allows coroutines
|
||||
/// to suspend execution until some condition is true.
|
||||
/// @{
|
||||
typedef struct { char _[48]; } neco_cond;
|
||||
typedef struct { int64_t _0; intptr_t _1[5]; } neco_cond;
|
||||
#define NECO_COND_INITIALIZER { 0 }
|
||||
|
||||
int neco_cond_init(neco_cond *cond);
|
||||
@@ -208,7 +211,7 @@ int neco_dial_dl(const char *network, const char *address, int64_t deadline);
|
||||
/// @}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// cancellation
|
||||
// cancelation
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// @defgroup Cancelation Cancelation
|
||||
@@ -225,8 +228,9 @@ int neco_cancel_dl(int64_t id, int64_t deadline);
|
||||
int neco_setcanceltype(int type, int *oldtype);
|
||||
int neco_setcancelstate(int state, int *oldstate);
|
||||
|
||||
#define neco_cleanup_push(routine, arg) {__neco_c0(&(char[32]){0},routine,arg);
|
||||
#define neco_cleanup_pop(execute) __neco_c1(execute);}
|
||||
#define neco_cleanup_push(routine, arg) {char __neco_handler[32]={0};\
|
||||
__neco_c0(__neco_handler,routine,arg);
|
||||
#define neco_cleanup_pop(execute) __neco_c1(execute);}
|
||||
|
||||
/// @}
|
||||
|
||||
@@ -261,13 +265,12 @@ int neco_signal_unwatch(int signo);
|
||||
|
||||
/// @}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// background worker
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// @defgroup Worker Background worker
|
||||
/// Run arbritary code in a background worker thread
|
||||
/// Run arbitrary code in a background worker thread
|
||||
/// @{
|
||||
|
||||
int neco_work(int64_t pin, void(*work)(void *udata), void *udata);
|
||||
@@ -422,9 +425,10 @@ ssize_t neco_stream_buffered_write_size(neco_stream *stream);
|
||||
#include <stdlib.h>
|
||||
|
||||
#define neco_main \
|
||||
static inline __neco_main0(int argc, char *argv[]); \
|
||||
__neco_main(int argc, char *argv[]); \
|
||||
static void _neco_main(int argc, void *argv[]) { \
|
||||
__neco_exit_prog(__neco_main0(*(int*)argv[0], *(char***)argv[1])); \
|
||||
(void)argc; \
|
||||
__neco_exit_prog(__neco_main(*(int*)argv[0], *(char***)argv[1])); \
|
||||
} \
|
||||
int main(int argc, char *argv[]) { \
|
||||
neco_env_setpaniconerror(true); \
|
||||
@@ -433,7 +437,7 @@ int main(int argc, char *argv[]) { \
|
||||
fprintf(stderr, "neco_start: %s (code %d)\n", neco_strerror(ret), ret); \
|
||||
return -1; \
|
||||
}; \
|
||||
int static inline __neco_main0
|
||||
int __neco_main
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private functions, not to be call directly
|
||||
@@ -449,4 +453,8 @@ void __neco_exit_prog(int);
|
||||
#define EAI_SYSTEM 11
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // NECO_H
|
||||
|
||||
+26
-13
@@ -6,6 +6,7 @@ set -e
|
||||
cd $(dirname "${BASH_SOURCE[0]}")
|
||||
|
||||
OK=0
|
||||
FAILS=
|
||||
finish() {
|
||||
rm -fr *.o
|
||||
rm -fr *.out
|
||||
@@ -16,7 +17,7 @@ finish() {
|
||||
rm -fr *.c.worker.js
|
||||
rm -fr *.c.wasm
|
||||
if [[ "$OK" != "1" ]]; then
|
||||
echo "FAIL"
|
||||
echo "FAIL [`echo "$FAILS" | xargs`]"
|
||||
fi
|
||||
}
|
||||
trap finish EXIT
|
||||
@@ -43,8 +44,10 @@ fi
|
||||
if [[ "$CC" == "" ]]; then
|
||||
CC=cc
|
||||
fi
|
||||
UCFLAGS=$CFLAGS
|
||||
CFLAGS=
|
||||
if [[ "$1" != "bench" ]]; then
|
||||
CFLAGS="-O0 -g3 -Wall -Wextra -fstrict-aliasing $CFLAGS"
|
||||
CFLAGS="-O0 -g2 -Wall -Wextra -fstrict-aliasing $CFLAGS"
|
||||
CCVERSHEAD="$($CC --version | head -n 1)"
|
||||
if [[ "$CCVERSHEAD" == "" ]]; then
|
||||
exit 1
|
||||
@@ -52,8 +55,10 @@ if [[ "$1" != "bench" ]]; then
|
||||
|
||||
if [[ "$CCVERSHEAD" == *"clang"* ]]; then
|
||||
CLANGVERS="$(echo "$CCVERSHEAD" | awk '{print $4}' | awk -F'[ .]+' '{print $1}')"
|
||||
INSTALLDIR="$($CC --version | grep InstalledDir)"
|
||||
INSTALLDIR="${INSTALLDIR#* }"
|
||||
fi
|
||||
|
||||
|
||||
if [[ "$CC" == *"zig"* ]]; then
|
||||
# echo Zig does not support asans
|
||||
NOSANS=1
|
||||
@@ -109,14 +114,15 @@ elif [[ "`uname`" == *"_NT-"* ]]; then
|
||||
fi
|
||||
|
||||
if [[ "$CC" == *"zig"* ]]; then
|
||||
# Without -O3, 'zig cc' has quirks issues on Mac OS.
|
||||
CFLAGS="$CFLAGS -O3"
|
||||
# Stack unwinding is not supported yet
|
||||
# https://github.com/ziglang/zig/issues/9046
|
||||
CFLAGS="$CFLAGS -DLLCO_NOUNWIND"
|
||||
# Without -O1, 'zig cc' has quirks issues on Mac OS.
|
||||
if [[ "`uname`" == "Darwin" ]]; then
|
||||
CFLAGS="$CFLAGS -O1"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
CFLAGS="$CFLAGS $UCFLAGS"
|
||||
|
||||
CC=${CC:-cc}
|
||||
echo "CC: $CC"
|
||||
@@ -205,6 +211,7 @@ else
|
||||
if [[ "$WITHCOV" == "1" ]]; then
|
||||
export LLVM_PROFILE_FILE="$f.profraw"
|
||||
fi
|
||||
set +e
|
||||
if [[ "$VALGRIND" == "1" ]]; then
|
||||
valgrind --leak-check=yes ./$f.test $@
|
||||
elif [[ "$CC" == "emcc" ]]; then
|
||||
@@ -212,24 +219,30 @@ else
|
||||
else
|
||||
./$f.test $@
|
||||
fi
|
||||
|
||||
if [[ "$?" != "0" ]]; then
|
||||
FAILS="$FAILS$(echo "$f" | cut -f 1 -d '.') "
|
||||
fi
|
||||
set -e
|
||||
done
|
||||
OK=1
|
||||
echo "OK"
|
||||
if [[ "$FAILS" == "" ]]; then
|
||||
OK=1
|
||||
echo "OK"
|
||||
fi
|
||||
|
||||
|
||||
if [[ "$COVREGIONS" == "" ]]; then
|
||||
COVREGIONS="false"
|
||||
fi
|
||||
|
||||
if [[ "$WITHCOV" == "1" ]]; then
|
||||
$LLVM_PROFDATA merge *.profraw -o test.profdata
|
||||
$LLVM_COV report *.test ../neco.c -ignore-filename-regex=.test. \
|
||||
$INSTALLDIR/$LLVM_PROFDATA merge *.profraw -o test.profdata
|
||||
$INSTALLDIR/$LLVM_COV report *.test ../neco.c -ignore-filename-regex=.test. \
|
||||
-j=4 \
|
||||
-show-functions=true \
|
||||
-instr-profile=test.profdata > /tmp/test.cov.sum.txt
|
||||
# echo coverage: $(cat /tmp/test.cov.sum.txt | grep TOTAL | awk '{ print $NF }')
|
||||
echo covered: "$(cat /tmp/test.cov.sum.txt | grep TOTAL | awk '{ print $7; }') (lines)"
|
||||
$LLVM_COV show *.test ../neco.c -ignore-filename-regex=.test. \
|
||||
$INSTALLDIR/$LLVM_COV show *.test ../neco.c -ignore-filename-regex=.test. \
|
||||
-j=4 \
|
||||
-show-regions=true \
|
||||
-show-expansions=$COVREGIONS \
|
||||
|
||||
+3
-3
@@ -96,14 +96,14 @@ void co_sched1(int argc, void *argv[]) {
|
||||
int *i = argv[1];
|
||||
a[(*i)++] = 'B';
|
||||
assert(neco_yield() == NECO_OK);
|
||||
a[(*i)++] = 'D';
|
||||
a[(*i)++] = 'F';
|
||||
}
|
||||
|
||||
void co_sched2(int argc, void *argv[]) {
|
||||
assert(argc == 2);
|
||||
char *a = argv[0];
|
||||
int *i = argv[1];
|
||||
a[(*i)++] = 'E';
|
||||
a[(*i)++] = 'D';
|
||||
assert(neco_yield() == NECO_OK);
|
||||
a[(*i)++] = 'G';
|
||||
}
|
||||
@@ -116,7 +116,7 @@ void co_sched(int argc, void *argv[]) {
|
||||
assert(neco_start(co_sched1, 2, a, i) == NECO_OK);
|
||||
a[(*i)++] = 'C';
|
||||
assert(neco_start(co_sched2, 2, a, i) == NECO_OK);
|
||||
a[(*i)++] = 'F';
|
||||
a[(*i)++] = 'E';
|
||||
assert(neco_yield() == NECO_OK);
|
||||
a[(*i)++] = 'H';
|
||||
}
|
||||
|
||||
@@ -343,30 +343,6 @@ void test_sync_cond_deadline(void) {
|
||||
expect(neco_start(co_sync_cond_deadline, 0), NECO_OK);
|
||||
}
|
||||
|
||||
void co_sync_mutex_rw_order_child2(int argc, void *argv[]) {
|
||||
assert(argc == 2);
|
||||
neco_mutex *mu = argv[0];
|
||||
struct order *order = argv[1];
|
||||
order_add(order, 17);
|
||||
expect(neco_mutex_rdlock(mu), NECO_OK);
|
||||
order_add(order, 23);
|
||||
expect(neco_mutex_unlock(mu), NECO_OK);
|
||||
order_add(order, 26);
|
||||
}
|
||||
|
||||
void co_sync_mutex_rw_order_child3(int argc, void *argv[]) {
|
||||
assert(argc == 2);
|
||||
neco_mutex *mu = argv[0];
|
||||
struct order *order = argv[1];
|
||||
order_add(order, 19);
|
||||
expect(neco_mutex_trylock(mu), NECO_BUSY);
|
||||
order_add(order, 20);
|
||||
expect(neco_mutex_rdlock(mu), NECO_OK);
|
||||
order_add(order, 24);
|
||||
expect(neco_mutex_unlock(mu), NECO_OK);
|
||||
order_add(order, 27);
|
||||
}
|
||||
|
||||
void co_sync_mutex_rw_order_child(int argc, void *argv[]) {
|
||||
assert(argc == 2);
|
||||
neco_mutex *mu = argv[0];
|
||||
@@ -382,12 +358,6 @@ void co_sync_mutex_rw_order_child(int argc, void *argv[]) {
|
||||
order_add(order, 13);
|
||||
expect(neco_yield(), NECO_OK);
|
||||
order_add(order, 14);
|
||||
expect(neco_start(co_sync_mutex_rw_order_child2, 2, mu, order), NECO_OK);
|
||||
order_add(order, 18);
|
||||
expect(neco_start(co_sync_mutex_rw_order_child3, 2, mu, order), NECO_OK);
|
||||
order_add(order, 21);
|
||||
expect(neco_mutex_unlock(mu), NECO_OK);
|
||||
order_add(order, 28);
|
||||
}
|
||||
|
||||
void co_sync_mutex_rw_order(int argc, void *argv[]) {
|
||||
@@ -414,15 +384,6 @@ void co_sync_mutex_rw_order(int argc, void *argv[]) {
|
||||
order_add(&order, 12);
|
||||
expect(neco_mutex_unlock(mu), NECO_OK);
|
||||
order_add(&order, 15);
|
||||
expect(neco_mutex_tryrdlock(mu), NECO_BUSY);
|
||||
order_add(&order, 16);
|
||||
expect(neco_mutex_rdlock(mu), NECO_OK);
|
||||
order_add(&order, 22);
|
||||
expect(neco_mutex_unlock(mu), NECO_OK);
|
||||
order_add(&order, 25);
|
||||
expect(neco_yield(), NECO_OK);
|
||||
order_add(&order, 29);
|
||||
expect(neco_mutex_destroy(mu), NECO_OK);
|
||||
order_check(&order);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ void co_work_runner(int argc, void *argv[]) {
|
||||
|
||||
void co_work(int argc, void *argv[]) {
|
||||
(void)argc; (void)argv;
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
expect(neco_start(co_work_runner, 1, &i), NECO_OK);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user