mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2025-01-13 19:50:25 +03:00
Make taskq_wait() block until the queue is empty
Under Illumos taskq_wait() returns when there are no more tasks in the queue. This behavior differs from ZoL and FreeBSD where taskq_wait() returns when all the tasks in the queue at the beginning of the taskq_wait() call are complete. New tasks added whilst taskq_wait() is running will be ignored. This difference in semantics makes it possible that new subtle issues could be introduced when porting changes from Illumos. To avoid that possibility the taskq_wait() function is being updated such that it blocks until the queue in empty. The previous behavior remains available through the taskq_wait_outstanding() interface. Note that this function was previously called taskq_wait_all() but has been renamed to avoid confusion. Signed-off-by: Chris Dunlop <chris@onthe.net.au> Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov> Closes #455
This commit is contained in:
parent
dc5e8b7041
commit
a876b0305e
@ -119,7 +119,7 @@ extern void taskq_init_ent(taskq_ent_t *);
|
|||||||
extern taskq_t *taskq_create(const char *, int, pri_t, int, int, uint_t);
|
extern taskq_t *taskq_create(const char *, int, pri_t, int, int, uint_t);
|
||||||
extern void taskq_destroy(taskq_t *);
|
extern void taskq_destroy(taskq_t *);
|
||||||
extern void taskq_wait_id(taskq_t *, taskqid_t);
|
extern void taskq_wait_id(taskq_t *, taskqid_t);
|
||||||
extern void taskq_wait_all(taskq_t *, taskqid_t);
|
extern void taskq_wait_outstanding(taskq_t *, taskqid_t);
|
||||||
extern void taskq_wait(taskq_t *);
|
extern void taskq_wait(taskq_t *);
|
||||||
extern int taskq_cancel_id(taskq_t *, taskqid_t);
|
extern int taskq_cancel_id(taskq_t *, taskqid_t);
|
||||||
extern int taskq_member(taskq_t *, void *);
|
extern int taskq_member(taskq_t *, void *);
|
||||||
|
@ -327,6 +327,33 @@ taskq_find(taskq_t *tq, taskqid_t id, int *active)
|
|||||||
return (NULL);
|
return (NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Theory for the taskq_wait_id(), taskq_wait_outstanding(), and
|
||||||
|
* taskq_wait() functions below.
|
||||||
|
*
|
||||||
|
* Taskq waiting is accomplished by tracking the lowest outstanding task
|
||||||
|
* id and the next available task id. As tasks are dispatched they are
|
||||||
|
* added to the tail of the pending, priority, or delay lists. As worker
|
||||||
|
* threads become available the tasks are removed from the heads of these
|
||||||
|
* lists and linked to the worker threads. This ensures the lists are
|
||||||
|
* kept sorted by lowest to highest task id.
|
||||||
|
*
|
||||||
|
* Therefore the lowest outstanding task id can be quickly determined by
|
||||||
|
* checking the head item from all of these lists. This value is stored
|
||||||
|
* with the taskq as the lowest id. It only needs to be recalculated when
|
||||||
|
* either the task with the current lowest id completes or is canceled.
|
||||||
|
*
|
||||||
|
* By blocking until the lowest task id exceeds the passed task id the
|
||||||
|
* taskq_wait_outstanding() function can be easily implemented. Similarly,
|
||||||
|
* by blocking until the lowest task id matches the next task id taskq_wait()
|
||||||
|
* can be implemented.
|
||||||
|
*
|
||||||
|
* Callers should be aware that when there are multiple worked threads it
|
||||||
|
* is possible for larger task ids to complete before smaller ones. Also
|
||||||
|
* when the taskq contains delay tasks with small task ids callers may
|
||||||
|
* block for a considerable length of time waiting for them to expire and
|
||||||
|
* execute.
|
||||||
|
*/
|
||||||
static int
|
static int
|
||||||
taskq_wait_id_check(taskq_t *tq, taskqid_t id)
|
taskq_wait_id_check(taskq_t *tq, taskqid_t id)
|
||||||
{
|
{
|
||||||
@ -351,34 +378,8 @@ taskq_wait_id(taskq_t *tq, taskqid_t id)
|
|||||||
}
|
}
|
||||||
EXPORT_SYMBOL(taskq_wait_id);
|
EXPORT_SYMBOL(taskq_wait_id);
|
||||||
|
|
||||||
/*
|
|
||||||
* The taskq_wait() function will block until all previously submitted
|
|
||||||
* tasks have been completed. A previously submitted task is defined as
|
|
||||||
* a task with a lower task id than the current task queue id. Note that
|
|
||||||
* all task id's are assigned monotonically at dispatch time.
|
|
||||||
*
|
|
||||||
* Waiting for all previous tasks to complete is accomplished by tracking
|
|
||||||
* the lowest outstanding task id. As tasks are dispatched they are added
|
|
||||||
* added to the tail of the pending, priority, or delay lists. And as
|
|
||||||
* worker threads become available the tasks are removed from the heads
|
|
||||||
* of these lists and linked to the worker threads. This ensures the
|
|
||||||
* lists are kept in lowest to highest task id order.
|
|
||||||
*
|
|
||||||
* Therefore the lowest outstanding task id can be quickly determined by
|
|
||||||
* checking the head item from all of these lists. This value is stored
|
|
||||||
* with the task queue as the lowest id. It only needs to be recalculated
|
|
||||||
* when either the task with the current lowest id completes or is canceled.
|
|
||||||
*
|
|
||||||
* By blocking until the lowest task id exceeds the current task id when
|
|
||||||
* the function was called we ensure all previous tasks have completed.
|
|
||||||
*
|
|
||||||
* NOTE: When there are multiple worked threads it is possible for larger
|
|
||||||
* task ids to complete before smaller ones. Conversely when the task
|
|
||||||
* queue contains delay tasks with small task ids, you may block for a
|
|
||||||
* considerable length of time waiting for them to expire and execute.
|
|
||||||
*/
|
|
||||||
static int
|
static int
|
||||||
taskq_wait_check(taskq_t *tq, taskqid_t id)
|
taskq_wait_outstanding_check(taskq_t *tq, taskqid_t id)
|
||||||
{
|
{
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
@ -389,26 +390,42 @@ taskq_wait_check(taskq_t *tq, taskqid_t id)
|
|||||||
return (rc);
|
return (rc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The taskq_wait_outstanding() function will block until all tasks with a
|
||||||
|
* lower taskqid than the passed 'id' have been completed. Note that all
|
||||||
|
* task id's are assigned monotonically at dispatch time. Zero may be
|
||||||
|
* passed for the id to indicate all tasks dispatch up to this point,
|
||||||
|
* but not after, should be waited for.
|
||||||
|
*/
|
||||||
void
|
void
|
||||||
taskq_wait_all(taskq_t *tq, taskqid_t id)
|
taskq_wait_outstanding(taskq_t *tq, taskqid_t id)
|
||||||
{
|
{
|
||||||
wait_event(tq->tq_wait_waitq, taskq_wait_check(tq, id));
|
wait_event(tq->tq_wait_waitq,
|
||||||
|
taskq_wait_outstanding_check(tq, id ? id : tq->tq_next_id - 1));
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(taskq_wait_all);
|
EXPORT_SYMBOL(taskq_wait_outstanding);
|
||||||
|
|
||||||
|
static int
|
||||||
|
taskq_wait_check(taskq_t *tq)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&tq->tq_lock, tq->tq_lock_flags);
|
||||||
|
rc = (tq->tq_lowest_id == tq->tq_next_id);
|
||||||
|
spin_unlock_irqrestore(&tq->tq_lock, tq->tq_lock_flags);
|
||||||
|
|
||||||
|
return (rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The taskq_wait() function will block until the taskq is empty.
|
||||||
|
* This means that if a taskq re-dispatches work to itself taskq_wait()
|
||||||
|
* callers will block indefinitely.
|
||||||
|
*/
|
||||||
void
|
void
|
||||||
taskq_wait(taskq_t *tq)
|
taskq_wait(taskq_t *tq)
|
||||||
{
|
{
|
||||||
taskqid_t id;
|
wait_event(tq->tq_wait_waitq, taskq_wait_check(tq));
|
||||||
|
|
||||||
ASSERT(tq);
|
|
||||||
|
|
||||||
/* Wait for the largest outstanding taskqid */
|
|
||||||
spin_lock_irqsave(&tq->tq_lock, tq->tq_lock_flags);
|
|
||||||
id = tq->tq_next_id - 1;
|
|
||||||
spin_unlock_irqrestore(&tq->tq_lock, tq->tq_lock_flags);
|
|
||||||
|
|
||||||
taskq_wait_all(tq, id);
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(taskq_wait);
|
EXPORT_SYMBOL(taskq_wait);
|
||||||
|
|
||||||
|
@ -588,10 +588,10 @@ splat_taskq_test4(struct file *file, void *arg)
|
|||||||
* next pending task as soon as it completes its current task. This
|
* next pending task as soon as it completes its current task. This
|
||||||
* means that tasks do not strictly complete in order in which they
|
* means that tasks do not strictly complete in order in which they
|
||||||
* were dispatched (increasing task id). This is fine but we need to
|
* were dispatched (increasing task id). This is fine but we need to
|
||||||
* verify that taskq_wait_all() blocks until the passed task id and all
|
* verify taskq_wait_outstanding() blocks until the passed task id and
|
||||||
* lower task ids complete. We do this by dispatching the following
|
* all lower task ids complete. We do this by dispatching the following
|
||||||
* specific sequence of tasks each of which block for N time units.
|
* specific sequence of tasks each of which block for N time units.
|
||||||
* We then use taskq_wait_all() to unblock at specific task id and
|
* We then use taskq_wait_outstanding() to unblock at specific task id and
|
||||||
* verify the only the expected task ids have completed and in the
|
* verify the only the expected task ids have completed and in the
|
||||||
* correct order. The two cases of interest are:
|
* correct order. The two cases of interest are:
|
||||||
*
|
*
|
||||||
@ -602,17 +602,17 @@ splat_taskq_test4(struct file *file, void *arg)
|
|||||||
*
|
*
|
||||||
* The following table shows each task id and how they will be
|
* The following table shows each task id and how they will be
|
||||||
* scheduled. Each rows represent one time unit and each column
|
* scheduled. Each rows represent one time unit and each column
|
||||||
* one of the three worker threads. The places taskq_wait_all()
|
* one of the three worker threads. The places taskq_wait_outstanding()
|
||||||
* must unblock for a specific id are identified as well as the
|
* must unblock for a specific id are identified as well as the
|
||||||
* task ids which must have completed and their order.
|
* task ids which must have completed and their order.
|
||||||
*
|
*
|
||||||
* +-----+ <--- taskq_wait_all(tq, 8) unblocks
|
* +-----+ <--- taskq_wait_outstanding(tq, 8) unblocks
|
||||||
* | | Required Completion Order: 1,2,4,5,3,8,6,7
|
* | | Required Completion Order: 1,2,4,5,3,8,6,7
|
||||||
* +-----+ |
|
* +-----+ |
|
||||||
* | | |
|
* | | |
|
||||||
* | | +-----+
|
* | | +-----+
|
||||||
* | | | 8 |
|
* | | | 8 |
|
||||||
* | | +-----+ <--- taskq_wait_all(tq, 3) unblocks
|
* | | +-----+ <--- taskq_wait_outstanding(tq, 3) unblocks
|
||||||
* | | 7 | | Required Completion Order: 1,2,4,5,3
|
* | | 7 | | Required Completion Order: 1,2,4,5,3
|
||||||
* | +-----+ |
|
* | +-----+ |
|
||||||
* | 6 | | |
|
* | 6 | | |
|
||||||
@ -755,13 +755,13 @@ splat_taskq_test5_impl(struct file *file, void *arg, boolean_t prealloc)
|
|||||||
|
|
||||||
splat_vprint(file, SPLAT_TASKQ_TEST5_NAME, "Taskq '%s' "
|
splat_vprint(file, SPLAT_TASKQ_TEST5_NAME, "Taskq '%s' "
|
||||||
"waiting for taskqid %d completion\n", tq_arg.name, 3);
|
"waiting for taskqid %d completion\n", tq_arg.name, 3);
|
||||||
taskq_wait_all(tq, 3);
|
taskq_wait_outstanding(tq, 3);
|
||||||
if ((rc = splat_taskq_test_order(&tq_arg, order1)))
|
if ((rc = splat_taskq_test_order(&tq_arg, order1)))
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
splat_vprint(file, SPLAT_TASKQ_TEST5_NAME, "Taskq '%s' "
|
splat_vprint(file, SPLAT_TASKQ_TEST5_NAME, "Taskq '%s' "
|
||||||
"waiting for taskqid %d completion\n", tq_arg.name, 8);
|
"waiting for taskqid %d completion\n", tq_arg.name, 8);
|
||||||
taskq_wait_all(tq, 8);
|
taskq_wait_outstanding(tq, 8);
|
||||||
rc = splat_taskq_test_order(&tq_arg, order2);
|
rc = splat_taskq_test_order(&tq_arg, order2);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
@ -923,7 +923,7 @@ splat_taskq_test6_impl(struct file *file, void *arg, boolean_t prealloc)
|
|||||||
splat_vprint(file, SPLAT_TASKQ_TEST6_NAME, "Taskq '%s' "
|
splat_vprint(file, SPLAT_TASKQ_TEST6_NAME, "Taskq '%s' "
|
||||||
"waiting for taskqid %d completion\n", tq_arg.name,
|
"waiting for taskqid %d completion\n", tq_arg.name,
|
||||||
SPLAT_TASKQ_ORDER_MAX);
|
SPLAT_TASKQ_ORDER_MAX);
|
||||||
taskq_wait_all(tq, SPLAT_TASKQ_ORDER_MAX);
|
taskq_wait_outstanding(tq, SPLAT_TASKQ_ORDER_MAX);
|
||||||
rc = splat_taskq_test_order(&tq_arg, order);
|
rc = splat_taskq_test_order(&tq_arg, order);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
@ -1030,7 +1030,7 @@ splat_taskq_test7_impl(struct file *file, void *arg, boolean_t prealloc)
|
|||||||
if (tq_arg->flag == 0) {
|
if (tq_arg->flag == 0) {
|
||||||
splat_vprint(file, SPLAT_TASKQ_TEST7_NAME,
|
splat_vprint(file, SPLAT_TASKQ_TEST7_NAME,
|
||||||
"Taskq '%s' waiting\n", tq_arg->name);
|
"Taskq '%s' waiting\n", tq_arg->name);
|
||||||
taskq_wait_all(tq, SPLAT_TASKQ_DEPTH_MAX);
|
taskq_wait_outstanding(tq, SPLAT_TASKQ_DEPTH_MAX);
|
||||||
}
|
}
|
||||||
|
|
||||||
error = (tq_arg->depth == SPLAT_TASKQ_DEPTH_MAX ? 0 : -EINVAL);
|
error = (tq_arg->depth == SPLAT_TASKQ_DEPTH_MAX ? 0 : -EINVAL);
|
||||||
|
Loading…
Reference in New Issue
Block a user