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.
Showing the changes to kernel (1 of 1 file). Show all
Unified · Split
| @@ -5777,6 +5777,21 @@ static void scx_sub_disable(struct scx_sched *sch) · 5776 unmodified lines | ||
| 5777 | 5777 | } |
| 5778 | 5778 | |
| 5779 | 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 | 5795 | scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) { |
| 5781 | 5796 | /* |
| 5782 | 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 | 5806 | scx_set_task_state(p, SCX_TASK_READY); |
| 5792 | 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 | 5811 | put_task_struct(p); |
| 5797 | 5812 | } |
| 5798 | 5813 | scx_task_iter_stop(&sti); |
| @@ -7212,6 +7227,21 @@ static void scx_sub_enable_workfn(struct kthread_work *work) · 1413 unmodified lines | ||
| 7212 | 7227 | goto abort; |
| 7213 | 7228 | |
| 7214 | 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 | 7245 | p->scx.flags |= SCX_TASK_SUB_INIT; |
| 7216 | 7246 | task_rq_unlock(rq, p, &rf); |
| 7217 | 7247 | |