unlink_dsq_...itch_rq_lock(9)

πŸ“¦ version 7.3.1, in nixpkgs unstable

okay so picture this: you're deep in the scheduler, you've got locks everywhere, and you need to move a task to a different DSQ without deadlocking yourself. bestie, this page is the whole choreography. πŸ’ƒ


✨ NAME ✨

unlink_dsq_and_switch_rq_lock β€” unlink a task and switch over to its rq lock. it's giving "break up with one lock, get with another." πŸ’”βž‘οΈπŸ’–


🌸 SYNOPSIS 🌸

bool unlink_dsq_and_switch_rq_lock (struct task_struct *p , struct
       scx_dispatch_q *dsq , struct rq *locked_rq , struct rq *src_rq );

πŸ¦‹ ARGUMENTS πŸ¦‹

  • p β€” the target task. our main character. πŸ‘‘
  • dsq β€” the locked DSQ that p is currently chilling on. πŸ”’
  • locked_rq β€” the rq that's locked right now.
  • src_rq β€” the rq p is currently on, and it's stable as long as dsq is locked.

πŸ’œ DESCRIPTION πŸ’œ

so you're calling this with dsq and locked_rq both locked. we wanna move p to a different DSQ β€” any local DSQ included β€” but we are not locking src_rq. and look, locking src_rq is literally required when you're transferring into a local DSQ. and even when you're transferring into a non-local DSQ? it's still better to use the same mechanism, because it protects against dequeues and keeps the invariant that p->scx.dsq can only change while src_rq is locked. and yeah, stuff like scx_dump_task depends on exactly that invariant. no cap. πŸ”₯

we want to grab src_rq, but that can deadlock if we try it while locking dsq. so the plan: unlink p from dsq, drop its lock, and then lock src_rq. but bestie 😭 this can race with dequeue, and dequeue can't drop the rq lock or fail β€” so we do a little dancing from our side. 🩰 genuinely iconic footwork.

here's the trick: p->scx.holding_cpu is set to this CPU before dsq is unlocked. if p gets dequeued after we unlock dsq but before we lock src_rq, then holding_cpu would be cleared to -1. and yeah, other CPUs may have updated it to different values afterwards β€” but since this operation can't be preempted or recurse, holding_cpu can never become this CPU again before we're done. so we can tell whether we lost to dequeue by just testing whether holding_cpu still points to this CPU. πŸ•΅οΈβ€β™€οΈ see scx_dispatch_dequeue for the counterpart.

on return: dsq is unlocked and src_rq is locked. returns true if p is still valid, false if we lost to dequeue. πŸ’€ (that's the whole vibe check, fr fr)

🧚 SEE ALSO 🧚

the kernel file is ./kernel/sched/ext/ext.c, and honestly the whole friend group showed up:

scx_is_descendant(9), rq_is_open(9), scx_fill_cmask_scratch(9), scx_call_op_set_cpumask(9), nldsq_next_task(9), nldsq_cursor_next_task(9), nldsq_cursor_lost_task(9), scx_task_iter_start(9), scx_task_iter_unlock(9), scx_task_iter_relock(9), scx_task_iter_stop(9), scx_task_iter_next(9), scx_task_iter_next_locked(9), scx_dump_event(9), wait_ops_state(9), scx_cpu_valid(9), schedule_deferred(9), schedule_deferred_locked(9), dsq_insert_head(9), set_task_slice_keep_oob(9), scx_task_slice_ended(9), move_remote_task_to_local_dsq(9), move_task_between_dsqs(9), dispatch_to_local_dsq(9), finish_dispatch(9), scx_prio_less(9), __scx_init_task(9), task_dead_and_done(9), scx_tg_sched(9), scx_tg_knob_sched(9), handle_lockup(9), scx_rcu_cpu_stall(9), scx_softlockup(9), scx_hardlockup(9), unbypass_renotify_idle(9), scx_bypass(9), scx_propagate_exit_irq_workfn(9), scx_flush_disable_work(9), print_scx_info(9), scx_bpf_dsq_insert___v2(9), __scx_bpf_dsq_insert_vtime(9), scx_bpf_dispatch_nr_slots(9), scx_bpf_dispatch_cancel(9), scx_bpf_dsq_move_to_local___v2(9), scx_bpf_dsq_move_set_slice(9), scx_bpf_dsq_move_set_vtime(9), scx_bpf_dsq_move(9), scx_bpf_dsq_move_vtime(9), scx_bpf_reenqueue_local(9), scx_bpf_create_dsq(9), scx_bpf_task_set_slice(9), scx_bpf_task_set_dsq_vtime(9), scx_bpf_kick_cpu(9), scx_bpf_kick_cid(9), scx_bpf_dsq_nr_queued(9), scx_bpf_destroy_dsq(9), bpf_iter_scx_dsq_new(9), bpf_iter_scx_dsq_next(9), bpf_iter_scx_dsq_destroy(9), scx_bpf_dsq_peek(9), scx_bpf_dsq_reenq(9), scx_bpf_reenqueue_local___v2(9), scx_bpf_exit_bstr(9), scx_bpf_error_bstr(9), scx_bpf_dump_bstr(9), scx_bpf_cpuperf_cap(9), scx_bpf_cidperf_cap(9), scx_bpf_cpuperf_cur(9), scx_bpf_cidperf_cur(9), scx_bpf_cpuperf_set(9), scx_bpf_cidperf_set(9), scx_bpf_nr_node_ids(9), scx_bpf_nr_cpu_ids(9), scx_bpf_nr_cids(9), scx_bpf_nr_online_cids(9), scx_bpf_online_cmask(9), scx_bpf_this_cid(9), scx_bpf_get_possible_cpumask(9), scx_bpf_get_online_cpumask(9), scx_bpf_put_cpumask(9), scx_bpf_task_running(9), scx_bpf_task_cpu(9), scx_bpf_task_cid(9), scx_bpf_locked_rq(9), scx_bpf_cpu_curr(9), scx_bpf_cid_curr(9), scx_bpf_tid_to_task(9), scx_bpf_now(9), scx_bpf_events(9), scx_bpf_task_cgroup(9)


🌈 that's the whole page, bestie! this one lives in ./kernel/sched/ext, October 2026, straight out of the Kernel API Manual. go forth and unlink responsibly 🫢✨

πŸ“¦ where this page comes from