usvi / libruuvitag

The Unlicense
0 stars 0 forks source link

Need to try creating my own event loop #4

Open usvi opened 1 year ago

usvi commented 1 year ago

Due to the dbus-1 library locking itself on dbus_connection_read_write_dispatch, I think I need to create my own event loop. I want to cleanly break on signal from another thread.

usvi commented 1 year ago

Been verifying this. Nothing else seems to work. I have been trying many gimmics.. nothing. Of course there are no guarantees that own loop would work either. But maybe it is worth checkin.

usvi commented 1 year ago

I found that wpa_supplicant may have what we need.

usvi commented 1 year ago

Researching wpa_supplicant.

usvi commented 1 year ago

(Some sources say something about self pipe trick.)

usvi commented 1 year ago

About pselect / select:

"If none of the selected descriptors are ready for the requested operation, the pselect() or select() function shall block until at least one of the requested operations becomes ready, until the timeout occurs, or until interrupted by a signal. The timeout parameter controls how long the" ... "EINTR The function was interrupted before any of the selected events occurred and before the timeout interval expired."

src/utils/eloop.c has:
#ifdef CONFIG_ELOOP_SELECT
        eloop_sock_table_set_fds(&eloop.readers, rfds);
        eloop_sock_table_set_fds(&eloop.writers, wfds);
        eloop_sock_table_set_fds(&eloop.exceptions, efds);
        res = select(eloop.max_sock + 1, rfds, wfds, efds,
                 timeout ? &_tv : NULL);
#endif /* CONFIG_ELOOP_SELECT */

...

