From 6f38fb5644faff70851d815b4bc994c62fa45d4b Mon Sep 17 00:00:00 2001 From: RZHuangJeff Date: Wed, 14 Apr 2021 14:26:22 +0800 Subject: [PATCH 1/3] Refactor coroutine macros Since for the original version, programmers may misuse these macros, to avoid such situation, we have modified macros that are used for controlling coroutines and macros that programmers use for declaring coroutine functions and their corresponding contexts. First of all, cr_proto was introduced as the generator of coroutine function declarations, and all coroutines should be specified with this macro. By introducing cr_proto, we have wrapped the argument that refers to the coroutine context into a fixed name variable called ctx, which will be refered by controlling macros such as cr_begin, cr_end, cr_wait and so on. Invoking these macros is valid only in a coroutine function that was generated by cr_proto, otherwise, compiler will report errors. Next, we have modified structure cr that its field local is removed, and macro cr_context is introduced for programmers to declare the coroutine contexts. The name assigned to cr_context should be identical with the one used to generate the coroutine function. After generated a coroutine function and its corresponding context, programmers could launch that coroutine via macro cr_run. Finally, to declare static variables in coroutine, macro cr_local is introduced, which helps to mark wheather a variable is a part of coroutine or just for temporary usage. Abstract using of coroutine macros With in this version, a set of macros are introduced, which aim to provide standard style for someone declare or access the coroutine function and its corresponding context. One should declare a coroutine function with cr_func_def macro, and its corresponding context with cr_context macro, the argument name provides to those macros for the same coroutine should be identical. After declaration, one could run a coroutine by cr_run macro. Wrapping a set of cr_run macros in a loop makes several coroutines execute concurrently. Since the declaration of a coroutine funciton is wrapped by macro cr_func_def, there is no need to provide the context as argument of macros that contorl the coroutine, such as cr_begin, cr_end, cr_wait etc. That argument was hardcoded as __ct in the body of those macros. To pass arguments to a coroutine, one can provide a pointer to that argument as parameter of cr_context_init macro, and to extact them in the coroutine, two macros, cr_arg and cr_arg_member, were introduced. Macro cr_arg will cast __ct->arg to the pointer of corresponding type, while cr_arg_member will return a pointer to the specific member of given structure type that __ct->arg points to. Finally, to define a static variable with in the coroutine, one can define them with cr_local macro, which helps to hightlight wheather a variable is a part of that coroutine or just for temporary usage. Rename macros and variables To make the name of macros and variables indicate purpose of themself more clearly, some of them were renamed. Following lists the modified macros and variables: * Macro cr_func_def is renamed as cr_define, which will avoid confusing and make programmers focus on dealing body of coroutine. * Hardcoded __ct is renamed as ctx which stands for context.\ * The name of structure sock_w_loc_t is changed to conn_data_t, since it aims to hold data used for connection. Make coroutine function be declared in C-Style Macro cr_proto is introduced to replace cr_define, which makes programmers to declare a coroutine function in C-style, that is, more flexible than cr_define. Macro cr_run is modified to make respond to cr_proto. The new version of cr_run accepts variable length argument list. From now, programmers could specify arguments that are going to be passed to coroutines, by listing arguments after name while starting coroutine with macro cr_run. Since the way of passing arguments was changed, the field arg in structure cr is removed and macros that aimed to fetch value from arg are also removed. --- tinync/tinync.c | 117 ++++++++++++++++++++++++++---------------------- 1 file changed, 64 insertions(+), 53 deletions(-) diff --git a/tinync/tinync.c b/tinync/tinync.c index 74ee73b..71220d3 100644 --- a/tinync/tinync.c +++ b/tinync/tinync.c @@ -14,40 +14,50 @@ enum { struct cr { void *label; int status; - void *local; /* private local storage */ }; -#define cr_init() \ +#define cr_context_name(name) __cr_context_##name +#define cr_context(name) struct cr cr_context_name(name) +#define cr_context_init() \ { \ .label = NULL, .status = CR_BLOCKED \ } -#define cr_begin(o) \ - do { \ - if ((o)->status == CR_FINISHED) \ - return; \ - if ((o)->label) \ - goto *(o)->label; \ + +#define cr_func_name(name) __cr_func_##name +#define cr_proto(name, ...) cr_func_name(name)(struct cr * ctx, ##__VA_ARGS__) + +#define cr_run(name, ...) \ + cr_func_name(name)(&cr_context_name(name), ##__VA_ARGS__) + +#define cr_local static + +#define cr_begin() \ + do { \ + if ((ctx)->status == CR_FINISHED) \ + return; \ + if ((ctx)->label) \ + goto *(ctx)->label; \ } while (0) #define cr_label(o, stat) \ do { \ (o)->status = (stat); \ __cr_line(label) : (o)->label = &&__cr_line(label); \ } while (0) -#define cr_end(o) cr_label(o, CR_FINISHED) +#define cr_end() cr_label(ctx, CR_FINISHED) -#define cr_status(o) (o)->status +#define cr_status(name) cr_context_name(name).status -#define cr_wait(o, cond) \ - do { \ - cr_label(o, CR_BLOCKED); \ - if (!(cond)) \ - return; \ +#define cr_wait(cond) \ + do { \ + cr_label(ctx, CR_BLOCKED); \ + if (!(cond)) \ + return; \ } while (0) -#define cr_exit(o, stat) \ - do { \ - cr_label(o, stat); \ - return; \ +#define cr_exit(stat) \ + do { \ + cr_label(ctx, stat); \ + return; \ } while (0) #define cr_queue(T, size) \ @@ -70,9 +80,9 @@ struct cr { (cr_queue_empty(q) ? NULL : &(q)->buf[(q)->r++ % cr_queue_len(q)]) /* Wrap system calls and other functions that return -1 and set errno */ -#define cr_sys(o, call) \ - cr_wait(o, (errno = 0) || !(((call) == -1) && \ - (errno == EAGAIN || errno == EWOULDBLOCK || \ +#define cr_sys(call) \ + cr_wait((errno = 0) || \ + !(((call) == -1) && (errno == EAGAIN || errno == EWOULDBLOCK || \ errno == EINPROGRESS || errno == EINTR))) #include @@ -87,47 +97,47 @@ struct cr { typedef cr_queue(uint8_t, 4096) byte_queue_t; -static void stdin_loop(struct cr *o, byte_queue_t *out) +static void cr_proto(stdin_loop, byte_queue_t *out) { - static uint8_t b; - static int r; - cr_begin(o); + cr_local uint8_t b; + cr_local int r; + cr_begin(); for (;;) { - cr_sys(o, r = read(STDIN_FILENO, &b, 1)); + cr_sys(r = read(STDIN_FILENO, &b, 1)); if (r == 0) { - cr_wait(o, cr_queue_empty(out)); - cr_exit(o, 1); + cr_wait(cr_queue_empty(out)); + cr_exit(1); } - cr_wait(o, !cr_queue_full(out)); + cr_wait(!cr_queue_full(out)); cr_queue_push(out, b); } - cr_end(o); + cr_end(); } -static void socket_write_loop(struct cr *o, int fd, byte_queue_t *in) +static void cr_proto(socket_write_loop, byte_queue_t *in, int fd) { - static uint8_t *b; - cr_begin(o); + cr_local uint8_t *b; + cr_begin(); for (;;) { - cr_wait(o, !cr_queue_empty(in)); + cr_wait(!cr_queue_empty(in)); b = cr_queue_pop(in); - cr_sys(o, send(fd, b, 1, 0)); + cr_sys(send(fd, b, 1, 0)); } - cr_end(o); + cr_end(); } -static void socket_read_loop(struct cr *o, int fd) +static void cr_proto(socket_read_loop, int fd) { - static uint8_t b; - static int r; - cr_begin(o); + cr_local uint8_t b; + cr_local int r; + cr_begin(); for (;;) { - cr_sys(o, r = recv(fd, &b, 1, 0)); + cr_sys(r = recv(fd, &b, 1, 0)); if (r == 0) - cr_exit(o, 1); - cr_sys(o, write(STDOUT_FILENO, &b, 1)); + cr_exit(1); + cr_sys(write(STDOUT_FILENO, &b, 1)); } - cr_end(o); + cr_end(); } static int nonblock(int fd) @@ -176,13 +186,14 @@ int main(int argc, char *argv[]) }; connect(fd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)); - struct cr cr_stdin = cr_init(); - struct cr cr_socket_read = cr_init(); - struct cr cr_socket_write = cr_init(); byte_queue_t queue = cr_queue_init(); - while (cr_status(&cr_stdin) == CR_BLOCKED && - cr_status(&cr_socket_read) == CR_BLOCKED) { + cr_context(stdin_loop) = cr_context_init(); + cr_context(socket_read_loop) = cr_context_init(); + cr_context(socket_write_loop) = cr_context_init(); + + while (cr_status(stdin_loop) == CR_BLOCKED && + cr_status(socket_read_loop) == CR_BLOCKED) { if (cr_queue_empty(&queue)) { fd_set fds; FD_ZERO(&fds); @@ -190,9 +201,9 @@ int main(int argc, char *argv[]) FD_SET(fd, &fds); select(fd + 1, &fds, NULL, NULL, NULL); } - socket_read_loop(&cr_socket_read, fd); - socket_write_loop(&cr_socket_write, fd, &queue); - stdin_loop(&cr_stdin, &queue); + cr_run(socket_read_loop, fd); + cr_run(socket_write_loop, &queue, fd); + cr_run(stdin_loop, &queue); } close(fd); From bcb985fd5a774491957e5623cd126bab34812811 Mon Sep 17 00:00:00 2001 From: RZHuangJeff Date: Tue, 20 Apr 2021 09:05:52 +0800 Subject: [PATCH 2/3] Fix error in Makefile Add the missing suffix .o after one of tinync of target clean. --- tinync/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tinync/Makefile b/tinync/Makefile index bfe7704..605ad50 100644 --- a/tinync/Makefile +++ b/tinync/Makefile @@ -6,6 +6,6 @@ tinync: tinync.o tinync.o : tinync.c clean: - rm -f tinync tinync + rm -f tinync.o tinync .PHONY: all clean From 30f7f8b0dd7940fcc6756105091228cb5b48c42e Mon Sep 17 00:00:00 2001 From: RZHuangJeff Date: Tue, 20 Apr 2021 09:08:16 +0800 Subject: [PATCH 3/3] Add README.md README.md is introduced to help programmers to figure out the usage of coroutine macros. Add README.md README.md is introduced to help programmers to figure out the usage of coroutine macros. Fix typo of README.md Modify README.md Following modifications are performed: * Add hyperlink to coroutine. * Use word "Internal" instead of "Usage" * Change to C-style comment. * Modify the description in Coroutine Context section. Improve README.md --- tinync/README.md | 85 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 tinync/README.md diff --git a/tinync/README.md b/tinync/README.md new file mode 100644 index 0000000..4250342 --- /dev/null +++ b/tinync/README.md @@ -0,0 +1,85 @@ +# Tinync +Tinync is a simplified implementation of [nc](https://en.wikipedia.org/wiki/Netcat), which aims to demonstrate the usage of [coroutine](https://en.wikipedia.org/wiki/Coroutine) macros and their effects. + +## Internals +### Declaring a Coroutine Function +At the very beginning, a coroutine function should be declared first, coroutine function is part of program that will be executed simultaneously with other coroutines. All coroutines should be specified with the macro `cr_proto`, following shows the example: +```cpp +static void cr_proto(coroutine_name, parameter_declarations) +{ + /* coroutine body */ +} +``` + +### Controlling Macros +With in the coroutine, there are several macros for controlling the behavior of coroutine, following are list of them: +* `cr_begin` aims to initiate the context of coroutine when the coroutine is first invoked, or resume previous execution for the rest of invocations. This macro should be placed before other cotrolling macros except `cr_local`. +* `cr_end` ends up a coroutine that will mark the status of coroutine as finished, which could be detected outside to ensure whether a coroutine finisihed its job or not. +* `cr_wait` is used for waiting a condition happened. Once the condition it is waiting for has not happened yet, it will pause the current coroutine and switch to another. +* `cr_exit` not only yields a coroutine but also updates its state with given state. +* `cr_sys` is a wrapper of `cr_wait` which performs waiting on system calls and other functions that return -1 and set `errno`. +* `cr_local` is a marker for programmers to recognize a variable related to coroutine easily. + +In `tinync.c`, we can see the combinations of these macros: +```cpp +static void cr_proto(stdin_loop, byte_queue_t *out) +{ + /* b and r are variables used in coroutine whose + * value will be preserved across pauses. + */ + cr_local uint8_t b; + cr_local int r; + + /* Initiates the context of this coroutine. */ + cr_begin(); + for (;;) { + /* Wait for read system call to become successful. */ + cr_sys(r = read(STDIN_FILENO, &b, 1)); + if (r == 0) { + /* Wait until queue out is flushed. */ + cr_wait(cr_queue_empty(out)); + + /* Exit the coroutine with status update as finished. */ + cr_exit(1); + } + /* Wait until there is place in queue out. */ + cr_wait(!cr_queue_full(out)); + cr_queue_push(out, b); + } + /* End up this coroutine, status will be updated as finished. */ + cr_end(); +} +``` + +### Coroutine Context +Context is an important part for coroutine, which preserves the execution point of a coroutine that could be resumed later. To define a context for a coroutine, use `cr_context` macro and initiates it with macro `cr_context_init`. It is important to **assign an identical name to context and its corresponding function**. With example presented at the beginning, its corresponding context should be specified as follows: +```cpp +cr_context(coroutine_name) = cr_context_init(); +``` + +### Launching and Monitoring +Now, all required preparations are done, programmers may launch a coroutine via `cr_run` macro and monitor it with `cr_status` macro. To execute several coroutines simultaneously, place `cr_run`s that launch coroutines in a loop and keep tracking their status until all of them are finished. Following shows a simple example: +```cpp +while (cr_status(coroutine_1) != CR_FINISHED && + cr_status(coroutine_2) != CR_FINISHED && + cr_status(coroutine_3) != CR_FINISHED) { + cr_run(coroutine_1); + cr_run(coroutine_2); + cr_run(coroutine_3); +} +``` + +## Run the Sample Program +Tinync is a sample program that handles several coroutines to maintain communication with remote while accept user input simultaneously. To compile it, use `make`: +```shell +$ make +``` +This sample requires two terminals, let's say `T1` and `T2`. Before launching `tinync`, start `nc` in `T1` first: +```shell +$ nc -l 127.0.0.1 9000 +``` +Then launch `tinync` in `T2`: +```shell +$ tinync 127.0.0.1 9000 +``` +Now, any words typed in `T1` will be recived and presented in `T2`, and vice versa. \ No newline at end of file