Blame docs/src/guide/utilities.rst

Packit b5b901
Utilities
Packit b5b901
=========
Packit b5b901
Packit b5b901
This chapter catalogues tools and techniques which are useful for common tasks.
Packit b5b901
The `libev man page`_ already covers some patterns which can be adopted to
Packit b5b901
libuv through simple API changes. It also covers parts of the libuv API that
Packit b5b901
don't require entire chapters dedicated to them.
Packit b5b901
Packit b5b901
Timers
Packit b5b901
------
Packit b5b901
Packit b5b901
Timers invoke the callback after a certain time has elapsed since the timer was
Packit b5b901
started. libuv timers can also be set to invoke at regular intervals instead of
Packit b5b901
just once.
Packit b5b901
Packit b5b901
Simple use is to init a watcher and start it with a ``timeout``, and optional ``repeat``.
Packit b5b901
Timers can be stopped at any time.
Packit b5b901
Packit b5b901
.. code-block:: c
Packit b5b901
Packit b5b901
    uv_timer_t timer_req;
Packit b5b901
Packit b5b901
    uv_timer_init(loop, &timer_req);
Packit b5b901
    uv_timer_start(&timer_req, callback, 5000, 2000);
Packit b5b901
Packit b5b901
will start a repeating timer, which first starts 5 seconds (the ``timeout``) after the execution
Packit b5b901
of ``uv_timer_start``, then repeats every 2 seconds (the ``repeat``). Use:
Packit b5b901
Packit b5b901
.. code-block:: c
Packit b5b901
Packit b5b901
    uv_timer_stop(&timer_req);
Packit b5b901
Packit b5b901
to stop the timer. This can be used safely from within the callback as well.
Packit b5b901
Packit b5b901
The repeat interval can be modified at any time with::
Packit b5b901
Packit b5b901
    uv_timer_set_repeat(uv_timer_t *timer, int64_t repeat);
Packit b5b901
Packit b5b901
which will take effect **when possible**. If this function is called from
Packit b5b901
a timer callback, it means:
Packit b5b901
Packit b5b901
* If the timer was non-repeating, the timer has already been stopped. Use
Packit b5b901
  ``uv_timer_start`` again.
Packit b5b901
* If the timer is repeating, the next timeout has already been scheduled, so
Packit b5b901
  the old repeat interval will be used once more before the timer switches to
Packit b5b901
  the new interval.
Packit b5b901
Packit b5b901
The utility function::
Packit b5b901
Packit b5b901
    int uv_timer_again(uv_timer_t *)
Packit b5b901
Packit b5b901
applies **only to repeating timers** and is equivalent to stopping the timer
Packit b5b901
and then starting it with both initial ``timeout`` and ``repeat`` set to the
Packit b5b901
old ``repeat`` value. If the timer hasn't been started it fails (error code
Packit b5b901
``UV_EINVAL``) and returns -1.
Packit b5b901
Packit b5b901
An actual timer example is in the :ref:`reference count section
Packit b5b901
<reference-count>`.
Packit b5b901
Packit b5b901
.. _reference-count:
Packit b5b901
Packit b5b901
Event loop reference count
Packit b5b901
--------------------------
Packit b5b901
Packit b5b901
The event loop only runs as long as there are active handles. This system
Packit b5b901
works by having every handle increase the reference count of the event loop
Packit b5b901
when it is started and decreasing the reference count when stopped. It is also
Packit b5b901
possible to manually change the reference count of handles using::
Packit b5b901
Packit b5b901
    void uv_ref(uv_handle_t*);
Packit b5b901
    void uv_unref(uv_handle_t*);
