diff options
Diffstat (limited to 'kernel')
-rw-r--r-- | kernel/workqueue.c | 32 |
1 files changed, 29 insertions, 3 deletions
diff --git a/kernel/workqueue.c b/kernel/workqueue.c index c68277c204ab..bce1074bdec1 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c | |||
@@ -534,11 +534,37 @@ static void __queue_work(unsigned int cpu, struct workqueue_struct *wq, | |||
534 | 534 | ||
535 | debug_work_activate(work); | 535 | debug_work_activate(work); |
536 | 536 | ||
537 | /* determine gcwq to use */ | 537 | /* |
538 | * Determine gcwq to use. SINGLE_CPU is inherently | ||
539 | * NON_REENTRANT, so test it first. | ||
540 | */ | ||
538 | if (!(wq->flags & WQ_SINGLE_CPU)) { | 541 | if (!(wq->flags & WQ_SINGLE_CPU)) { |
539 | /* just use the requested cpu for multicpu workqueues */ | 542 | struct global_cwq *last_gcwq; |
543 | |||
544 | /* | ||
545 | * It's multi cpu. If @wq is non-reentrant and @work | ||
546 | * was previously on a different cpu, it might still | ||
547 | * be running there, in which case the work needs to | ||
548 | * be queued on that cpu to guarantee non-reentrance. | ||
549 | */ | ||
540 | gcwq = get_gcwq(cpu); | 550 | gcwq = get_gcwq(cpu); |
541 | spin_lock_irqsave(&gcwq->lock, flags); | 551 | if (wq->flags & WQ_NON_REENTRANT && |
552 | (last_gcwq = get_work_gcwq(work)) && last_gcwq != gcwq) { | ||
553 | struct worker *worker; | ||
554 | |||
555 | spin_lock_irqsave(&last_gcwq->lock, flags); | ||
556 | |||
557 | worker = find_worker_executing_work(last_gcwq, work); | ||
558 | |||
559 | if (worker && worker->current_cwq->wq == wq) | ||
560 | gcwq = last_gcwq; | ||
561 | else { | ||
562 | /* meh... not running there, queue here */ | ||
563 | spin_unlock_irqrestore(&last_gcwq->lock, flags); | ||
564 | spin_lock_irqsave(&gcwq->lock, flags); | ||
565 | } | ||
566 | } else | ||
567 | spin_lock_irqsave(&gcwq->lock, flags); | ||
542 | } else { | 568 | } else { |
543 | unsigned int req_cpu = cpu; | 569 | unsigned int req_cpu = cpu; |
544 | 570 | ||