Add new API, to make a time limited call of the coroutine.
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@openvz.org>
---
include/qemu/coroutine.h | 13 ++++++
util/meson.build | 1 +
util/qemu-co-timeout.c | 89 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 103 insertions(+)
create mode 100644 util/qemu-co-timeout.c
diff --git a/include/qemu/coroutine.h b/include/qemu/coroutine.h
index c828a95ee0..8704b05da8 100644
--- a/include/qemu/coroutine.h
+++ b/include/qemu/coroutine.h
@@ -316,6 +316,19 @@ static inline void coroutine_fn qemu_co_sleep_ns(QEMUClockType type, int64_t ns)
qemu_co_sleep_ns_wakeable(&w, type, ns);
}
+typedef void CleanupFunc(void *opaque);
+/**
+ * Run entry in a coroutine and start timer. Wait for entry to finish or for
+ * timer to elapse, what happen first. If entry finished, return 0, if timer
+ * elapsed earlier, return -ETIMEDOUT.
+ *
+ * Be careful, entry execution is not canceled, user should handle it somehow.
+ * If @clean is provided, it's called after coroutine finish if timeout
+ * happened.
+ */
+int coroutine_fn qemu_co_timeout(CoroutineEntry *entry, void *opaque,
+ uint64_t timeout_ns, CleanupFunc clean);
+
/**
* Wake a coroutine if it is sleeping in qemu_co_sleep_ns. The timer will be
* deleted. @sleep_state must be the variable whose address was given to
diff --git a/util/meson.build b/util/meson.build
index f6ee74ad0c..249891db72 100644
--- a/util/meson.build
+++ b/util/meson.build
@@ -83,6 +83,7 @@ if have_block
util_ss.add(files('block-helpers.c'))
util_ss.add(files('qemu-coroutine-sleep.c'))
util_ss.add(files('qemu-co-shared-resource.c'))
+ util_ss.add(files('qemu-co-timeout.c'))
util_ss.add(files('thread-pool.c', 'qemu-timer.c'))
util_ss.add(files('readline.c'))
util_ss.add(files('throttle.c'))
diff --git a/util/qemu-co-timeout.c b/util/qemu-co-timeout.c
new file mode 100644
index 0000000000..00cd335649
--- /dev/null
+++ b/util/qemu-co-timeout.c
@@ -0,0 +1,89 @@
+/*
+ * Helper functionality for distributing a fixed total amount of
+ * an abstract resource among multiple coroutines.
+ *
+ * Copyright (c) 2022 Virtuozzo International GmbH
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/coroutine.h"
+#include "block/aio.h"
+
+typedef struct QemuCoTimeoutState {
+ CoroutineEntry *entry;
+ void *opaque;
+ QemuCoSleep sleep_state;
+ bool marker;
+ CleanupFunc *clean;
+} QemuCoTimeoutState;
+
+static void coroutine_fn qemu_co_timeout_entry(void *opaque)
+{
+ QemuCoTimeoutState *s = opaque;
+
+ s->entry(s->opaque);
+
+ if (s->marker) {
+ assert(!s->sleep_state.to_wake);
+ /* .marker set by qemu_co_timeout, it have been failed */
+ if (s->clean) {
+ s->clean(s->opaque);
+ }
+ g_free(s);
+ } else {
+ s->marker = true;
+ qemu_co_sleep_wake(&s->sleep_state);
+ }
+}
+
+int coroutine_fn qemu_co_timeout(CoroutineEntry *entry, void *opaque,
+ uint64_t timeout_ns, CleanupFunc clean)
+{
+ QemuCoTimeoutState *s;
+ Coroutine *co;
+
+ if (timeout_ns == 0) {
+ entry(opaque);
+ return 0;
+ }
+
+ s = g_new(QemuCoTimeoutState, 1);
+ *s = (QemuCoTimeoutState) {
+ .entry = entry,
+ .opaque = opaque,
+ .clean = clean
+ };
+
+ co = qemu_coroutine_create(qemu_co_timeout_entry, s);
+
+ aio_co_enter(qemu_get_current_aio_context(), co);
+ qemu_co_sleep_ns_wakeable(&s->sleep_state, QEMU_CLOCK_REALTIME, timeout_ns);
+
+ if (s->marker) {
+ /* .marker set by qemu_co_timeout_entry, success */
+ g_free(s);
+ return 0;
+ }
+
+ /* Don't free s, as we can't cancel qemu_co_timeout_entry execution */
+ s->marker = true;
+ return -ETIMEDOUT;
+}
--
2.35.1
On 01.04.22 11:19, Vladimir Sementsov-Ogievskiy wrote: > Add new API, to make a time limited call of the coroutine. > > Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@openvz.org> > --- > include/qemu/coroutine.h | 13 ++++++ > util/meson.build | 1 + > util/qemu-co-timeout.c | 89 ++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 103 insertions(+) > create mode 100644 util/qemu-co-timeout.c I don’t really understand what this does. Intuitively, I would have assumed this makes the first yield of the respective coroutine not return when the timeout has elapsed, and instead, we switch back to qemu_co_timeout(), which returns to its callers. But it looks like when this yield happens and we switch back to qemu_co_timeout(), the coroutine actually isn’t cancelled, and will just continue running, actually. Is that right? On first look, this looks like it’ll be quite difficult to think about what happens when this is used, because the coroutine in question is no longer run in sequence with its caller, but actually might run in parallel (even though it’s still a coroutine, so it’ll remain cooperative multitasking).
01.04.2022 16:13, Hanna Reitz wrote: > On 01.04.22 11:19, Vladimir Sementsov-Ogievskiy wrote: >> Add new API, to make a time limited call of the coroutine. >> >> Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@openvz.org> >> --- >> include/qemu/coroutine.h | 13 ++++++ >> util/meson.build | 1 + >> util/qemu-co-timeout.c | 89 ++++++++++++++++++++++++++++++++++++++++ >> 3 files changed, 103 insertions(+) >> create mode 100644 util/qemu-co-timeout.c > > I don’t really understand what this does. Intuitively, I would have assumed this makes the first yield of the respective coroutine not return when the timeout has elapsed, and instead, we switch back to qemu_co_timeout(), which returns to its callers. > > But it looks like when this yield happens and we switch back to qemu_co_timeout(), the coroutine actually isn’t cancelled, and will just continue running, actually. Is that right? On first look, this looks like it’ll be quite difficult to think about what happens when this is used, because the coroutine in question is no longer run in sequence with its caller, but actually might run in parallel (even though it’s still a coroutine, so it’ll remain cooperative multitasking). > Yes, the coroutine continue execution in parallel. That's a generic interface, and there is no way to "cancel" generic coroutine. So, caller should care about it. -- Best regards, Vladimir
© 2016 - 2026 Red Hat, Inc.