1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
|
Basics
======
libuv offers an **asynchronous**, **event-driven** style of programming. Its
core job is to provide an event loop and callback-based notifications of I/O
and other activities. libuv offers core utilities like timers, non-blocking
networking support, asynchronous file system access, child processes, and more.
Event loops
-----------
In event-driven programming, an application expresses interest in certain events
and responds to them when they occur. The responsibility of gathering events
from the operating system or monitoring other sources of events is handled by
libuv, and the user can register callbacks to be invoked when an event occurs.
The event loop usually keeps running *forever*. In pseudocode:
.. code-block:: ocaml
while there are still live objects that could generate events do
let e = get the next event in
if there is a callback waiting on e then
call the callback
done
Some examples of events are:
- A file is ready for writing
- A socket has data ready to be read
- A timer has timed out
This event loop is inside :api:`Luv.Loop.run <Loop/index.html#val-run>`, which
binds to ``uv_run``, the main function of libuv.
The most common activity of systems programs is to deal with input and output,
rather than a lot of number-crunching. The problem with using conventional
input/output functions (``read``, ``fprintf``, etc.) is that they are
**blocking**. The actual write to a hard disk, or reading from a network, takes
a disproportionately long time compared to the speed of the processor. The
functions don't return until the task is done, so that your program is blocked
doing nothing until then. For programs which require high performance this is a
major obstacle, as other activities and other I/O operations are kept waiting.
One of the standard solutions is to use threads. Each blocking I/O operation is
started in a separate thread, or in a thread pool. When the blocking function
gets invoked in the thread, the processor can schedule another thread to run,
which actually needs the CPU.
The approach followed by libuv uses another style, which is the **asynchronous,
non-blocking** style. Most modern operating systems provide event notification
subsystems. For example, a normal ``read`` call on a socket would block until
the sender actually sent something. Instead, the application can request the
operating system to watch the socket and put an event notification into a queue.
The application can inspect the events at its convenience and grab the data,
perhaps doing some number crunching in the meantime to use the processor to the
maximum. It is **asynchronous** because the application expressed interest at
one point, then used the data at another point in its execution sequence. It is
**non-blocking** because the application process was free to do other tasks. in
between. This fits in well with libuv's event-loop approach, since the operating
system's events can be treated as libuv events. The non-blocking ensures that
other events can continue to be handled as fast as they come in (and the
hardware allows).
Hello, world!
-------------
With the basics out of the way, let's write our first libuv program. It does
nothing, except start a loop which will exit immediately.
.. rubric:: :example:`hello_world.ml`
.. literalinclude:: ../example/hello_world.ml
:language: ocaml
:linenos:
This program exits immediately because it has no events to process. A libuv
event loop has to be told to watch out for events using the various libuv API
functions.
Here's a slightly more interesting program, which waits for one second, prints
“Hello, world!” and then exits:
.. rubric:: :example:`delay.ml`
.. literalinclude:: ../example/delay.ml
:language: ocaml
:linenos:
Console output
--------------
The first two examples used OCaml's own ``print_endline`` for console output. In
a fully asynchronous program, you may want to use libuv to write to the console
(or STDOUT) instead. Luv offers at least three ways of doing this.
Using the pre-opened file :api:`Luv.File.stdout <File/index.html#val-stdout>`:
.. rubric:: :example:`print_using_file.ml`
.. literalinclude:: ../example/print_using_file.ml
:language: ocaml
:linenos:
Wrapping :api:`Luv.File.stdout <File/index.html#val-stdout>` in a pipe with
:api:`Luv.Pipe.open_ <Pipe/index.html#val-open_>`:
.. rubric:: :example:`print_using_pipe.ml`
.. literalinclude:: ../example/print_using_pipe.ml
:language: ocaml
:linenos:
Wrapping :api:`Luv.File.stdout <File/index.html#val-stdout>` in a TTY handle
with :api:`Luv.TTY.init <TTY/index.html#val-init>`:
.. rubric:: :example:`print_using_tty.ml`
.. literalinclude:: ../example/print_using_tty.ml
:language: ocaml
:linenos:
As you can see, all of these are too low-level and verbose for ordinary use.
They would need to be wrapped in a higher-level library. The examples will
continue to use ``print_endline``, which is fine for most purposes.
.. _libuv-error-handling:
Error handling
--------------
When functions fail, they produce one of the error codes in :api:`Luv.Error.t
<Error/index.html#type-t>`, wrapped in the ``Error`` side of a ``result``. In
the last example above, we used :api:`Luv.Timer.init
<Timer/index.html#val-init>`, which has signature
.. code-block:: ocaml
Luv.Timer.init : unit -> (Luv.Timer.t, Luv.Error.t) result
If a call to ``Luv.Timer.init`` succeeds, it will produce ``Ok timer``. If it
fails, it might produce ``Error `ENOMEM``.
Asynchronous operations that can fail pass the ``result`` to their callback,
instead of returning it:
.. code-block:: ocaml
Luv.File.unlink : string -> ((unit, Luv.Error.t) result -> unit) -> unit
You can get the error name and description as strings by calling
:api:`Luv.Error.err_name <Error/index.html#val-err_name>` and
:api:`Luv.Error.strerror <Error/index.html#val-strerror>`, respectively.
libuv has several different conventions for returning errors. Luv translates all
of them into the above scheme: synchronous operations return a ``result``, and
asynchronous operations pass a ``result`` to their callback.
There is only a handful of exceptions to this, but they are all easy to
understand. For example, :api:`Luv.Timer.start <Timer/index.html#val-start>` can
fail immediately, but if it starts, it can only call its callback with success.
So, it has signature
.. code-block:: ocaml
Luv.Timer.start :
Luv.Timer.t -> int -> (unit -> unit) -> (unit, Luv.Error.t) result
Luv does not raise exceptions. In addition, callbacks you pass to Luv APIs
shouldn't raise exceptions at the top level (they can use exceptions interally).
This is because such exceptions can't be allowed to go up the stack into libuv.
If a callback passed to Luv raises an exception, Luv catches it, prints a stack
trace to ``STDERR``, and then calls ``exit 2``. You can change this behavior by
installing your own handler:
.. code-block:: ocaml
Luv.Error.set_on_unhandled_exception (fun exn -> (* ... *))
Generally, only applications, rather than libraries, should call this function.
Handles
-------
libuv works by the user expressing interest in particular events. This is
usually done by creating a **handle** to an I/O device, timer or process, and
then calling a function on that handle.
The following handle types are available:
- :api:`Luv.Timer <Timer/index.html>` — timers
- :api:`Luv.Signal <Signal/index.html>` — signals
- :api:`Luv.Process <Process/index.html>` — subprocesses
- :api:`Luv.TCP <TCP/index.html>` — TCP sockets
- :api:`Luv.UDP <UDP/index.html>` — UDP sockets
- :api:`Luv.Pipe <Pipe/index.html>` — pipes
- :api:`Luv.TTY <TTY/index.html>` — consoles
- :api:`Luv.FS_event <FS_event/index.html>` — filesystem events
- :api:`Luv.FS_poll <FS_poll/index.html>` — filesystem polling
- :api:`Luv.Poll <Poll/index.html>` — file descriptor polling
- :api:`Luv.Async <Async/index.html>` — inter-loop communication
- :api:`Luv.Prepare <Prepare/index.html>` — pre-I/O callbacks
- :api:`Luv.Check <Check/index.html>` — post-I/O callbacks
- :api:`Luv.Idle <Idle/index.html>` — per-iteration callbacks
In addition to the above true handles, there is also :api:`Luv.File
<File/index.html>`, which has a similar basic interface, yet is not a
proper libuv handle kind.
Example
-------
The remaining chapters of this guide will exercise many of the modules mentioned
above, but let's work with a simple one now — that is, in addition to the timer
"Hello, world!" example we've already seen!
Here is an example of using a :api:`Luv.Idle.t <Idle/index.html#type-t>` handle.
It adds a callback, that is to be called once on every iteration of the event
loop:
.. rubric:: :example:`idle.ml`
.. literalinclude:: ../example/idle.ml
:language: ocaml
:linenos:
The error handling in this example is not robust! It is using ``Result.get_ok``
and ``ignore`` to avoid dealing with potential ``Error``. If you are writing a
robust application, or a library, please use a better error-handling mechanism
instead.
Requests
--------
libuv also has **requests**. Whereas handles are long-lived, a request is a
short-lived representation of one particular instance of an asynchronous
operation. Requests are used by libuv, for example, to return complex results.
Luv manages requests automatically, so the user generally does not have to deal
with them at all.
A few request types are still exposed, however, because they are cancelable, and
can be passed, at the user's discretion, to :api:`Luv.Request.cancel
<Request/index.html#val-cancel>`. Explicit use of these requests is still
optional, however — they are always taken through optional arguments. So, when
using Luv, it is possible to ignore the existence of requests entirely.
See :api:`Luv.File.Request <File/Request/index.html>` for a typical request
type, with example usage. This represents filesystem requests, which are
cancelable. Functions in :api:`Luv.File <File/index.html>` have signatures like
.. code-block:: ocaml
Luv.File.unlink :
?request:Luv.File.Request.t ->
string ->
((unit, Luv.Error.t) result -> unit) ->
unit
...in case the user needs the ability to cancel the request. This guide will
generally omit the ``?request`` argument, because it is rarely used, and always
has only this one purpose.
Here are all the exposed request types in Luv:
- :api:`Luv.File.Request <File/Request/index.html>`
- :api:`Luv.DNS.Addr_info.Request <DNS/Addr_info/Request/index.html>`
- :api:`Luv.DNS.Name_info.Request <DNS/Name_info/Request/index.html>`
- :api:`Luv.Random.Request <Random/Request/index.html>`
- :api:`Luv.Thread_pool.Request <Thread_pool/Request/index.html>`
Everything else
---------------
libuv, and Luv, also offer a large number of other operations, all implemented
in a cross-platform fashion. See the full module listing in the
:api:`API index <index.html#api-reference>`. For example, one can list all
environment variables using
.. code-block:: ocaml
Luv.Env.environ : unit -> ((string * string) list, Luv.Error.t) result
Buffers
-------
Luv functions use data buffers of type :api:`Luv.Buffer.t
<Buffer/index.html#type-t>`. These are ordinary OCaml bigstrings (that is,
chunks of data managed from OCaml, but stored in the C heap). They are
compatible with at least the following bigstring libraries:
- Bigarray_ from OCaml's standard library
- Lwt_bytes_ from Lwt
- bigstringaf_
.. _Bigarray: https://caml.inria.fr/pub/docs/manual-ocaml/libref/Bigarray.Array1.html
.. _Lwt_bytes: https://ocsigen.org/lwt/dev/api/Lwt_bytes
.. _bigstringaf: https://github.com/inhabitedtype/bigstringaf
System calls are usually able to work with part of a buffer, by taking a pointer
to the buffer, an offset into the buffer, and a length of space, starting at the
offset, to work with. Luv functions instead take only a buffer. To work with
part of an existing buffer, use :api:`Luv.Buffer.sub
<Buffer/index.html#val-sub>` to create a view into the buffer, and then pass the
view to Luv.
Many libuv (and Luv) functions work with *lists* of buffers. For example,
:api:`Luv.File.write <File/index.html#val-write>` takes such a list. This simply
means that libuv will pull data from the buffers consecutively for writing.
Likewise, :api:`Luv.File.read <File/index.html#val-read>` takes a list of
buffers, which it will consecutively fill. This is known as *scatter-gather
I/O*. It could be said that libuv (and Luv) expose an API more similar to
readv_ and writev_ than read_ and write_.
.. _readv: http://man7.org/linux/man-pages/man3/readv.3p.html
.. _writev: http://man7.org/linux/man-pages/man3/writev.3p.html
.. _read: http://man7.org/linux/man-pages/man3/read.3p.html
.. _write: http://man7.org/linux/man-pages/man3/write.3p.html
Luv has a couple helpers for manipulating lists of buffers:
- :api:`Luv.Buffer.total_size <Buffer/index.html#val-total_size>` returns the
total number of bytes across all the buffers in a buffer list.
- :api:`Luv.Buffer.drop <Buffer/index.html#val-drop>` returns a new buffer list,
with the first N bytes removed from its front. This is useful to advance the
buffer references after a partial read into, or write from, a buffer list,
before beginning the next read or write.
Integer types
-------------
Luv usually represents C integer types with ordinary OCaml integers, such as
``int``, sometimes ``int64``. However, in some APIs, to avoid losing precision,
it seems important to preserve the full width and signedness of the original C
type. In such cases, you will encounter types from ocaml-integers_, such as
``Unsigned.Size_t.t``. You can use module ``Unsigned.Size_t`` to perform
operations at that type, or you can choose to convert to an OCaml integer with
``Unsigned.Size_t.to_int``.
.. _ocaml-integers: https://github.com/ocamllabs/ocaml-integers
|