Skip to content

Commit fc3107c

Browse files
committed
Update docs/design pattern
1 parent 7387d33 commit fc3107c

File tree

2 files changed

+426
-0
lines changed

2 files changed

+426
-0
lines changed

README.md

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
## Table of Contents
66

77
* [Introduction](#introduction)
8+
* [Design](#design)
89
* [Synopsis](#synopsis)
910
* [Usage](#usage)
1011
* [Installation](#installation)
@@ -236,10 +237,222 @@ See `branches` for previous setup, `main` is an complete makeover of previous im
236237
Similar approach has been made for ***C++20***, an implementation in [uvco](https://github.com/dermesser/uvco).
237238
The *[tests](https://github.com/dermesser/uvco/tree/master/test)* presented there currently being reimplemented for *C89* here, this project will be considered stable when *completed*. And another approach in [libasync](https://github.com/btrask/libasync) mixing [libco](https://github.com/higan-emu/libco) with **libuv**. Both approaches are **Linux** only.
238239
240+
## Design
241+
242+
The *intergration* pattern for all **libuv** functions taking *callback* is as [queue-work.c](https://github.com/zelang-dev/uv_coroutine/tree/main/examples/queue-work.c) example:
243+
244+
```c
245+
#define USE_CORO
246+
#include "raii.h"
247+
248+
#define FIB_UNTIL 25
249+
250+
long fib_(long t) {
251+
if (t == 0 || t == 1)
252+
return 1;
253+
else
254+
return fib_(t-1) + fib_(t-2);
255+
}
256+
257+
void_t fib(params_t req) {
258+
int n = req->integer;
259+
if (random() % 2)
260+
sleepfor(1);
261+
else
262+
sleepfor(3);
263+
264+
long fib = fib_(n);
265+
fprintf(stderr, "%dth fibonacci is %lu in thrd: #%d\033[0K\n", n, fib, coro_thrd_id());
266+
267+
return casting(fib);
268+
}
269+
270+
void after_fib(int status, rid_t id) {
271+
fprintf(stderr, "Done calculating %dth fibonacci, result: %d\n", status, result_for(id).integer);
272+
}
273+
274+
int main(int argc, char **argv) {
275+
rid_t data[FIB_UNTIL];
276+
int i;
277+
278+
waitgroup_t wg = waitgroup_ex(FIB_UNTIL);
279+
for (i = 0; i < FIB_UNTIL; i++) {
280+
data[i] = go(fib, 1, casting(i));
281+
}
282+
waitresult_t wgr = waitfor(wg);
283+
284+
if ($size(wgr) == FIB_UNTIL)
285+
for (i = 0; i < FIB_UNTIL; i++) {
286+
after_fib(i, data[i]);
287+
}
288+
289+
return 0;
290+
}
291+
```
292+
293+
Every system **thread** has a **run queue** assigned, a ~tempararay~ *FIFO queue*,
294+
it holds **coroutine** *tasks*. This assignment is based on *system cpu cores* available,
295+
and set at startup, a coroutine **thread pool** set before `uv_main` is called.
296+
297+
When a **go/coroutine** is created, it's given a *index* `result id` from *global* **array** like struct,
298+
a `coroutine id`, and `thread id`. Then placed into a *global* **run queue**, a *hashtable*,
299+
the *key* being `result id`, for schedularing. The `thread id` determines which *thread pool*
300+
coroutine gets assigned to.
301+
302+
These three *data structures* are atomically accessable by all threads.
303+
304+
The **main thread** determines and move *coroutines* from *global queue* to each *thread queue*.
305+
306+
Each **thread's** *scheduler* manages it's own *local* **run queue** of *coroutines* by ~thread local storage~.
307+
It takes `coroutine tasks` from it's ~tempararay~ *FIFO queue* to *local storage*.
308+
309+
All **libuv** functions *outlined* is as **example**, but a `waitgroup_ex(1)` of *one*.
310+
Currently, demonstrating `true` *libuv/Coroutine* **multi threading** is *disabled* by how current *tests* and *examples* startup.
311+
312+
If the number of *coroutines created* before first `yield()` encountered does not equal **cpu core** *count* plus *one*.
313+
Then **main thread** will move all *coroutines* to itself, set each *coroutine* `thread id` to itself,
314+
and *mark* whole system feature *disabled*.
315+
239316
## Synopsis
240317

241318
```c
319+
/* Creates an coroutine of given function with arguments,
320+
and add to schedular, same behavior as Go. */
321+
C_API rid_t go(callable_t, u64, ...);
322+
323+
/* Returns results of an completed coroutine, by `result id`, will panic,
324+
if called before `waitfor` returns, `coroutine` still running, or no result
325+
possible function. */
326+
C_API value_t result_for(rid_t);
327+
328+
/* Check status of an `result id` */
329+
C_API bool result_is_ready(rid_t);
330+
331+
/* Explicitly give up the CPU for at least ms milliseconds.
332+
Other tasks continue to run during this time. */
333+
C_API u32 sleepfor(u32 ms);
334+
335+
/* Creates an coroutine of given function with argument,
336+
and immediately execute. */
337+
C_API void launch(func_t, u64, ...);
338+
339+
/* Yield execution to another coroutine and reschedule current. */
340+
C_API void yield(void);
242341

342+
/* Suspends the execution of current `Generator/Coroutine`, and passing ~data~.
343+
WILL PANIC if not an ~Generator~ function called in.
344+
WILL `yield` until ~data~ is retrived using `yield_for`. */
345+
C_API void yielding(void_t);
346+
347+
/* Creates an `Generator/Coroutine` of given function with arguments,
348+
MUST use `yielding` to pass data, and `yield_for` to get data. */
349+
C_API generator_t generator(callable_t, u64, ...);
350+
351+
/* Resume specified ~coroutine/generator~, returning data from `yielding`. */
352+
C_API value_t yield_for(generator_t);
353+
354+
/* Return `generator id` in scope for last `yield_for` execution. */
355+
C_API rid_t yield_id(void);
356+
357+
/* Defer execution `LIFO` of given function with argument,
358+
to when current coroutine exits/returns. */
359+
C_API void defer(func_t, void_t);
360+
361+
/* Same as `defer` but allows recover from an Error condition throw/panic,
362+
you must call `catching` inside function to mark Error condition handled. */
363+
C_API void defer_recover(func_t, void_t);
364+
365+
/* Compare `err` to current error condition of coroutine,
366+
will mark exception handled, if `true`. */
367+
C_API bool catching(string_t);
368+
369+
/* Get current error condition string. */
370+
C_API string_t catch_message(void);
371+
372+
/* Creates/initialize the next series/collection of coroutine's created
373+
to be part of `wait group`, same behavior of Go's waitGroups.
374+
375+
All coroutines here behaves like regular functions, meaning they return values,
376+
and indicate a terminated/finish status.
377+
378+
The initialization ends when `waitfor` is called, as such current coroutine will pause,
379+
and execution will begin and wait for the group of coroutines to finished. */
380+
C_API waitgroup_t waitgroup(void);
381+
C_API waitgroup_t waitgroup_ex(u32 capacity);
382+
383+
/* Pauses current coroutine, and begin execution of coroutines in `wait group` object,
384+
will wait for all to finish.
385+
386+
Returns `vector/array` of `results id`, accessible using `result_for` function. */
387+
C_API waitresult_t waitfor(waitgroup_t);
388+
389+
C_API awaitable_t async(callable_t, u64, ...);
390+
C_API value_t await(awaitable_t);
391+
392+
/* Calls ~fn~ (with ~number of args~ then ~actaul arguments~) in separate thread,
393+
returning without waiting for the execution of ~fn~ to complete.
394+
The value returned by ~fn~ can be accessed
395+
by calling `thrd_get()`. */
396+
C_API future thrd_async(thrd_func_t fn, size_t, ...);
397+
398+
/* Calls ~fn~ (with ~args~ as argument) in separate thread, returning without waiting
399+
for the execution of ~fn~ to complete. The value returned by ~fn~ can be accessed
400+
by calling `thrd_get()`. */
401+
C_API future thrd_launch(thrd_func_t fn, void_t args);
402+
403+
/* Returns the value of `future` ~promise~, a thread's shared object, If not ready, this
404+
function blocks the calling thread and waits until it is ready. */
405+
C_API values_type thrd_get(future);
406+
407+
/* This function blocks the calling thread and waits until `future` is ready,
408+
will execute provided `yield` callback function continuously. */
409+
C_API void thrd_wait(future, wait_func yield);
410+
411+
/* Same as `thrd_wait`, but `yield` execution to another coroutine and reschedule current,
412+
until `thread` ~future~ is ready, completed execution. */
413+
C_API void thrd_until(future);
414+
415+
/* Check status of `future` object state, if `true` indicates thread execution has ended,
416+
any call thereafter to `thrd_get` is guaranteed non-blocking. */
417+
C_API bool thrd_is_done(future);
418+
C_API uintptr_t thrd_self(void);
419+
C_API size_t thrd_cpu_count(void);
420+
421+
/* Return/create an arbitrary `vector/array` set of `values`,
422+
only available within `thread/future` */
423+
C_API vectors_t thrd_data(size_t, ...);
424+
425+
/* Return/create an single `vector/array` ~value~,
426+
only available within `thread/future` */
427+
#define $(val) thrd_data(1, (val))
428+
429+
/* Return/create an pair `vector/array` ~values~,
430+
only available within `thread/future` */
431+
#define $$(val1, val2) thrd_data(2, (val1), (val2))
432+
433+
/* Request/return raw memory of given `size`,
434+
using smart memory pointer's lifetime scope handle.
435+
DO NOT `free`, will be freed with given `func`,
436+
when scope smart pointer panics/returns/exits. */
437+
C_API void_t malloc_full(memory_t *scope, size_t size, func_t func);
438+
439+
/* Request/return raw memory of given `size`,
440+
using smart memory pointer's lifetime scope handle.
441+
DO NOT `free`, will be freed with given `func`,
442+
when scope smart pointer panics/returns/exits. */
443+
C_API void_t calloc_full(memory_t *scope, int count, size_t size, func_t func);
444+
445+
/* Returns protected raw memory pointer of given `size`,
446+
DO NOT FREE, will `throw/panic` if memory request fails.
447+
This uses current `context` smart pointer, being in `guard` blocks,
448+
inside `thread/future`, or active `coroutine` call. */
449+
C_API void_t malloc_local(size_t size);
450+
451+
/* Returns protected raw memory pointer of given `size`,
452+
DO NOT FREE, will `throw/panic` if memory request fails.
453+
This uses current `context` smart pointer, being in `guard` blocks,
454+
inside `thread/future`, or active `coroutine` call. */
455+
C_API void_t calloc_local(int count, size_t size);
243456
```
244457
245458
## Usage

0 commit comments

Comments
 (0)