|
160
|
1 Basics of libuv
|
|
|
2 ===============
|
|
|
3
|
|
|
4 libuv enforces an **asynchronous**, **event-driven** style of programming. Its
|
|
|
5 core job is to provide an event loop and callback based notifications of I/O
|
|
|
6 and other activities. libuv offers core utilities like timers, non-blocking
|
|
|
7 networking support, asynchronous file system access, child processes and more.
|
|
|
8
|
|
|
9 Event loops
|
|
|
10 -----------
|
|
|
11
|
|
|
12 In event-driven programming, an application expresses interest in certain events
|
|
|
13 and respond to them when they occur. The responsibility of gathering events
|
|
|
14 from the operating system or monitoring other sources of events is handled by
|
|
|
15 libuv, and the user can register callbacks to be invoked when an event occurs.
|
|
|
16 The event-loop usually keeps running *forever*. In pseudocode:
|
|
|
17
|
|
|
18 .. code-block:: python
|
|
|
19
|
|
|
20 while there are still events to process:
|
|
|
21 e = get the next event
|
|
|
22 if there is a callback associated with e:
|
|
|
23 call the callback
|
|
|
24
|
|
|
25 Some examples of events are:
|
|
|
26
|
|
|
27 * File is ready for writing
|
|
|
28 * A socket has data ready to be read
|
|
|
29 * A timer has timed out
|
|
|
30
|
|
|
31 This event loop is encapsulated by ``uv_run()`` -- the end-all function when using
|
|
|
32 libuv.
|
|
|
33
|
|
|
34 The most common activity of systems programs is to deal with input and output,
|
|
|
35 rather than a lot of number-crunching. The problem with using conventional
|
|
|
36 input/output functions (``read``, ``fprintf``, etc.) is that they are
|
|
|
37 **blocking**. The actual write to a hard disk or reading from a network, takes
|
|
|
38 a disproportionately long time compared to the speed of the processor. The
|
|
|
39 functions don't return until the task is done, so that your program is doing
|
|
|
40 nothing. For programs which require high performance this is a major roadblock
|
|
|
41 as other activities and other I/O operations are kept waiting.
|
|
|
42
|
|
|
43 One of the standard solutions is to use threads. Each blocking I/O operation is
|
|
|
44 started in a separate thread (or in a thread pool). When the blocking function
|
|
|
45 gets invoked in the thread, the operating system can schedule another thread to run,
|
|
|
46 which actually needs the CPU.
|
|
|
47
|
|
|
48 The approach followed by libuv uses another style, which is the **asynchronous,
|
|
|
49 non-blocking** style. Most modern operating systems provide event notification
|
|
|
50 subsystems. For example, a normal ``read`` call on a socket would block until
|
|
|
51 the sender actually sent something. Instead, the application can request the
|
|
|
52 operating system to watch the socket and put an event notification in the
|
|
|
53 queue. The application can inspect the events at its convenience (perhaps doing
|
|
|
54 some number crunching before to use the processor to the maximum) and grab the
|
|
|
55 data. It is **asynchronous** because the application expressed interest at one
|
|
|
56 point, then used the data at another point (in time and space). It is
|
|
|
57 **non-blocking** because the application process was free to do other tasks.
|
|
|
58 This fits in well with libuv's event-loop approach, since the operating system
|
|
|
59 events can be treated as just another libuv event. The non-blocking ensures
|
|
|
60 that other events can continue to be handled as fast as they come in [#]_.
|
|
|
61
|
|
|
62 .. NOTE::
|
|
|
63
|
|
|
64 How the I/O is run in the background is not of our concern, but due to the
|
|
|
65 way our computer hardware works, with the thread as the basic unit of the
|
|
|
66 processor, libuv and OSes will usually run background/worker threads and/or
|
|
|
67 polling to perform tasks in a non-blocking manner.
|
|
|
68
|
|
|
69 Bert Belder, one of the libuv core developers has a small video explaining the
|
|
|
70 architecture of libuv and its background. If you have no prior experience with
|
|
|
71 either libuv or libev, it is a quick, useful watch.
|
|
|
72
|
|
|
73 libuv's event loop is explained in more detail in the `documentation
|
|
|
74 <https://docs.libuv.org/en/v1.x/design.html#the-i-o-loop>`_.
|
|
|
75
|
|
|
76 .. raw:: html
|
|
|
77
|
|
|
78 <iframe width="560" height="315"
|
|
|
79 src="https://www.youtube-nocookie.com/embed/nGn60vDSxQ4" frameborder="0"
|
|
|
80 allowfullscreen></iframe>
|
|
|
81
|
|
|
82 Hello World
|
|
|
83 -----------
|
|
|
84
|
|
|
85 With the basics out of the way, let's write our first libuv program. It does
|
|
|
86 nothing, except start a loop which will exit immediately.
|
|
|
87
|
|
|
88 .. rubric:: helloworld/main.c
|
|
|
89 .. literalinclude:: ../../code/helloworld/main.c
|
|
|
90 :language: c
|
|
|
91 :linenos:
|
|
|
92
|
|
|
93 This program quits immediately because it has no events to process. A libuv
|
|
|
94 event loop has to be told to watch out for events using the various API
|
|
|
95 functions.
|
|
|
96
|
|
|
97 Starting with libuv v1.0, users should allocate the memory for the loops before
|
|
|
98 initializing it with ``uv_loop_init(uv_loop_t *)``. This allows you to plug in
|
|
|
99 custom memory management. Remember to de-initialize the loop using
|
|
|
100 ``uv_loop_close(uv_loop_t *)`` and then delete the storage. The examples never
|
|
|
101 close loops since the program quits after the loop ends and the system will
|
|
|
102 reclaim memory. Production grade projects, especially long running systems
|
|
|
103 programs, should take care to release correctly.
|
|
|
104
|
|
|
105 Default loop
|
|
|
106 ++++++++++++
|
|
|
107
|
|
|
108 A default loop is provided by libuv and can be accessed using
|
|
|
109 ``uv_default_loop()``. You should use this loop if you only want a single
|
|
|
110 loop.
|
|
|
111
|
|
|
112 .. rubric:: default-loop/main.c
|
|
|
113 .. literalinclude:: ../../code/default-loop/main.c
|
|
|
114 :language: c
|
|
|
115 :linenos:
|
|
|
116
|
|
|
117 .. note::
|
|
|
118
|
|
|
119 node.js uses the default loop as its main loop. If you are writing bindings
|
|
|
120 you should be aware of this.
|
|
|
121
|
|
|
122 .. _libuv-error-handling:
|
|
|
123
|
|
|
124 Error handling
|
|
|
125 --------------
|
|
|
126
|
|
|
127 Initialization functions or synchronous functions which may fail return a negative number on error. Async functions that may fail will pass a status parameter to their callbacks. The error messages are defined as ``UV_E*`` `constants`_.
|
|
|
128
|
|
|
129 .. _constants: https://docs.libuv.org/en/v1.x/errors.html#error-constants
|
|
|
130
|
|
|
131 You can use the ``uv_strerror(int)`` and ``uv_err_name(int)`` functions
|
|
|
132 to get a ``const char *`` describing the error or the error name respectively.
|
|
|
133
|
|
|
134 I/O read callbacks (such as for files and sockets) are passed a parameter ``nread``. If ``nread`` is less than 0, there was an error (UV_EOF is the end of file error, which you may want to handle differently).
|
|
|
135
|
|
|
136 Handles and Requests
|
|
|
137 --------------------
|
|
|
138
|
|
|
139 libuv works by the user expressing interest in particular events. This is
|
|
|
140 usually done by creating a **handle** to an I/O device, timer or process.
|
|
|
141 Handles are opaque structs named as ``uv_TYPE_t`` where type signifies what the
|
|
|
142 handle is used for.
|
|
|
143
|
|
|
144 .. rubric:: libuv watchers
|
|
|
145 .. code-block:: c
|
|
|
146
|
|
|
147 /* Handle types. */
|
|
|
148 typedef struct uv_loop_s uv_loop_t;
|
|
|
149 typedef struct uv_handle_s uv_handle_t;
|
|
|
150 typedef struct uv_dir_s uv_dir_t;
|
|
|
151 typedef struct uv_stream_s uv_stream_t;
|
|
|
152 typedef struct uv_tcp_s uv_tcp_t;
|
|
|
153 typedef struct uv_udp_s uv_udp_t;
|
|
|
154 typedef struct uv_pipe_s uv_pipe_t;
|
|
|
155 typedef struct uv_tty_s uv_tty_t;
|
|
|
156 typedef struct uv_poll_s uv_poll_t;
|
|
|
157 typedef struct uv_timer_s uv_timer_t;
|
|
|
158 typedef struct uv_prepare_s uv_prepare_t;
|
|
|
159 typedef struct uv_check_s uv_check_t;
|
|
|
160 typedef struct uv_idle_s uv_idle_t;
|
|
|
161 typedef struct uv_async_s uv_async_t;
|
|
|
162 typedef struct uv_process_s uv_process_t;
|
|
|
163 typedef struct uv_fs_event_s uv_fs_event_t;
|
|
|
164 typedef struct uv_fs_poll_s uv_fs_poll_t;
|
|
|
165 typedef struct uv_signal_s uv_signal_t;
|
|
|
166
|
|
|
167 /* Request types. */
|
|
|
168 typedef struct uv_req_s uv_req_t;
|
|
|
169 typedef struct uv_getaddrinfo_s uv_getaddrinfo_t;
|
|
|
170 typedef struct uv_getnameinfo_s uv_getnameinfo_t;
|
|
|
171 typedef struct uv_shutdown_s uv_shutdown_t;
|
|
|
172 typedef struct uv_write_s uv_write_t;
|
|
|
173 typedef struct uv_connect_s uv_connect_t;
|
|
|
174 typedef struct uv_udp_send_s uv_udp_send_t;
|
|
|
175 typedef struct uv_fs_s uv_fs_t;
|
|
|
176 typedef struct uv_work_s uv_work_t;
|
|
|
177 typedef struct uv_random_s uv_random_t;
|
|
|
178
|
|
|
179 /* None of the above. */
|
|
|
180 typedef struct uv_env_item_s uv_env_item_t;
|
|
|
181 typedef struct uv_cpu_info_s uv_cpu_info_t;
|
|
|
182 typedef struct uv_interface_address_s uv_interface_address_t;
|
|
|
183 typedef struct uv_dirent_s uv_dirent_t;
|
|
|
184 typedef struct uv_passwd_s uv_passwd_t;
|
|
|
185 typedef struct uv_utsname_s uv_utsname_t;
|
|
|
186 typedef struct uv_statfs_s uv_statfs_t;
|
|
|
187
|
|
|
188
|
|
|
189 Handles represent long-lived objects. Async operations on such handles are
|
|
|
190 identified using **requests**. A request is short-lived (usually used across
|
|
|
191 only one callback) and usually indicates one I/O operation on a handle.
|
|
|
192 Requests are used to preserve context between the initiation and the callback
|
|
|
193 of individual actions. For example, an UDP socket is represented by
|
|
|
194 a ``uv_udp_t``, while individual writes to the socket use a ``uv_udp_send_t``
|
|
|
195 structure that is passed to the callback after the write is done.
|
|
|
196
|
|
|
197 Handles are setup by a corresponding::
|
|
|
198
|
|
|
199 uv_TYPE_init(uv_loop_t *, uv_TYPE_t *)
|
|
|
200
|
|
|
201 function.
|
|
|
202
|
|
|
203 Callbacks are functions which are called by libuv whenever an event the watcher
|
|
|
204 is interested in has taken place. Application specific logic will usually be
|
|
|
205 implemented in the callback. For example, an IO watcher's callback will receive
|
|
|
206 the data read from a file, a timer callback will be triggered on timeout and so
|
|
|
207 on.
|
|
|
208
|
|
|
209 Idling
|
|
|
210 ++++++
|
|
|
211
|
|
|
212 Here is an example of using an idle handle. The callback is called once on
|
|
|
213 every turn of the event loop. A use case for idle handles is discussed in
|
|
|
214 :doc:`utilities`. Let us use an idle watcher to look at the watcher life cycle
|
|
|
215 and see how ``uv_run()`` will now block because a watcher is present. The idle
|
|
|
216 watcher is stopped when the count is reached and ``uv_run()`` exits since no
|
|
|
217 event watchers are active.
|
|
|
218
|
|
|
219 .. rubric:: idle-basic/main.c
|
|
|
220 .. literalinclude:: ../../code/idle-basic/main.c
|
|
|
221 :language: c
|
|
|
222 :emphasize-lines: 6,10,14-17
|
|
|
223
|
|
|
224 Storing context
|
|
|
225 +++++++++++++++
|
|
|
226
|
|
|
227 In callback based programming style you'll often want to pass some 'context' --
|
|
|
228 application specific information -- between the call site and the callback. All
|
|
|
229 handles and requests have a ``void* data`` member which you can set to the
|
|
|
230 context and cast back in the callback. This is a common pattern used throughout
|
|
|
231 the C library ecosystem. In addition ``uv_loop_t`` also has a similar data
|
|
|
232 member.
|
|
|
233
|
|
|
234 ----
|
|
|
235
|
|
|
236 .. [#] Depending on the capacity of the hardware of course.
|