Skip to content

Commit 9a042a6

Browse files
authored
Merge pull request #1 from RZHuangJeff/tinync-imprv
Refactor the convention for coroutines
2 parents 39ee302 + 30f7f8b commit 9a042a6

File tree

3 files changed

+150
-54
lines changed

3 files changed

+150
-54
lines changed

tinync/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ tinync: tinync.o
66
tinync.o : tinync.c
77

88
clean:
9-
rm -f tinync tinync
9+
rm -f tinync.o tinync
1010

1111
.PHONY: all clean

tinync/README.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Tinync
2+
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.
3+
4+
## Internals
5+
### Declaring a Coroutine Function
6+
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:
7+
```cpp
8+
static void cr_proto(coroutine_name, parameter_declarations)
9+
{
10+
/* coroutine body */
11+
}
12+
```
13+
14+
### Controlling Macros
15+
With in the coroutine, there are several macros for controlling the behavior of coroutine, following are list of them:
16+
* `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`.
17+
* `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.
18+
* `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.
19+
* `cr_exit` not only yields a coroutine but also updates its state with given state.
20+
* `cr_sys` is a wrapper of `cr_wait` which performs waiting on system calls and other functions that return -1 and set `errno`.
21+
* `cr_local` is a marker for programmers to recognize a variable related to coroutine easily.
22+
23+
In `tinync.c`, we can see the combinations of these macros:
24+
```cpp
25+
static void cr_proto(stdin_loop, byte_queue_t *out)
26+
{
27+
/* b and r are variables used in coroutine whose
28+
* value will be preserved across pauses.
29+
*/
30+
cr_local uint8_t b;
31+
cr_local int r;
32+
33+
/* Initiates the context of this coroutine. */
34+
cr_begin();
35+
for (;;) {
36+
/* Wait for read system call to become successful. */
37+
cr_sys(r = read(STDIN_FILENO, &b, 1));
38+
if (r == 0) {
39+
/* Wait until queue out is flushed. */
40+
cr_wait(cr_queue_empty(out));
41+
42+
/* Exit the coroutine with status update as finished. */
43+
cr_exit(1);
44+
}
45+
/* Wait until there is place in queue out. */
46+
cr_wait(!cr_queue_full(out));
47+
cr_queue_push(out, b);
48+
}
49+
/* End up this coroutine, status will be updated as finished. */
50+
cr_end();
51+
}
52+
```
53+
54+
### Coroutine Context
55+
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:
56+
```cpp
57+
cr_context(coroutine_name) = cr_context_init();
58+
```
59+
60+
### Launching and Monitoring
61+
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:
62+
```cpp
63+
while (cr_status(coroutine_1) != CR_FINISHED &&
64+
cr_status(coroutine_2) != CR_FINISHED &&
65+
cr_status(coroutine_3) != CR_FINISHED) {
66+
cr_run(coroutine_1);
67+
cr_run(coroutine_2);
68+
cr_run(coroutine_3);
69+
}
70+
```
71+
72+
## Run the Sample Program
73+
Tinync is a sample program that handles several coroutines to maintain communication with remote while accept user input simultaneously. To compile it, use `make`:
74+
```shell
75+
$ make
76+
```
77+
This sample requires two terminals, let's say `T1` and `T2`. Before launching `tinync`, start `nc` in `T1` first:
78+
```shell
79+
$ nc -l 127.0.0.1 9000
80+
```
81+
Then launch `tinync` in `T2`:
82+
```shell
83+
$ tinync 127.0.0.1 9000
84+
```
85+
Now, any words typed in `T1` will be recived and presented in `T2`, and vice versa.

tinync/tinync.c

