scx_task_iter's cgroup-scoped mode can return tasks whose sched_ext_dead() has already completed: cgroup_task_dead() removes from cset->tasks after sched_ext_dead() in finish_task_switch() and is irq-work deferred on PREEMPT_RT. The global mode is fine - sched_ext_dead() removes from scx_tasks via list_del_init() first. Callers (sub-sched enable prep/abort/apply, scx_sub_disable(), scx_fail_parent()) assume returned tasks are still on @sch and trip WARN_ON_ONCE() or operate on torn-down state otherwise. Set %SCX_TASK_OFF_TASKS in sched_ext_dead() under @p's rq lock and have scx_task_iter_next_locked() skip flagged tasks under the same lock. Setter and reader serialize on the per-task rq lock - no race.
Showing the changes to kernel (1 of 2 files). Show all
Unified · Split
| @@ -928,16 +928,27 @@ static struct task_struct *scx_task_iter_next_locked(struct scx_task_iter *iter) · 927 unmodified lines | ||
| 928 | 928 | * |
| 929 | 929 | * Test for idle_sched_class as only init_tasks are on it. |
| 930 | 930 | */ |
| 931 | − if (p->sched_class != &idle_sched_class) | |
| 932 | − break; | |
| 933 | − } | |
| 934 | − if (!p) | |
| 935 | − return NULL; | |
| 931 | + if (p->sched_class == &idle_sched_class) | |
| 932 | + continue; | |
| 936 | 933 | |
| 937 | − iter->rq = task_rq_lock(p, &iter->rf); | |
| 938 | − iter->locked_task = p; | |
| 934 | + iter->rq = task_rq_lock(p, &iter->rf); | |
| 935 | + iter->locked_task = p; | |
| 939 | 936 | |
| 940 | − return p; | |
| 937 | + /* | |
| 938 | + * cgroup_task_dead() removes the dead tasks from cset->tasks | |
| 939 | + * after sched_ext_dead() and cgroup iteration may see tasks | |
| 940 | + * which already finished sched_ext_dead(). %SCX_TASK_OFF_TASKS | |
| 941 | + * is set by sched_ext_dead() under @p's rq lock. Test it to | |
| 942 | + * avoid visiting tasks which are already dead from SCX POV. | |
| 943 | + */ | |
| 944 | + if (p->scx.flags & SCX_TASK_OFF_TASKS) { | |
| 945 | + __scx_task_iter_rq_unlock(iter); | |
| 946 | + continue; | |
| 947 | + } | |
| 948 | + | |
| 949 | + return p; | |
| 950 | + } | |
| 951 | + return NULL; | |
| 941 | 952 | } |
| 942 | 953 | |
| 943 | 954 | /** |
| @@ -3850,6 +3861,11 @@ void sched_ext_dead(struct task_struct *p) · 2906 unmodified lines | ||
| 3850 | 3861 | /* |
| 3851 | 3862 | * @p is off scx_tasks and wholly ours. scx_root_enable()'s READY -> |
| 3852 | 3863 | * ENABLED transitions can't race us. Disable ops for @p. |
| 3864 | + * | |
| 3865 | + * %SCX_TASK_OFF_TASKS synchronizes against cgroup task iteration - see | |
| 3866 | + * scx_task_iter_next_locked(). NONE tasks need no marking: cgroup | |
| 3867 | + * iteration is only used from sub-sched paths, which require root | |
| 3868 | + * enabled. Root enable transitions every live task to at least READY. | |
| 3853 | 3869 | */ |
| 3854 | 3870 | if (scx_get_task_state(p) != SCX_TASK_NONE) { |
| 3855 | 3871 | struct rq_flags rf; |
| @@ -3857,6 +3873,7 @@ void sched_ext_dead(struct task_struct *p) · 1 unmodified line | ||
| 3857 | 3873 | |
| 3858 | 3874 | rq = task_rq_lock(p, &rf); |
| 3859 | 3875 | scx_disable_and_exit_task(scx_task_sched(p), p); |
| 3876 | + p->scx.flags |= SCX_TASK_OFF_TASKS; | |
| 3860 | 3877 | task_rq_unlock(rq, p, &rf); |
| 3861 | 3878 | } |
| 3862 | 3879 | } |