Why is enif_consume_timeslice/2 a no-op on dirty schedulers since OTP 22?

Between OTP 21 and OTP 22, enif_consume_timeslice/2 gained an early return on dirty schedulers:

if (sched < 0)
    return 0; /* no-op on dirty scheduler */

Before, it worked identically on normal and dirty. The documented well-behaved-NIF pattern (call enif_consume_timeslice, on non-zero call enif_schedule_nif) is now silently broken for dirty NIFs, the return branch never fires. Also, reductions are no longer credited for CPU time spent on dirty schedulers.

Concrete case: an on-device LLM inference NIF. When the number of concurrent sessions exceeds the dirty CPU scheduler count, queued sessions must wait for a full forward pass to complete before making any progress, since the pre-emption granularity is one whole NIF call. Pre-OTP-22, the C side could yield mid-forward-pass via enif_consume_timeslice + enif_schedule_nif, so all sessions would advance concurrently at roughly (scheduler_count / session_count) of full speed.

Was pre-OTP-22 behavior considered incorrect? And is elapsed-based enif_schedule_nif with DIRTY_JOB_* flags (ignoring enif_consume_timeslice’s return) the intended replacement now?

Zab.

Yes, reductions aren’t a thing on dirty schedulers, and reading the old code it was buggy on top of it all.

If you want the old behavior minus the bug, you can replicate it by declaring int reds = 100; and decrementing it towards 0 yourself in your NIF.