summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorKent Overstreet <kent.overstreet@linux.dev>2024-05-03 14:43:54 -0400
committerKent Overstreet <kent.overstreet@linux.dev>2024-05-08 17:29:22 -0400
commit4c5b7294dedbdfae46f77262c0744879bb9b7221 (patch)
tree250afe51caab7ba04bdd8cd0233871d6b19ca229 /lib
parent9a768ab75bef37eac23022f296e9be63b5f85565 (diff)
closures: closure_sync_timeout()
Add a new variant of closure_sync_timeout() that takes a timeout. Note that when this returns -ETIME the closure will still be waiting on something, i.e. it's not safe to return if you've got a stack allocated closure. Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
Diffstat (limited to 'lib')
-rw-r--r--lib/closure.c37
1 files changed, 37 insertions, 0 deletions
diff --git a/lib/closure.c b/lib/closure.c
index c16540552d61..07409e9e35a5 100644
--- a/lib/closure.c
+++ b/lib/closure.c
@@ -139,6 +139,43 @@ void __sched __closure_sync(struct closure *cl)
}
EXPORT_SYMBOL(__closure_sync);
+int __sched __closure_sync_timeout(struct closure *cl, unsigned long timeout)
+{
+ struct closure_syncer s = { .task = current };
+ int ret = 0;
+
+ cl->s = &s;
+ continue_at(cl, closure_sync_fn, NULL);
+
+ while (1) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ if (s.done)
+ break;
+ if (!timeout) {
+ /*
+ * Carefully undo the continue_at() - but only if it
+ * hasn't completed, i.e. the final closure_put() hasn't
+ * happened yet:
+ */
+ unsigned old, new, v = atomic_read(&cl->remaining);
+ do {
+ old = v;
+ if (!old || (old & CLOSURE_RUNNING))
+ goto success;
+
+ new = old + CLOSURE_REMAINING_INITIALIZER;
+ } while ((v = atomic_cmpxchg(&cl->remaining, old, new)) != old);
+ ret = -ETIME;
+ }
+
+ timeout = schedule_timeout(timeout);
+ }
+success:
+ __set_current_state(TASK_RUNNING);
+ return ret;
+}
+EXPORT_SYMBOL(__closure_sync_timeout);
+
#ifdef CONFIG_DEBUG_CLOSURES
static LIST_HEAD(closure_list);