target: Fix race between multiple invocations of target_qf_do_work()
When work is scheduled with schedule_work(), the work can end up running on multiple CPUs at the same time -- this happens if the work is already running on one CPU and schedule_work() is called on another CPU. This leads to list corruption with target_qf_do_work(), which is roughly doing: spin_lock(...); list_for_each_entry_safe(...) { list_del(...); spin_unlock(...); // do stuff spin_lock(...); } With multiple CPUs running this code, one CPU can end up deleting the list entry that the other CPU is about to work on. Fix this by splicing the list entries onto a local list and then operating on that in the work function. This way, each invocation of target_qf_do_work() operates on its own local list and so multiple invocations don't corrupt each other's list. This also avoids dropping and reacquiring the lock for each list entry. Signed-off-by: Roland Dreier <roland@purestorage.com> Signed-off-by: Nicholas Bellinger <nab@linux-iscsi.org>
This commit is contained in:
parent
a7f934d4f1
commit
bcac364a24
1 changed files with 4 additions and 5 deletions
|
@ -977,15 +977,17 @@ static void target_qf_do_work(struct work_struct *work)
|
|||
{
|
||||
struct se_device *dev = container_of(work, struct se_device,
|
||||
qf_work_queue);
|
||||
LIST_HEAD(qf_cmd_list);
|
||||
struct se_cmd *cmd, *cmd_tmp;
|
||||
|
||||
spin_lock_irq(&dev->qf_cmd_lock);
|
||||
list_for_each_entry_safe(cmd, cmd_tmp, &dev->qf_cmd_list, se_qf_node) {
|
||||
list_splice_init(&dev->qf_cmd_list, &qf_cmd_list);
|
||||
spin_unlock_irq(&dev->qf_cmd_lock);
|
||||
|
||||
list_for_each_entry_safe(cmd, cmd_tmp, &qf_cmd_list, se_qf_node) {
|
||||
list_del(&cmd->se_qf_node);
|
||||
atomic_dec(&dev->dev_qf_count);
|
||||
smp_mb__after_atomic_dec();
|
||||
spin_unlock_irq(&dev->qf_cmd_lock);
|
||||
|
||||
pr_debug("Processing %s cmd: %p QUEUE_FULL in work queue"
|
||||
" context: %s\n", cmd->se_tfo->get_fabric_name(), cmd,
|
||||
|
@ -997,10 +999,7 @@ static void target_qf_do_work(struct work_struct *work)
|
|||
* has been added to head of queue
|
||||
*/
|
||||
transport_add_cmd_to_queue(cmd, cmd->t_state);
|
||||
|
||||
spin_lock_irq(&dev->qf_cmd_lock);
|
||||
}
|
||||
spin_unlock_irq(&dev->qf_cmd_lock);
|
||||
}
|
||||
|
||||
unsigned char *transport_dump_cmd_direction(struct se_cmd *cmd)
|
||||
|
|
Loading…
Reference in a new issue