diff mbox series

[RFC,v4,01/12] kthread: Add kthread_queue_flush_work()

Message ID 20200508204751.155488-2-lyude@redhat.com (mailing list archive)
State New, archived
Headers show
Series drm/nouveau: Introduce CRC support for gf119+ | expand

Commit Message

Lyude Paul May 8, 2020, 8:46 p.m. UTC
Currently, it's only possible to flush on a kthread_work in contexts
where it's possible to block. This can be kind of painful though when
trying to implement new types of delayed work which use kthread_work,
since it means we'd need to drop any spinlocks for new delayed work
implementations before we can actually call kthread_flush_work().

In the time between dropping locks and calling kthread_flush_work(), the
work might have already executed once and have gotten re-queued by the
time we're ready. This would mean that once the user finally executes
kthread_flush_work(), we'd accidentally wait for someone else's queued
work instead of our own.

For DRM vblank works it's preferable that we just return immediately
during such races, instead of blocking on the re-queue. Additionally, we
also want to be able to use kthread_flush_work structs in our own
contexts so that we can block until a vblank work's target vblank has
passed, _and_ said work has executed once since. And finally, we also
want to be able to finish flushing on a work early if it's been
cancelled at any point (e.g. before or after it's actually been queued
on the kthread_worker).

So, let's make all of these things possible by exposing struct
kthread_flush_work, and then splitting kthread_flush_work() into two
functions: kthread_queue_flush_work(); which handles possibly queuing up
the kthread_flush_work on the work's respective kthread_worker, and
kthread_flush_work(); which performs a simple synchronous flush just
like before. We also add a DEFINE_KTHREAD_FLUSH_WORK() macro, which
simply initializes a kthread_flush_work struct inline (I can't see
anyone needing to use a kthread_flush_work that gets used outside of the
scope of a single function, and that seems like it would be a bit
overkill anyway).

Cc: Daniel Vetter <daniel@ffwll.ch>
Cc: Tejun Heo <tj@kernel.org>
Cc: Ville Syrjälä <ville.syrjala@linux.intel.com>
Cc: dri-devel@lists.freedesktop.org
Cc: nouveau@lists.freedesktop.org
Signed-off-by: Lyude Paul <lyude@redhat.com>
---
 include/linux/kthread.h | 16 ++++++++
 kernel/kthread.c        | 87 ++++++++++++++++++++++++++---------------
 2 files changed, 71 insertions(+), 32 deletions(-)

Comments

Tejun Heo May 11, 2020, 2:49 p.m. UTC | #1
Hello,

On Fri, May 08, 2020 at 04:46:51PM -0400, Lyude Paul wrote:
> +bool kthread_queue_flush_work(struct kthread_work *work,
> +			      struct kthread_flush_work *fwork);
> +void __kthread_flush_work_fn(struct kthread_work *work);

As an exposed interface, this doesn't seem great. What the user wants to say
is "wait for the current instance of this guy" and the interface is asking
them to queue an extra work item whose queueing return state should be
checked and depending on that result wait on its internal completion.

I'm skeptical this is a good idea in general given that unless you define
"this instance" at the time of queueing the work item which is being
waited-upon, there's no way to guarantee that the instance you're queueing
the flush work item on is the instance you want unless the queuer is holding
external synchronization which prevents the instance from running. That's a
really confusing semantics to expose in the interface.

What the above means is that the ordering that you want is only defined
through your own locking and that maybe suggests that the sequencing should
be implemented on that side too. It may be a bit more code but a sequence
counter + wait queue might be the better solution here.

