Source-Changes-HG archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

[src/trunk]: src/sys/external/bsd/common/linux Fix flush_delayed_work semanti...



details:   https://anonhg.NetBSD.org/src/rev/bedd16161200
branches:  trunk
changeset: 366368:bedd16161200
user:      riastradh <riastradh%NetBSD.org@localhost>
date:      Mon Aug 27 15:05:44 2018 +0000

description:
Fix flush_delayed_work semantics: run it now if it was scheduled.

diffstat:

 sys/external/bsd/common/linux/linux_work.c |  83 +++++++++++++++--------------
 1 files changed, 42 insertions(+), 41 deletions(-)

diffs (126 lines):

diff -r 37bab2956bbe -r bedd16161200 sys/external/bsd/common/linux/linux_work.c
--- a/sys/external/bsd/common/linux/linux_work.c        Mon Aug 27 15:05:30 2018 +0000
+++ b/sys/external/bsd/common/linux/linux_work.c        Mon Aug 27 15:05:44 2018 +0000
@@ -1,4 +1,4 @@
-/*     $NetBSD: linux_work.c,v 1.37 2018/08/27 15:05:30 riastradh Exp $        */
+/*     $NetBSD: linux_work.c,v 1.38 2018/08/27 15:05:44 riastradh Exp $        */
 
 /*-
  * Copyright (c) 2018 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: linux_work.c,v 1.37 2018/08/27 15:05:30 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: linux_work.c,v 1.38 2018/08/27 15:05:44 riastradh Exp $");
 
 #include <sys/types.h>
 #include <sys/atomic.h>
@@ -1384,8 +1384,9 @@
 /*
  * flush_delayed_work(dw)
  *
- *     If dw is scheduled to run after a delay, cancel it.  If dw is
- *     queued or currently executing, wait for it to complete.
+ *     If dw is scheduled to run after a delay, queue it immediately
+ *     instead.  Then, if dw is queued or currently executing, wait
+ *     for it to complete.
  */
 void
 flush_delayed_work(struct delayed_work *dw)
@@ -1397,58 +1398,58 @@
                return;
 
        mutex_enter(&wq->wq_lock);
-       if (__predict_true(dw->work.work_queue == wq)) {
+       if (__predict_false(dw->work.work_queue != wq)) {
+               /*
+                * Moved off the queue already (and possibly to another
+                * queue, though that would be ill-advised), so it must
+                * have completed, and we have nothing more to do.
+                */
+       } else {
                switch (dw->dw_state) {
                case DELAYED_WORK_IDLE:
                        /*
                         * It has a workqueue assigned and the callout
                         * is idle, so it must be in progress or on the
-                        * queue.  In that case, wait for it to
-                        * complete.  Waiting for the whole queue to
-                        * flush is overkill, but doesn't hurt.
+                        * queue.  In that case, we'll wait for it to
+                        * complete.
                         */
-                       flush_workqueue_locked(wq);
                        break;
                case DELAYED_WORK_SCHEDULED:
-                       /*
-                        * If it is scheduled, mark it cancelled and
-                        * try to stop the callout before it starts.
-                        *
-                        * If it's too late and the callout has already
-                        * begun to execute, we must wait for it to
-                        * complete.  But we got in soon enough to ask
-                        * the callout not to run.
-                        *
-                        * If we stopped the callout before it started,
-                        * then we must destroy the callout and
-                        * dissociate it from the workqueue ourselves.
-                        */
-                       dw->dw_state = DELAYED_WORK_CANCELLED;
-                       if (!callout_halt(&dw->dw_callout, &wq->wq_lock))
-                               cancel_delayed_work_done(wq, dw);
-                       break;
                case DELAYED_WORK_RESCHEDULED:
-                       /*
-                        * If it is being rescheduled, the callout has
-                        * already fired.  We must ask it to cancel and
-                        * wait for it to complete.
-                        */
-                       dw->dw_state = DELAYED_WORK_CANCELLED;
-                       dw->dw_resched = -1;
-                       (void)callout_halt(&dw->dw_callout, &wq->wq_lock);
-                       break;
                case DELAYED_WORK_CANCELLED:
                        /*
-                        * If it is being cancelled, the callout has
-                        * already fired.  We need only wait for it to
-                        * complete.
+                        * The callout is scheduled, and may have even
+                        * started.  Mark it as scheduled so that if
+                        * the callout has fired it will queue the work
+                        * itself.  Try to stop the callout -- if we
+                        * can, queue the work now; if we can't, wait
+                        * for the callout to complete, which entails
+                        * queueing it.
                         */
-                       (void)callout_halt(&dw->dw_callout, &wq->wq_lock);
+                       dw->dw_state = DELAYED_WORK_SCHEDULED;
+                       if (!callout_halt(&dw->dw_callout, &wq->wq_lock)) {
+                               /*
+                                * We stopped it before it ran.  No
+                                * state change in the interim is
+                                * possible.  Destroy the callout and
+                                * queue it ourselves.
+                                */
+                               KASSERT(dw->dw_state ==
+                                   DELAYED_WORK_SCHEDULED);
+                               dw_callout_destroy(wq, dw);
+                               TAILQ_INSERT_TAIL(&wq->wq_queue, &dw->work,
+                                   work_entry);
+                               cv_broadcast(&wq->wq_cv);
+                       }
                        break;
                default:
-                       panic("invalid delayed work state: %d",
-                           dw->dw_state);
+                       panic("invalid delayed work state: %d", dw->dw_state);
                }
+               /*
+                * Waiting for the whole queue to flush is overkill,
+                * but doesn't hurt.
+                */
+               flush_workqueue_locked(wq);
        }
        mutex_exit(&wq->wq_lock);
 }



Home | Main Index | Thread Index | Old Index