Packit b5b901
Packit b5b901
These functions can be used to allow a loop to exit even when a watcher is
Packit b5b901
active or to use custom objects to keep the loop alive.
Packit b5b901
Packit b5b901
The latter can be used with interval timers. You might have a garbage collector
Packit b5b901
which runs every X seconds, or your network service might send a heartbeat to
Packit b5b901
others periodically, but you don't want to have to stop them along all clean
Packit b5b901
exit paths or error scenarios. Or you want the program to exit when all your
Packit b5b901
other watchers are done. In that case just unref the timer immediately after
Packit b5b901
creation so that if it is the only watcher running then ``uv_run`` will still
Packit b5b901
exit.
Packit b5b901
Packit b5b901
This is also used in node.js where some libuv methods are being bubbled up to
Packit b5b901
the JS API. A ``uv_handle_t`` (the superclass of all watchers) is created per
Packit b5b901
JS object and can be ref/unrefed.
Packit b5b901
Packit b5b901
.. rubric:: ref-timer/main.c
Packit b5b901
.. literalinclude:: ../../code/ref-timer/main.c
Packit b5b901
    :linenos:
Packit b5b901
    :lines: 5-8, 17-
Packit b5b901
    :emphasize-lines: 9
Packit b5b901
Packit b5b901
We initialize the garbage collector timer, then immediately ``unref`` it.
Packit b5b901
Observe how after 9 seconds, when the fake job is done, the program
Packit b5b901
automatically exits, even though the garbage collector is still running.
Packit b5b901
Packit b5b901
Idler pattern
Packit b5b901
-------------
Packit b5b901
Packit b5b901
The callbacks of idle handles are invoked once per event loop. The idle
Packit b5b901
callback can be used to perform some very low priority activity. For example,
Packit b5b901
you could dispatch a summary of the daily application performance to the
Packit b5b901
developers for analysis during periods of idleness, or use the application's
Packit b5b901
CPU time to perform SETI calculations :) An idle watcher is also useful in
Packit b5b901
a GUI application. Say you are using an event loop for a file download. If the
Packit b5b901
TCP socket is still being established and no other events are present your
Packit b5b901
event loop will pause (**block**), which means your progress bar will freeze
Packit b5b901
and the user will face an unresponsive application. In such a case queue up and
Packit b5b901
idle watcher to keep the UI operational.
Packit b5b901
Packit b5b901
.. rubric:: idle-compute/main.c
Packit b5b901
.. literalinclude:: ../../code/idle-compute/main.c
Packit b5b901
    :linenos:
Packit b5b901
    :lines: 5-9, 34-
Packit b5b901
    :emphasize-lines: 13
Packit b5b901
Packit b5b901
Here we initialize the idle watcher and queue it up along with the actual
Packit b5b901
events we are interested in. ``crunch_away`` will now be called repeatedly
Packit b5b901
until the user types something and presses Return. Then it will be interrupted
Packit b5b901
for a brief amount as the loop deals with the input data, after which it will
Packit b5b901
keep calling the idle callback again.
Packit b5b901
Packit b5b901
.. rubric:: idle-compute/main.c
Packit b5b901
.. literalinclude:: ../../code/idle-compute/main.c
Packit b5b901
    :linenos:
Packit b5b901
    :lines: 10-19