Thanks.
Daniel Vetter May 11, 2020, 3:02 p.m. UTC | #2
On Mon, May 11, 2020 at 4:49 PM Tejun Heo <tj@kernel.org> wrote:
>
> Hello,
>
> On Fri, May 08, 2020 at 04:46:51PM -0400, Lyude Paul wrote:
> > +bool kthread_queue_flush_work(struct kthread_work *work,
> > +                           struct kthread_flush_work *fwork);
> > +void __kthread_flush_work_fn(struct kthread_work *work);
>
> As an exposed interface, this doesn't seem great. What the user wants to say
> is "wait for the current instance of this guy" and the interface is asking
> them to queue an extra work item whose queueing return state should be
> checked and depending on that result wait on its internal completion.
>
> I'm skeptical this is a good idea in general given that unless you define
> "this instance" at the time of queueing the work item which is being
> waited-upon, there's no way to guarantee that the instance you're queueing
> the flush work item on is the instance you want unless the queuer is holding
> external synchronization which prevents the instance from running. That's a
> really confusing semantics to expose in the interface.
>
> What the above means is that the ordering that you want is only defined
> through your own locking and that maybe suggests that the sequencing should
> be implemented on that side too. It may be a bit more code but a sequence
> counter + wait queue might be the better solution here.

Aside from this, flush_$stuff interfaces are very easy to deadlock.
That's why e.g. flush_work() for normal workqueues has lockdep
annotations (lockdep doesn't see through wait/wake_up dependencies
without some help because cross-release didn't land for real). So I
think if we need something like this, it needs to be a lot more
explicit, and come with the right lockdep annotations.
-Daniel
diff mbox series

Patch

diff --git a/include/linux/kthread.h b/include/linux/kthread.h
index 8bbcaad7ef0f..0006540ce7f9 100644
--- a/include/linux/kthread.h
+++ b/include/linux/kthread.h
@@ -105,6 +105,11 @@  struct kthread_delayed_work {
 	struct timer_list timer;
 };
 
+struct kthread_flush_work {
+	struct kthread_work	work;
+	struct completion	done;
+};
+
 #define KTHREAD_WORKER_INIT(worker)	{				\
 	.lock = __RAW_SPIN_LOCK_UNLOCKED((worker).lock),		\
 	.work_list = LIST_HEAD_INIT((worker).work_list),		\
@@ -122,6 +127,11 @@  struct kthread_delayed_work {
 				     TIMER_IRQSAFE),			\
 	}
 
+#define KTHREAD_FLUSH_WORK_INIT(fwork) { \
+	KTHREAD_WORK_INIT((fwork).work, __kthread_flush_work_fn), \
+	COMPLETION_INITIALIZER_ONSTACK((fwork).done), \
+	}
+
 #define DEFINE_KTHREAD_WORKER(worker)					\
 	struct kthread_worker worker = KTHREAD_WORKER_INIT(worker)
 
