scx_sub_enable_workfn()'s init pass and scx_sub_disable() migration both drop the rq lock to call __scx_init_task() against the other sched. A TASK_DEAD @p can fall through sched_ext_dead() in that window. sched_ext_dead() runs ops.exit_task() on the sched @p was attached to, not on the sched whose init just completed, so the new allocation leaks. Reuse the DEAD signal set by sched_ext_dead(). After __scx_init_task() returns, take task_rq_lock(p) and check for DEAD; on hit, call scx_sub_init_cancel_task() against the sub sched the init ran for and drop @p; on miss, proceed as before.
Unified · Split
| @@ -5777,6 +5777,21 @@ static void scx_sub_disable(struct scx_sched *sch) · 5776 unmodified lines | |||
| 5777 | } | 5777 | } |
| 5778 | 5778 | ||
| 5779 | rq = task_rq_lock(p, &rf); | 5779 | rq = task_rq_lock(p, &rf); |
| 5780 | |||
| 5781 | if (scx_get_task_state(p) == SCX_TASK_DEAD) { | ||
| 5782 | /* | ||
| 5783 | * sched_ext_dead() raced us between __scx_init_task() | ||
| 5784 | * and this rq lock and ran exit_task() on @sch (the | ||
| 5785 | * sched @p was on at that point), not on $parent. | ||
| 5786 | * $parent's just-completed init is owed an exit_task() | ||
| 5787 | * and we issue it here. | ||
| 5788 | */ | ||
| 5789 | scx_sub_init_cancel_task(parent, p); | ||
| 5790 | task_rq_unlock(rq, p, &rf); | ||
| 5791 | put_task_struct(p); | ||
| 5792 | continue; | ||
| 5793 | } | ||
| 5794 | |||
| 5780 | scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) { | 5795 | scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) { |
| 5781 | /* | 5796 | /* |
| 5782 | * $p is initialized for $parent and still attached to | 5797 | * $p is initialized for $parent and still attached to |
| @@ -5791,8 +5806,8 @@ static void scx_sub_disable(struct scx_sched *sch) · 8 unmodified lines | |||
| 5791 | scx_set_task_state(p, SCX_TASK_READY); | 5806 | scx_set_task_state(p, SCX_TASK_READY); |
| 5792 | scx_enable_task(parent, p); | 5807 | scx_enable_task(parent, p); |
| 5793 | } | 5808 | } |
| 5794 | task_rq_unlock(rq, p, &rf); | ||
| 5795 | 5809 | ||
| 5810 | task_rq_unlock(rq, p, &rf); | ||
| 5796 | put_task_struct(p); | 5811 | put_task_struct(p); |
| 5797 | } | 5812 | } |
| 5798 | scx_task_iter_stop(&sti); | 5813 | scx_task_iter_stop(&sti); |
| @@ -7212,6 +7227,21 @@ static void scx_sub_enable_workfn(struct kthread_work *work) · 1413 unmodified lines | |||
| 7212 | goto abort; | 7227 | goto abort; |
| 7213 | 7228 | ||
| 7214 | rq = task_rq_lock(p, &rf); | 7229 | rq = task_rq_lock(p, &rf); |
| 7230 | |||
| 7231 | if (scx_get_task_state(p) == SCX_TASK_DEAD) { | ||
| 7232 | /* | ||
| 7233 | * sched_ext_dead() raced us between __scx_init_task() | ||
| 7234 | * and this rq lock and ran exit_task() on $parent (the | ||
| 7235 | * sched @p was on at that point), not on @sch. @sch's | ||
| 7236 | * just-completed init is owed an exit_task() and we | ||
| 7237 | * issue it here. | ||
| 7238 | */ | ||
| 7239 | scx_sub_init_cancel_task(sch, p); | ||
| 7240 | task_rq_unlock(rq, p, &rf); | ||
| 7241 | put_task_struct(p); | ||
| 7242 | continue; | ||
| 7243 | } | ||
| 7244 | |||
| 7215 | p->scx.flags |= SCX_TASK_SUB_INIT; | 7245 | p->scx.flags |= SCX_TASK_SUB_INIT; |
| 7216 | task_rq_unlock(rq, p, &rf); | 7246 | task_rq_unlock(rq, p, &rf); |
| 7217 | 7247 | ||