Packit b5b901
Packit b5b901
.. _baton:
Packit b5b901
Packit b5b901
Passing data to worker thread
Packit b5b901
-----------------------------
Packit b5b901
Packit b5b901
When using ``uv_queue_work`` you'll usually need to pass complex data through
Packit b5b901
to the worker thread. The solution is to use a ``struct`` and set
Packit b5b901
``uv_work_t.data`` to point to it. A slight variation is to have the
Packit b5b901
``uv_work_t`` itself as the first member of this struct (called a baton [#]_).
Packit b5b901
This allows cleaning up the work request and all the data in one free call.
Packit b5b901
Packit b5b901
.. code-block:: c
Packit b5b901
    :linenos:
Packit b5b901
    :emphasize-lines: 2
Packit b5b901
Packit b5b901
    struct ftp_baton {
Packit b5b901
        uv_work_t req;
Packit b5b901
        char *host;
Packit b5b901
        int port;
Packit b5b901
        char *username;
Packit b5b901
        char *password;
Packit b5b901
    }
Packit b5b901
Packit b5b901
.. code-block:: c
Packit b5b901
    :linenos:
Packit b5b901
    :emphasize-lines: 2
Packit b5b901
Packit b5b901
    ftp_baton *baton = (ftp_baton*) malloc(sizeof(ftp_baton));
Packit b5b901
    baton->req.data = (void*) baton;
Packit b5b901
    baton->host = strdup("my.webhost.com");
Packit b5b901
    baton->port = 21;
Packit b5b901
    // ...
Packit b5b901
Packit b5b901
    uv_queue_work(loop, &baton->req, ftp_session, ftp_cleanup);
Packit b5b901
Packit b5b901
Here we create the baton and queue the task.
Packit b5b901
Packit b5b901
Now the task function can extract the data it needs:
Packit b5b901
Packit b5b901
.. code-block:: c
Packit b5b901
    :linenos:
Packit b5b901
    :emphasize-lines: 2, 12
Packit b5b901
Packit b5b901
    void ftp_session(uv_work_t *req) {
Packit b5b901
        ftp_baton *baton = (ftp_baton*) req->data;
Packit b5b901
Packit b5b901
        fprintf(stderr, "Connecting to %s\n", baton->host);
Packit b5b901
    }
Packit b5b901
Packit b5b901
    void ftp_cleanup(uv_work_t *req) {
Packit b5b901
        ftp_baton *baton = (ftp_baton*) req->data;
Packit b5b901
Packit b5b901
        free(baton->host);
Packit b5b901
        // ...
Packit b5b901
        free(baton);
Packit b5b901
    }
Packit b5b901
Packit b5b901
We then free the baton which also frees the watcher.
Packit b5b901
Packit b5b901
External I/O with polling
Packit b5b901
-------------------------
Packit b5b901
Packit b5b901
Usually third-party libraries will handle their own I/O, and keep track of
Packit b5b901
their sockets and other files internally. In this case it isn't possible to use
Packit b5b901
the standard stream I/O operations, but the library can still be integrated
Packit b5b901
into the libuv event loop. All that is required is that the library allow you
Packit b5b901
to access the underlying file descriptors and provide functions that process
Packit b5b901
tasks in small increments as decided by your application. Some libraries though
Packit b5b901
will not allow such access, providing only a standard blocking function which
Packit b5b901
will perform the entire I/O transaction and only then return. It is unwise to
Packit Service e08953
use these in the event loop thread, use the :ref:`threadpool` instead. Of
Packit b5b901
course, this will also mean losing granular control on the library.
Packit b5b901
Packit b5b901
The ``uv_poll`` section of libuv simply watches file descriptors using the
Packit b5b901
operating system notification mechanism. In some sense, all the I/O operations
Packit b5b901
that libuv implements itself are also backed by ``uv_poll`` like code. Whenever
Packit b5b901
the OS notices a change of state in file descriptors being polled, libuv will
Packit b5b901
invoke the associated callback.
Packit b5b901
Packit b5b901
Here we will walk through a simple download manager that will use libcurl_ to
Packit b5b901
download files. Rather than give all control to libcurl, we'll instead be
Packit b5b901
using the libuv event loop, and use the non-blocking, async multi_ interface to
Packit b5b901
progress with the download whenever libuv notifies of I/O readiness.
Packit b5b901
Packit Service e08953
.. _libcurl: https://curl.haxx.se/libcurl/
Packit Service e08953
.. _multi: https://curl.haxx.se/libcurl/c/libcurl-multi.html
Packit b5b901
Packit b5b901
.. rubric:: uvwget/main.c - The setup
Packit b5b901
.. literalinclude:: ../../code/uvwget/main.c
Packit b5b901
    :linenos:
Packit b5b901
    :lines: 1-9,140-
Packit b5b901
    :emphasize-lines: 7,21,24-25
Packit b5b901
Packit b5b901
The way each library is integrated with libuv will vary. In the case of
Packit b5b901
libcurl, we can register two callbacks. The socket callback ``handle_socket``
Packit b5b901
is invoked whenever the state of a socket changes and we have to start polling
Packit b5b901
it. ``start_timeout`` is called by libcurl to notify us of the next timeout
Packit b5b901
interval, after which we should drive libcurl forward regardless of I/O status.
Packit b5b901
This is so that libcurl can handle errors or do whatever else is required to
Packit b5b901
get the download moving.
Packit b5b901
Packit b5b901
Our downloader is to be invoked as::
Packit b5b901
Packit b5b901
    $ ./uvwget [url1] [url2] ...
Packit b5b901
Packit b5b901
So we add each argument as an URL
Packit b5b901
Packit b5b901
.. rubric:: uvwget/main.c - Adding urls
Packit b5b901
.. literalinclude:: ../../code/uvwget/main.c
Packit b5b901
    :linenos:
Packit b5b901
    :lines: 39-56
Packit b5b901
    :emphasize-lines: 13-14
Packit b5b901
Packit b5b901
We let libcurl directly write the data to a file, but much more is possible if
Packit b5b901
you so desire.
Packit b5b901
Packit b5b901
``start_timeout`` will be called immediately the first time by libcurl, so
Packit b5b901
things are set in motion. This simply starts a libuv `timer <Timers>`_ which
Packit b5b901
drives ``curl_multi_socket_action`` with ``CURL_SOCKET_TIMEOUT`` whenever it
Packit b5b901
times out. ``curl_multi_socket_action`` is what drives libcurl, and what we
Packit b5b901
call whenever sockets change state. But before we go into that, we need to poll
Packit b5b901
on sockets whenever ``handle_socket`` is called.
Packit b5b901
Packit b5b901
.. rubric:: uvwget/main.c - Setting up polling
Packit b5b901
.. literalinclude:: ../../code/uvwget/main.c
Packit b5b901
    :linenos:
Packit b5b901
    :lines: 102-140
Packit b5b901
    :emphasize-lines: 9,11,15,21,24
Packit b5b901
Packit b5b901
We are interested in the socket fd ``s``, and the ``action``. For every socket
Packit b5b901
we create a ``uv_poll_t`` handle if it doesn't exist, and associate it with the
Packit b5b901
socket using ``curl_multi_assign``. This way ``socketp`` points to it whenever
Packit b5b901
the callback is invoked.
Packit b5b901
Packit b5b901
In the case that the download is done or fails, libcurl requests removal of the
Packit b5b901
poll. So we stop and free the poll handle.
Packit b5b901
Packit b5b901
Depending on what events libcurl wishes to watch for, we start polling with
Packit b5b901
``UV_READABLE`` or ``UV_WRITABLE``. Now libuv will invoke the poll callback
Packit b5b901
whenever the socket is ready for reading or writing. Calling ``uv_poll_start``
Packit b5b901
multiple times on the same handle is acceptable, it will just update the events
Packit b5b901
mask with the new value. ``curl_perform`` is the crux of this program.
Packit b5b901
Packit b5b901
.. rubric:: uvwget/main.c - Driving libcurl.
Packit b5b901
.. literalinclude:: ../../code/uvwget/main.c
Packit b5b901
    :linenos:
Packit b5b901
    :lines: 81-95
Packit b5b901
    :emphasize-lines: 2,6-7,12
Packit b5b901
Packit b5b901
The first thing we do is to stop the timer, since there has been some progress
Packit b5b901
in the interval. Then depending on what event triggered the callback, we set
Packit b5b901
the correct flags. Then we call ``curl_multi_socket_action`` with the socket
Packit b5b901
that progressed and the flags informing about what events happened. At this
Packit b5b901
point libcurl does all of its internal tasks in small increments, and will
Packit b5b901
attempt to return as fast as possible, which is exactly what an evented program
Packit b5b901
wants in its main thread. libcurl keeps queueing messages into its own queue
Packit b5b901
about transfer progress. In our case we are only interested in transfers that
Packit b5b901
are completed. So we extract these messages, and clean up handles whose
Packit b5b901
transfers are done.
Packit b5b901
Packit b5b901
.. rubric:: uvwget/main.c - Reading transfer status.
Packit b5b901
.. literalinclude:: ../../code/uvwget/main.c
Packit b5b901
    :linenos:
Packit b5b901
    :lines: 58-79
Packit b5b901
    :emphasize-lines: 6,9-10,13-14
Packit b5b901
Packit b5b901
Check & Prepare watchers
Packit b5b901
------------------------
Packit b5b901
Packit b5b901
TODO
Packit b5b901
Packit b5b901
Loading libraries
Packit b5b901
-----------------
Packit b5b901
Packit b5b901
libuv provides a cross platform API to dynamically load `shared libraries`_.
Packit b5b901
This can be used to implement your own plugin/extension/module system and is
Packit b5b901
used by node.js to implement ``require()`` support for bindings. The usage is
Packit b5b901
quite simple as long as your library exports the right symbols. Be careful with
Packit b5b901
sanity and security checks when loading third party code, otherwise your
Packit b5b901
program will behave unpredictably. This example implements a very simple
Packit b5b901
plugin system which does nothing except print the name of the plugin.
Packit b5b901
Packit b5b901
Let us first look at the interface provided to plugin authors.
Packit b5b901
Packit b5b901
.. rubric:: plugin/plugin.h
Packit b5b901
.. literalinclude:: ../../code/plugin/plugin.h
Packit b5b901
    :linenos:
Packit b5b901
Packit b5b901
You can similarly add more functions that plugin authors can use to do useful
Packit b5b901
things in your application [#]_. A sample plugin using this API is:
Packit b5b901
Packit b5b901
.. rubric:: plugin/hello.c
Packit b5b901
.. literalinclude:: ../../code/plugin/hello.c
Packit b5b901
    :linenos:
Packit b5b901
Packit b5b901
Our interface defines that all plugins should have an ``initialize`` function
Packit b5b901
which will be called by the application. This plugin is compiled as a shared
Packit b5b901
library and can be loaded by running our application::
Packit b5b901
Packit b5b901
    $ ./plugin libhello.dylib
Packit b5b901
    Loading libhello.dylib
Packit b5b901
    Registered plugin "Hello World!"
Packit b5b901
Packit b5b901
.. NOTE::
Packit b5b901
Packit b5b901
    The shared library filename will be different depending on platforms. On
Packit b5b901
    Linux it is ``libhello.so``.
Packit b5b901
Packit b5b901
This is done by using ``uv_dlopen`` to first load the shared library
Packit b5b901
``libhello.dylib``. Then we get access to the ``initialize`` function using
Packit b5b901
``uv_dlsym`` and invoke it.
Packit b5b901
Packit b5b901
.. rubric:: plugin/main.c
Packit b5b901
.. literalinclude:: ../../code/plugin/main.c
Packit b5b901
    :linenos:
Packit b5b901
    :lines: 7-
Packit b5b901
    :emphasize-lines: 15, 18, 24
Packit b5b901
Packit b5b901
``uv_dlopen`` expects a path to the shared library and sets the opaque
Packit b5b901
``uv_lib_t`` pointer. It returns 0 on success, -1 on error. Use ``uv_dlerror``
Packit b5b901
to get the error message.
Packit b5b901
Packit b5b901
``uv_dlsym`` stores a pointer to the symbol in the second argument in the third
Packit b5b901
argument. ``init_plugin_function`` is a function pointer to the sort of
Packit b5b901
function we are looking for in the application's plugins.
Packit b5b901
Packit Service e08953
.. _shared libraries: https://en.wikipedia.org/wiki/Shared_library#Shared_libraries
Packit b5b901
Packit b5b901
TTY
Packit b5b901
---
Packit b5b901
Packit b5b901
Text terminals have supported basic formatting for a long time, with a `pretty
Packit b5b901
standardised`_ command set. This formatting is often used by programs to
Packit b5b901
improve the readability of terminal output. For example ``grep --colour``.
Packit b5b901
libuv provides the ``uv_tty_t`` abstraction (a stream) and related functions to
Packit b5b901
implement the ANSI escape codes across all platforms. By this I mean that libuv
Packit b5b901
converts ANSI codes to the Windows equivalent, and provides functions to get
Packit b5b901
terminal information.
Packit b5b901
Packit Service e08953
.. _pretty standardised: https://en.wikipedia.org/wiki/ANSI_escape_sequences
Packit b5b901
Packit b5b901
The first thing to do is to initialize a ``uv_tty_t`` with the file descriptor
Packit b5b901
it reads/writes from. This is achieved with::
Packit b5b901
Packit b5b901
    int uv_tty_init(uv_loop_t*, uv_tty_t*, uv_file fd, int unused)
Packit b5b901
Packit b5b901
The ``unused`` parameter is now auto-detected and ignored. It previously needed
Packit b5b901
to be set to use ``uv_read_start()`` on the stream.
Packit b5b901
Packit b5b901
It is then best to use ``uv_tty_set_mode`` to set the mode to *normal*
Packit b5b901
which enables most TTY formatting, flow-control and other settings. Other_ modes
Packit b5b901
are also available.
Packit b5b901
Packit b5b901
.. _Other: http://docs.libuv.org/en/v1.x/tty.html#c.uv_tty_mode_t
Packit b5b901
Packit b5b901
Remember to call ``uv_tty_reset_mode`` when your program exits to restore the
Packit b5b901
state of the terminal. Just good manners. Another set of good manners is to be
Packit b5b901
aware of redirection. If the user redirects the output of your command to
Packit b5b901
a file, control sequences should not be written as they impede readability and
Packit b5b901
``grep``. To check if the file descriptor is indeed a TTY, call
Packit b5b901
``uv_guess_handle`` with the file descriptor and compare the return value with
Packit b5b901
``UV_TTY``.
Packit b5b901
Packit b5b901
Here is a simple example which prints white text on a red background:
Packit b5b901
Packit b5b901
.. rubric:: tty/main.c
Packit b5b901
.. literalinclude:: ../../code/tty/main.c
Packit b5b901
    :linenos:
Packit b5b901
    :emphasize-lines: 11-12,14,17,27
Packit b5b901
Packit b5b901
The final TTY helper is ``uv_tty_get_winsize()`` which is used to get the
Packit b5b901
width and height of the terminal and returns ``0`` on success. Here is a small
Packit b5b901
program which does some animation using the function and character position
Packit b5b901
escape codes.
Packit b5b901
Packit b5b901
.. rubric:: tty-gravity/main.c
Packit b5b901
.. literalinclude:: ../../code/tty-gravity/main.c
Packit b5b901
    :linenos:
Packit b5b901
    :emphasize-lines: 19,25,38
Packit b5b901
Packit b5b901
The escape codes are:
Packit b5b901
Packit b5b901
======  =======================
Packit b5b901
Code    Meaning
Packit b5b901
======  =======================
Packit b5b901
*2* J    Clear part of the screen, 2 is entire screen
Packit b5b901
H        Moves cursor to certain position, default top-left
Packit b5b901
*n* B    Moves cursor down by n lines
Packit b5b901
*n* C    Moves cursor right by n columns
Packit b5b901
m        Obeys string of display settings, in this case green background (40+2), white text (30+7)
Packit b5b901
======  =======================
Packit b5b901
Packit b5b901
As you can see this is very useful to produce nicely formatted output, or even
Packit b5b901
console based arcade games if that tickles your fancy. For fancier control you
Packit b5b901
can try `ncurses`_.
Packit b5b901
Packit Service e08953
.. _ncurses: https://www.gnu.org/software/ncurses/ncurses.html
Packit b5b901
Packit b5b901
.. versionchanged:: 1.23.1: the `readable` parameter is now unused and ignored.
Packit b5b901
                    The appropriate value will now be auto-detected from the kernel.
Packit b5b901
Packit b5b901
----
Packit b5b901
Packit b5b901
.. [#] I was first introduced to the term baton in this context, in Konstantin
Packit b5b901
       Käfer's excellent slides on writing node.js bindings --
Packit Service e08953
       https://kkaefer.com/node-cpp-modules/#baton
Packit b5b901
.. [#] mfp is My Fancy Plugin
Packit b5b901
Packit b5b901
.. _libev man page: http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#COMMON_OR_USEFUL_IDIOMS_OR_BOTH