@@ -132,6 +142,9 @@  struct kthread_delayed_work {
 	struct kthread_delayed_work dwork =				\
 		KTHREAD_DELAYED_WORK_INIT(dwork, fn)
 
+#define DEFINE_KTHREAD_FLUSH_WORK(fwork) \
+	struct kthread_flush_work fwork = KTHREAD_FLUSH_WORK_INIT(fwork);
+
 /*
  * kthread_worker.lock needs its own lockdep class key when defined on
  * stack with lockdep enabled.  Use the following macros in such cases.
@@ -190,6 +203,9 @@  bool kthread_mod_delayed_work(struct kthread_worker *worker,
 			      struct kthread_delayed_work *dwork,
 			      unsigned long delay);
 
+bool kthread_queue_flush_work(struct kthread_work *work,
+			      struct kthread_flush_work *fwork);
+void __kthread_flush_work_fn(struct kthread_work *work);
 void kthread_flush_work(struct kthread_work *work);
 void kthread_flush_worker(struct kthread_worker *worker);
 
diff --git a/kernel/kthread.c b/kernel/kthread.c
index bfbfa481be3a..c1f8ec9d5836 100644
--- a/kernel/kthread.c
+++ b/kernel/kthread.c
@@ -943,52 +943,78 @@  bool kthread_queue_delayed_work(struct kthread_worker *worker,
 }
 EXPORT_SYMBOL_GPL(kthread_queue_delayed_work);
 
-struct kthread_flush_work {
-	struct kthread_work	work;
-	struct completion	done;
-};
-
-static void kthread_flush_work_fn(struct kthread_work *work)
-{
-	struct kthread_flush_work *fwork =
-		container_of(work, struct kthread_flush_work, work);
-	complete(&fwork->done);
-}
-
 /**
- * kthread_flush_work - flush a kthread_work
- * @work: work to flush
+ * kthread_queue_flush_work - try queuing a kthread_flush_work after a
+ * queued kthread_work to synchronize with later.
+ * @work: The &kthread_work to synchronize with later
+ * @fwork: The &kthread_flush_work to queue
+ *
+ * When working with &kthread_work structs in contexts where sleeping isn't
+ * possible it may be desirable to synchronize with a &kthread_work that's
+ * currently queued, but only after we've entered a context where it's safe to
+ * sleep again, and while making sure we don't block on any later re-queues of
+ * the work.
+ *
+ * If @work is queued or executing when kthread_queue_flush_work() is called,
+ * @fwork will be scheduled for execution immediately after @work. The caller
+ * can then later synchronize on @fwork.done, which will complete once @work
+ * has executed once or been cancelled since kthread_queue_flush_work() was
+ * called.
+ *
+ * Returns: %true% if @fwork was queued,and the caller needs to call
+ * wait_for_completion() on @fwork.done to finish synchronizing, %false%
+ * otherwise.
  *
- * If @work is queued or executing, wait for it to finish execution.
  */
-void kthread_flush_work(struct kthread_work *work)
+bool kthread_queue_flush_work(struct kthread_work *work,
+			      struct kthread_flush_work *fwork)
 {
-	struct kthread_flush_work fwork = {
-		KTHREAD_WORK_INIT(fwork.work, kthread_flush_work_fn),
-		COMPLETION_INITIALIZER_ONSTACK(fwork.done),
-	};
 	struct kthread_worker *worker;
-	bool noop = false;
+	unsigned long flags;
+	bool queued = true;
 
 	worker = work->worker;
 	if (!worker)
-		return;
+		return false;
 
-	raw_spin_lock_irq(&worker->lock);
+	raw_spin_lock_irqsave(&worker->lock, flags);
 	/* Work must not be used with >1 worker, see kthread_queue_work(). */
 	WARN_ON_ONCE(work->worker != worker);
 
 	if (!list_empty(&work->node))
-		kthread_insert_work(worker, &fwork.work, work->node.next);
+		kthread_insert_work(worker, &fwork->work, work->node.next);
 	else if (worker->current_work == work)
-		kthread_insert_work(worker, &fwork.work,
+		kthread_insert_work(worker, &fwork->work,
 				    worker->work_list.next);
 	else
-		noop = true;
+		queued = false;
 
-	raw_spin_unlock_irq(&worker->lock);
+	raw_spin_unlock_irqrestore(&worker->lock, flags);
+	return queued;
+}
+EXPORT_SYMBOL_GPL(kthread_queue_flush_work);
+
+void __kthread_flush_work_fn(struct kthread_work *work)
+{
+	struct kthread_flush_work *fwork =
+		container_of(work, struct kthread_flush_work, work);
+	complete(&fwork->done);
+}
+EXPORT_SYMBOL_GPL(__kthread_flush_work_fn);
+
+/**
+ * kthread_flush_work - flush a kthread_work
+ * @work: work to flush
+ *
+ * If @work is queued or executing, wait for it to finish execution.
+ */
+void kthread_flush_work(struct kthread_work *work)
+{
+	bool queued;
+	DEFINE_KTHREAD_FLUSH_WORK(fwork);
 
-	if (!noop)
+	queued = kthread_queue_flush_work(work, &fwork);
+	if (queued)
 		wait_for_completion(&fwork.done);
 }
 EXPORT_SYMBOL_GPL(kthread_flush_work);
@@ -1170,10 +1196,7 @@  EXPORT_SYMBOL_GPL(kthread_cancel_delayed_work_sync);
  */
 void kthread_flush_worker(struct kthread_worker *worker)
 {
-	struct kthread_flush_work fwork = {
-		KTHREAD_WORK_INIT(fwork.work, kthread_flush_work_fn),
-		COMPLETION_INITIALIZER_ONSTACK(fwork.done),
-	};
+	DEFINE_KTHREAD_FLUSH_WORK(fwork);
 
 	kthread_queue_work(worker, &fwork.work);
 	wait_for_completion(&fwork.done);