Lines changed: 64 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -14,40 +14,50 @@ enum {
1414
struct cr {
1515
void *label;
1616
int status;
17-
void *local; /* private local storage */
1817
};
1918

20-
#define cr_init() \
19+
#define cr_context_name(name) __cr_context_##name
20+
#define cr_context(name) struct cr cr_context_name(name)
21+
#define cr_context_init() \
2122
{ \
2223
.label = NULL, .status = CR_BLOCKED \
2324
}
24-
#define cr_begin(o) \
25-
do { \
26-
if ((o)->status == CR_FINISHED) \
27-
return; \
28-
if ((o)->label) \
29-
goto *(o)->label; \
25+
26+
#define cr_func_name(name) __cr_func_##name
27+
#define cr_proto(name, ...) cr_func_name(name)(struct cr * ctx, ##__VA_ARGS__)
28+
29+
#define cr_run(name, ...) \
30+
cr_func_name(name)(&cr_context_name(name), ##__VA_ARGS__)
31+
32+
#define cr_local static
33+
34+
#define cr_begin() \
35+
do { \
36+
if ((ctx)->status == CR_FINISHED) \
37+
return; \
38+
if ((ctx)->label) \
39+
goto *(ctx)->label; \
3040
} while (0)
3141
#define cr_label(o, stat) \
3242
do { \
3343
(o)->status = (stat); \
3444
__cr_line(label) : (o)->label = &&__cr_line(label); \
3545
} while (0)
36-
#define cr_end(o) cr_label(o, CR_FINISHED)
46+
#define cr_end() cr_label(ctx, CR_FINISHED)
3747

38-
#define cr_status(o) (o)->status
48+
#define cr_status(name) cr_context_name(name).status
3949

40-
#define cr_wait(o, cond) \
41-
do { \
42-
cr_label(o, CR_BLOCKED); \
43-
if (!(cond)) \
44-
return; \
50+
#define cr_wait(cond) \
51+
do { \
52+
cr_label(ctx, CR_BLOCKED); \
53+
if (!(cond)) \
54+
return; \
4555
} while (0)
4656

47-
#define cr_exit(o, stat) \
48-
do { \
49-
cr_label(o, stat); \
50-
return; \
57+
#define cr_exit(stat) \
58+
do { \
59+
cr_label(ctx, stat); \
60+
return; \
5161
} while (0)
5262

5363
#define cr_queue(T, size) \
@@ -70,9 +80,9 @@ struct cr {
7080
(cr_queue_empty(q) ? NULL : &(q)->buf[(q)->r++ % cr_queue_len(q)])
7181

7282
/* Wrap system calls and other functions that return -1 and set errno */
73-
#define cr_sys(o, call) \
74-
cr_wait(o, (errno = 0) || !(((call) == -1) && \
75-
(errno == EAGAIN || errno == EWOULDBLOCK || \
83+
#define cr_sys(call) \
84+
cr_wait((errno = 0) || \
85+
!(((call) == -1) && (errno == EAGAIN || errno == EWOULDBLOCK || \
7686
errno == EINPROGRESS || errno == EINTR)))
7787

7888
#include <arpa/inet.h>
@@ -87,47 +97,47 @@ struct cr {
8797

8898
typedef cr_queue(uint8_t, 4096) byte_queue_t;
8999

90-
static void stdin_loop(struct cr *o, byte_queue_t *out)
100+
static void cr_proto(stdin_loop, byte_queue_t *out)
91101
{
92-
static uint8_t b;
93-
static int r;
94-
cr_begin(o);
102+
cr_local uint8_t b;
103+
cr_local int r;
104+
cr_begin();
95105
for (;;) {
96-
cr_sys(o, r = read(STDIN_FILENO, &b, 1));
106+
cr_sys(r = read(STDIN_FILENO, &b, 1));
97107
if (r == 0) {
98-
cr_wait(o, cr_queue_empty(out));
99-
cr_exit(o, 1);
108+
cr_wait(cr_queue_empty(out));
109+
cr_exit(1);
100110
}
101-
cr_wait(o, !cr_queue_full(out));
111+
cr_wait(!cr_queue_full(out));
102112
cr_queue_push(out, b);
103113
}
104-
cr_end(o);
114+
cr_end();
105115
}
106116

107-
static void socket_write_loop(struct cr *o, int fd, byte_queue_t *in)
117+
static void cr_proto(socket_write_loop, byte_queue_t *in, int fd)
108118
{
109-
static uint8_t *b;
110-
cr_begin(o);
119+
cr_local uint8_t *b;
120+
cr_begin();
111121
for (;;) {
112-
cr_wait(o, !cr_queue_empty(in));
122+
cr_wait(!cr_queue_empty(in));
113123
b = cr_queue_pop(in);
114-
cr_sys(o, send(fd, b, 1, 0));
124+
cr_sys(send(fd, b, 1, 0));
115125
}
116-
cr_end(o);
126+
cr_end();
117127
}
118128

119-
static void socket_read_loop(struct cr *o, int fd)
129+
static void cr_proto(socket_read_loop, int fd)
120130
{
121-
static uint8_t b;
122-
static int r;
123-
cr_begin(o);
131+
cr_local uint8_t b;
132+
cr_local int r;
133+
cr_begin();
124134
for (;;) {
125-
cr_sys(o, r = recv(fd, &b, 1, 0));
135+
cr_sys(r = recv(fd, &b, 1, 0));
126136
if (r == 0)
127-
cr_exit(o, 1);
128-
cr_sys(o, write(STDOUT_FILENO, &b, 1));
137+
cr_exit(1);
138+
cr_sys(write(STDOUT_FILENO, &b, 1));
129139
}
130-
cr_end(o);
140+
cr_end();
131141
}
132142

133143
static int nonblock(int fd)
@@ -176,23 +186,24 @@ int main(int argc, char *argv[])
176186
};
177187
connect(fd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in));
178188

179-
struct cr cr_stdin = cr_init();
180-
struct cr cr_socket_read = cr_init();
181-
struct cr cr_socket_write = cr_init();
182189
byte_queue_t queue = cr_queue_init();
183190

184-
while (cr_status(&cr_stdin) == CR_BLOCKED &&
185-
cr_status(&cr_socket_read) == CR_BLOCKED) {
191+
cr_context(stdin_loop) = cr_context_init();
192+
cr_context(socket_read_loop) = cr_context_init();
193+
cr_context(socket_write_loop) = cr_context_init();
194+
195+
while (cr_status(stdin_loop) == CR_BLOCKED &&
196+
cr_status(socket_read_loop) == CR_BLOCKED) {
186197
if (cr_queue_empty(&queue)) {
187198
fd_set fds;
188199
FD_ZERO(&fds);
189200
FD_SET(STDIN_FILENO, &fds);
190201
FD_SET(fd, &fds);
191202
select(fd + 1, &fds, NULL, NULL, NULL);
192203
}
193-
socket_read_loop(&cr_socket_read, fd);
194-
socket_write_loop(&cr_socket_write, fd, &queue);
195-
stdin_loop(&cr_stdin, &queue);
204+
cr_run(socket_read_loop, fd);
205+
cr_run(socket_write_loop, &queue, fd);
206+
cr_run(stdin_loop, &queue);
196207
}
197208

198209
close(fd);

0 commit comments

Comments
 (0)