Question about scheduling priority in Elixir

Hi, I’m fairly new to Elixir, and was messing around with Tasks, GenServers and Supervisors, and had a question on scheduling.

I want to know if it’s possible to create a Task with a higher priority than default, such that it gets preference when CPU is available to run something over other processes.

I read that Erlang/Elixir uses preemptive round-robin scheduling, which lets a process run until a certain number of reductions (function calls?) are done by it, or if it calls receive and waits for input, then switches to some other process. This seems to give equal priority to all processes, and it makes sure every process gets some CPU time per round.

I want to know if I can create higher priority tasks, such that when scheduler moves on from a process and it has a choice of which process to switch to, it always chooses the process of the high priority task, even if there are other processes in the current round that haven’t gotten CPU time yet.

For example, say I have a premium API and a free API. Both APIs compute something and send the result somewhere.

The free API when called creates a task and runs its logic, and this can come under normal scheduling logic.

But the premium API should create a task such that the scheduler always gives that task process CPU and priority to run when it’s not blocked/waiting for something, even if that starves the free API task processes.

I couldn’t find any options relating to this in Task.async() or Task.Supervisor.Async() that are relevant to this

Please lmk if I have the right understanding of scheduling and if what I asked is possible, thanks

1 Like

You can fiddle with a processes priority flag, but this is effectively global, as in, your API.Premium task with high priority, will also starve out any web server processes (assuming they’re on the same BEAM), not just API.Free tasks.

Marking free as low is probably the better way. The semantics there are a bit different to what you describe, but it seems most blessed path. I think high is intended for very special cases.

The docs point out a few other pitfalls.

Process — Elixir v1.17.3@spec flag(:priority, priority_level()) :: priority_level() → See :erlang.process_flag/2 for more information.

Sets the process priority. Level is an atom. Four priority levels exist: low, normal, high, and max. Default is normal.

Execution of processes on priority normal and low are interleaved. Processes on priority low are selected for execution less frequently than processes on priority normal

When runnable processes on priority high exist, no processes on priority low or normal are selected for execution. Notice however that this does not mean that no processes on priority low or normal can run when processes are running on priority high. When using multiple schedulers, more processes can be running in parallel than processes on priority high. That is, a low and a high priority process can execute at the same time.

There is also :erlang.yield(), also discouraged in the docs.

Tries to give other processes with the same or higher priority (if any) a chance to execute before returning. There is no guarantee that any other process runs between the invocation and return of erlang:yield/0.

As well as other naughty things like bump_reductions and suspend_process (all discouraged).

It all looks to me as something implicitly put in the “applications concern” bucket, where you

  • do broad time slicing after each task and delay running another task until it comes off cool down, or
  • write your tasks to periodically suspend themselves (eg process 100 things, save state and then wait for a :keep_going message to resume) by telling a supervisor to wake them up when the deserve it, and the supervisor itself manages the priority queue.
  • Maintain a pool where there are more premium workers than free ones, so the system would naturally “balance out” to running premium tasks more often. Premium tasks wont run faster, but they’ll drain quicker, where as free tasks will sit waiting.
  • run two beams and nice the process.
  • run free tier on worse hardware :slight_smile:

That kind of deal is either super easy or super annoying depending on the kind if work.

3 Likes

The priority_level documentation you linked is what I was looking for, thanks!

Yeah, I think in the free and premium example, marking the free one with low priority is better, if I marked the premium ones with high then I would have to mark all other important processes as high too, to keep them from potentially starving, and it might mess with the built in OTP processes that have normal priority.

I was just wondering if there was a library or built-in option I could pass in to manage priority just amongst tasks in a task supervisor, without affecting everything else. Looks like it either has to be application level with priority flag, or I have to manage the scheduling myself in the

Thanks for the answer