if (res < 0 && errno != EINTR && errno != 0) {
    wpa_printf(MSG_ERROR, "eloop: %s: %s",

... Also:

static void eloop_handle_signal(int sig)

and

int eloop_register_signal(int sig, eloop_signal_handler handler,
              void *user_data)

...

signal(sig, eloop_handle_signal);
usvi commented 1 year ago
/**
 * integrate_with_eloop - Register our mainloop integration with dbus
 * @connection: connection to the system message bus
 * @priv: a dbus control interface data structure
 * Returns: 0 on success, -1 on failure
 */
static int integrate_with_eloop(struct wpas_dbus_priv *priv)
{
    if (!dbus_connection_set_watch_functions(priv->con, add_watch,
                         remove_watch, watch_toggled,
                         priv, NULL) ||
        !dbus_connection_set_timeout_functions(priv->con, add_timeout,
                           remove_timeout,
                           timeout_toggled, priv,
                           NULL)) {
        wpa_printf(MSG_ERROR, "dbus: Failed to set callback functions");
        return -1;
    }

    if (eloop_register_signal(SIGPOLL, process_wakeup_main, priv))
        return -1;
    dbus_connection_set_wakeup_main_function(priv->con, wakeup_main,
                         priv, NULL);

    return 0;
}
usvi commented 1 year ago
static int wpas_dbus_init_common_finish(struct wpas_dbus_priv *priv)
{
    /* Tell dbus about our mainloop integration functions */
    integrate_with_eloop(priv);

    /*
     * Dispatch initial DBus messages that may have come in since the bus
     * name was claimed above. Happens when clients are quick to notice the
     * service.
     *
     * FIXME: is there a better solution to this problem?
     */
    eloop_register_timeout(0, 50, dispatch_initial_dbus_messages,
                   priv->con, NULL);

    return 0;
}
usvi commented 1 year ago
struct wpas_dbus_priv * wpas_dbus_init(struct wpa_global *global)
{
    struct wpas_dbus_priv *priv;

    priv = os_zalloc(sizeof(*priv));
    if (priv == NULL)
        return NULL;
    priv->global = global;

    if (wpas_dbus_init_common(priv) < 0 ||
#ifdef CONFIG_CTRL_IFACE_DBUS_NEW
        wpas_dbus_ctrl_iface_init(priv) < 0 ||
#endif /* CONFIG_CTRL_IFACE_DBUS_NEW */
        wpas_dbus_init_common_finish(priv) < 0) {
        wpas_dbus_deinit(priv);
        return NULL;
    }

    return priv;
}
usvi commented 1 year ago
int wpas_notify_supplicant_initialized(struct wpa_global *global)
{
#ifdef CONFIG_CTRL_IFACE_DBUS_NEW
    if (global->params.dbus_ctrl_interface) {
        global->dbus = wpas_dbus_init(global);
        if (global->dbus == NULL)
            return -1;
    }
usvi commented 1 year ago
/**
 * wpa_supplicant_init - Initialize %wpa_supplicant
 * @params: Parameters for %wpa_supplicant
 * Returns: Pointer to global %wpa_supplicant data, or %NULL on failure
 *
 * This function is used to initialize %wpa_supplicant. After successful
 * initialization, the returned data pointer can be used to add and remove
 * network interfaces, and eventually, to deinitialize %wpa_supplicant.
 */
struct wpa_global * wpa_supplicant_init(struct wpa_params *params)
{
    struct wpa_global *global;
    int ret, i;

    if (params == NULL)
        return NULL;

#ifdef CONFIG_DRIVER_NDIS

...

    if (wpas_notify_supplicant_initialized(global)) {
        wpa_supplicant_deinit(global);
        return NULL;
usvi commented 1 year ago
int main(int argc, char *argv[])
{
    int c, i;
    struct wpa_interface *ifaces, *iface;
    int iface_count, exitcode = -1;
    struct wpa_params params;
    struct wpa_global *global;

...

    global = wpa_supplicant_init(¶ms);
    if (global == NULL) {
        wpa_printf(MSG_ERROR, "Failed to initialize wpa_supplicant");
        exitcode = -1;
        goto out;
    } else {
        wpa_printf(MSG_INFO, "Successfully initialized "
               "wpa_supplicant");
usvi commented 1 year ago

main() => wpa_supplicant_init() => wpas_notify_supplicant_initialized() => wpas_dbus_init() => wpas_dbus_init_common() && wpas_dbus_ctrl_iface_init() && wpas_dbus_init_common_finish() => integrate_with_eloop() => eloop_register_signal( eloop_handle_signal() )

wpas_dbus_init_common() => dbus_bus_get(DBUS_BUS_SYSTEM, &error);

wpa_dbus_ctrl_iface_init(priv, WPAS_DBUS_NEW_PATH, WPAS_DBUS_NEW_SERVICE, obj_desc) => DBusObjectPathVTable wpa_vtable = { &free_dbus_object_desc_cb, &message_handler, NULL, NULL, NULL, NULL}; && dbus_connection_register_object_path(iface->con, dbus_path, &wpa_vtable, obj_desc))

usvi commented 1 year ago

From dbus, probably does not help:

 * Queues incoming messages and sends outgoing messages for this
 * connection, optionally blocking in the process. Each call to
 * _dbus_connection_do_iteration_unlocked() will call select() or poll() one
 * time and then read or write data if possible.
usvi commented 1 year ago

Worth checking, vtable stuff: https://gist.github.com/plauche/bb296b43e42281d4e230ec397054e468

usvi commented 1 year ago

Worth looking:

eloop_run();

usvi commented 1 year ago

Interesting comment:

"Is it possible for asynchronous sending and reading messages in D-bus using the function calls pastebin.com/4E6XB3qb ? I didn't understand why dbus_connection_set_dispatch_status_function(), dbus_connection_set_watch_functions() and dbus_connection_set_timeout_functions() functions are required? My understanding is, if we register object path(i.e DBusObjectPathVTable) then d-bus daemon will call "message_function" of vtable when any message received for the connection. Please let me know if this understanding is not correct. –  user3693586 Nov 27, 2017 at 10:31"

usvi commented 1 year ago

Funny: https://lists.gnu.org/archive/html/gpsd-dev/2021-08/msg00080.html

"Except shorten the timeout, or re-write that loop. I looked at rewriting that loop, and the innertubes are full of people that failed to replace dbus_connection_read_write_dispatch()."

usvi commented 1 year ago

eloop_run() has interesting:

ifdef CONFIG_ELOOP_SELECT

fd_set *rfds, *wfds, *efds;
usvi commented 1 year ago

Interesting:

    eloop.readers.changed = 0;
    eloop.writers.changed = 0;
    eloop.exceptions.changed = 0;

    eloop_process_pending_signals();
usvi commented 1 year ago

/**

usvi commented 1 year ago

Extremely important:

fd = dbus_watch_get_unix_fd(watch);

usvi commented 1 year ago

It seems the best way to do things is to scavenge dbus reference and see where it is in wpa_supplicant. We can do this!

usvi commented 1 year ago

Interesting: https://git.sr.ht/~anteater/mmsd/blob/6e7822d7c7325983c582650dd33fb05607e6ae2b/gdbus/mainloop.c

usvi commented 1 year ago

dbus_connection_set_watch_functions

Sets the watch functions for the connection.

"These functions are responsible for making the application's main loop aware of file descriptors that need to be monitored for events, using select() or poll()"

usvi commented 1 year ago

wpa_supplicant uses nothing of read write dispatch

usvi commented 1 year ago

Important:

ifdef CONFIG_ELOOP_SELECT

usvi commented 1 year ago

I presented myself the question: Why we keep even loop in a thread?

The thing is the event loop is a self-contained application so to speak. But we need external functions to manipulate its state. So, regular exported functions. There is of course init and deinit. We need more, and will make more.

usvi commented 1 year ago

static int integrate_with_eloop(struct wpas_dbus_priv *priv) { if (!dbus_connection_set_watch_functions(priv->con, add_watch, remove_watch, watch_toggled, priv, NULL) ||

usvi commented 1 year ago

So, first comes dbus system connection, then integration.

usvi commented 1 year ago

This first after setting up the bus: dbus_connection_set_watch_functions

Explore if this needed: dbus_connection_set_timeout_functions

usvi commented 1 year ago

Interesting: https://stackoverflow.com/questions/9378593/dbuswatch-and-dbustimeout-examples

usvi commented 1 year ago

More interestings: https://clusterlabs.org/pacemaker/doxygen/Pacemaker-1.1.18/dbus_8c_source.html

usvi commented 1 year ago

man 2 select:

   #include <stdio.h>
   #include <stdlib.h>
   #include <sys/time.h>
   #include <sys/types.h>
   #include <unistd.h>

   int
   main(void)
   {
       fd_set rfds;
       struct timeval tv;
       int retval;

       /* Watch stdin (fd 0) to see when it has input. */

       FD_ZERO(&rfds);
       FD_SET(0, &rfds);

       /* Wait up to five seconds. */

       tv.tv_sec = 5;
       tv.tv_usec = 0;

       retval = select(1, &rfds, NULL, NULL, &tv);
       /* Don't rely on the value of tv now! */

       if (retval == -1)
           perror("select()");
       else if (retval)
           printf("Data is available now.\n");
           /* FD_ISSET(0, &rfds) will be true. */
       else
           printf("No data within five seconds.\n");

       exit(EXIT_SUCCESS);
   }
usvi commented 1 year ago

I get it now. We never watch individual fds. We watch sets.