Queuing multiple, separate function calls in the main loop

173 views
Skip to first unread message

Jason Heeris

unread,
Aug 11, 2020, 7:42:30 AM8/11/20
to libuv
I'm developing a control system that uses libuv (1.34.2) on a GNU/Linux system. My control system basically has a mutex-protected in-memory message queue, posts my custom messages to it, and calls async_send(). The handler is an async handler run by the main loop.

You could think of each message as more or less corresponding to single, non-blocking function to be called in the main loop. So the handler pops a message off, uses it to decide what non-blocking function to call, and calls it.

The problem I want to solve is that if I get a bunch of messages all at once in the queue (eg. after libuv coalesces async_send calls, or my control code posts multiple messages at once), I (a) don't want to monopolise the mutex and (b) don't want to monopolise the main loop. (a) is solved by popping the messages off the queue, storing them, releasing the mutex and then processing the messages outside the mutex lock. But (b) is a bit harder. I want to let libuv handle other things in between the function call for each message (if it needs to).

Is there a way to give libuv a function to run once, queued up in the main loop? I couldn't find any API functions that simply add a one-off function call to the loop.

Ben Noordhuis

unread,
Aug 11, 2020, 7:51:07 AM8/11/20
to li...@googlegroups.com
There isn't*, but if you want to slice the work into smaller pieces,
you can use e.g. a uv_timer_t, uv_idle_t or uv_async_t to split it up.

You'll have to think about queuing, of course - what if items come in
faster than the main thread can dispatch them?

* Or rather, there is - you could use the uv_after_work_cb to
uv_queue_work() for that - but it'd be rather inefficient.

Ariel Machado

unread,
Aug 11, 2020, 7:57:24 AM8/11/20
to libuv

In a quick response, the libuv main loop runs on a single thread, so if you want libuv to continue processing other things while you are processing a message invoking the functions, I think a worker thread uv_work_t should be created to process the message and call respective function (consumer), and leave the main thread to receive new messages and add them to the queue (producer).


Jason Heeris

unread,
Aug 11, 2020, 8:52:21 AM8/11/20
to libuv
Thanks for all the information!

Just to clarify a couple of things about the threading:
  1. The device (an embedded device) has a single core anyway. The threads are only there to manage blocking code that can't be (or hasn't been) broken up into events.
  2. I do the message handling in the main loop because a lot of it relates to a GUI which has to be serviced via a single thread or locked with mutexes. The GUI event loop is already managed by a libuv timer.
> if you want libuv to continue processing other things while you are processing a message invoking the functions

I am not so concerned about processing things in actual parallel, it's more that if libuv has some other things ready to go (eg. requests to handle, buffers to fill) I was wondering if I could be "polite" and interleave my handlers with libuv's, still in one thread. Kind of like yielding in other async frameworks (eg. Python's asyncio).

> what if items come in faster than the main thread can dispatch them?

It's an embedded system with a known set of things-to-do, so I can make a decent estimate of the maximum number of events that can accumulate (and a caller will know when pushing to the message queue fails, and can... theoretically... handle that).

After reading the API docs again, I think that a timer with delay = 0 and repeat = 0 is essentially what I want to do - one for each message. Or perhaps a uv_async_t and then an immediate async_send? The docs say that the async handler can be invoked at any time after that call, including before the calling function returns, but I assume that only applies when you're calling it from a thread different to the one the main loop is running in?

Thanks,
Jason

Ben Noordhuis

unread,
Aug 11, 2020, 11:43:16 AM8/11/20
to li...@googlegroups.com
On Tue, Aug 11, 2020 at 2:52 PM Jason Heeris <jason....@gmail.com> wrote:
>
> After reading the API docs again, I think that a timer with delay = 0 and repeat = 0 is essentially what I want to do - one for each message. Or perhaps a uv_async_t and then an immediate async_send? The docs say that the async handler can be invoked at any time after that call, including before the calling function returns, but I assume that only applies when you're calling it from a thread different to the one the main loop is running in?

Calling uv_async_send() from the callback is okay.

A uv_idle_t might be marginally more efficient (one fewer system call.)

Jason Heeris

unread,
Aug 11, 2020, 9:17:09 PM8/11/20
to libuv
On Wednesday, 12 August 2020 at 1:43:16 am UTC+10 Ben Noordhuis wrote:
A uv_idle_t might be marginally more efficient (one fewer system call.)

I took the various suggestions here and wrote a few different versions (timer with 0 delay and repeat, async, idle with idle stop call in callback). It turned out that the idle call led to nicer code anyway, and I expect for someone else to read is a little less surprising the  alternatives.

Is this something that might be useful in libuv in general? If I were to come back to this and open a PR to add such a thing, would it be worth the time, or unlikely to be generally useful?

Ben Noordhuis

unread,
Aug 12, 2020, 5:21:46 AM8/12/20
to li...@googlegroups.com
"This" meaning "one-shot callback"? It's never been requested before
(AFAIR anyway) and it's easy to construct from existing functionality.
I'm leaning towards 'no' but I'm willing to be persuaded.
Reply all
Reply to author
Forward
0 new messages