A simple program that copies its stdin
to stdout
written in pure C using libuv.
For the sake of simplicity it is assumed that both streams can be handled with as pipes.
There are some points that should be mentioned:
- [1] The structure describing a write request should be allocated on the heap.
- See also
- The rationale described on stackoverflow.com: "libuv + C++ segfaults".
- [2] The allocated buffers should be somehow tracked in the program to be freed up to prevent the memory leak or to get the result of a request. The libuv suggested techniques are: using manual packaging/embedding the request and the buffer description structures into some sort of operational context enclosing structure, or using
uv_req_t.data
pointer member to the user-defined arbitrary data. - See also
- Discussions at github.com/joyent/libuv/issues:
- [3] Be sure that things stay valid until some conditions in the future to perform asynchronous operations.
- See also
- Discussion at github.com/joyent/libuv/issues: "Lifetime of buffers on uv_write #344".
- [4] Be prepared to run into high memory consumption for libuv I/O queues in some cases.
- See also
- Discussion on libuv mailing list at groups.google.com/group/libuv: "memory issue with a simple libuv program".
example/cpio-uv.c
8 sighandler_t sigpipe_handler =
signal(SIGPIPE, SIG_IGN);
12 #define PRINT_UV_ERR(code, printf_args...) do {\ 14 fprintf(stderr, "" printf_args);\ 15 fprintf(stderr, ": %s (%i): %s\n", uv_err_name(code), (int)(code), uv_strerror(code));\ 25 const size_t BUFFER_SIZE = 8192;
26 const size_t WRITE_QUEUE_SIZE_UPPER_LIMIT = 128*BUFFER_SIZE,
27 WRITE_QUEUE_SIZE_LOWER_LIMIT = 16*BUFFER_SIZE;
28 enum { RD_UNKNOWN, RD_STOP, RD_PAUSE, RD_START } rdcmd_state = RD_UNKNOWN;
30 int wr_err_reported = 0;
34 void alloc_cb(uv_handle_t*,
size_t, uv_buf_t*);
35 void read_cb(uv_stream_t*, ssize_t,
const uv_buf_t*);
36 void write_cb(uv_write_t*,
int);
39 int main(
int _argc,
char *_argv[])
43 uv_loop_t *loop = uv_default_loop();
45 uv_pipe_init(loop, &in, 0);
46 ret = uv_pipe_open(&in, fileno(stdin));
49 PRINT_UV_ERR(ret,
"stdin open");
53 uv_pipe_init(loop, &out, 0);
54 ret = uv_pipe_open(&out, fileno(stdout));
57 PRINT_UV_ERR(ret,
"stdout open");
61 rdcmd_state = RD_START;
62 ret = uv_read_start((uv_stream_t*)&in, alloc_cb, read_cb);
65 PRINT_UV_ERR(ret,
"read initiation");
69 return uv_run(loop, UV_RUN_DEFAULT);
74 void alloc_cb(uv_handle_t *_handle,
size_t _suggested_size, uv_buf_t *_buf)
77 *_buf = uv_buf_init((
char*)
malloc(BUFFER_SIZE), BUFFER_SIZE);
81 void read_cb(uv_stream_t *_stream, ssize_t _nread,
const uv_buf_t *_buf)
85 if (_nread != UV_EOF) PRINT_UV_ERR(_nread,
"read");
86 uv_read_stop(_stream);
91 uv_buf_t buf = uv_buf_init(_buf->base, _nread);
94 uv_write_t *wr = (uv_write_t*)
malloc(
sizeof(uv_write_t));
97 wr->data = _buf->base;
100 int ret = uv_write(wr, (uv_stream_t*)&out, &buf, 1, write_cb);
105 PRINT_UV_ERR(ret,
"write initiation");
106 rdcmd_state = RD_STOP;
107 uv_read_stop((uv_stream_t*)&in);
112 if (rdcmd_state == RD_START && out.write_queue_size >= WRITE_QUEUE_SIZE_UPPER_LIMIT)
114 rdcmd_state = RD_PAUSE;
115 uv_read_stop((uv_stream_t*)&in);
121 void write_cb(uv_write_t *_wr,
int _status)
125 if (!wr_err_reported)
128 PRINT_UV_ERR(_status,
"write");
132 rdcmd_state = RD_STOP;
133 uv_read_stop((uv_stream_t*)&in);
138 if (rdcmd_state == RD_PAUSE && out.write_queue_size <= WRITE_QUEUE_SIZE_LOWER_LIMIT)
140 rdcmd_state = RD_START;
141 uv_read_start((uv_stream_t*)&in, alloc_cb, read_cb);
The following is the above program being rewritten using uvcc. All the considered points are taken into account by design of uvcc.
example/cpio-uvcc.cpp
7 sighandler_t sigpipe_handler =
signal(SIGPIPE, SIG_IGN);
11 #define PRINT_UV_ERR(code, printf_args...) do {\ 13 fprintf(stderr, "" printf_args);\ 14 fprintf(stderr, ": %s (%i): %s\n", ::uv_err_name(code), (int)(code), ::uv_strerror(code));\ 23 constexpr
std::size_t WRITE_QUEUE_SIZE_UPPER_LIMIT = 128*BUFFER_SIZE,
24 WRITE_QUEUE_SIZE_LOWER_LIMIT = 16*BUFFER_SIZE;
26 bool wr_err_reported =
false;
33 int main(
int _argc,
char *_argv[])
37 PRINT_UV_ERR(in.uv_status(),
"stdin open");
38 return in.uv_status();
42 PRINT_UV_ERR(out.uv_status(),
"stdout open");
43 return out.uv_status();
52 PRINT_UV_ERR(in.uv_status(),
"read initiation");
53 return in.uv_status();
65 if (_nread != UV_EOF) PRINT_UV_ERR(_nread,
"read");
70 _buffer.
len() = _nread;
73 wr.on_request() = write_cb;
78 PRINT_UV_ERR(wr.
uv_status(),
"write initiation");
82 in.read_pause(out.write_queue_size() >= WRITE_QUEUE_SIZE_UPPER_LIMIT);
94 wr_err_reported =
true;
100 in.read_resume(out.write_queue_size() <= WRITE_QUEUE_SIZE_LOWER_LIMIT);
int run(stream &_stream, const buffer &_buf)
Run the request.
decltype(uv_t::len) & len(const std::size_t _i=0) const noexcept
The .len field of the _i-th buffer structure.
int read_stop() const
Stop reading data from the I/O endpoint.
The base class for the libuv handles.
static loop & Default() noexcept
Returns the initialized loop that can be used as a global default loop throughout the program...
int run(::uv_run_mode _mode)
Go into a loop and process events and their callbacks with the current thread.
int uv_status() const noexcept
The status value returned by the last executed libuv API function on this request.
Stream write request type.
The base class for handles representing I/O endpoints: a file, TCP/UDP socket, pipe, TTY.
Encapsulates uv_buf_t data type and provides uv_buf_t[] functionality.
Here is a performance comparison between the two variants:
[mike@u250 /mnt/sda3/wroot/libuv/uvcc/uvcc.git]
$ cat /dev/zero | build/example/cpio-uv | dd of=/dev/null iflag=fullblock bs=1M count=10000
write: EPIPE (-32): broken pipe
10000+0 records in
10000+0 records out
10485760000 bytes (10 GB) copied, 38.5126 s, 272 MB/s
[mike@u250 /mnt/sda3/wroot/libuv/uvcc/uvcc.git]
$ cat /dev/zero | build/example/cpio-uv | dd of=/dev/null iflag=fullblock bs=1M count=10000
write: EPIPE (-32): broken pipe
10000+0 records in
10000+0 records out
10485760000 bytes (10 GB) copied, 38.4723 s, 273 MB/s
[mike@u250 /mnt/sda3/wroot/libuv/uvcc/uvcc.git]
$ cat /dev/zero | build/example/cpio-uvcc | dd of=/dev/null iflag=fullblock bs=1M count=10000
write: EPIPE (-32): broken pipe
10000+0 records in
10000+0 records out
10485760000 bytes (10 GB) copied, 38.7677 s, 270 MB/s
[mike@u250 /mnt/sda3/wroot/libuv/uvcc/uvcc.git]
$ cat /dev/zero | build/example/cpio-uvcc | dd of=/dev/null iflag=fullblock bs=1M count=10000
write: EPIPE (-32): broken pipe
10000+0 records in
10000+0 records out
10485760000 bytes (10 GB) copied, 38.9242 s, 269 MB/s
Obviously there is an impact of reference counting operations and other C++ related overheads that slightly reduce the performance.